diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000000..62ab68a18e --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,13 @@ +Thanks for using RxJava but before you post an issue, please consider the following points: + + - [ ] Please include the library version number, including the minor and patch version, in the issue text. In addition, if you'd include the major version in the title (such as `2.x`) that would be great. + + - [ ] If you think you found a bug, please include a code sample that reproduces the problem. Dumping a stacktrace is usually not enough for us. + + - [ ] RxJava has more than 150 operators, we recommend searching the [javadoc](http://reactivex.io/RxJava/1.x/javadoc/rx/Observable.html) for keywords of what you try to accomplish. + + - [ ] If you have a question/issue about a library/technology built on top of RxJava (such as Retrofit, RxNetty, etc.), please consider asking a question on StackOverflow first (then maybe on their issue list). + + - [ ] Questions like "how do I X with RxJava" are generally better suited for StackOverflow (where it may already have an answer). + + - [ ] Please avoid cross-posting questions on StackOverflow, this issue list, the Gitter room or the mailing list. \ No newline at end of file diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000000..885315565f --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,7 @@ +Thank you for contributing to RxJava. Before pressing the "Create Pull Request" button, please consider the following points: + + - [ ] Please give a description about what and why you are contributing, even if it's trivial. + + - [ ] Please include the issue list number(s) or other PR numbers in the description if you are contributing in response to those. + + - [ ] Please include a reasonable set of unit tests if you contribute new code or change an existing one. If you contribute an operator, (if applicable) please make sure you have tests for working with an `empty`, `just`, `range` of values as well as an `error` source, with and/or without backpressure and see if unsubscription/cancellation propagates correctly. diff --git a/.travis.yml b/.travis.yml index 7a9db57ab4..e9ae55c471 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,15 @@ language: java jdk: -- oraclejdk7 -sudo: false +- oraclejdk8 +sudo: required # as per http://blog.travis-ci.com/2014-12-17-faster-builds-with-container-based-infrastructure/ +git: + depth: 50 + +# prevent travis running gradle assemble; let the build script do it anyway +install: true + # script for build and release via Travis to Bintray script: gradle/buildViaTravis.sh diff --git a/CHANGES.md b/CHANGES.md index efc3a614d5..31299c26f8 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,571 @@ # RxJava Releases # +### Version 1.3.8 - March 31, 2018 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex%7Crxjava%7C1.3.8%7C)) + +RxJava 1.x is now officially **end-of-life (EOL)**. No further developments, bugfixes, enhancements, javadoc changes, maintenance will be provided by this project after version **1.3.8**. + +Users are encourage to [migrate to 2.x](https://github.com/ReactiveX/RxJava/wiki/What's-different-in-2.0). In accordance, the wiki will be updated in the coming months to describe 2.x features and properties. + +#### Bugfixes + +- [Pull 5935](https://github.com/ReactiveX/RxJava/pull/5935): Fix `take()` to route late errors to `RxJavaHooks`. + +### Version 1.3.7 - March 21, 2018 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex%7Crxjava%7C1.3.7%7C)) + +#### Bugfixes + +- [Pull 5917](https://github.com/ReactiveX/RxJava/pull/5917): Fix and deprecate evicting `groupBy` and add a new overload with the corrected signature. + +### Version 1.3.6 - February 15, 2018 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex%7Crxjava%7C1.3.6%7C)) + +#### Bugfixes + +- [Pull 5850](https://github.com/ReactiveX/RxJava/pull/5850): Fix a race condition that may make `OperatorMaterialize` emit the wrong signals. +- [Pull 5851](https://github.com/ReactiveX/RxJava/pull/5851): Fix a race condition in `OperatorMerge.InnerSubscriber#onError` causing incorrect terminal event. + +### Version 1.3.5 - January 27, 2018 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex%7Crxjava%7C1.3.5%7C)) + +#### Other + +- [Pull 5820](https://github.com/ReactiveX/RxJava/pull/5820): `RxJavaPlugins` lookup workaround for `System.getProperties()` access restrictions. + +### Version 1.3.4 - November 19, 2017 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex%7Crxjava%7C1.3.4%7C)) + +#### Bugfixes + +- [Pull 5696](https://github.com/ReactiveX/RxJava/pull/5696): Fix `Completable.concat` to use `replace` and don't dispose the old `Subscription` on switching to the next source. +- [Pull 5726](https://github.com/ReactiveX/RxJava/pull/5726): Fix broken backpressure through `unsubscribeOn()`. + +#### Documentation + +- [Pull 5719](https://github.com/ReactiveX/RxJava/pull/5719): Document the timed `take()` operator will signal the `onCompleted` event on the given `Scheduler` when it times out. + +### Version 1.3.3 - October 19, 2017 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex%7Crxjava%7C1.3.3%7C)) + +#### Bugfixes + +- [Pull 5660](https://github.com/ReactiveX/RxJava/pull/5660): Fix `timeout` (timed, selector) unsubscribe bug. + +### Version 1.3.2 - September 15, 2017 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex%7Crxjava%7C1.3.2%7C)) + +#### Bugfixes + +- [Pull 5602](https://github.com/ReactiveX/RxJava/pull/5602): Workaround for CHM.keySet bad type with Java 8 compiler + +### Version 1.3.1 - September 10, 2017 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex%7Crxjava%7C1.3.1%7C)) + +#### API changes +*Remark: submitted & merged before the feature freeze of June 1st.* + +- [Pull 5332](https://github.com/ReactiveX/RxJava/pull/5332): Add the `cast` operator to `Single`. + +#### Bugfixes + +- [Pull 5430](https://github.com/ReactiveX/RxJava/pull/5430): Fix premature cleanup in `AsyncOnSubscribe` when the last `Observable` is still running. +- [Pull 5437](https://github.com/ReactiveX/RxJava/pull/5437): `TestSubscriber::assertValuesAndClear` should reset `valueCount`. +- [Pull 5470](https://github.com/ReactiveX/RxJava/pull/5470): Fix eager call to `RxJavHooks.onError` in `SafeCompletableSuscriber`. + +### Version 1.3.0 - May 5, 2017 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex%7Crxjava%7C1.3.0%7C)) + +#### Summary + +Version 1.3.0 is the next minor release of the 1.x series of RxJava containing many formerly experimental API components promoted to standard. Most notably the `Completable` base reactive type is now standard as well. + +Note that the experimental `rx.Observable.fromEmitter()` has been removed in favor for the now also standard `Observable.create(Action1> emitter, Emitter.BackpressureMode backpressure)` + +The planned lifecycle of the 1.x line is as follows: + +Date | Remark +------------|------------------- + **June 1, 2017** | Feature freeze, only bugfixes from this on. + **September 1, 2017** | Release `1.4.0` finalizing the remaining API components. + **March 31, 2018** | End of development. + +The following components have been promoted to standard: + +**Classes, interfaces** + +- **classes**: `AssemblyStackTraceException`, `RxJavaCompletableExecutionHook`, `RxJavaHooks`, `UnicastSubject`, `BlockingSingle`, `Completable`, `AssertableSubscriber`, `AsyncCompletableSubscriber`, `SafeCompletableSubscriber` +- **interfaces**: `Cancellable`, `Emitter`, `SingleEmitter`, `CompletableEmitter`, `CompletableSubscriber`, `BackpressureOverflow.Strategy` + +**Operators** + +- **`Observable`**: `create`, `unsafeCreate`, `to`, `zip(Observable[], FuncN)`, `flatMapCompletable`, `flatMapSingle`, `groupby(Func1, Func1, Func1, Map>)`, `onTerminateDetach`, `rebatchRequests`, `subscribeOn(Scheduler, boolean)`, `sorted`, `withLatestFrom`, `test`, `toCompletable`, `concatDelayError`, `mergeDelayError`, `switchOnNextDelayError`, `using(Func0, Func1, Action1, boolean)`, `concatMapDelayError`, `delaySubscription(Observable)`, `distinctUntilChanged(Func2)`, `concatEager`, `concatMapEager`, `onBackpressureBuffer(long, Action0, BackpressureOverflow.Strategy)`, `switchMapDelayError`, `toSortedList(int)`, `toSortedList(Func2, int)` +- **`Completable`**: `fromEmitter`, `test` +- **`Single`**: `fromEmitter`, `merge`, `mergeDelayError`, `cache`, `to`, `doOnEach`, `doOnSuccess`, `test`, `onErrorResumeNext`, `toCompletable`, `doOnError`, `doOnSubscribe`, `delay`, `defer`, `doOnUnsubscribe`, `doAfterTerminate`, `flatMapCompletable`, `lift`, `toBlocking`, `using`, `delaySubscription(Observable)` +- **`TestSubscriber`**: `getCompletions`, `awaitValueCount`, `assertValuesAndClear` +- **`SyncOnSubscriber`**: `createSingleState`, `createStateful`, `createStateless` + +**Other** + +- `Schedulers.reset` +- `CompositeException(Throwable...)` constructor +- `Exceptions.throwOrReport` (4 overloads) +- `BlockingObservable.subscribe` (6 overloads) +- **`RxJavaSchedulersHook`**: `createComputationScheduler`, `createIoScheduler`, `createNewThreadScheduler` +- **internal**: `AssertableSubscriberObservable`, `FlatMapCompletable`, `FlatMapSingle`, `SchedulerWhen`, `BackpressureDrainManager`, `BlockingUtils`. +- **`RxJavaPlugins`**: `reset`, `getCompletableExecutionHook`, `registerCompletableExecutionHook` +- **`RxJavaErrorHandler`**: `handleOnNextValueRendering`, `render` + +In addition, the class `AsyncOnsubscribe` with its 7 factory methods and `Observable.create(AsyncOnSubscribe)` have been promoted to **beta**. + +#### Acknowledgements + +Thanks to all who contributed to the 1.x line in the past 6 months (in order they appear on the [commit](https://github.com/ReactiveX/RxJava/commits/1.x) page): + +@mtiidla, @dhendry, @mostroverkhov, @yshrsmz, @BraisGabin, @cesar1000, @Jawnnypoo, @chanx2, @abersnaze, @davidmoten, @ortex, @marwinxxii, @ekchang, @pyricau, @JakeWharton, @VictorAlbertos + + +### Version 1.2.10 - April 26, 2017 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex%7Crxjava%7C1.2.10%7C)) + +#### Bugfixes + +- [Pull 5225](https://github.com/ReactiveX/RxJava/pull/5225): Fix `Completable.onErrorResumeNext` unsubscribe not propagated. + +#### Other + +- [Pull 5250](https://github.com/ReactiveX/RxJava/pull/5250): Defer creation of the `TimeoutException` when using the `Single.timeout()` operator. +- [Pull 5258](https://github.com/ReactiveX/RxJava/pull/5258): Use IntelliJ IDE friendly assertion failure message. + +### Version 1.2.9 - March 24, 2017 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex%7Crxjava%7C1.2.9%7C)) + +#### API enhancements +- [Pull 5146](https://github.com/ReactiveX/RxJava/pull/5146): Add `Single.unsubscribeOn`. +- [Pull 5195](https://github.com/ReactiveX/RxJava/pull/5195): Enhance `UnicastSubject` with optional delay error behavior. + +#### Bugfixes + +- [Pull 5141](https://github.com/ReactiveX/RxJava/pull/5141): Fix timed `replay()` not terminating when all items timeout. +- [Pull 5181](https://github.com/ReactiveX/RxJava/pull/5181): `replay().refCount()` avoid leaking items between connections. + +### Version 1.2.7 - February 24, 2017 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex%7Crxjava%7C1.2.7%7C)) + +#### Deprecation of `create(OnSubscribe)` + +The method started out in RxJava 0.x as a simple and direct way for implementing custom operators because 0.x had a much simpler protocol requirements. Over the years, as the `Observable` protocol evolved and `create` became a powerful and complicated extension point of RxJava that required users to implement the `Observable` protocol, including cancellation and backpressure manually. + +Unfortunately, guides, blogs, StackOverflow answers and mere typical user behavior still leads to this `create` method and lots of confusion, unstoppable sequences and `MissingBackpressureException`. We tried remedying the situation by introducing `fromEmitter` with limited discoverability success. + +**Therefore, as of 1.2.7 the `create()` method is now deprecated** (but won't be removed due to binary compatibility requirements). In addition `fromEmitter` has been deprecate-renamed to `create(Action1, BackpressureMode)` and the experimental-marked `fromEmitter` itself will be removed in 1.3.0. + +Since the functionality of `create()` was useful for writing (custom) operators inside and outside of RxJava, the new `unsafeCreate(OnSubscribe)` method was introduced as the replacement. + +The new `create()` and `unsafeCreate()` methods will be fast-tracked to become standard in 1.3.0. + +#### API enhancements + +- [Pull 5086](https://github.com/ReactiveX/RxJava/pull/5086): Deprecate `create()`, add alternatives +- [Pull 5092](https://github.com/ReactiveX/RxJava/pull/5092): Add `Single.merge(Observable>)`, `Observable.flatMapSingle()` & `Observable.flatMapCompletable`. +- [Pull 5091](https://github.com/ReactiveX/RxJava/pull/5091): Add `subscribeOn(Scheduler, boolean)` avoid same-pool deadlock. + +#### API deprecations + +- [Pull 5086](https://github.com/ReactiveX/RxJava/pull/5086): + - Deprecate `Observable.create(OnSubscribe)`, + - Deprecate `fromEmitter` in favor of `Observable.create(Action1, BackpressureMode)`. + +#### Bugfixes + +- [Pull 5091](https://github.com/ReactiveX/RxJava/pull/5091): `create(Action1, BackpressureMode)`+`subscribeOn` avoid same-pool deadlock. +- [Pull 5123](https://github.com/ReactiveX/RxJava/pull/5123): `throttleFirst` detecting clock-drift backwards to open the gate + +#### Other + +- [Pull 5125](https://github.com/ReactiveX/RxJava/pull/5125): reduce stack depth with `switchIfEmpty` + +### Version 1.2.6 - February 3, 2017 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex%7Crxjava%7C1.2.6%7C)) + +#### Documentation + +- [Pull 5000](https://github.com/ReactiveX/RxJava/pull/5000): Add which are the other stardard methods of create +- [Pull 5007](https://github.com/ReactiveX/RxJava/pull/5007): update `sample(time)` diagram to indicate emission of last +- [Pull 5048](https://github.com/ReactiveX/RxJava/pull/5048): Improve the javadoc of `BehaviorSubject` + +#### Bugfixes + +- [Pull 5030](https://github.com/ReactiveX/RxJava/pull/5030): fix `groupBy` consuming the upstream in an unbounded manner + +### Version 1.2.5 - January 6, 2017 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex%7Crxjava%7C1.2.5%7C)) + +#### Documentation + +- [Pull 4963](https://github.com/ReactiveX/RxJava/pull/4963): Add missing marbles, fix image sizes + +#### Bugfixes + +- [Pull 4941](https://github.com/ReactiveX/RxJava/pull/4941): Fix `Completable.mergeDelayError` to check unsafe availability + +### Version 1.2.4 - December 15, 2016 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex%7Crxjava%7C1.2.4%7C)) + +#### Other + +- [Pull 4912](https://github.com/ReactiveX/RxJava/pull/4912): Fix `resolveAndroidApiVersion` when running under Robolectric +- [Pull 4908](https://github.com/ReactiveX/RxJava/pull/4908): Use `t` instead of value to allow for IDE naming. +- [Pull 4884](https://github.com/ReactiveX/RxJava/pull/4884): enable `TestScheduler` with nanosecond periodic scheduling + +### Version 1.2.3 - November 23, 2016 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex%7Crxjava%7C1.2.3%7C)) + +#### Documentation enhancements + +- [Pull 4846](https://github.com/ReactiveX/RxJava/pull/4846): Specify the system parameters configuring the schedulers in the `Schedulers` javadoc. + +#### API enhancements + +- [Pull 4777](https://github.com/ReactiveX/RxJava/pull/4777): add `AssertableSubscriber` to provide method chained version of `TestSubscriber` and support a `test()` method on the base reactive classes. +- [Pull 4851](https://github.com/ReactiveX/RxJava/pull/4851): add `Single.fromEmitter` +- [Pull 4852](https://github.com/ReactiveX/RxJava/pull/4852): `Single.takeUntil` `CancellationException` message enhancements + +#### Performance enhancements + +- [Pull 4846](https://github.com/ReactiveX/RxJava/pull/4846): remove ObjectPool, code style cleanups + +#### Bugfixes + +- [Pull 4826](https://github.com/ReactiveX/RxJava/pull/4826): `Schedule.when()` bug fix +- [Pull 4830](https://github.com/ReactiveX/RxJava/pull/4830): `Completable.doAfterTerminate` to run after `onError` as well. +- [Pull 4841](https://github.com/ReactiveX/RxJava/pull/4841): replace non-serializable value of `OnNextValue` with its `toString`. +- [Pull 4849](https://github.com/ReactiveX/RxJava/pull/4849): fix `Completable.concat` & `merge` hanging in async situations. + +### Version 1.2.2 - November 3, 2016 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex%7Crxjava%7C1.2.2%7C)) + +Note that the interface `Cancellable` has been moved to `rx.functions` affecting `CompletableEmitter` and the experimental `Observable.fromEmitter(Action1> emitter, AsyncEmitter.BackpressureMode backpressure)` has been removed. + +Another important clarification was added to the javadoc about using `SingleSubscriber`: due to the internal enhancements of `Single`, a `SingleSubscriber` is no longer wrapped into a `Subscriber`+`SafeSubscriber` which setup used to call `unsubscribe` on the `SingleSubscriber` yielding `isUnsubscribed() == true` when the source terminated. Therefore, when one extends the class `SingleSubscriber`, the `unsubscribe()` should be called manually to yield the given expecation mentioned before: + +``` java +Subscritpion s = Single.just(1).subscribe(new SingleSubscriber() { + @Override public void onSuccess(Integer t) { + System.out.println("Success"); + unsubscribe(); + } + + @Override public void onError(Throwable e) { + e.printStackTrace(); + unsubscribe(); + } +}); + +assertTrue(s.isUnsubscribed()); +``` +#### Documentation enhancements + +- [Pull 4693](https://github.com/ReactiveX/RxJava/pull/4693): improve `timer` javadoc +- [Pull 4769](https://github.com/ReactiveX/RxJava/pull/4769): Add note to `SingleSubscriber` doc about unsubscribe invocation in `onSuccess` and `onError`. + +#### API enhancements + +- [Pull 4725](https://github.com/ReactiveX/RxJava/pull/4725): remove `AsyncEmitter` deprecations +- [Pull 4757](https://github.com/ReactiveX/RxJava/pull/4757): Add `cache()` to `Single` + +#### Performance enhancements + +- [Pull 4676](https://github.com/ReactiveX/RxJava/pull/4676): Make identity function a singleton. +- [Pull 4764](https://github.com/ReactiveX/RxJava/pull/4764): `zip` - check local boolean before volatile in boolean and + +#### Bugfixes + +- [Pull 4716](https://github.com/ReactiveX/RxJava/pull/4716): fix`subscribe(Action1 [, Action1])` to report `isUnsubscribed` true after the callbacks were invoked +- [Pull 4740](https://github.com/ReactiveX/RxJava/pull/4740): Error when tracking exception with unknown cause +- [Pull 4791](https://github.com/ReactiveX/RxJava/pull/4791): Add null check to `Observable.switchIfEmpty` + +### Version 1.2.1 - October 5, 2016 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex%7Crxjava%7C1.2.1%7C)) + +#### API enhancements + + - [Pull 4555](https://github.com/ReactiveX/RxJava/pull/4555): enhance generics of `doOnError` and `doOnRequest` + - [Pull 4580](https://github.com/ReactiveX/RxJava/pull/4580): rename `AsyncEmitter` to `Emitter` + +#### Performance enhancements + + - [Pull 4621](https://github.com/ReactiveX/RxJava/pull/4621): `NotificationLite` - reduce allocations + - [Pull 4648](https://github.com/ReactiveX/RxJava/pull/4648): rework `Single` internals to reduce overhead and stack depth + +#### Deprecations + + - [Pull 4580](https://github.com/ReactiveX/RxJava/pull/4580): `CompletableEmitter.setCancellation` will change its type in 1.2.2. + - [Pull 4648](https://github.com/ReactiveX/RxJava/pull/4648): Deprecate `Single(Observable.OnSubscribe)` constructor + +#### Bugfixes + + - [Pull 4641](https://github.com/ReactiveX/RxJava/pull/4641): `SafeSubscriber` not to call `RxJavaHooks` before delivering the error + +### Version 1.2.0 - September 17, 2016 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex%7Crxjava%7C1.2.0%7C)) + +This is a minor release that is functionally equivalent to 1.1.10 minus the removal of some deprecated experimental APIs. + +#### Promote `@Beta` to standard (`@since 1.2`) + + - in `rx.Observable` + - `create(SyncOnSubscribe)` + - `doOnRequest(Action1)` + - `flatMap(Func1>, Func1>, Func0>, int)` + - `flatMap(Func1>, int)` + - `flatMap(Func1>, Func2, int)` + - `flatMapIterable(Func1>, int) +rx.Observable.flatMapIterable(Func1>, Func2, int)` + - `fromCallable(Callable)` + - `toSingle()` + - `rx.Single` (the class itself) + - `fromCallable(Callable)` + - `rx.SingleSubscriber` + - in `rx.observables.ConnectableObservable` + - `autoConnect()` + - `autoConnect(int, Action1)` + - `autoConnect(int)` + - `rx.observables.SyncOnSubscribe` + - in `rx.subjects.AsyncSubject` + - `getThrowable()` + - `getValue()` + - `hasCompleted()` + - `hasThrowable()` + - `hasValue()` + - in `rx.subjects.BehaviorSubject` + - `getThrowable()` + - `getValue()` + - `getValues()` + - `getValues(T[])` + - `hasCompleted()` + - `hasThrowable()` + - `hasValue()` + - in `rx.subjects.PublishSubject` + - `getThrowable()` + - `hasCompleted()` + - `hasThrowable()` + - in `rx.subjects.ReplaySubject` + - `getThrowable()` + - `getValue()` + - `getValues()` + - `getValues(T[])` + - `hasAnyValue()` + - `hasCompleted()` + - `hasThrowable()` + - `hasValue()` + - `size()` + +#### Promote `@Experimental` to `@Beta` + + - `rx.BackpressureOverflow` + - in `rx.Observable` + - `concatDelayError(Iterable>)` + - `concatDelayError(Observable>)` + - `concatEager(Iterable>, int)` + - `concatEager(Iterable>)` + - `concatEager(Observable>, int)` + - `concatEager(Observable>)` + - `concatEager(Observable, Observable, Observable, Observable, Observable, Observable, Observable, Observable, Observable)` + - `concatEager(Observable, Observable, Observable, Observable, Observable, Observable, Observable, Observable)` + - `concatEager(Observable, Observable, Observable, Observable, Observable, Observable, Observable)` + - `concatEager(Observable, Observable, Observable, Observable, Observable, Observable)` + - `concatEager(Observable, Observable, Observable, Observable, Observable)` + - `concatEager(Observable, Observable, Observable, Observable)` + - `concatEager(Observable, Observable, Observable)` + - `concatEager(Observable, Observable)` + - `concatMapDelayError(Func1>)` + - `concatMapEager(Func1>, int, int)` + - `concatMapEager(Func1>, int)` + - `concatMapEager(Func1>)` + - `delaySubscription(Observable)` + - `distinctUntilChanged(Func2)` + - `mergeDelayError(Observable>, int)` + - `onBackpressureBuffer(long, Action0, Strategy)` + - `switchMapDelayError(Func1>)` + - `switchOnNextDelayError(Observable>)` + - `toCompletable()` + - `toSortedList(Func2, int)` + - `toSortedList(int)` + - `using(Func0, Func1>, Action1, boolean)` + - in `rx.observables.BlockingObservable` + - `subscribe()` + - `subscribe(Action1, Action1, Action0)` + - `subscribe(Action1, Action1)` + - `subscribe(Action1)` + - `subscribe(Observer)` + - `subscribe(Subscriber)` + - `rx.Completable` + - in `rx.Single` + - `defer(Callable>)` + - `delay(long, TimeUnit, Scheduler)` + - `delay(long, TimeUnit)` + - `delaySubscription(Observable)` + - `doAfterTerminate(Action0)` + - `doOnError(Action1)` + - `doOnSubscribe(Action0)` + - `doOnSuccess(Action1)` + - `doOnUnsubscribe(Action0)` + - `lift(Operator)` + - `onErrorResumeNext(Func1>)` + - `onErrorResumeNext(Single)` + - `toBlocking()` + - `toCompletable()` + - `using(Func0, Func1>, Action1, boolean)` + - `using(Func0, Func1>, Action1)` + - `rx.exceptions.CompositeException.CompositeException(Throwable...)` + - in `rx.exceptions.Exceptions` + - `throwOrReport(Throwable, Observer, Object)` + - `throwOrReport(Throwable, Observer)` + - `throwOrReport(Throwable, SingleSubscriber)` +- `rx.singles.BlockingSingle` + +#### Removed + - in `rx.Observable` + - `extend(Func1, R>)` : use `to(Func1)` instead + - `fromAsync()` : renamed to `fromEmitter()` + - in `rx.Completable` + - `CompletableSubscriber` : now `rx.CompletableSubscriber` + - `CompletableOnSubscribe` : renamed to `Completable.OnSubscribe` + - `CompletableOperator` : renamed to `Completable.Operator` + - `CompletableTransformer` : renamed to `Completable.Transformer` + +### Version 1.1.10 - September 5, 2016 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex%7Crxjava%7C1.1.10%7C)) + +The release contains a few javadoc and internal cleanups, some enhancements and some deprecations. + +**Notable renames:** + + - `fromAsync` is now `fromEmitter` + - `rx.Completable.CompletableSubscriber` is now `rx.CompletableSubscriber` + +#### API enhancements + + - [Pull 4423](https://github.com/ReactiveX/RxJava/pull/4423): add free-form conversion operator `to(Func1)` to `Observable`, `Single` and `Completable`. + - [Pull 4425](https://github.com/ReactiveX/RxJava/pull/4425): Rename `Completable` helper interfaces by dropping the `Completable` prefix, move `rx.Completable.CompletableSubscriber` to `rx.CompletableSubscriber`. + - [Pull 4442](https://github.com/ReactiveX/RxJava/pull/4442): Rename `fromAsync` to `fromEmitter`. + - [Pull 4452](https://github.com/ReactiveX/RxJava/pull/4452): Enhance generics on Observable.onErrorResumeNext and onErrorReturn + - [Pull 4442](https://github.com/ReactiveX/RxJava/pull/4442): Add `Completable.fromEmitter` + - [Pull 4453](https://github.com/ReactiveX/RxJava/pull/4453): Remove `throws InterruptedException` from `TestSubscriber.awaitValueCount()` + - [Pull 4460](https://github.com/ReactiveX/RxJava/pull/4460): Add `Completable.doOnEach(Action1)` + - [Pull 4461](https://github.com/ReactiveX/RxJava/pull/4461): Add `Single.doOnEach` + +#### Deprecations + + - [Pull 4425](https://github.com/ReactiveX/RxJava/pull/4425): Deprecate `CompletableOnSubscribe`, `CompletableOperator` and `CompletableTransformer` and rename them by dropping the `Completable` prefix + - [Pull 4442](https://github.com/ReactiveX/RxJava/pull/4442): Deprecate `Observable.fromAsync` by renaming it to `Observable.fromEmitter`. + - [Pull 4466](https://github.com/ReactiveX/RxJava/pull/4466): Deprecate `Notification.createOnCompleted(Class)` + +#### Bugfixes + + - [Pull 4397](https://github.com/ReactiveX/RxJava/pull/4397): Fix multiple values produced by `throttleFirst` with `TestScheduler` + - [Pull 4427](https://github.com/ReactiveX/RxJava/pull/4427): Fix `Observable.fromEmitter` (formerly `Observable.fromAsync`) post-complete event suppression + - [Pull 4447](https://github.com/ReactiveX/RxJava/pull/4447): Fix type parameters of `Observable.withLatestFrom` + + +### Version 1.1.9 - August 12, 2016 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex%7Crxjava%7C1.1.9%7C)) + +This release contains mostly internal cleanups, reinforced Observable-protocol adherence and minor javadoc fixes. + +**Warning**: the backpressure-behavior of `PublishSubject` has been changed. In earlier versions, when you called `PublishSubject.onNext` too frequently, that usually triggered a `MissingBackpressureException` in some downstream operator (`observeOn`, `zip`, etc.) and often it was not obvious who to blame for it. With 1.1.9, `PublishSubject` now tracks the request amounts of each of its children and refuses to overflow them, signalling a `MissingBackpressureException` to them instead which now points to the right operator. + +#### API enhancements + + - [Pull 4226](https://github.com/ReactiveX/RxJava/pull/4226): Add `Single.flatMapCompletable`. + - [Pull 4225](https://github.com/ReactiveX/RxJava/pull/4225): `PublishSubject` now signals `MissingBackpressureException` when backpressured. + - [Pull 4264](https://github.com/ReactiveX/RxJava/pull/4264): Add `Observable.sorted()` + overloads: sorts and re-emits each element of a finite sequence. + - [Pull 4261](https://github.com/ReactiveX/RxJava/pull/4261): Add `concatDelayError` multiple arguments. + - [Pull 4330](https://github.com/ReactiveX/RxJava/pull/4330): Add `Observable.concat(Iterable)` overload. + - [Pull 4322](https://github.com/ReactiveX/RxJava/pull/4322): Add `TestSubscriber.assertValuesAndClear` + +#### Performance enhancements + + - [Pull 4232](https://github.com/ReactiveX/RxJava/pull/4232): Less allocation in operator `amb`. + - [Pull 4233](https://github.com/ReactiveX/RxJava/pull/4233): Less allocation in `autoConnect`. + - [Pull 4236](https://github.com/ReactiveX/RxJava/pull/4236): Less allocation in `join`. + - [Pull 4237](https://github.com/ReactiveX/RxJava/pull/4237): Less allocation in `groupJoin` . + - [Pull 4239](https://github.com/ReactiveX/RxJava/pull/4239): Less allocation in `skip` with time. + - [Pull 4262](https://github.com/ReactiveX/RxJava/pull/4262): Less allocation in `doOnEach`. + - [Pull 4328](https://github.com/ReactiveX/RxJava/pull/4328): Compact `MultipleAssignmentSubscription` and `SerialSubscription` + +#### Bugfixes + + - [Pull 4231](https://github.com/ReactiveX/RxJava/pull/4231): `Schedulers.io()` workers now wait until a blocking task finishes before becoming available again. + - [Pull 4244](https://github.com/ReactiveX/RxJava/pull/4244): Fix `all` multiple terminal events. + - [Pull 4241](https://github.com/ReactiveX/RxJava/pull/4241): Fix reentrancy bug in `repeatWhen` and `retryWhen` when the resubscription happens. + - [Pull 4225](https://github.com/ReactiveX/RxJava/pull/4225): `PublishSubject` now checks for unsubscribed child while dispatching events. + - [Pull 4245](https://github.com/ReactiveX/RxJava/pull/4245): Fix `any` multiple terminal events. + - [Pull 4246](https://github.com/ReactiveX/RxJava/pull/4246): Fix `reduce` multiple terminal events. + - [Pull 4250](https://github.com/ReactiveX/RxJava/pull/4250): Fix `onBackpressureDrop` multiple terminal events. + - [Pull 4252](https://github.com/ReactiveX/RxJava/pull/4252): Fix `collect` multiple terminal events. + - [Pull 4251](https://github.com/ReactiveX/RxJava/pull/4251): Fix `toMap` multiple terminal events and backpressure behavior. + - [Pull 4270](https://github.com/ReactiveX/RxJava/pull/4270): Fix `toMultimap` multiple terminal events . + - [Pull 4311](https://github.com/ReactiveX/RxJava/pull/4311): Fix `Schedulers.from()` to call `RxJavaHooks.onScheduleAction`. + +### Version 1.1.8 - July 23, 2016 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex%7Crxjava%7C1.1.8%7C)) + +#### Bugfixes + + - [Pull 4209](https://github.com/ReactiveX/RxJava/pull/4209): `merge`/`flatMap` to keep scalar/inner element relative order. + - [Pull 4215](https://github.com/ReactiveX/RxJava/pull/4215): Fix assembly tracking replacing original exception. + - [Pull 4229](https://github.com/ReactiveX/RxJava/pull/4229): fix replay() retaining reference to the child Subscriber + +### Version 1.1.7 - July 10, 2016 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex%7Crxjava%7C1.1.7%7C)) + +This release has several documentation fixes (`AsyncSubject`, `doOnEach`, `cache`, `scan`, `reduce`, backpressure descriptions) and internal cleanups based on tool feedback (code-coverage and PMD). + +**Warning: behavior change in the time-bound `replay()` operator**: the aging of the values continues after the termination of the source and thus late subscribers no longer get old data. For example, a given `just(1).replay(1, TimeUnit.MINUTE)` a subscriber subscribing after 2 minutes won't get `1` but only `onCompleted`. + +**Warning: behavior change in `timestamp()` and `timeInterval()` (no arguments) operators**: they now take timing information from the `computation` scheduler instead of the `immediate` scheduler. This change now allows hooking these operators' time source. + +**Warning**: the parameter order of `Completable.subscribe(onError, onComplete)` has been changed to `Completable.subscribe(onComplete, onError)` to better match the callback order in the other base reactive classes, namely the most significant signal comes first (`Observer.onNext`, `SingleSubscriber.onSuccess`, and now `CompletableSubscriber.onCompleted`). + +#### The new RxJavaHooks API + +PR #4007 introduced a new way of hooking into the lifecycle of the base reactive types (`Observable`, `Single`, `Completable`) and the `Scheduler`s. The original `RxJavaPlugins`' design was too much focused on class-initialization time hooking and didn't properly allow hooking up different behavior after that. There is a `reset()` available on it but sometimes that doesn't work as expected. + +The new class `rx.plugins.RxJavaHooks` allows changing the hooks at runtime, allowing tests to temporarily hook onto an internal behavior and then un-hook once the test completed. + +```java +RxJavaHooks.setOnObservableCreate(s -> { + System.out.println("Observable created"); + return s; +}); + +Observable.just(1).subscribe(System.out::println); + +RxJavaHooks.reset(); +// or +RxJavaHooks.setOnObservableCreate(null); +``` + +It is now also possible to override what `Scheduler`s the `Schedulers.computation()`, `.io()` and `.newThread()` returns without the need to fiddle with `Schedulers`' own reset behavior: + +```java +RxJavaHooks.setOnComputationScheduler(current -> Schedulers.immediate()); + +Observable.just(1).subscribeOn(Schedulers.computation()) +.subscribe(v -> System.out.println(Thread.currentThread())); +``` + +By default, all `RxJavaHooks` delegate to the original `RxJavaPlugins` callbacks so if you have hooks the old way, they still work. `RxJavaHooks.reset()` resets to this delegation and `RxJavaHooks.clear()` clears all hooks (i.e., everything becomes a pass-through hook). + +#### API enhancements + + - [Pull 3966](https://github.com/ReactiveX/RxJava/pull/3966): Add multi-other `withLatestFrom` operators. + - [Pull 3720](https://github.com/ReactiveX/RxJava/pull/3720): Add vararg of `Subscription`s to composite subscription. + - [Pull 4034](https://github.com/ReactiveX/RxJava/pull/4034): `distinctUntilChanged` with direct value comparator - alternative. + - [Pull 4036](https://github.com/ReactiveX/RxJava/pull/4036): Added zip function with Observable array. + - [Pull 4020](https://github.com/ReactiveX/RxJava/pull/4020): Add `AsyncCompletableSubscriber` that exposes `unsubscribe()`. + - [Pull 4011](https://github.com/ReactiveX/RxJava/pull/4011): Deprecate `TestObserver`, enhance `TestSubscriber` a bit. + - [Pull 4007](https://github.com/ReactiveX/RxJava/pull/4007): new hook management proposal + - [Pull 4173](https://github.com/ReactiveX/RxJava/pull/4173): allow customizing GenericScheduledExecutorService via RxJavaHooks + - [Pull 3931](https://github.com/ReactiveX/RxJava/pull/3931): add `groupBy` overload with `evictingMapFactory` + - [Pull 4140](https://github.com/ReactiveX/RxJava/pull/4140): Change `Completable.subscribe(onError, onComplete)` to `(onComplete, onError)` + - [Pull 4154](https://github.com/ReactiveX/RxJava/pull/4154): Ability to create custom schedulers with behavior based on composing operators via `Scheduler.when`. + - [Pull 4179](https://github.com/ReactiveX/RxJava/pull/4179): New `fromAsync` to bridge the callback world with the reactive. + +#### API deprecations + + - [Pull 4011](https://github.com/ReactiveX/RxJava/pull/4011): Deprecate `TestObserver`, enhance `TestSubscriber` a bit. + - [Pull 4007](https://github.com/ReactiveX/RxJava/pull/4007): new hook management proposal (deprecates some `RxJavaPlugins` methods). + +#### Performance enhancements + + - [Pull 4097](https://github.com/ReactiveX/RxJava/pull/4097): update `map()` and `filter()` to implement `OnSubscribe` directly. + - [Pull 4176](https://github.com/ReactiveX/RxJava/pull/4176): Optimize collect, reduce and takeLast(1) + +#### Bugfixes + + - [Pull 4027](https://github.com/ReactiveX/RxJava/pull/4027): fix `Completable.onErrorComplete(Func1)` not relaying function crash. + - [Pull 4051](https://github.com/ReactiveX/RxJava/pull/4051): fix `ReplaySubject` anomaly around caughtUp by disabling that optimization. + ### Version 1.1.6 - June 15, 2016 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex%7Crxjava%7C1.1.6%7C)) #### API enhancements diff --git a/README.md b/README.md index da3619c8df..a0eabf5886 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,17 @@ # RxJava: Reactive Extensions for the JVM +## End-of-Life notice + +As of March 31, 2018, The RxJava 1.x branch and version is end-of-life (EOL). No further development, bugfixes, documentation changes, PRs, releases or maintenance will be performed by the project on the 1.x line. + +Users are encouraged to migrate to [3.x](https://github.com/ReactiveX/RxJava) which is currently the only official RxJava version being managed. + +---------------------------------- + + +[![codecov.io](http://codecov.io/github/ReactiveX/RxJava/coverage.svg?branch=1.x)](http://codecov.io/github/ReactiveX/RxJava?branch=1.x) +[![Maven Central](https://maven-badges.herokuapp.com/maven-central/io.reactivex/rxjava/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.reactivex/rxjava) + RxJava is a Java VM implementation of [Reactive Extensions](http://reactivex.io): a library for composing asynchronous and event-based programs by using observable sequences. 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. @@ -15,12 +27,6 @@ It extends the [observer pattern](http://en.wikipedia.org/wiki/Observer_pattern) Learn more about RxJava on the Wiki Home. -## Master Build Status - - -[![codecov.io](http://codecov.io/github/ReactiveX/RxJava/coverage.svg?branch=1.x)](http://codecov.io/github/ReactiveX/RxJava?branch=1.x) - - ## Communication - Google Group: [RxJava](http://groups.google.com/d/forum/rxjava) @@ -104,7 +110,7 @@ $ cd RxJava/ $ ./gradlew build ``` -Futher details on building can be found on the [Getting Started](https://github.com/ReactiveX/RxJava/wiki/Getting-Started) page of the wiki. +Further details on building can be found on the [Getting Started](https://github.com/ReactiveX/RxJava/wiki/Getting-Started) page of the wiki. ## Bugs and Feedback diff --git a/build.gradle b/build.gradle index 9f846638b2..8d0d2fa087 100644 --- a/build.gradle +++ b/build.gradle @@ -1,18 +1,33 @@ buildscript { - repositories { jcenter() } - dependencies { classpath 'com.netflix.nebula:gradle-rxjava-project-plugin:4.0.0' } + repositories { + jcenter() + } + dependencies { + classpath 'com.netflix.nebula:gradle-rxjava-project-plugin:4.0.0' + classpath 'ru.vyarus:gradle-animalsniffer-plugin:1.1.0' + } } 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' -apply plugin: 'pmd' +//apply plugin: 'pmd' +apply plugin: 'findbugs' apply plugin: 'jacoco' +apply plugin: 'ru.vyarus.animalsniffer' apply plugin: 'nebula.rxjava-project' +repositories { + mavenCentral() +} + dependencies { + signature 'org.codehaus.mojo.signature:java16:1.1@signature' + testCompile 'junit:junit:4.12' testCompile 'org.mockito:mockito-core:1.10.19' + testCompile 'com.google.guava:guava:24.0-jre' + testCompile 'com.pushtorefresh.java-private-constructor-checker:checker:1.2.0' perfCompile 'org.openjdk.jmh:jmh-core:1.11.3' perfCompile 'org.openjdk.jmh:jmh-generator-annprocess:1.11.3' @@ -46,7 +61,11 @@ if (project.hasProperty('release.useLastTag')) { } test{ - maxHeapSize = "2g" + maxHeapSize = "1200m" +} + +license { + excludes(["**/*.md", "**/*.txt", "**/unsafe/*.java", "**/atomic/*.java", "**/Beta.java", "**/Experimental.java"]) } jacoco { @@ -62,37 +81,56 @@ jacocoTestReport { build.dependsOn jacocoTestReport -pmd { - toolVersion = '5.4.2' - ignoreFailures = true - sourceSets = [sourceSets.main] - ruleSets = [] - ruleSetFiles = files('pmd.xml') +//pmd { +// toolVersion = '5.4.2' +// ignoreFailures = true +// sourceSets = [sourceSets.main] +// ruleSets = [] +// ruleSetFiles = files('pmd.xml') +//} + +//pmdMain { +// reports { +// html.enabled = true +// xml.enabled = true +// } +//} + +//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 + +animalsniffer { + annotation = 'rx.internal.util.SuppressAnimalSniffer' } -pmdMain { - reports { - html.enabled = true - xml.enabled = true - } +findbugs { + ignoreFailures true + toolVersion = '3.0.1' + effort = 'max' + reportLevel = 'low' + sourceSets = [sourceSets.main] } -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.") +findbugsMain { + reports { + html.enabled = false // Findbugs can only have on report enabled + xml.enabled = true } } - -build.dependsOn pmdPrint \ No newline at end of file diff --git a/gradle/buildViaTravis.sh b/gradle/buildViaTravis.sh index d98e5eb603..d3d2ce6b96 100755 --- a/gradle/buildViaTravis.sh +++ b/gradle/buildViaTravis.sh @@ -1,9 +1,18 @@ #!/bin/bash # This script will build the project. +git fsck --full + +buildTag="$TRAVIS_TAG" + +if [ "$buildTag" != "" ] && [ "${buildTag:0:3}" != "v1." ]; then + echo -e "Wrong tag on the 1.x brach: $buildTag : build stopped" + exit 1 +fi + if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then echo -e "Build Pull Request #$TRAVIS_PULL_REQUEST => Branch [$TRAVIS_BRANCH]" - ./gradlew -Prelease.useLastTag=true build + ./gradlew -Prelease.useLastTag=true build --stacktrace 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 @@ -12,5 +21,5 @@ elif [ "$TRAVIS_PULL_REQUEST" == "false" ] && [ "$TRAVIS_TAG" != "" ]; then ./gradlew -Prelease.travisci=true -Prelease.useLastTag=true -PbintrayUser="${bintrayUser}" -PbintrayKey="${bintrayKey}" -PsonatypeUsername="${sonatypeUsername}" -PsonatypePassword="${sonatypePassword}" final --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 + ./gradlew -Prelease.useLastTag=true build --stacktrace fi diff --git a/pmd.xml b/pmd.xml index e6fbccdd95..339af7d5ac 100644 --- a/pmd.xml +++ b/pmd.xml @@ -71,7 +71,6 @@ - @@ -134,9 +133,6 @@ - - - @@ -160,7 +156,6 @@ - @@ -183,8 +178,6 @@ - - diff --git a/src/main/java/rx/BackpressureOverflow.java b/src/main/java/rx/BackpressureOverflow.java index 761a2c4a80..6603a6d220 100644 --- a/src/main/java/rx/BackpressureOverflow.java +++ b/src/main/java/rx/BackpressureOverflow.java @@ -15,15 +15,43 @@ */ package rx; -import rx.annotations.Experimental; import rx.exceptions.MissingBackpressureException; /** * Generic strategy and default implementations to deal with backpressure buffer overflows. + * + * @since 1.3 */ -@Experimental public final class BackpressureOverflow { + private BackpressureOverflow() { + throw new IllegalStateException("No instances!"); + } + + /** + * Signal a MissingBackressureException due to lack of requests. + */ + public static final BackpressureOverflow.Strategy ON_OVERFLOW_ERROR = Error.INSTANCE; + + /** + * By default, signal a MissingBackressureException due to lack of requests. + */ + public static final BackpressureOverflow.Strategy ON_OVERFLOW_DEFAULT = ON_OVERFLOW_ERROR; + + /** + * Drop the oldest value in the buffer. + */ + public static final BackpressureOverflow.Strategy ON_OVERFLOW_DROP_OLDEST = DropOldest.INSTANCE; + + /** + * Drop the latest value. + */ + public static final BackpressureOverflow.Strategy ON_OVERFLOW_DROP_LATEST = DropLatest.INSTANCE; + + /** + * Represents a callback called when a value is about to be dropped + * due to lack of downstream requests. + */ public interface Strategy { /** @@ -31,26 +59,18 @@ public interface Strategy { * drop the item currently causing backpressure. * * @return true to request drop of the oldest item, false to drop the newest. - * @throws MissingBackpressureException + * @throws MissingBackpressureException if the strategy should signal MissingBackpressureException */ boolean mayAttemptDrop() throws MissingBackpressureException; } - public static final BackpressureOverflow.Strategy ON_OVERFLOW_DEFAULT = Error.INSTANCE; - - public static final BackpressureOverflow.Strategy ON_OVERFLOW_ERROR = Error.INSTANCE; - - public static final BackpressureOverflow.Strategy ON_OVERFLOW_DROP_OLDEST = DropOldest.INSTANCE; - - public static final BackpressureOverflow.Strategy ON_OVERFLOW_DROP_LATEST = DropLatest.INSTANCE; - /** * Drop oldest items from the buffer making room for newer ones. */ - static class DropOldest implements BackpressureOverflow.Strategy { + static final class DropOldest implements BackpressureOverflow.Strategy { static final DropOldest INSTANCE = new DropOldest(); - private DropOldest() {} + private DropOldest() { } @Override public boolean mayAttemptDrop() { @@ -62,10 +82,10 @@ public boolean mayAttemptDrop() { * Drop most recent items, but not {@code onError} nor unsubscribe from source * (as {code OperatorOnBackpressureDrop}). */ - static class DropLatest implements BackpressureOverflow.Strategy { + static final class DropLatest implements BackpressureOverflow.Strategy { static final DropLatest INSTANCE = new DropLatest(); - private DropLatest() {} + private DropLatest() { } @Override public boolean mayAttemptDrop() { @@ -76,11 +96,11 @@ public boolean mayAttemptDrop() { /** * {@code onError} a MissingBackpressureException and unsubscribe from source. */ - static class Error implements BackpressureOverflow.Strategy { + static final class Error implements BackpressureOverflow.Strategy { static final Error INSTANCE = new Error(); - private Error() {} + private Error() { } @Override public boolean mayAttemptDrop() throws MissingBackpressureException { diff --git a/src/main/java/rx/Completable.java b/src/main/java/rx/Completable.java index e5f7c4a068..1ecc1e1993 100644 --- a/src/main/java/rx/Completable.java +++ b/src/main/java/rx/Completable.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -20,87 +20,67 @@ import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicBoolean; -import rx.Observable.OnSubscribe; -import rx.annotations.Experimental; import rx.exceptions.*; import rx.functions.*; +import rx.internal.observers.AssertableSubscriberObservable; import rx.internal.operators.*; import rx.internal.util.*; import rx.observers.*; -import rx.plugins.*; +import rx.plugins.RxJavaHooks; import rx.schedulers.Schedulers; import rx.subscriptions.*; /** * Represents a deferred computation without any value but only indication for completion or exception. - * + * * The class follows a similar event pattern as Reactive-Streams: onSubscribe (onError|onComplete)? + * + * @since 1.3 */ -@Experimental public class Completable { + /** The actual subscription action. */ + private final OnSubscribe onSubscribe; + /** * Callback used for building deferred computations that takes a CompletableSubscriber. */ - public interface CompletableOnSubscribe extends Action1 { - + public interface OnSubscribe extends Action1 { + } - + /** * Convenience interface and callback used by the lift operator that given a child CompletableSubscriber, * return a parent CompletableSubscriber that does any kind of lifecycle-related transformations. */ - public interface CompletableOperator extends Func1 { - - } - - /** - * Represents the subscription API callbacks when subscribing to a Completable instance. - */ - public interface CompletableSubscriber { - /** - * Called once the deferred computation completes normally. - */ - void onCompleted(); - - /** - * Called once if the deferred computation 'throws' an exception. - * @param e the exception, not null. - */ - void onError(Throwable e); - - /** - * Called once by the Completable to set a Subscription on this instance which - * then can be used to cancel the subscription at any time. - * @param d the Subscription instance to call dispose on for cancellation, not null - */ - void onSubscribe(Subscription d); - } - + public interface Operator extends Func1 { + + } + /** * Convenience interface and callback used by the compose operator to turn a Completable into another * Completable fluently. */ - public interface CompletableTransformer extends Func1 { - + public interface Transformer extends Func1 { + } - + /** Single instance of a complete Completable. */ - static final Completable COMPLETE = create(new CompletableOnSubscribe() { + static final Completable COMPLETE = new Completable(new OnSubscribe() { @Override - public void call(CompletableSubscriber s) { + public void call(rx.CompletableSubscriber s) { s.onSubscribe(Subscriptions.unsubscribed()); s.onCompleted(); } - }); - + }, false); // hook is handled in complete() + /** Single instance of a never Completable. */ - static final Completable NEVER = create(new CompletableOnSubscribe() { + static final Completable NEVER = new Completable(new OnSubscribe() { @Override - public void call(CompletableSubscriber s) { + public void call(rx.CompletableSubscriber s) { s.onSubscribe(Subscriptions.unsubscribed()); } - }); - + }, false); // hook is handled in never() + /** * Returns a Completable which terminates as soon as one of the source Completables * terminates (normally or with an error) and cancels all other Completables. @@ -116,16 +96,16 @@ public static Completable amb(final Completable... sources) { if (sources.length == 1) { return sources[0]; } - - return create(new CompletableOnSubscribe() { + + return create(new OnSubscribe() { @Override - public void call(final CompletableSubscriber s) { + public void call(final rx.CompletableSubscriber s) { final CompositeSubscription set = new CompositeSubscription(); s.onSubscribe(set); final AtomicBoolean once = new AtomicBoolean(); - - CompletableSubscriber inner = new CompletableSubscriber() { + + rx.CompletableSubscriber inner = new rx.CompletableSubscriber() { @Override public void onCompleted() { if (once.compareAndSet(false, true)) { @@ -148,9 +128,9 @@ public void onError(Throwable e) { public void onSubscribe(Subscription d) { set.add(d); } - + }; - + for (Completable c : sources) { if (set.isUnsubscribed()) { return; @@ -168,14 +148,14 @@ public void onSubscribe(Subscription d) { if (once.get() || set.isUnsubscribed()) { return; } - + // no need to have separate subscribers because inner is stateless c.unsafeSubscribe(inner); } } }); } - + /** * Returns a Completable which terminates as soon as one of the source Completables * terminates (normally or with an error) and cancels all other Completables. @@ -185,16 +165,32 @@ public void onSubscribe(Subscription d) { */ public static Completable amb(final Iterable sources) { requireNonNull(sources); - - return create(new CompletableOnSubscribe() { + + return create(new OnSubscribe() { @Override - public void call(final CompletableSubscriber s) { + public void call(final rx.CompletableSubscriber s) { final CompositeSubscription set = new CompositeSubscription(); s.onSubscribe(set); + Iterator it; + + try { + it = sources.iterator(); + } catch (Throwable e) { + s.onError(e); + return; + } + + if (it == null) { + s.onError(new NullPointerException("The iterator returned is null")); + return; + } + + boolean empty = true; + final AtomicBoolean once = new AtomicBoolean(); - - CompletableSubscriber inner = new CompletableSubscriber() { + + rx.CompletableSubscriber inner = new rx.CompletableSubscriber() { @Override public void onCompleted() { if (once.compareAndSet(false, true)) { @@ -217,32 +213,16 @@ public void onError(Throwable e) { public void onSubscribe(Subscription d) { set.add(d); } - + }; - - Iterator it; - - try { - it = sources.iterator(); - } catch (Throwable e) { - s.onError(e); - return; - } - - if (it == null) { - s.onError(new NullPointerException("The iterator returned is null")); - return; - } - - boolean empty = true; - + for (;;) { if (once.get() || set.isUnsubscribed()) { return; } - + boolean b; - + try { b = it.hasNext(); } catch (Throwable e) { @@ -254,22 +234,22 @@ public void onSubscribe(Subscription d) { } return; } - + if (!b) { if (empty) { s.onCompleted(); } break; } - + empty = false; - + if (once.get() || set.isUnsubscribed()) { return; } Completable c; - + try { c = it.next(); } catch (Throwable e) { @@ -281,7 +261,7 @@ public void onSubscribe(Subscription d) { } return; } - + if (c == null) { NullPointerException npe = new NullPointerException("One of the sources is null"); if (once.compareAndSet(false, true)) { @@ -292,26 +272,30 @@ public void onSubscribe(Subscription d) { } return; } - + if (once.get() || set.isUnsubscribed()) { return; } - + // no need to have separate subscribers because inner is stateless c.unsafeSubscribe(inner); } } }); } - + /** * Returns a Completable instance that completes immediately when subscribed to. - * @return a Completable instance that completes immediately + * @return a Completable instance that completes immediately */ public static Completable complete() { - return COMPLETE; + OnSubscribe cos = RxJavaHooks.onCreate(COMPLETE.onSubscribe); + if (cos == COMPLETE.onSubscribe) { + return COMPLETE; + } + return new Completable(cos, false); } - + /** * Returns a Completable which completes only when all sources complete, one after another. * @param sources the sources to concatenate @@ -328,7 +312,7 @@ public static Completable concat(Completable... sources) { } return create(new CompletableOnSubscribeConcatArray(sources)); } - + /** * Returns a Completable which completes only when all sources complete, one after another. * @param sources the sources to concatenate @@ -337,10 +321,10 @@ public static Completable concat(Completable... sources) { */ public static Completable concat(Iterable sources) { requireNonNull(sources); - + return create(new CompletableOnSubscribeConcatIterable(sources)); } - + /** * Returns a Completable which completes only when all sources complete, one after another. * @param sources the sources to concatenate @@ -350,7 +334,7 @@ public static Completable concat(Iterable sources) { public static Completable concat(Observable sources) { return concat(sources, 2); } - + /** * Returns a Completable which completes only when all sources complete, one after another. * @param sources the sources to concatenate @@ -365,7 +349,7 @@ public static Completable concat(Observable sources, int } return create(new CompletableOnSubscribeConcat(sources, prefetch)); } - + /** * Constructs a Completable instance by wrapping the given onSubscribe callback. * @param onSubscribe the callback which will receive the CompletableSubscriber instances @@ -373,18 +357,18 @@ public static Completable concat(Observable sources, int * @return the created Completable instance * @throws NullPointerException if onSubscribe is null */ - public static Completable create(CompletableOnSubscribe onSubscribe) { + public static Completable create(OnSubscribe onSubscribe) { requireNonNull(onSubscribe); try { return new Completable(onSubscribe); - } catch (NullPointerException ex) { + } catch (NullPointerException ex) { // NOPMD throw ex; } catch (Throwable ex) { RxJavaHooks.onError(ex); throw toNpe(ex); - } + } } - + /** * Defers the subscription to a Completable instance returned by a supplier. * @param completableFunc0 the supplier that returns the Completable that will be subscribed to. @@ -392,11 +376,11 @@ public static Completable create(CompletableOnSubscribe onSubscribe) { */ public static Completable defer(final Func0 completableFunc0) { requireNonNull(completableFunc0); - return create(new CompletableOnSubscribe() { + return create(new OnSubscribe() { @Override - public void call(CompletableSubscriber s) { + public void call(rx.CompletableSubscriber s) { Completable c; - + try { c = completableFunc0.call(); } catch (Throwable e) { @@ -404,18 +388,18 @@ public void call(CompletableSubscriber s) { s.onError(e); return; } - + if (c == null) { s.onSubscribe(Subscriptions.unsubscribed()); s.onError(new NullPointerException("The completable returned is null")); return; } - + c.unsafeSubscribe(s); } }); } - + /** * Creates a Completable which calls the given error supplier for each subscriber * and emits its returned Throwable. @@ -428,18 +412,18 @@ public void call(CompletableSubscriber s) { */ public static Completable error(final Func0 errorFunc0) { requireNonNull(errorFunc0); - return create(new CompletableOnSubscribe() { + return create(new OnSubscribe() { @Override - public void call(CompletableSubscriber s) { + public void call(rx.CompletableSubscriber s) { s.onSubscribe(Subscriptions.unsubscribed()); Throwable error; - + try { error = errorFunc0.call(); } catch (Throwable e) { error = e; } - + if (error == null) { error = new NullPointerException("The error supplied is null"); } @@ -456,15 +440,15 @@ public void call(CompletableSubscriber s) { */ public static Completable error(final Throwable error) { requireNonNull(error); - return create(new CompletableOnSubscribe() { + return create(new OnSubscribe() { @Override - public void call(CompletableSubscriber s) { + public void call(rx.CompletableSubscriber s) { s.onSubscribe(Subscriptions.unsubscribed()); s.onError(error); } }); } - + /** * Returns a Completable instance that runs the given Action0 for each subscriber and * emits either an unchecked exception or simply completes. @@ -474,9 +458,9 @@ public void call(CompletableSubscriber s) { */ public static Completable fromAction(final Action0 action) { requireNonNull(action); - return create(new CompletableOnSubscribe() { + return create(new OnSubscribe() { @Override - public void call(CompletableSubscriber s) { + public void call(rx.CompletableSubscriber s) { BooleanSubscription bs = new BooleanSubscription(); s.onSubscribe(bs); try { @@ -493,7 +477,7 @@ public void call(CompletableSubscriber s) { } }); } - + /** * Returns a Completable which when subscribed, executes the callable function, ignores its * normal result and emits onError or onCompleted only. @@ -502,9 +486,9 @@ public void call(CompletableSubscriber s) { */ public static Completable fromCallable(final Callable callable) { requireNonNull(callable); - return create(new CompletableOnSubscribe() { + return create(new OnSubscribe() { @Override - public void call(CompletableSubscriber s) { + public void call(rx.CompletableSubscriber s) { BooleanSubscription bs = new BooleanSubscription(); s.onSubscribe(bs); try { @@ -521,7 +505,44 @@ public void call(CompletableSubscriber s) { } }); } - + + /** + * Provides an API (in a cold Completable) that bridges the Completable-reactive world + * with the callback-based world. + *

The {@link CompletableEmitter} allows registering a callback for + * cancellation/unsubscription of a resource. + *

+ * Example: + *


+     * Completable.fromEmitter(emitter -> {
+     *     Callback listener = new Callback() {
+     *         @Override
+     *         public void onEvent(Event e) {
+     *             emitter.onCompleted();
+     *         }
+     *
+     *         @Override
+     *         public void onFailure(Exception e) {
+     *             emitter.onError(e);
+     *         }
+     *     };
+     *
+     *     AutoCloseable c = api.someMethod(listener);
+     *
+     *     emitter.setCancellation(c::close);
+     *
+     * });
+     * 
+ *

All of the CompletableEmitter's methods are thread-safe and ensure the + * Completable's protocol are held. + * @param producer the callback invoked for each incoming CompletableSubscriber + * @return the new Completable instance + * @since 1.3 + */ + public static Completable fromEmitter(Action1 producer) { + return create(new CompletableFromEmitter(producer)); + } + /** * Returns a Completable instance that reacts to the termination of the given Future in a blocking fashion. *

@@ -533,7 +554,7 @@ public static Completable fromFuture(Future future) { requireNonNull(future); return fromObservable(Observable.from(future)); } - + /** * Returns a Completable instance that subscribes to the given flowable, ignores all values and * emits only the terminal event. @@ -543,9 +564,9 @@ public static Completable fromFuture(Future future) { */ public static Completable fromObservable(final Observable flowable) { requireNonNull(flowable); - return create(new CompletableOnSubscribe() { + return create(new OnSubscribe() { @Override - public void call(final CompletableSubscriber cs) { + public void call(final rx.CompletableSubscriber cs) { Subscriber subscriber = new Subscriber() { @Override @@ -578,9 +599,9 @@ public void onNext(Object t) { */ public static Completable fromSingle(final Single single) { requireNonNull(single); - return create(new CompletableOnSubscribe() { + return create(new OnSubscribe() { @Override - public void call(final CompletableSubscriber s) { + public void call(final rx.CompletableSubscriber s) { SingleSubscriber te = new SingleSubscriber() { @Override @@ -592,14 +613,14 @@ public void onError(Throwable e) { public void onSuccess(Object value) { s.onCompleted(); } - + }; s.onSubscribe(te); single.subscribe(te); } }); } - + /** * 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. @@ -617,7 +638,7 @@ public static Completable merge(Completable... sources) { } return create(new CompletableOnSubscribeMergeArray(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. @@ -640,7 +661,7 @@ public static Completable merge(Iterable sources) { public static Completable merge(Observable sources) { return merge0(sources, Integer.MAX_VALUE, false); } - + /** * 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. @@ -652,9 +673,9 @@ public static Completable merge(Observable sources) { */ public static Completable merge(Observable sources, int maxConcurrency) { return merge0(sources, maxConcurrency, false); - + } - + /** * Returns a Completable instance that keeps subscriptions to a limited number of sources at once and * completes only when all source Completables terminate in one way or another, combining any exceptions @@ -673,7 +694,7 @@ protected static Completable merge0(Observable sources, i } return create(new CompletableOnSubscribeMerge(sources, maxConcurrency, delayErrors)); } - + /** * Returns a Completable 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 @@ -712,10 +733,10 @@ public static Completable mergeDelayError(Observable sour return merge0(sources, Integer.MAX_VALUE, true); } - + /** - * Returns a Completable that subscribes to a limited number of inner Completables at once in - * the source sequence and delays any error emitted by either the sources + * Returns a Completable that subscribes to a limited number of inner Completables at once 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. * @param sources the sequence of Completables @@ -726,15 +747,19 @@ public static Completable mergeDelayError(Observable sour public static Completable mergeDelayError(Observable sources, int maxConcurrency) { return merge0(sources, maxConcurrency, true); } - + /** * Returns a Completable that never calls onError or onComplete. * @return the singleton instance that never calls onError or onComplete */ public static Completable never() { - return NEVER; + OnSubscribe cos = RxJavaHooks.onCreate(NEVER.onSubscribe); + if (cos == NEVER.onSubscribe) { + return NEVER; + } + return new Completable(cos, false); } - + /** * Java 7 backport: throws a NullPointerException if o is null. * @param o the object to check @@ -747,7 +772,7 @@ static T requireNonNull(T o) { } return o; } - + /** * Returns a Completable instance that fires its onComplete event after the given delay elapsed. * @param delay the delay time @@ -757,7 +782,7 @@ static T requireNonNull(T o) { public static Completable timer(long delay, TimeUnit unit) { return timer(delay, unit, Schedulers.computation()); } - + /** * Returns a Completable instance that fires its onCompleted event after the given delay elapsed * by using the supplied scheduler. @@ -769,9 +794,9 @@ public static Completable timer(long delay, TimeUnit unit) { public static Completable timer(final long delay, final TimeUnit unit, final Scheduler scheduler) { requireNonNull(unit); requireNonNull(scheduler); - return create(new CompletableOnSubscribe() { + return create(new OnSubscribe() { @Override - public void call(final CompletableSubscriber s) { + public void call(final rx.CompletableSubscriber s) { MultipleAssignmentSubscription mad = new MultipleAssignmentSubscription(); s.onSubscribe(mad); if (!mad.isUnsubscribed()) { @@ -791,7 +816,7 @@ public void call() { } }); } - + /** * Creates a NullPointerException instance and sets the given Throwable as its initial cause. * @param ex the Throwable instance to use as cause, not null (not verified) @@ -802,33 +827,33 @@ static NullPointerException toNpe(Throwable ex) { npe.initCause(ex); return npe; } - + /** - * Returns a Completable instance which manages a resource along + * Returns a Completable instance which manages a resource along * with a custom Completable instance while the subscription is active. *

* This overload performs an eager unsubscription before the terminal event is emitted. - * + * * @param the resource type - * @param resourceFunc0 the supplier that returns a resource to be managed. + * @param resourceFunc0 the supplier that returns a resource to be managed. * @param completableFunc1 the function that given a resource returns a Completable instance that will be subscribed to * @param disposer the consumer that disposes the resource created by the resource supplier * @return the new Completable instance */ - public static Completable using(Func0 resourceFunc0, - Func1 completableFunc1, + public static Completable using(Func0 resourceFunc0, + Func1 completableFunc1, Action1 disposer) { return using(resourceFunc0, completableFunc1, disposer, true); } - + /** - * Returns a Completable instance which manages a resource along + * Returns a Completable instance which manages a resource along * with a custom Completable instance while the subscription is active and performs eager or lazy * resource disposition. *

* If this overload performs a lazy unsubscription after the terminal event is emitted. * Exceptions thrown at this time will be delivered to RxJavaPlugins only. - * + * * @param the resource type * @param resourceFunc0 the supplier that returns a resource to be managed * @param completableFunc1 the function that given a resource returns a non-null @@ -838,19 +863,19 @@ public static Completable using(Func0 resourceFunc0, * resource is disposed after the terminal event has been emitted * @return the new Completable instance */ - public static Completable using(final Func0 resourceFunc0, - final Func1 completableFunc1, - final Action1 disposer, + public static Completable using(final Func0 resourceFunc0, + final Func1 completableFunc1, + final Action1 disposer, final boolean eager) { requireNonNull(resourceFunc0); requireNonNull(completableFunc1); requireNonNull(disposer); - - return create(new CompletableOnSubscribe() { + + return create(new OnSubscribe() { @Override - public void call(final CompletableSubscriber s) { - final R resource; - + public void call(final rx.CompletableSubscriber s) { + final R resource; // NOPMD + try { resource = resourceFunc0.call(); } catch (Throwable e) { @@ -858,9 +883,9 @@ public void call(final CompletableSubscriber s) { s.onError(e); return; } - + Completable cs; - + try { cs = completableFunc1.call(resource); } catch (Throwable e) { @@ -875,12 +900,12 @@ public void call(final CompletableSubscriber s) { return; } Exceptions.throwIfFatal(e); - + s.onSubscribe(Subscriptions.unsubscribed()); s.onError(e); return; } - + if (cs == null) { try { disposer.call(resource); @@ -895,10 +920,10 @@ public void call(final CompletableSubscriber s) { s.onError(new NullPointerException("The completable supplied is null")); return; } - + final AtomicBoolean once = new AtomicBoolean(); - - cs.unsafeSubscribe(new CompletableSubscriber() { + + cs.unsafeSubscribe(new rx.CompletableSubscriber() { Subscription d; void dispose() { d.unsubscribe(); @@ -923,9 +948,9 @@ public void onCompleted() { } } } - + s.onCompleted(); - + if (!eager) { dispose(); } @@ -942,14 +967,14 @@ public void onError(Throwable e) { } } } - + s.onError(e); - + if (!eager) { dispose(); } } - + @Override public void onSubscribe(Subscription d) { this.d = d; @@ -964,19 +989,27 @@ public void call() { } }); } - - /** The actual subscription action. */ - private final CompletableOnSubscribe onSubscribe; - + /** * Constructs a Completable instance with the given onSubscribe callback. * @param onSubscribe the callback that will receive CompletableSubscribers when they subscribe, * not null (not verified) */ - protected Completable(CompletableOnSubscribe onSubscribe) { + protected Completable(OnSubscribe onSubscribe) { this.onSubscribe = RxJavaHooks.onCreate(onSubscribe); } - + + /** + * Constructs a Completable instance with the given onSubscribe callback without calling the onCreate + * hook. + * @param onSubscribe the callback that will receive CompletableSubscribers when they subscribe, + * not null (not verified) + * @param useHook if false, RxJavaHooks.onCreate won't be called + */ + protected Completable(OnSubscribe onSubscribe, boolean useHook) { + this.onSubscribe = useHook ? RxJavaHooks.onCreate(onSubscribe) : onSubscribe; + } + /** * Returns a Completable that emits the a terminated event of either this Completable * or the other Completable whichever fires first. @@ -988,7 +1021,7 @@ public final Completable ambWith(Completable other) { requireNonNull(other); return amb(this, other); } - + /** * Subscribes to and awaits the termination of this Completable instance in a blocking manner and * rethrows any exception emitted. @@ -997,8 +1030,8 @@ public final Completable ambWith(Completable other) { public final void await() { final CountDownLatch cdl = new CountDownLatch(1); final Throwable[] err = new Throwable[1]; - - unsafeSubscribe(new CompletableSubscriber() { + + unsafeSubscribe(new rx.CompletableSubscriber() { @Override public void onCompleted() { @@ -1015,9 +1048,9 @@ public void onError(Throwable e) { public void onSubscribe(Subscription d) { // ignored } - + }); - + if (cdl.getCount() == 0) { if (err[0] != null) { Exceptions.propagate(err[0]); @@ -1033,7 +1066,7 @@ public void onSubscribe(Subscription d) { Exceptions.propagate(err[0]); } } - + /** * 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. @@ -1045,11 +1078,11 @@ public void onSubscribe(Subscription d) { */ public final boolean await(long timeout, TimeUnit unit) { requireNonNull(unit); - + final CountDownLatch cdl = new CountDownLatch(1); final Throwable[] err = new Throwable[1]; - - unsafeSubscribe(new CompletableSubscriber() { + + unsafeSubscribe(new rx.CompletableSubscriber() { @Override public void onCompleted() { @@ -1066,9 +1099,9 @@ public void onError(Throwable e) { public void onSubscribe(Subscription d) { // ignored } - + }); - + if (cdl.getCount() == 0) { if (err[0] != null) { Exceptions.propagate(err[0]); @@ -1088,7 +1121,7 @@ public void onSubscribe(Subscription d) { } return b; } - + /** * Calls the given transformer function with this instance and returns the function's resulting * Completable. @@ -1096,16 +1129,16 @@ public void onSubscribe(Subscription d) { * @return the Completable returned by the function * @throws NullPointerException if transformer is null */ - public final Completable compose(CompletableTransformer transformer) { + public final Completable compose(Transformer transformer) { return to(transformer); } - + /** - * Returns an Observable which will subscribe to this Completable and once that is completed then - * will subscribe to the {@code next} Observable. An error event from this Completable will be - * propagated to the downstream subscriber and will result in skipping the subscription of the - * Observable. - * + * Returns an Observable which will subscribe to this Completable and once that is completed then + * will subscribe to the {@code next} Observable. An error event from this Completable will be + * propagated to the downstream subscriber and will result in skipping the subscription of the + * Observable. + * * @param the value type of the next Observable * @param next the Observable to subscribe after this Completable is completed, not null * @return Observable that composes this Completable and next @@ -1147,7 +1180,7 @@ public final Single andThen(Single next) { public final Completable andThen(Completable next) { return concatWith(next); } - + /** * Concatenates this Completable with another Completable. * @param other the other Completable, not null @@ -1169,7 +1202,7 @@ public final Completable concatWith(Completable other) { public final Completable delay(long delay, TimeUnit unit) { return delay(delay, unit, Schedulers.computation(), false); } - + /** * Returns a Completable which delays the emission of the completion event by the given time while * running on the specified scheduler. @@ -1182,7 +1215,7 @@ public final Completable delay(long delay, TimeUnit unit) { public final Completable delay(long delay, TimeUnit unit, Scheduler scheduler) { return delay(delay, unit, scheduler, false); } - + /** * 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. @@ -1196,17 +1229,17 @@ public final Completable delay(long delay, TimeUnit unit, Scheduler scheduler) { public final Completable delay(final long delay, final TimeUnit unit, final Scheduler scheduler, final boolean delayError) { requireNonNull(unit); requireNonNull(scheduler); - return create(new CompletableOnSubscribe() { + return create(new OnSubscribe() { @Override - public void call(final CompletableSubscriber s) { + public void call(final rx.CompletableSubscriber s) { final CompositeSubscription set = new CompositeSubscription(); - + final Scheduler.Worker w = scheduler.createWorker(); set.add(w); - - unsafeSubscribe(new CompletableSubscriber() { - + unsafeSubscribe(new rx.CompletableSubscriber() { + + @Override public void onCompleted() { set.add(w.schedule(new Action0() { @@ -1244,33 +1277,46 @@ public void onSubscribe(Subscription d) { set.add(d); s.onSubscribe(set); } - + }); } }); } /** - * Returns a Completable which calls the given onComplete callback if this Completable completes. - * @param onComplete the callback to call when this emits an onComplete event + * Returns a Completable which calls the given onCompleted callback if this Completable completes. + * @param onCompleted the callback to call when this emits an onComplete event * @return the new Completable instance * @throws NullPointerException if onComplete is null - * @deprecated Use {@link #doOnCompleted(Action0)} instead. */ - @Deprecated public final Completable doOnComplete(Action0 onComplete) { - return doOnCompleted(onComplete); + public final Completable doOnCompleted(Action0 onCompleted) { + return doOnLifecycle(Actions.empty(), Actions.empty(), onCompleted, Actions.empty(), Actions.empty()); } /** - * Returns a Completable which calls the given onCompleted callback if this Completable completes. - * @param onCompleted the callback to call when this emits an onComplete event + * Returns a Completable which calls the given onNotification callback when this Completable emits an error or completes. + * @param onNotification the notification callback * @return the new Completable instance - * @throws NullPointerException if onComplete is null + * @throws NullPointerException if onNotification is null */ - public final Completable doOnCompleted(Action0 onCompleted) { - return doOnLifecycle(Actions.empty(), Actions.empty(), onCompleted, Actions.empty(), Actions.empty()); + public final Completable doOnEach(final Action1> onNotification) { + if (onNotification == null) { + throw new IllegalArgumentException("onNotification is null"); + } + + return doOnLifecycle(Actions.empty(), new Action1() { + @Override + public void call(final Throwable throwable) { + onNotification.call(Notification.createOnError(throwable)); + } + }, new Action0() { + @Override + public void call() { + onNotification.call(Notification.createOnCompleted()); + } + }, Actions.empty(), Actions.empty()); } - + /** * Returns a Completable which calls the given onUnsubscribe callback if the child subscriber cancels * the subscription. @@ -1281,7 +1327,7 @@ public final Completable doOnCompleted(Action0 onCompleted) { public final Completable doOnUnsubscribe(Action0 onUnsubscribe) { return doOnLifecycle(Actions.empty(), Actions.empty(), Actions.empty(), Actions.empty(), onUnsubscribe); } - + /** * Returns a Completable which calls the given onError callback if this Completable emits an error. * @param onError the error callback @@ -1298,25 +1344,25 @@ public final Completable doOnError(Action1 onError) { * @param onSubscribe the consumer called when a CompletableSubscriber subscribes. * @param onError the consumer called when this emits an onError event * @param onComplete the runnable called just before when this Completable completes normally - * @param onAfterComplete the runnable called after this Completable completes normally + * @param onAfterTerminate the runnable called after this Completable terminates * @param onUnsubscribe the runnable called when the child cancels the subscription * @return the new Completable instance */ protected final Completable doOnLifecycle( - final Action1 onSubscribe, - final Action1 onError, - final Action0 onComplete, - final Action0 onAfterComplete, + final Action1 onSubscribe, + final Action1 onError, + final Action0 onComplete, + final Action0 onAfterTerminate, final Action0 onUnsubscribe) { requireNonNull(onSubscribe); requireNonNull(onError); requireNonNull(onComplete); - requireNonNull(onAfterComplete); + requireNonNull(onAfterTerminate); requireNonNull(onUnsubscribe); - return create(new CompletableOnSubscribe() { + return create(new OnSubscribe() { @Override - public void call(final CompletableSubscriber s) { - unsafeSubscribe(new CompletableSubscriber() { + public void call(final rx.CompletableSubscriber s) { + unsafeSubscribe(new rx.CompletableSubscriber() { @Override public void onCompleted() { @@ -1326,11 +1372,11 @@ public void onCompleted() { s.onError(e); return; } - + s.onCompleted(); - + try { - onAfterComplete.call(); + onAfterTerminate.call(); } catch (Throwable e) { RxJavaHooks.onError(e); } @@ -1343,13 +1389,19 @@ public void onError(Throwable e) { } catch (Throwable ex) { e = new CompositeException(Arrays.asList(e, ex)); } - + s.onError(e); + + try { + onAfterTerminate.call(); + } catch (Throwable ex) { + RxJavaHooks.onError(ex); + } } @Override public void onSubscribe(final Subscription d) { - + try { onSubscribe.call(d); } catch (Throwable ex) { @@ -1358,7 +1410,7 @@ public void onSubscribe(final Subscription d) { s.onError(ex); return; } - + s.onSubscribe(Subscriptions.create(new Action0() { @Override public void call() { @@ -1371,12 +1423,12 @@ public void call() { } })); } - + }); } }); } - + /** * Returns a Completable instance that calls the given onSubscribe callback with the disposable * that child subscribers receive on subscription. @@ -1387,7 +1439,7 @@ public void call() { public final Completable doOnSubscribe(Action1 onSubscribe) { return doOnLifecycle(onSubscribe, Actions.empty(), Actions.empty(), Actions.empty(), Actions.empty()); } - + /** * Returns a Completable instance that calls the given onTerminate callback just before this Completable * completes normally or with an exception @@ -1402,46 +1454,18 @@ public void call(Throwable e) { } }, onTerminate, Actions.empty(), Actions.empty()); } - - /** - * Returns a completable that first runs this Completable - * and then the other completable. - *

- * This is an alias for {@link #concatWith(Completable)}. - * @param other the other Completable, not null - * @return the new Completable instance - * @throws NullPointerException if other is null - * @deprecated Use {@link #andThen(rx.Completable)} instead. - */ - @Deprecated - public final Completable endWith(Completable other) { - return andThen(other); - } - - /** - * Returns an Observable that first runs this Completable instance and - * resumes with the given next Observable. - * @param the value type of the next Observable - * @param next the next Observable to continue - * @return the new Observable instance - * @deprecated Use {@link #andThen(rx.Observable)} instead. - */ - @Deprecated - public final Observable endWith(Observable next) { - return andThen(next); - } /** * Returns a Completable instance that calls the given onAfterComplete callback after this * Completable completes normally. - * @param onAfterComplete the callback to call after this Completable emits an onComplete event. + * @param onAfterTerminate the callback to call after this Completable emits an onCompleted or onError event. * @return the new Completable instance * @throws NullPointerException if onAfterComplete is null */ - public final Completable doAfterTerminate(Action0 onAfterComplete) { - return doOnLifecycle(Actions.empty(), Actions.empty(), Actions.empty(), onAfterComplete, Actions.empty()); + public final Completable doAfterTerminate(Action0 onAfterTerminate) { + return doOnLifecycle(Actions.empty(), Actions.empty(), Actions.empty(), onAfterTerminate, Actions.empty()); } - + /** * Subscribes to this Completable instance and blocks until it terminates, then returns null or * the emitted exception if any. @@ -1451,8 +1475,8 @@ public final Completable doAfterTerminate(Action0 onAfterComplete) { public final Throwable get() { final CountDownLatch cdl = new CountDownLatch(1); final Throwable[] err = new Throwable[1]; - - unsafeSubscribe(new CompletableSubscriber() { + + unsafeSubscribe(new rx.CompletableSubscriber() { @Override public void onCompleted() { @@ -1469,9 +1493,9 @@ public void onError(Throwable e) { public void onSubscribe(Subscription d) { // ignored } - + }); - + if (cdl.getCount() == 0) { return err[0]; } @@ -1482,9 +1506,9 @@ public void onSubscribe(Subscription d) { } return err[0]; } - + /** - * Subscribes to this Completable instance and blocks until it terminates or the specified timeout + * 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. * @param timeout the time amount to wait for the terminal event * @param unit the time unit of the timeout parameter @@ -1494,11 +1518,11 @@ public void onSubscribe(Subscription d) { */ public final Throwable get(long timeout, TimeUnit unit) { requireNonNull(unit); - + final CountDownLatch cdl = new CountDownLatch(1); final Throwable[] err = new Throwable[1]; - - unsafeSubscribe(new CompletableSubscriber() { + + unsafeSubscribe(new rx.CompletableSubscriber() { @Override public void onCompleted() { @@ -1515,9 +1539,9 @@ public void onError(Throwable e) { public void onSubscribe(Subscription d) { // ignored } - + }); - + if (cdl.getCount() == 0) { return err[0]; } @@ -1533,24 +1557,24 @@ public void onSubscribe(Subscription d) { Exceptions.propagate(new TimeoutException()); return null; } - + /** * Lifts a CompletableSubscriber transformation into the chain of Completables. * @param onLift the lifting function that transforms the child subscriber with a parent subscriber. * @return the new Completable instance * @throws NullPointerException if onLift is null */ - public final Completable lift(final CompletableOperator onLift) { + public final Completable lift(final Operator onLift) { requireNonNull(onLift); - return create(new CompletableOnSubscribe() { + return create(new OnSubscribe() { @Override - public void call(CompletableSubscriber s) { + public void call(rx.CompletableSubscriber s) { try { - CompletableOperator onLiftDecorated = RxJavaHooks.onCompletableLift(onLift); - CompletableSubscriber sw = onLiftDecorated.call(s); - + Operator onLiftDecorated = RxJavaHooks.onCompletableLift(onLift); + rx.CompletableSubscriber sw = onLiftDecorated.call(s); + unsafeSubscribe(sw); - } catch (NullPointerException ex) { + } catch (NullPointerException ex) { // NOPMD throw ex; } catch (Throwable ex) { throw toNpe(ex); @@ -1570,7 +1594,7 @@ public final Completable mergeWith(Completable other) { requireNonNull(other); return merge(this, other); } - + /** * Returns a Completable which emits the terminal events from the thread of the specified scheduler. * @param scheduler the scheduler to emit terminal events on @@ -1579,18 +1603,18 @@ public final Completable mergeWith(Completable other) { */ public final Completable observeOn(final Scheduler scheduler) { requireNonNull(scheduler); - return create(new CompletableOnSubscribe() { + return create(new OnSubscribe() { @Override - public void call(final CompletableSubscriber s) { - + public void call(final rx.CompletableSubscriber s) { + final SubscriptionList ad = new SubscriptionList(); - + final Scheduler.Worker w = scheduler.createWorker(); ad.add(w); - + s.onSubscribe(ad); - - unsafeSubscribe(new CompletableSubscriber() { + + unsafeSubscribe(new rx.CompletableSubscriber() { @Override public void onCompleted() { @@ -1624,12 +1648,12 @@ public void call() { public void onSubscribe(Subscription d) { ad.add(d); } - + }); } }); } - + /** * Returns a Completable instance that if this Completable emits an error, it will emit an onComplete * and swallow the throwable. @@ -1642,17 +1666,17 @@ 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. - * @param predicate the predicate to call when an Throwable is emitted which should return true + * @param predicate the predicate to call when a Throwable is emitted which should return true * if the Throwable should be swallowed and replaced with an onComplete. * @return the new Completable instance */ public final Completable onErrorComplete(final Func1 predicate) { requireNonNull(predicate); - - return create(new CompletableOnSubscribe() { + + return create(new OnSubscribe() { @Override - public void call(final CompletableSubscriber s) { - unsafeSubscribe(new CompletableSubscriber() { + public void call(final rx.CompletableSubscriber s) { + unsafeSubscribe(new rx.CompletableSubscriber() { @Override public void onCompleted() { @@ -1662,7 +1686,7 @@ public void onCompleted() { @Override public void onError(Throwable e) { boolean b; - + try { b = predicate.call(e); } catch (Throwable ex) { @@ -1670,7 +1694,7 @@ public void onError(Throwable e) { e = new CompositeException(Arrays.asList(e, ex)); b = false; } - + if (b) { s.onCompleted(); } else { @@ -1682,12 +1706,12 @@ public void onError(Throwable e) { public void onSubscribe(Subscription d) { s.onSubscribe(d); } - + }); } }); } - + /** * 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 @@ -1698,11 +1722,12 @@ public void onSubscribe(Subscription d) { */ public final Completable onErrorResumeNext(final Func1 errorMapper) { requireNonNull(errorMapper); - return create(new CompletableOnSubscribe() { + return create(new OnSubscribe() { @Override - public void call(final CompletableSubscriber s) { + public void call(final rx.CompletableSubscriber s) { final SerialSubscription sd = new SerialSubscription(); - unsafeSubscribe(new CompletableSubscriber() { + s.onSubscribe(sd); + unsafeSubscribe(new rx.CompletableSubscriber() { @Override public void onCompleted() { @@ -1712,7 +1737,7 @@ public void onCompleted() { @Override public void onError(Throwable e) { Completable c; - + try { c = errorMapper.call(e); } catch (Throwable ex) { @@ -1720,15 +1745,15 @@ public void onError(Throwable e) { s.onError(e); return; } - + if (c == null) { NullPointerException npe = new NullPointerException("The completable returned is null"); e = new CompositeException(Arrays.asList(e, npe)); s.onError(e); return; } - - c.unsafeSubscribe(new CompletableSubscriber() { + + c.unsafeSubscribe(new rx.CompletableSubscriber() { @Override public void onCompleted() { @@ -1744,7 +1769,7 @@ public void onError(Throwable e) { public void onSubscribe(Subscription d) { sd.set(d); } - + }); } @@ -1752,12 +1777,12 @@ public void onSubscribe(Subscription d) { public void onSubscribe(Subscription d) { sd.set(d); } - + }); } }); } - + /** * Returns a Completable that repeatedly subscribes to this Completable until cancelled. * @return the new Completable instance @@ -1765,7 +1790,7 @@ public void onSubscribe(Subscription d) { public final Completable repeat() { return fromObservable(toObservable().repeat()); } - + /** * Returns a Completable that subscribes repeatedly at most the given times to this Completable. * @param times the number of times the resubscription should happen @@ -1775,7 +1800,7 @@ public final Completable repeat() { public final Completable repeat(long times) { return fromObservable(toObservable().repeat(times)); } - + /** * 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. @@ -1789,7 +1814,7 @@ public final Completable repeatWhen(Func1, ? requireNonNull(handler); // FIXME do a null check in Observable return fromObservable(toObservable().repeatWhen(handler)); } - + /** * Returns a Completable that retries this Completable as long as it emits an onError event. * @return the new Completable instance @@ -1797,7 +1822,7 @@ public final Completable repeatWhen(Func1, ? public final Completable retry() { return fromObservable(toObservable().retry()); } - + /** * Returns a Completable that retries this Completable in case of an error as long as the predicate * returns true. @@ -1857,7 +1882,7 @@ public final Observable startWith(Observable other) { requireNonNull(other); return this.toObservable().startWith(other); } - + /** * Subscribes to this Completable and returns a Subscription which can be used to cancel * the subscription. @@ -1865,25 +1890,25 @@ public final Observable startWith(Observable other) { */ public final Subscription subscribe() { final MultipleAssignmentSubscription mad = new MultipleAssignmentSubscription(); - unsafeSubscribe(new CompletableSubscriber() { + unsafeSubscribe(new rx.CompletableSubscriber() { @Override public void onCompleted() { mad.unsubscribe(); } - + @Override public void onError(Throwable e) { RxJavaHooks.onError(e); mad.unsubscribe(); deliverUncaughtException(e); } - + @Override public void onSubscribe(Subscription d) { mad.set(d); } }); - + return mad; } /** @@ -1896,9 +1921,9 @@ public void onSubscribe(Subscription d) { */ public final Subscription subscribe(final Action0 onComplete) { requireNonNull(onComplete); - + final MultipleAssignmentSubscription mad = new MultipleAssignmentSubscription(); - unsafeSubscribe(new CompletableSubscriber() { + unsafeSubscribe(new rx.CompletableSubscriber() { boolean done; @Override public void onCompleted() { @@ -1914,37 +1939,37 @@ public void onCompleted() { } } } - + @Override public void onError(Throwable e) { RxJavaHooks.onError(e); mad.unsubscribe(); deliverUncaughtException(e); } - + @Override public void onSubscribe(Subscription d) { mad.set(d); } }); - + return mad; } /** * Subscribes to this Completable and calls back either the onError or onComplete functions. - * - * @param onError the consumer that is called if this Completable emits an error + * * @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 Subscription that can be used for cancelling the subscription asynchronously * @throws NullPointerException if either callback is null */ - public final Subscription subscribe(final Action1 onError, final Action0 onComplete) { - requireNonNull(onError); + public final Subscription subscribe(final Action0 onComplete, final Action1 onError) { requireNonNull(onComplete); - + requireNonNull(onError); + final MultipleAssignmentSubscription mad = new MultipleAssignmentSubscription(); - unsafeSubscribe(new CompletableSubscriber() { + unsafeSubscribe(new rx.CompletableSubscriber() { boolean done; @Override public void onCompleted() { @@ -1959,7 +1984,7 @@ public void onCompleted() { mad.unsubscribe(); } } - + @Override public void onError(Throwable e) { if (!done) { @@ -1970,7 +1995,7 @@ public void onError(Throwable e) { deliverUncaughtException(e); } } - + void callOnError(Throwable e) { try { onError.call(e); @@ -1982,17 +2007,17 @@ void callOnError(Throwable e) { mad.unsubscribe(); } } - + @Override public void onSubscribe(Subscription d) { mad.set(d); } }); - + return mad; } - private static void deliverUncaughtException(Throwable e) { + static void deliverUncaughtException(Throwable e) { Thread thread = Thread.currentThread(); thread.getUncaughtExceptionHandler().uncaughtException(thread, e); } @@ -2002,13 +2027,13 @@ private static void deliverUncaughtException(Throwable e) { * @param s the CompletableSubscriber, not null * @throws NullPointerException if s is null */ - public final void unsafeSubscribe(CompletableSubscriber s) { + public final void unsafeSubscribe(rx.CompletableSubscriber s) { requireNonNull(s); try { - CompletableOnSubscribe onSubscribeDecorated = RxJavaHooks.onCompletableStart(this, this.onSubscribe); - + OnSubscribe onSubscribeDecorated = RxJavaHooks.onCompletableStart(this, this.onSubscribe); + onSubscribeDecorated.call(s); - } catch (NullPointerException ex) { + } catch (NullPointerException ex) { // NOPMD throw ex; } catch (Throwable ex) { Exceptions.throwIfFatal(ex); @@ -2024,7 +2049,7 @@ public final void unsafeSubscribe(CompletableSubscriber s) { * @param s the CompletableSubscriber, not null * @throws NullPointerException if s is null */ - public final void subscribe(CompletableSubscriber s) { + public final void subscribe(rx.CompletableSubscriber s) { if (!(s instanceof SafeCompletableSubscriber)) { s = new SafeCompletableSubscriber(s); } @@ -2049,30 +2074,30 @@ public final void unsafeSubscribe(final Subscriber s) { * @param callOnStart if true, the Subscriber.onStart will be called * @throws NullPointerException if s is null */ - private final void unsafeSubscribe(final Subscriber s, boolean callOnStart) { + private void unsafeSubscribe(final Subscriber s, boolean callOnStart) { requireNonNull(s); try { if (callOnStart) { s.onStart(); } - unsafeSubscribe(new CompletableSubscriber() { + unsafeSubscribe(new rx.CompletableSubscriber() { @Override public void onCompleted() { s.onCompleted(); } - + @Override public void onError(Throwable e) { s.onError(e); } - + @Override public void onSubscribe(Subscription d) { s.add(d); } }); RxJavaHooks.onObservableReturn(s); - } catch (NullPointerException ex) { + } catch (NullPointerException ex) { // NOPMD throw ex; } catch (Throwable ex) { Exceptions.throwIfFatal(ex); @@ -2107,14 +2132,14 @@ public final void subscribe(Subscriber s) { */ public final Completable subscribeOn(final Scheduler scheduler) { requireNonNull(scheduler); - - return create(new CompletableOnSubscribe() { + + return create(new OnSubscribe() { @Override - public void call(final CompletableSubscriber s) { + public void call(final rx.CompletableSubscriber s) { // FIXME cancellation of this schedule - + final Scheduler.Worker w = scheduler.createWorker(); - + w.schedule(new Action0() { @Override public void call() { @@ -2140,7 +2165,7 @@ public void call() { public final Completable timeout(long timeout, TimeUnit unit) { return timeout0(timeout, unit, Schedulers.computation(), null); } - + /** * Returns a Completable that runs this Completable and switches to the other Completable * in case this Completable doesn't complete within the given time. @@ -2154,7 +2179,7 @@ public final Completable timeout(long timeout, TimeUnit unit, Completable other) requireNonNull(other); return timeout0(timeout, unit, Schedulers.computation(), other); } - + /** * 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 @@ -2168,7 +2193,7 @@ public final Completable timeout(long timeout, TimeUnit unit, Completable other) public final Completable timeout(long timeout, TimeUnit unit, Scheduler scheduler) { return timeout0(timeout, unit, scheduler, null); } - + /** * 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 @@ -2184,7 +2209,7 @@ public final Completable timeout(long timeout, TimeUnit unit, Scheduler schedule requireNonNull(other); return timeout0(timeout, unit, scheduler, other); } - + /** * Returns a Completable that runs this Completable and optionally switches to the other Completable * in case this Completable doesn't complete within the given time while "waiting" on @@ -2192,7 +2217,7 @@ public final Completable timeout(long timeout, TimeUnit unit, Scheduler schedule * @param timeout the timeout value * @param unit the timeout unit * @param scheduler the scheduler to use to wait for completion - * @param other the other Completable instance to switch to in case of a timeout, + * @param other the other Completable instance to switch to in case of a timeout, * if null a TimeoutException is emitted instead * @return the new Completable instance * @throws NullPointerException if unit or scheduler @@ -2202,15 +2227,16 @@ public final Completable timeout0(long timeout, TimeUnit unit, Scheduler schedul requireNonNull(scheduler); return create(new CompletableOnSubscribeTimeout(this, timeout, unit, scheduler, other)); } - + /** - * Allows fluent conversion to another type via a function callback. - * @param the output type as determined by the converter function - * @param converter the function called with this which should return some other value. - * @return the converted value - * @throws NullPointerException if converter is null + * Calls the specified converter function during assembly time and returns its resulting value. + *

+ * This allows fluent conversion to any other type. + * @param the resulting object type + * @param converter the function that receives the current Single instance and returns a value + * @return the value returned by the function */ - public final U to(Func1 converter) { + public final R to(Func1 converter) { return converter.call(this); } @@ -2221,14 +2247,14 @@ public final U to(Func1 converter) { * @return the new Observable created */ public final Observable toObservable() { - return Observable.create(new OnSubscribe() { + return Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber s) { unsafeSubscribe(s); } }); } - + /** * Converts this Completable into a Single which when this Completable completes normally, * calls the given supplier and emits its returned value through onSuccess. @@ -2242,7 +2268,7 @@ public final Single toSingle(final Func0 completionValueFunc return Single.create(new rx.Single.OnSubscribe() { @Override public void call(final SingleSubscriber s) { - unsafeSubscribe(new CompletableSubscriber() { + unsafeSubscribe(new rx.CompletableSubscriber() { @Override public void onCompleted() { @@ -2254,7 +2280,7 @@ public void onCompleted() { s.onError(e); return; } - + if (v == null) { s.onError(new NullPointerException("The value supplied is null")); } else { @@ -2271,12 +2297,12 @@ public void onError(Throwable e) { public void onSubscribe(Subscription d) { s.add(d); } - + }); } }); } - + /** * Converts this Completable into a Single which when this Completable completes normally, * emits the given value through onSuccess. @@ -2294,9 +2320,9 @@ public T call() { } }); } - + /** - * Returns a Completable which makes sure when a subscriber cancels the subscription, the + * Returns a Completable which makes sure when a subscriber cancels the subscription, the * dispose is called on the specified scheduler * @param scheduler the target scheduler where to execute the cancellation * @return the new Completable instance @@ -2304,10 +2330,10 @@ public T call() { */ public final Completable unsubscribeOn(final Scheduler scheduler) { requireNonNull(scheduler); - return create(new CompletableOnSubscribe() { + return create(new OnSubscribe() { @Override - public void call(final CompletableSubscriber s) { - unsafeSubscribe(new CompletableSubscriber() { + public void call(final rx.CompletableSubscriber s) { + unsafeSubscribe(new rx.CompletableSubscriber() { @Override public void onCompleted() { @@ -2338,9 +2364,31 @@ public void call() { } })); } - + }); } }); } + + // ------------------------------------------------------------------------- + // Fluent test support, super handy and reduces test preparation boilerplate + // ------------------------------------------------------------------------- + /** + * Creates an AssertableSubscriber that requests {@code Long.MAX_VALUE} and subscribes + * it to this Observable. + *

+ *
Backpressure:
+ *
The returned AssertableSubscriber consumes this Observable in an unbounded fashion.
+ *
Scheduler:
+ *
{@code test} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 1.2.3 - experimental + * @return the new AssertableSubscriber instance + * @since 1.3 + */ + public final AssertableSubscriber test() { + AssertableSubscriberObservable ts = AssertableSubscriberObservable.create(Long.MAX_VALUE); + subscribe(ts); + return ts; + } } \ No newline at end of file diff --git a/src/main/java/rx/CompletableEmitter.java b/src/main/java/rx/CompletableEmitter.java new file mode 100644 index 0000000000..dc5f83efaa --- /dev/null +++ b/src/main/java/rx/CompletableEmitter.java @@ -0,0 +1,62 @@ +/* + * Copyright 2016 Netflix, Inc. + * + * 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 rx; + +import rx.functions.Cancellable; + +/** + * Abstraction over a {@link CompletableSubscriber} that gets either an onCompleted or onError + * signal and allows registering an cancellation/unsubscription callback. + *

+ * All methods are thread-safe; calling onCompleted or onError twice or one after the other has + * no effect. + * @since 1.3 + */ +public interface CompletableEmitter { + + /** + * Notifies the CompletableSubscriber that the {@link Completable} has finished + * sending push-based notifications. + *

+ * The {@link Observable} will not call this method if it calls {@link #onError}. + */ + void onCompleted(); + + /** + * Notifies the CompletableSubscriber that the {@link Completable} has experienced an error condition. + *

+ * If the {@link Completable} calls this method, it will not thereafter call + * {@link #onCompleted}. + * + * @param t + * the exception encountered by the Observable + */ + void onError(Throwable t); + + /** + * Sets a Subscription on this emitter; any previous Subscription + * or Cancellation will be unsubscribed/cancelled. + * @param s the subscription, null is allowed + */ + void setSubscription(Subscription s); + + /** + * Sets a Cancellable on this emitter; any previous Subscription + * or Cancellation will be unsubscribed/cancelled. + * @param c the cancellable resource, null is allowed + */ + void setCancellation(Cancellable c); +} diff --git a/src/main/java/rx/CompletableSubscriber.java b/src/main/java/rx/CompletableSubscriber.java new file mode 100644 index 0000000000..054a97474e --- /dev/null +++ b/src/main/java/rx/CompletableSubscriber.java @@ -0,0 +1,40 @@ +/* + * Copyright 2016 Netflix, Inc. + * + * 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 rx; + +/** + * Represents the subscription API callbacks when subscribing to a Completable instance. + * @since 1.3 + */ +public interface CompletableSubscriber { + /** + * Called once the deferred computation completes normally. + */ + void onCompleted(); + + /** + * Called once if the deferred computation 'throws' an exception. + * @param e the exception, not null. + */ + void onError(Throwable e); + + /** + * Called once by the Completable to set a Subscription on this instance which + * then can be used to cancel the subscription at any time. + * @param d the Subscription instance to call dispose on for cancellation, not null + */ + void onSubscribe(Subscription d); +} diff --git a/src/main/java/rx/Emitter.java b/src/main/java/rx/Emitter.java new file mode 100644 index 0000000000..3a81d7f01e --- /dev/null +++ b/src/main/java/rx/Emitter.java @@ -0,0 +1,83 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * 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 rx; + +import rx.functions.Cancellable; + +/** + * Abstraction over a RxJava Subscriber that allows associating + * a resource with it and exposes the current number of downstream + * requested amount. + *

+ * The onNext, onError and onCompleted methods should be called + * in a sequential manner, just like the Observer's methods. The + * other methods are thread-safe. + * + * @param the value type to emit + * @since 1.3 + */ +public interface Emitter extends Observer { + + /** + * Sets a Subscription on this emitter; any previous Subscription + * or Cancellation will be unsubscribed/cancelled. + * @param s the subscription, null is allowed + */ + void setSubscription(Subscription s); + + /** + * Sets a Cancellable on this emitter; any previous Subscription + * or Cancellation will be unsubscribed/cancelled. + * @param c the cancellable resource, null is allowed + */ + void setCancellation(Cancellable c); + /** + * The current outstanding request amount. + *

This method it thread-safe. + * @return the current outstanding request amount + */ + long requested(); + + /** + * Options to handle backpressure in the emitter. + */ + enum BackpressureMode { + /** + * No backpressure is applied as the onNext calls pass through the Emitter; + * note that this may cause {@link rx.exceptions.MissingBackpressureException} or {@link IllegalStateException} + * somewhere downstream. + */ + NONE, + /** + * Signals a {@link rx.exceptions.MissingBackpressureException} if the downstream can't keep up. + */ + ERROR, + /** + * Buffers (unbounded) all onNext calls until the downstream can consume them. + */ + BUFFER, + /** + * Drops the incoming onNext value if the downstream can't keep up. + */ + DROP, + /** + * Keeps the latest onNext value and overwrites it with newer ones until the downstream + * can consume it. + */ + LATEST + } +} diff --git a/src/main/java/rx/Notification.java b/src/main/java/rx/Notification.java index 71fb95c745..565e57fb25 100644 --- a/src/main/java/rx/Notification.java +++ b/src/main/java/rx/Notification.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -66,9 +66,11 @@ public static Notification createOnCompleted() { * Creates and returns a {@code Notification} of variety {@code Kind.OnCompleted}. * * @param the actual value type held by the Notification - * @param type + * @param type the type token to help with type inference of {@code } + * @deprecated this method does the same as {@link #createOnCompleted()} and does not use the passed in type hence it's useless. * @return an {@code OnCompleted} variety of {@code Notification} */ + @Deprecated @SuppressWarnings("unchecked") public static Notification createOnCompleted(Class type) { return (Notification) ON_COMPLETED; @@ -82,7 +84,7 @@ private Notification(Kind kind, T value, Throwable e) { /** * Retrieves the exception associated with this (onError) notification. - * + * * @return the Throwable associated with this (onError) notification */ public Throwable getThrowable() { @@ -91,7 +93,7 @@ public Throwable getThrowable() { /** * Retrieves the item associated with this (onNext) notification. - * + * * @return the item associated with this (onNext) notification */ public T getValue() { @@ -100,7 +102,7 @@ public T getValue() { /** * Indicates whether this notification has an item associated with it. - * + * * @return a boolean indicating whether or not this notification has an item associated with it */ public boolean hasValue() { @@ -110,7 +112,7 @@ public boolean hasValue() { /** * Indicates whether this notification has an exception associated with it. - * + * * @return a boolean indicating whether this notification has an exception associated with it */ public boolean hasThrowable() { @@ -119,7 +121,7 @@ public boolean hasThrowable() { /** * Retrieves the kind of this notification: {@code OnNext}, {@code OnError}, or {@code OnCompleted} - * + * * @return the kind of the notification: {@code OnNext}, {@code OnError}, or {@code OnCompleted} */ public Kind getKind() { @@ -128,7 +130,7 @@ public Kind getKind() { /** * Indicates whether this notification represents an {@code onError} event. - * + * * @return a boolean indicating whether this notification represents an {@code onError} event */ public boolean isOnError() { @@ -137,7 +139,7 @@ public boolean isOnError() { /** * Indicates whether this notification represents an {@code onCompleted} event. - * + * * @return a boolean indicating whether this notification represents an {@code onCompleted} event */ public boolean isOnCompleted() { @@ -146,7 +148,7 @@ public boolean isOnCompleted() { /** * Indicates whether this notification represents an {@code onNext} event. - * + * * @return a boolean indicating whether this notification represents an {@code onNext} event */ public boolean isOnNext() { @@ -158,18 +160,12 @@ public boolean isOnNext() { * @param observer the target observer to call onXXX methods on based on the kind of this Notification instance */ public void accept(Observer observer) { - switch (kind) { - case OnNext: + if (kind == Kind.OnNext) { observer.onNext(getValue()); - break; - case OnError: - observer.onError(getThrowable()); - break; - case OnCompleted: + } else if (kind == Kind.OnCompleted) { observer.onCompleted(); - break; - default: - throw new AssertionError("Uncovered case: " + kind); + } else { + observer.onError(getThrowable()); } } @@ -182,22 +178,27 @@ public enum Kind { @Override public String toString() { - StringBuilder str = new StringBuilder("[").append(super.toString()).append(" ").append(getKind()); - if (hasValue()) - str.append(" ").append(getValue()); - if (hasThrowable()) - str.append(" ").append(getThrowable().getMessage()); - str.append("]"); + StringBuilder str = new StringBuilder(64).append('[').append(super.toString()) + .append(' ').append(getKind()); + if (hasValue()) { + str.append(' ').append(getValue()); + } + if (hasThrowable()) { + str.append(' ').append(getThrowable().getMessage()); + } + str.append(']'); return str.toString(); } @Override public int hashCode() { int hash = getKind().hashCode(); - if (hasValue()) + if (hasValue()) { hash = hash * 31 + getValue().hashCode(); - if (hasThrowable()) + } + if (hasThrowable()) { hash = hash * 31 + getThrowable().hashCode(); + } return hash; } @@ -216,18 +217,7 @@ public boolean equals(Object obj) { } Notification notification = (Notification) obj; - if (notification.getKind() != getKind()) { - return false; - } - - if (!(value == notification.value || (value != null && value.equals(notification.value)))) { - return false; - } - - if (!(throwable == notification.throwable || (throwable != null && throwable.equals(notification.throwable)))) { - return false; - } + return notification.getKind() == getKind() && (value == notification.value || (value != null && value.equals(notification.value))) && (throwable == notification.throwable || (throwable != null && throwable.equals(notification.throwable))); - return true; } } diff --git a/src/main/java/rx/Observable.java b/src/main/java/rx/Observable.java index 4507c4480f..99e84ec609 100644 --- a/src/main/java/rx/Observable.java +++ b/src/main/java/rx/Observable.java @@ -1,11 +1,11 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -18,10 +18,12 @@ import rx.annotations.*; import rx.exceptions.*; import rx.functions.*; +import rx.internal.observers.AssertableSubscriberObservable; import rx.internal.operators.*; import rx.internal.util.*; import rx.observables.*; import rx.observers.SafeSubscriber; +import rx.observers.AssertableSubscriber; import rx.plugins.*; import rx.schedulers.*; import rx.subscriptions.Subscriptions; @@ -38,7 +40,7 @@ *

* For more information see the ReactiveX * documentation. - * + * * @param * the type of the items emitted by the Observable */ @@ -49,9 +51,9 @@ public class Observable { /** * Creates an Observable with a Function to execute when it is subscribed to. *

- * Note: Use {@link #create(OnSubscribe)} to create an Observable, instead of this constructor, + * Note: Use {@link #unsafeCreate(OnSubscribe)} to create an Observable, instead of this constructor, * unless you specifically have a need for inheritance. - * + * * @param f * {@link OnSubscribe} to be executed when {@link #subscribe(Subscriber)} is called */ @@ -60,8 +62,71 @@ protected Observable(OnSubscribe f) { } /** - * Returns an Observable that will execute the specified function when a {@link Subscriber} subscribes to - * it. + * Constructs an Observable in an unsafe manner, that is, unsubscription and backpressure handling + * is the responsibility of the OnSubscribe implementation. + * @param the value type emitted + * @param f the callback to execute for each individual Subscriber that subscribes to the + * returned Observable + * @return the new Observable instance + * @deprecated 1.2.7 - inherently unsafe, use the other create() methods for basic cases or + * see {@link #unsafeCreate(OnSubscribe)} for advanced cases (such as custom operators) + * @see #create(SyncOnSubscribe) + * @see #create(AsyncOnSubscribe) + * @see #create(Action1, rx.Emitter.BackpressureMode) + */ + @Deprecated + public static Observable create(OnSubscribe f) { + return new Observable(RxJavaHooks.onCreate(f)); + } + + /** + * Provides an API (via a cold Observable) that bridges the reactive world with the callback-style, + * generally non-backpressured world. + *

+ * Example: + *


+     * Observable.<Event>create(emitter -> {
+     *     Callback listener = new Callback() {
+     *         @Override
+     *         public void onEvent(Event e) {
+     *             emitter.onNext(e);
+     *             if (e.isLast()) {
+     *                 emitter.onCompleted();
+     *             }
+     *         }
+     *
+     *         @Override
+     *         public void onFailure(Exception e) {
+     *             emitter.onError(e);
+     *         }
+     *     };
+     *
+     *     AutoCloseable c = api.someMethod(listener);
+     *
+     *     emitter.setCancellation(c::close);
+     *
+     * }, BackpressureMode.BUFFER);
+     * 
+ *

+ * You should call the Emitter's onNext, onError and onCompleted methods in a serialized fashion. The + * rest of its methods are thread-safe. + *

History: 1.2.7 - experimental + * @param the element type + * @param emitter the emitter that is called when a Subscriber subscribes to the returned {@code Observable} + * @param backpressure the backpressure mode to apply if the downstream Subscriber doesn't request (fast) enough + * @return the new Observable instance + * @see Emitter + * @see Emitter.BackpressureMode + * @see rx.functions.Cancellable + * @since 1.3 + */ + public static Observable create(Action1> emitter, Emitter.BackpressureMode backpressure) { + return unsafeCreate(new OnSubscribeCreate(emitter, backpressure)); + } + + /** + * Returns an Observable that executes the given OnSubscribe action for each individual Subscriber + * that subscribes; unsubscription and backpressure must be implemented manually. *

* *

@@ -75,10 +140,14 @@ protected Observable(OnSubscribe f) { * See Rx Design Guidelines (PDF) for detailed * information. *

+ *
Backpressure:
+ *
The {@code OnSubscribe} instance provided is responsible to be backpressure-aware or + * document the fact that the consumer of the returned {@code Observable} has to apply one of + * the {@code onBackpressureXXX} operators.
*
Scheduler:
- *
{@code create} does not operate by default on a particular {@link Scheduler}.
+ *
{@code unsafeCreate} does not operate by default on a particular {@link Scheduler}.
*
- * + *

History: 1.2.7 - experimental * @param * the type of the items that this Observable emits * @param f @@ -87,37 +156,40 @@ protected Observable(OnSubscribe f) { * @return an Observable that, when a {@link Subscriber} subscribes to it, will execute the specified * function * @see ReactiveX operators documentation: Create + * @since 1.3 */ - public static Observable create(OnSubscribe f) { + public static Observable unsafeCreate(OnSubscribe f) { return new Observable(RxJavaHooks.onCreate(f)); } /** - * Returns an Observable that respects the back-pressure semantics. When the returned Observable is - * subscribed to it will initiate the given {@link SyncOnSubscribe}'s life cycle for - * generating events. - * - *

Note: the {@code SyncOnSubscribe} provides a generic way to fulfill data by iterating - * over a (potentially stateful) function (e.g. reading data off of a channel, a parser, ). If your + * Returns an Observable that respects the back-pressure semantics. When the returned Observable is + * subscribed to it will initiate the given {@link SyncOnSubscribe}'s life cycle for + * generating events. + * + *

Note: the {@code SyncOnSubscribe} provides a generic way to fulfill data by iterating + * over a (potentially stateful) function (e.g. reading data off of a channel, a parser, ). If your * data comes directly from an asynchronous/potentially concurrent source then consider using the * {@link Observable#create(AsyncOnSubscribe) asynchronous overload}. - * + * *

- * + * *

* See Rx Design Guidelines (PDF) for detailed * information. *

+ *
Backpressure:
+ *
The operator honors backpressure and generates values on-demand (when requested).
*
Scheduler:
*
{@code create} does not operate by default on a particular {@link Scheduler}.
*
- * + * * @param * the type of the items that this Observable emits * @param the state type * @param syncOnSubscribe - * an implementation of {@link SyncOnSubscribe}. There are many static creation methods - * on the class for convenience. + * an implementation of {@link SyncOnSubscribe}. There are many static creation methods + * on the class for convenience. * @return an Observable that, when a {@link Subscriber} subscribes to it, will execute the specified * function * @see SyncOnSubscribe#createSingleState(Func0, Action2) @@ -127,38 +199,39 @@ public static Observable create(OnSubscribe f) { * @see SyncOnSubscribe#createStateless(Action1) * @see SyncOnSubscribe#createStateless(Action1, Action0) * @see ReactiveX operators documentation: Create - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.2 */ - @Beta public static Observable create(SyncOnSubscribe syncOnSubscribe) { - return create((OnSubscribe)syncOnSubscribe); + return unsafeCreate(syncOnSubscribe); } /** - * Returns an Observable that respects the back-pressure semantics. When the returned Observable is - * subscribed to it will initiate the given {@link AsyncOnSubscribe}'s life cycle for - * generating events. - * - *

Note: the {@code AsyncOnSubscribe} is useful for observable sources of data that are - * necessarily asynchronous (RPC, external services, etc). Typically most use cases can be solved + * Returns an Observable that respects the back-pressure semantics. When the returned Observable is + * subscribed to it will initiate the given {@link AsyncOnSubscribe}'s life cycle for + * generating events. + * + *

Note: the {@code AsyncOnSubscribe} is useful for observable sources of data that are + * necessarily asynchronous (RPC, external services, etc). Typically most use cases can be solved * with the {@link Observable#create(SyncOnSubscribe) synchronous overload}. - * + * *

- * + * *

* See Rx Design Guidelines (PDF) for detailed * information. *

+ *
Backpressure:
+ *
The operator honors backpressure and generates values on-demand (when requested).
*
Scheduler:
*
{@code create} does not operate by default on a particular {@link Scheduler}.
*
- * + * * @param * the type of the items that this Observable emits * @param the state type * @param asyncOnSubscribe - * an implementation of {@link AsyncOnSubscribe}. There are many static creation methods - * on the class for convenience. + * an implementation of {@link AsyncOnSubscribe}. There are many static creation methods + * on the class for convenience. * @return an Observable that, when a {@link Subscriber} subscribes to it, will execute the specified * function * @see AsyncOnSubscribe#createSingleState(Func0, Action3) @@ -168,11 +241,11 @@ public static Observable create(SyncOnSubscribe syncOnSubscribe) * @see AsyncOnSubscribe#createStateless(Action2) * @see AsyncOnSubscribe#createStateless(Action2, Action0) * @see ReactiveX operators documentation: Create - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 - beta */ - @Experimental + @Beta public static Observable create(AsyncOnSubscribe asyncOnSubscribe) { - return create((OnSubscribe)asyncOnSubscribe); + return unsafeCreate(asyncOnSubscribe); } /** @@ -193,37 +266,8 @@ public interface Operator extends Func1, Subscriber< } /** - * Passes all emitted values from this Observable to the provided conversion function to be collected and - * returned as a single value. Note that it is legal for a conversion function to return an Observable - * (enabling chaining). - * - * @param the output type of the conversion function - * @param conversion a function that converts from this {@code Observable} to an {@code R} - * @return an instance of R created by the provided conversion function - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) - */ - @Experimental - public R extend(Func1, ? extends R> conversion) { - return conversion.call(new OnSubscribeExtend(this)); - } - - /** - * Transforms a OnSubscribe.call() into an Observable.subscribe() call. - *

Note: has to be in Observable because it calls the package-private subscribe() method - * @param the value type - */ - static final class OnSubscribeExtend implements OnSubscribe { - final Observable parent; - OnSubscribeExtend(Observable parent) { - this.parent = parent; - } - @Override - public void call(Subscriber subscriber) { - subscriber.add(subscribe(subscriber, parent)); - } - } - - /** + * This method requires advanced knowledge about building operators; please consider + * other standard composition methods first; * Lifts a function to the current Observable and returns a new Observable that when subscribed to will pass * the values of the current Observable through the Operator function. *

@@ -237,10 +281,14 @@ public void call(Subscriber subscriber) { * Observable, use {@code lift}. 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) use {@link #compose}. *

+ *
Backpressure:
+ *
The {@code Operator} instance provided is responsible to be backpressure-aware or + * document the fact that the consumer of the returned {@code Observable} has to apply one of + * the {@code onBackpressureXXX} operators.
*
Scheduler:
*
{@code lift} does not operate by default on a particular {@link Scheduler}.
*
- * + * * @param the output value type * @param operator the Operator that implements the Observable-operating function to be applied to the source * Observable @@ -248,9 +296,9 @@ public void call(Subscriber subscriber) { * @see RxJava wiki: Implementing Your Own Operators */ public final Observable lift(final Operator operator) { - return create(new OnSubscribeLift(onSubscribe, operator)); + return unsafeCreate(new OnSubscribeLift(onSubscribe, operator)); } - + /** * Transform an Observable by applying a particular Transformer function to it. *

@@ -261,10 +309,13 @@ public final Observable lift(final Operator opera * Observable, use {@link #lift}. 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) use {@code compose}. *

+ *
Backpressure:
+ *
The operator itself doesn't interfere with the backpressure behavior which only depends + * on what kind of {@code Observable} the transformer returns.
*
Scheduler:
*
{@code compose} does not operate by default on a particular {@link Scheduler}.
*
- * + * * @param the value type of the output Observable * @param transformer implements the function that transforms the source Observable * @return the source Observable, transformed by the transformer function @@ -282,7 +333,7 @@ public Observable compose(Transformer transformer *

* This convenience interface has been introduced to work around the variance declaration * problems of type arguments. - * + * * @param the input Observable's value type * @param the output Observable's value type */ @@ -290,6 +341,19 @@ public interface Transformer extends Func1, Observable> { // cover for generics insanity } + /** + * Calls the specified converter function during assembly time and returns its resulting value. + *

+ * This allows fluent conversion to any other type. + * @param the resulting object type + * @param converter the function that receives the current Observable instance and returns a value + * @return the value returned by the function + * @since 1.3 + */ + public final R to(Func1, R> converter) { + return converter.call(this); + } + /** * Returns a Single that emits the single item emitted by the source Observable, if that Observable * emits only a single item. If the source Observable emits more than one item or no items, notify of an @@ -297,6 +361,9 @@ public interface Transformer extends Func1, Observable> { *

* *

+ *
Backpressure:
+ *
The operator ignores backpressure on the source {@code Observable} and the returned {@code Single} + * does not have a notion of backpressure.
*
Scheduler:
*
{@code toSingle} does not operate by default on a particular {@link Scheduler}.
*
@@ -307,9 +374,8 @@ public interface Transformer extends Func1, Observable> { * @throws NoSuchElementException * if the source observable emits no items * @see ReactiveX documentation: Single - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.2 */ - @Beta public Single toSingle() { return new Single(OnSubscribeSingle.create(this)); } @@ -319,10 +385,13 @@ public Single toSingle() { * {@code ignoreAllElements()}) and calls onCompleted when this source observable calls * onCompleted. Error terminal events are propagated. *

- * *

+ *
Backpressure:
+ *
The operator ignores backpressure on the source {@code Observable} and the returned {@code Completable} + * does not have a notion of backpressure.
*
Scheduler:
*
{@code toCompletable} does not operate by default on a particular {@link Scheduler}.
*
@@ -331,14 +400,12 @@ public Single toSingle() { * calls onCompleted * @see ReactiveX documentation: * Completable - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical - * with the release number) + * @since 1.3 */ - @Experimental public Completable toCompletable() { return Completable.fromObservable(this); } - + /* ********************************************************************************************************* * Operators Below Here @@ -351,10 +418,13 @@ public Completable toCompletable() { *

* *

+ *
Backpressure:
+ *
The operator itself doesn't interfere with backpressure which is determined by the winning + * {@code Observable}'s backpressure behavior.
*
Scheduler:
*
{@code amb} does not operate by default on a particular {@link Scheduler}.
*
- * + * * @param the common element type * @param sources * an Iterable of Observable sources competing to react first @@ -363,7 +433,7 @@ public Completable toCompletable() { * @see ReactiveX operators documentation: Amb */ public static Observable amb(Iterable> sources) { - return create(OnSubscribeAmb.amb(sources)); + return unsafeCreate(OnSubscribeAmb.amb(sources)); } /** @@ -372,10 +442,13 @@ public static Observable amb(Iterable> *

* *

+ *
Backpressure:
+ *
The operator itself doesn't interfere with backpressure which is determined by the winning + * {@code Observable}'s backpressure behavior.
*
Scheduler:
*
{@code amb} does not operate by default on a particular {@link Scheduler}.
*
- * + * * @param the common element type * @param o1 * an Observable competing to react first @@ -386,7 +459,7 @@ public static Observable amb(Iterable> * @see ReactiveX operators documentation: Amb */ public static Observable amb(Observable o1, Observable o2) { - return create(OnSubscribeAmb.amb(o1, o2)); + return unsafeCreate(OnSubscribeAmb.amb(o1, o2)); } /** @@ -395,10 +468,13 @@ public static Observable amb(Observable o1, Observable * *
+ *
Backpressure:
+ *
The operator itself doesn't interfere with backpressure which is determined by the winning + * {@code Observable}'s backpressure behavior.
*
Scheduler:
*
{@code amb} does not operate by default on a particular {@link Scheduler}.
*
- * + * * @param the common element base type * @param o1 * an Observable competing to react first @@ -411,7 +487,7 @@ public static Observable amb(Observable o1, ObservableReactiveX operators documentation: Amb */ public static Observable amb(Observable o1, Observable o2, Observable o3) { - return create(OnSubscribeAmb.amb(o1, o2, o3)); + return unsafeCreate(OnSubscribeAmb.amb(o1, o2, o3)); } /** @@ -420,10 +496,13 @@ public static Observable amb(Observable o1, Observable * *
+ *
Backpressure:
+ *
The operator itself doesn't interfere with backpressure which is determined by the winning + * {@code Observable}'s backpressure behavior.
*
Scheduler:
*
{@code amb} does not operate by default on a particular {@link Scheduler}.
*
- * + * * @param the common element base type * @param o1 * an Observable competing to react first @@ -438,7 +517,7 @@ public static Observable amb(Observable o1, ObservableReactiveX operators documentation: Amb */ public static Observable amb(Observable o1, Observable o2, Observable o3, Observable o4) { - return create(OnSubscribeAmb.amb(o1, o2, o3, o4)); + return unsafeCreate(OnSubscribeAmb.amb(o1, o2, o3, o4)); } /** @@ -447,6 +526,9 @@ public static Observable amb(Observable o1, Observable * *
+ *
Backpressure:
+ *
The operator itself doesn't interfere with backpressure which is determined by the winning + * {@code Observable}'s backpressure behavior.
*
Scheduler:
*
{@code amb} does not operate by default on a particular {@link Scheduler}.
*
@@ -467,7 +549,7 @@ public static Observable amb(Observable o1, ObservableReactiveX operators documentation: Amb */ public static Observable amb(Observable o1, Observable o2, Observable o3, Observable o4, Observable o5) { - return create(OnSubscribeAmb.amb(o1, o2, o3, o4, o5)); + return unsafeCreate(OnSubscribeAmb.amb(o1, o2, o3, o4, o5)); } /** @@ -476,10 +558,13 @@ public static Observable amb(Observable o1, Observable * *
+ *
Backpressure:
+ *
The operator itself doesn't interfere with backpressure which is determined by the winning + * {@code Observable}'s backpressure behavior.
*
Scheduler:
*
{@code amb} does not operate by default on a particular {@link Scheduler}.
*
- * + * * @param the common element base type * @param o1 * an Observable competing to react first @@ -498,7 +583,7 @@ public static Observable amb(Observable o1, ObservableReactiveX operators documentation: Amb */ public static Observable amb(Observable o1, Observable o2, Observable o3, Observable o4, Observable o5, Observable o6) { - return create(OnSubscribeAmb.amb(o1, o2, o3, o4, o5, o6)); + return unsafeCreate(OnSubscribeAmb.amb(o1, o2, o3, o4, o5, o6)); } /** @@ -507,6 +592,9 @@ public static Observable amb(Observable o1, Observable * *
+ *
Backpressure:
+ *
The operator itself doesn't interfere with backpressure which is determined by the winning + * {@code Observable}'s backpressure behavior.
*
Scheduler:
*
{@code amb} does not operate by default on a particular {@link Scheduler}.
*
@@ -531,7 +619,7 @@ public static Observable amb(Observable o1, ObservableReactiveX operators documentation: Amb */ public static Observable amb(Observable o1, Observable o2, Observable o3, Observable o4, Observable o5, Observable o6, Observable o7) { - return create(OnSubscribeAmb.amb(o1, o2, o3, o4, o5, o6, o7)); + return unsafeCreate(OnSubscribeAmb.amb(o1, o2, o3, o4, o5, o6, o7)); } /** @@ -540,6 +628,9 @@ public static Observable amb(Observable o1, Observable * *
+ *
Backpressure:
+ *
The operator itself doesn't interfere with backpressure which is determined by the winning + * {@code Observable}'s backpressure behavior.
*
Scheduler:
*
{@code amb} does not operate by default on a particular {@link Scheduler}.
*
@@ -566,7 +657,7 @@ public static Observable amb(Observable o1, ObservableReactiveX operators documentation: Amb */ public static Observable amb(Observable o1, Observable o2, Observable o3, Observable o4, Observable o5, Observable o6, Observable o7, Observable o8) { - return create(OnSubscribeAmb.amb(o1, o2, o3, o4, o5, o6, o7, o8)); + return unsafeCreate(OnSubscribeAmb.amb(o1, o2, o3, o4, o5, o6, o7, o8)); } /** @@ -575,10 +666,13 @@ public static Observable amb(Observable o1, Observable * *
+ *
Backpressure:
+ *
The operator itself doesn't interfere with backpressure which is determined by the winning + * {@code Observable}'s backpressure behavior.
*
Scheduler:
*
{@code amb} does not operate by default on a particular {@link Scheduler}.
*
- * + * * @param the common element base type * @param o1 * an Observable competing to react first @@ -603,7 +697,7 @@ public static Observable amb(Observable o1, ObservableReactiveX operators documentation: Amb */ public static Observable amb(Observable o1, Observable o2, Observable o3, Observable o4, Observable o5, Observable o6, Observable o7, Observable o8, Observable o9) { - return create(OnSubscribeAmb.amb(o1, o2, o3, o4, o5, o6, o7, o8, o9)); + return unsafeCreate(OnSubscribeAmb.amb(o1, o2, o3, o4, o5, o6, o7, o8, o9)); } /** @@ -613,6 +707,10 @@ public static Observable amb(Observable o1, Observable * *
+ *
Backpressure:
+ *
The returned {@code Observable} honors backpressure from downstream. The source {@code Observable}s + * are requested in a bounded manner, however, their backpressure is not enforced (the operator won't signal + * {@code MissingBackpressureException}) and may lead to {@code OutOfMemoryError} due to internal buffer bloat.
*
Scheduler:
*
{@code combineLatest} does not operate by default on a particular {@link Scheduler}.
*
@@ -642,10 +740,14 @@ public static Observable combineLatest(Observable o *

* *

+ *
Backpressure:
+ *
The returned {@code Observable} honors backpressure from downstream. The source {@code Observable}s + * are requested in a bounded manner, however, their backpressure is not enforced (the operator won't signal + * {@code MissingBackpressureException}) and may lead to {@code OutOfMemoryError} due to internal buffer bloat.
*
Scheduler:
*
{@code combineLatest} does not operate by default on a particular {@link Scheduler}.
*
- * + * * @param the element type of the first source * @param the element type of the second source * @param the element type of the third source @@ -674,10 +776,14 @@ public static Observable combineLatest(Observable * *
+ *
Backpressure:
+ *
The returned {@code Observable} honors backpressure from downstream. The source {@code Observable}s + * are requested in a bounded manner, however, their backpressure is not enforced (the operator won't signal + * {@code MissingBackpressureException}) and may lead to {@code OutOfMemoryError} due to internal buffer bloat.
*
Scheduler:
*
{@code combineLatest} does not operate by default on a particular {@link Scheduler}.
*
- * + * * @param the element type of the first source * @param the element type of the second source * @param the element type of the third source @@ -710,10 +816,14 @@ public static Observable combineLatest(Observable * *
+ *
Backpressure:
+ *
The returned {@code Observable} honors backpressure from downstream. The source {@code Observable}s + * are requested in a bounded manner, however, their backpressure is not enforced (the operator won't signal + * {@code MissingBackpressureException}) and may lead to {@code OutOfMemoryError} due to internal buffer bloat.
*
Scheduler:
*
{@code combineLatest} does not operate by default on a particular {@link Scheduler}.
*
- * + * * @param the element type of the first source * @param the element type of the second source * @param the element type of the third source @@ -749,10 +859,14 @@ public static Observable combineLatest(Observable * *
+ *
Backpressure:
+ *
The returned {@code Observable} honors backpressure from downstream. The source {@code Observable}s + * are requested in a bounded manner, however, their backpressure is not enforced (the operator won't signal + * {@code MissingBackpressureException}) and may lead to {@code OutOfMemoryError} due to internal buffer bloat.
*
Scheduler:
*
{@code combineLatest} does not operate by default on a particular {@link Scheduler}.
*
- * + * * @param the element type of the first source * @param the element type of the second source * @param the element type of the third source @@ -791,10 +905,14 @@ public static Observable combineLatest(Observable *

* *

+ *
Backpressure:
+ *
The returned {@code Observable} honors backpressure from downstream. The source {@code Observable}s + * are requested in a bounded manner, however, their backpressure is not enforced (the operator won't signal + * {@code MissingBackpressureException}) and may lead to {@code OutOfMemoryError} due to internal buffer bloat.
*
Scheduler:
*
{@code combineLatest} does not operate by default on a particular {@link Scheduler}.
*
- * + * * @param the element type of the first source * @param the element type of the second source * @param the element type of the third source @@ -836,10 +954,14 @@ public static Observable combineLatest(Observ *

* *

+ *
Backpressure:
+ *
The returned {@code Observable} honors backpressure from downstream. The source {@code Observable}s + * are requested in a bounded manner, however, their backpressure is not enforced (the operator won't signal + * {@code MissingBackpressureException}) and may lead to {@code OutOfMemoryError} due to internal buffer bloat.
*
Scheduler:
*
{@code combineLatest} does not operate by default on a particular {@link Scheduler}.
*
- * + * * @param the element type of the first source * @param the element type of the second source * @param the element type of the third source @@ -884,10 +1006,14 @@ public static Observable combineLatest(Ob *

* *

+ *
Backpressure:
+ *
The returned {@code Observable} honors backpressure from downstream. The source {@code Observable}s + * are requested in a bounded manner, however, their backpressure is not enforced (the operator won't signal + * {@code MissingBackpressureException}) and may lead to {@code OutOfMemoryError} due to internal buffer bloat.
*
Scheduler:
*
{@code combineLatest} does not operate by default on a particular {@link Scheduler}.
*
- * + * * @param the element type of the first source * @param the element type of the second source * @param the element type of the third source @@ -933,6 +1059,10 @@ public static Observable combineLates * the source Observables each time an item is received from any of the source Observables, where this * aggregation is defined by a specified function. *
+ *
Backpressure:
+ *
The returned {@code Observable} honors backpressure from downstream. The source {@code Observable}s + * are requested in a bounded manner, however, their backpressure is not enforced (the operator won't signal + * {@code MissingBackpressureException}) and may lead to {@code OutOfMemoryError} due to internal buffer bloat.
*
Scheduler:
*
{@code combineLatest} does not operate by default on a particular {@link Scheduler}.
*
@@ -950,7 +1080,7 @@ public static Observable combineLates * @see ReactiveX operators documentation: CombineLatest */ public static Observable combineLatest(List> sources, FuncN combineFunction) { - return create(new OnSubscribeCombineLatest(sources, combineFunction)); + return unsafeCreate(new OnSubscribeCombineLatest(sources, combineFunction)); } /** @@ -958,6 +1088,10 @@ public static Observable combineLatest(List + *
Backpressure:
+ *
The returned {@code Observable} honors backpressure from downstream. The source {@code Observable}s + * are requested in a bounded manner, however, their backpressure is not enforced (the operator won't signal + * {@code MissingBackpressureException}) and may lead to {@code OutOfMemoryError} due to internal buffer bloat.
*
Scheduler:
*
{@code combineLatest} does not operate by default on a particular {@link Scheduler}.
* @@ -975,7 +1109,7 @@ public static Observable combineLatest(ListReactiveX operators documentation: CombineLatest */ public static Observable combineLatest(Iterable> sources, FuncN combineFunction) { - return create(new OnSubscribeCombineLatest(sources, combineFunction)); + return unsafeCreate(new OnSubscribeCombineLatest(sources, combineFunction)); } /** @@ -983,8 +1117,12 @@ public static Observable combineLatest(Iterable + *
Backpressure:
+ *
The returned {@code Observable} honors backpressure from downstream. The source {@code Observable}s + * are requested in a bounded manner, however, their backpressure is not enforced (the operator won't signal + * {@code MissingBackpressureException}) and may lead to {@code OutOfMemoryError} due to internal buffer bloat.
*
Scheduler:
*
{@code combineLatest} does not operate by default on a particular {@link Scheduler}.
* @@ -1002,7 +1140,33 @@ public static Observable combineLatest(IterableReactiveX operators documentation: CombineLatest */ public static Observable combineLatestDelayError(Iterable> sources, FuncN combineFunction) { - return create(new OnSubscribeCombineLatest(null, sources, combineFunction, RxRingBuffer.SIZE, true)); + return unsafeCreate(new OnSubscribeCombineLatest(null, sources, combineFunction, RxRingBuffer.SIZE, true)); + } + + /** + * Flattens an Iterable of Observables into one Observable, one after the other, without + * interleaving them. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The {@code Observable} + * sources are expected to honor backpressure as well. + * If any of the source {@code Observable}s violate this, it may throw an + * {@code IllegalStateException} when the source {@code Observable} completes.
+ *
Scheduler:
+ *
{@code concat} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the common element base type + * @param sequences + * the Iterable of Observables + * @return an Observable that emits items that are the result of flattening the items emitted by the + * Observables in the Iterable, one after the other, without interleaving them + * @see ReactiveX operators documentation: Concat + */ + public static Observable concat(Iterable> sequences) { + return concat(from(sequences)); } /** @@ -1011,6 +1175,11 @@ public static Observable combineLatestDelayError(Iterable * *
+ *
Backpressure:
+ *
The operator honors backpressure from downstream. Both the outer and inner {@code Observable} + * sources are expected to honor backpressure as well. If the outer violates this, a + * {@code MissingBackpressureException} is signalled. If any of the inner {@code Observable}s violates + * this, it may throw an {@code IllegalStateException} when an inner {@code Observable} completes.
*
Scheduler:
*
{@code concat} does not operate by default on a particular {@link Scheduler}.
*
@@ -1033,6 +1202,11 @@ public static Observable concat(Observable * *
+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The {@code Observable} + * sources are expected to honor backpressure as well. + * If any of the source {@code Observable}s violate this, it may throw an + * {@code IllegalStateException} when the source {@code Observable} completes.
*
Scheduler:
*
{@code concat} does not operate by default on a particular {@link Scheduler}.
*
@@ -1056,6 +1230,11 @@ public static Observable concat(Observable t1, Observable * *
+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The {@code Observable} + * sources are expected to honor backpressure as well. + * If any of the source {@code Observable}s violate this, it may throw an + * {@code IllegalStateException} when the source {@code Observable} completes.
*
Scheduler:
*
{@code concat} does not operate by default on a particular {@link Scheduler}.
*
@@ -1081,10 +1260,15 @@ public static Observable concat(Observable t1, Observable * *
+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The {@code Observable} + * sources are expected to honor backpressure as well. + * If any of the source {@code Observable}s violate this, it may throw an + * {@code IllegalStateException} when the source {@code Observable} completes.
*
Scheduler:
*
{@code concat} does not operate by default on a particular {@link Scheduler}.
*
- * + * * @param the common element base type * @param t1 * an Observable to be concatenated @@ -1108,6 +1292,11 @@ public static Observable concat(Observable t1, Observable * *
+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The {@code Observable} + * sources are expected to honor backpressure as well. + * If any of the source {@code Observable}s violate this, it may throw an + * {@code IllegalStateException} when the source {@code Observable} completes.
*
Scheduler:
*
{@code concat} does not operate by default on a particular {@link Scheduler}.
*
@@ -1137,6 +1326,11 @@ public static Observable concat(Observable t1, Observable * *
+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The {@code Observable} + * sources are expected to honor backpressure as well. + * If any of the source {@code Observable}s violate this, it may throw an + * {@code IllegalStateException} when the source {@code Observable} completes.
*
Scheduler:
*
{@code concat} does not operate by default on a particular {@link Scheduler}.
*
@@ -1168,6 +1362,11 @@ public static Observable concat(Observable t1, Observable * *
+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The {@code Observable} + * sources are expected to honor backpressure as well. + * If any of the source {@code Observable}s violate this, it may throw an + * {@code IllegalStateException} when the source {@code Observable} completes.
*
Scheduler:
*
{@code concat} does not operate by default on a particular {@link Scheduler}.
*
@@ -1201,10 +1400,15 @@ public static Observable concat(Observable t1, Observable * *
+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The {@code Observable} + * sources are expected to honor backpressure as well. + * If any of the source {@code Observable}s violate this, it may throw an + * {@code IllegalStateException} when the source {@code Observable} completes.
*
Scheduler:
*
{@code concat} does not operate by default on a particular {@link Scheduler}.
*
- * + * * @param the common element base type * @param t1 * an Observable to be concatenated @@ -1236,10 +1440,15 @@ public static Observable concat(Observable t1, Observable * *
+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The {@code Observable} + * sources are expected to honor backpressure as well. + * If any of the source {@code Observable}s violate this, it may throw an + * {@code IllegalStateException} when the source {@code Observable} completes.
*
Scheduler:
*
{@code concat} does not operate by default on a particular {@link Scheduler}.
*
- * + * * @param the common element base type * @param t1 * an Observable to be concatenated @@ -1270,20 +1479,20 @@ public static Observable concat(Observable t1, Observable *
Backpressure:
*
{@code concatDelayError} fully supports backpressure.
*
Scheduler:
*
{@code concatDelayError} does not operate by default on a particular {@link Scheduler}.
* - * + * * @param the common element base type * @param sources the Observable sequence of Observables * @return the new Observable with the concatenating behavior + * @since 1.3 */ @SuppressWarnings({ "rawtypes", "unchecked" }) - @Experimental public static Observable concatDelayError(Observable> sources) { return sources.concatMapDelayError((Func1)UtilityFunctions.identity()); } @@ -1291,23 +1500,290 @@ public static Observable concatDelayError(Observable *
Backpressure:
- *
{@code concatDelayError} fully supports backpressure.
+ *
The operator honors backpressure from downstream. Both the outer and inner {@code Observable} + * sources are expected to honor backpressure as well. If the outer violates this, a + * {@code MissingBackpressureException} is signalled. If any of the inner {@code Observable}s violates + * this, it may throw an {@code IllegalStateException} when an inner {@code Observable} completes.
*
Scheduler:
*
{@code concatDelayError} does not operate by default on a particular {@link Scheduler}.
* - * + * * @param the common element base type * @param sources the Iterable sequence of Observables * @return the new Observable with the concatenating behavior + * @since 1.3 */ - @Experimental public static Observable concatDelayError(Iterable> sources) { return concatDelayError(from(sources)); } + /** + * Returns an Observable that emits the items emitted by two Observables, one after the other, without + * interleaving them, and delays any errors till all Observables terminate. + * + *
+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The {@code Observable} + * sources are expected to honor backpressure as well. + * If any of the source {@code Observable}s violate this, it may throw an + * {@code IllegalStateException} when the source {@code Observable} completes.
+ *
Scheduler:
+ *
{@code concatDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the common element base type + * @param t1 + * an Observable to be concatenated + * @param t2 + * an Observable to be concatenated + * @return an Observable with the concatenating behavior + * @since 1.3 + */ + public static Observable concatDelayError(Observable t1, Observable t2) { + return concatDelayError(just(t1, t2)); + } + + /** + * Returns an Observable that emits the items emitted by three Observables, one after the other, without + * interleaving them, and delays any errors till all Observables terminate. + * + *
+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The {@code Observable} + * sources are expected to honor backpressure as well. + * If any of the source {@code Observable}s violate this, it may throw an + * {@code IllegalStateException} when the source {@code Observable} completes.
+ *
Scheduler:
+ *
{@code concatDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the common element base type + * @param t1 + * an Observable to be concatenated + * @param t2 + * an Observable to be concatenated + * @param t3 + * an Observable to be concatenated + * @return an Observable with the concatenating behavior + * @since 1.3 + */ + public static Observable concatDelayError(Observable t1, Observable t2,Observable t3 ) { + return concatDelayError(just(t1, t2, t3)); + } + + /** + * Returns an Observable that emits the items emitted by four Observables, one after the other, without + * interleaving them, and delays any errors till all Observables terminate. + * + *
+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The {@code Observable} + * sources are expected to honor backpressure as well. + * If any of the source {@code Observable}s violate this, it may throw an + * {@code IllegalStateException} when the source {@code Observable} completes.
+ *
Scheduler:
+ *
{@code concatDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the common element base type + * @param t1 + * an Observable to be concatenated + * @param t2 + * an Observable to be concatenated + * @param t3 + * an Observable to be concatenated + * @param t4 + * an Observable to be concatenated + * @return an Observable with the concatenating behavior + * @since 1.3 + */ + public static Observable concatDelayError(Observable t1, Observable t2, Observable t3, Observable t4) { + return concatDelayError(just(t1, t2, t3, t4)); + } + + /** + * Returns an Observable that emits the items emitted by five Observables, one after the other, without + * interleaving them, and delays any errors till all Observables terminate. + * + *
+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The {@code Observable} + * sources are expected to honor backpressure as well. + * If any of the source {@code Observable}s violate this, it may throw an + * {@code IllegalStateException} when the source {@code Observable} completes.
+ *
Scheduler:
+ *
{@code concatDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the common element base type + * @param t1 + * an Observable to be concatenated + * @param t2 + * an Observable to be concatenated + * @param t3 + * an Observable to be concatenated + * @param t4 + * an Observable to be concatenated + * @param t5 + * an Observable to be concatenated + * @return an Observable with the concatenating behavior + * @since 1.3 + */ + public static Observable concatDelayError(Observable t1, Observable t2, Observable t3, Observable t4, Observable t5) { + return concatDelayError(just(t1, t2, t3, t4, t5)); + } + + /** + * Returns an Observable that emits the items emitted by six Observables, one after the other, without + * interleaving them, and delays any errors till all Observables terminate. + * + *
+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The {@code Observable} + * sources are expected to honor backpressure as well. + * If any of the source {@code Observable}s violate this, it may throw an + * {@code IllegalStateException} when the source {@code Observable} completes.
+ *
Scheduler:
+ *
{@code concatDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the common element base type + * @param t1 + * an Observable to be concatenated + * @param t2 + * an Observable to be concatenated + * @param t3 + * an Observable to be concatenated + * @param t4 + * an Observable to be concatenated + * @param t5 + * an Observable to be concatenated + * @param t6 + * an Observable to be concatenated + * @return an Observable with the concatenating behavior + * @since 1.3 + */ + public static Observable concatDelayError(Observable t1, Observable t2, Observable t3, Observable t4, Observable t5, Observable t6) { + return concatDelayError(just(t1, t2, t3, t4, t5, t6)); + } + + /** + * Returns an Observable that emits the items emitted by seven Observables, one after the other, without + * interleaving them, and delays any errors till all Observables terminate. + * + *
+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The {@code Observable} + * sources are expected to honor backpressure as well. + * If any of the source {@code Observable}s violate this, it may throw an + * {@code IllegalStateException} when the source {@code Observable} completes.
+ *
Scheduler:
+ *
{@code concatDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the common element base type + * @param t1 + * an Observable to be concatenated + * @param t2 + * an Observable to be concatenated + * @param t3 + * an Observable to be concatenated + * @param t4 + * an Observable to be concatenated + * @param t5 + * an Observable to be concatenated + * @param t6 + * an Observable to be concatenated + * @param t7 + * an Observable to be concatenated + * @return an Observable with the concatenating behavior + * @since 1.3 + */ + public static Observable concatDelayError(Observable t1, Observable t2, Observable t3, Observable t4, Observable t5, Observable t6, Observable t7) { + return concatDelayError(just(t1, t2, t3, t4, t5, t6, t7)); + } + + /** + * Returns an Observable that emits the items emitted by eight Observables, one after the other, without + * interleaving them, and delays any errors till all Observables terminate. + * + *
+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The {@code Observable} + * sources are expected to honor backpressure as well. + * If any of the source {@code Observable}s violate this, it may throw an + * {@code IllegalStateException} when the source {@code Observable} completes.
+ *
Scheduler:
+ *
{@code concatDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the common element base type + * @param t1 + * an Observable to be concatenated + * @param t2 + * an Observable to be concatenated + * @param t3 + * an Observable to be concatenated + * @param t4 + * an Observable to be concatenated + * @param t5 + * an Observable to be concatenated + * @param t6 + * an Observable to be concatenated + * @param t7 + * an Observable to be concatenated + * @param t8 + * an Observable to be concatenated + * @return an Observable with the concatenating behavior + * @since 1.3 + */ + public static Observable concatDelayError(Observable t1, Observable t2, Observable t3, Observable t4, Observable t5, Observable t6, Observable t7, Observable t8) { + return concatDelayError(just(t1, t2, t3, t4, t5, t6, t7, t8)); + } + + /** + * Returns an Observable that emits the items emitted by nine Observables, one after the other, without + * interleaving them, and delays any errors till all Observables terminate. + * + *
+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The {@code Observable} + * sources are expected to honor backpressure as well. + * If any of the source {@code Observable}s violate this, it may throw an + * {@code IllegalStateException} when the source {@code Observable} completes.
+ *
Scheduler:
+ *
{@code concatDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the common element base type + * @param t1 + * an Observable to be concatenated + * @param t2 + * an Observable to be concatenated + * @param t3 + * an Observable to be concatenated + * @param t4 + * an Observable to be concatenated + * @param t5 + * an Observable to be concatenated + * @param t6 + * an Observable to be concatenated + * @param t7 + * an Observable to be concatenated + * @param t8 + * an Observable to be concatenated + * @param t9 + * an Observable to be concatenated + * @return an Observable with the concatenating behavior + * @since 1.3 + */ + public static Observable concatDelayError(Observable t1, Observable t2, Observable t3, Observable t4, Observable t5, Observable t6, Observable t7, Observable t8, Observable t9) { + return concatDelayError(just(t1, t2, t3, t4, t5, t6, t7, t8, t9)); + } + /** * Returns an Observable that calls an Observable factory to create an Observable for each new Observer * that subscribes. That is, for each subscriber, the actual Observable that subscriber observes is @@ -1319,10 +1795,13 @@ public static Observable concatDelayError(Iterable + *
Backpressure:
+ *
The operator itself doesn't interfere with backpressure which is determined by the {@code Observable} + * returned by the {@code observableFactory}.
*
Scheduler:
*
{@code defer} does not operate by default on a particular {@link Scheduler}.
* - * + * * @param observableFactory * the Observable factory function to invoke for each {@link Observer} that subscribes to the * resulting Observable @@ -1333,7 +1812,7 @@ public static Observable concatDelayError(IterableReactiveX operators documentation: Defer */ public static Observable defer(Func0> observableFactory) { - return create(new OnSubscribeDefer(observableFactory)); + return unsafeCreate(new OnSubscribeDefer(observableFactory)); } /** @@ -1342,6 +1821,8 @@ public static Observable defer(Func0> observableFactory) { *

* *

+ *
Backpressure:
+ *
This source doesn't produce any elements and effectively ignores downstream backpressure.
*
Scheduler:
*
{@code empty} does not operate by default on a particular {@link Scheduler}.
*
@@ -1362,10 +1843,12 @@ public static Observable empty() { *

* *

+ *
Backpressure:
+ *
This source doesn't produce any elements and effectively ignores downstream backpressure.
*
Scheduler:
*
{@code error} does not operate by default on a particular {@link Scheduler}.
*
- * + * * @param exception * the particular Throwable to pass to {@link Observer#onError onError} * @param @@ -1375,7 +1858,7 @@ public static Observable empty() { * @see ReactiveX operators documentation: Throw */ public static Observable error(Throwable exception) { - return create(new OnSubscribeThrow(exception)); + return unsafeCreate(new OnSubscribeThrow(exception)); } /** @@ -1389,10 +1872,12 @@ public static Observable error(Throwable exception) { *

* Important note: This Observable is blocking; you cannot unsubscribe from it. *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream.
*
Scheduler:
*
{@code from} does not operate by default on a particular {@link Scheduler}.
*
- * + * * @param future * the source {@link Future} * @param @@ -1403,7 +1888,7 @@ public static Observable error(Throwable exception) { */ @SuppressWarnings("cast") public static Observable from(Future future) { - return (Observable)create(OnSubscribeToObservableFuture.toObservableFuture(future)); + return (Observable)unsafeCreate(OnSubscribeToObservableFuture.toObservableFuture(future)); } /** @@ -1417,10 +1902,12 @@ public static Observable from(Future future) { *

* Important note: This Observable is blocking; you cannot unsubscribe from it. *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream.
*
Scheduler:
*
{@code from} does not operate by default on a particular {@link Scheduler}.
*
- * + * * @param future * the source {@link Future} * @param timeout @@ -1435,7 +1922,7 @@ public static Observable from(Future future) { */ @SuppressWarnings("cast") public static Observable from(Future future, long timeout, TimeUnit unit) { - return (Observable)create(OnSubscribeToObservableFuture.toObservableFuture(future, timeout, unit)); + return (Observable)unsafeCreate(OnSubscribeToObservableFuture.toObservableFuture(future, timeout, unit)); } /** @@ -1447,10 +1934,12 @@ public static Observable from(Future future, long timeout, T * return value of the {@link Future#get} method of that object, by passing the object into the {@code from} * method. *
+ *
Backpressure:
+ *
The operator honors backpressure from downstream.
*
Scheduler:
*
you specify which {@link Scheduler} this operator will use
*
- * + * * @param future * the source {@link Future} * @param scheduler @@ -1465,7 +1954,7 @@ public static Observable from(Future future, long timeout, T public static Observable from(Future future, Scheduler scheduler) { // TODO in a future revision the Scheduler will become important because we'll start polling instead of blocking on the Future @SuppressWarnings("cast") - Observable o = (Observable)create(OnSubscribeToObservableFuture.toObservableFuture(future)); + Observable o = (Observable)unsafeCreate(OnSubscribeToObservableFuture.toObservableFuture(future)); return o.subscribeOn(scheduler); } @@ -1474,10 +1963,13 @@ public static Observable from(Future future, Scheduler sched *

* *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream and iterates the given {@code iterable} + * on demand (i.e., when requested).
*
Scheduler:
*
{@code from} does not operate by default on a particular {@link Scheduler}.
*
- * + * * @param iterable * the source {@link Iterable} sequence * @param @@ -1487,7 +1979,7 @@ public static Observable from(Future future, Scheduler sched * @see ReactiveX operators documentation: From */ public static Observable from(Iterable iterable) { - return create(new OnSubscribeFromIterable(iterable)); + return unsafeCreate(new OnSubscribeFromIterable(iterable)); } /** @@ -1495,10 +1987,13 @@ public static Observable from(Iterable iterable) { *

* *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream and iterates the given {@code array} + * on demand (i.e., when requested).
*
Scheduler:
*
{@code from} does not operate by default on a particular {@link Scheduler}.
*
- * + * * @param array * the source Array * @param @@ -1514,7 +2009,7 @@ public static Observable from(T[] array) { if (n == 1) { return just(array[0]); } - return create(new OnSubscribeFromArray(array)); + return unsafeCreate(new OnSubscribeFromArray(array)); } /** @@ -1526,6 +2021,8 @@ public static Observable from(T[] array) { * This allows you to defer the execution of the function you specify until an observer subscribes to the * Observable. That is to say, it makes the function "lazy." *
+ *
Backpressure:
+ *
The operator honors backpressure from downstream.
*
Scheduler:
*
{@code fromCallable} does not operate by default on a particular {@link Scheduler}.
*
@@ -1537,11 +2034,10 @@ public static Observable from(T[] array) { * the type of the item emitted by the Observable * @return an Observable whose {@link Observer}s' subscriptions trigger an invocation of the given function * @see #defer(Func0) - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.2 */ - @Beta public static Observable fromCallable(Callable func) { - return create(new OnSubscribeFromCallable(func)); + return unsafeCreate(new OnSubscribeFromCallable(func)); } /** @@ -1552,7 +2048,7 @@ public static Observable fromCallable(Callable func) { *
Scheduler:
*
{@code interval} operates by default on the {@code computation} {@link Scheduler}.
* - * + * * @param interval * interval size in time units (see below) * @param unit @@ -1570,10 +2066,14 @@ public static Observable interval(long interval, TimeUnit unit) { *

* *

+ *
Backpressure:
+ *
The operator generates values based on time and ignores downstream backpressure which + * may lead to {@code MissingBackpressureException} at some point in the chain. + * Consumers should consider applying one of the {@code onBackpressureXXX} operators as well.
*
Scheduler:
*
you specify which {@link Scheduler} this operator will use
*
- * + * * @param interval * interval size in time units (see below) * @param unit @@ -1593,13 +2093,14 @@ public static Observable interval(long interval, TimeUnit unit, Scheduler *

* *

- *
Backpressure Support:
- *
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}.
+ *
Backpressure:
+ *
The operator generates values based on time and ignores downstream backpressure which + * may lead to {@code MissingBackpressureException} at some point in the chain. + * Consumers should consider applying one of the {@code onBackpressureXXX} operators as well.
*
Scheduler:
*
{@code interval} operates by default on the {@code computation} {@link Scheduler}.
*
- * + * * @param initialDelay * the initial delay time to wait before emitting the first value of 0L * @param period @@ -1621,13 +2122,14 @@ public static Observable interval(long initialDelay, long period, TimeUnit *

* *

- *
Backpressure Support:
- *
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}.
+ *
Backpressure:
+ *
The operator generates values based on time and ignores downstream backpressure which + * may lead to {@code MissingBackpressureException} at some point in the chain. + * Consumers should consider applying one of the {@code onBackpressureXXX} operators as well.
*
Scheduler:
*
you specify which {@link Scheduler} this operator will use
*
- * + * * @param initialDelay * the initial delay time to wait before emitting the first value of 0L * @param period @@ -1642,7 +2144,7 @@ public static Observable interval(long initialDelay, long period, TimeUnit * @since 1.0.12 */ public static Observable interval(long initialDelay, long period, TimeUnit unit, Scheduler scheduler) { - return create(new OnSubscribeTimerPeriodically(initialDelay, period, unit, scheduler)); + return unsafeCreate(new OnSubscribeTimerPeriodically(initialDelay, period, unit, scheduler)); } /** @@ -1658,10 +2160,12 @@ public static Observable interval(long initialDelay, long period, TimeUnit * time, while the {@code just} method converts an Iterable into an Observable that emits the entire * Iterable as a single item. *
+ *
Backpressure:
+ *
The operator honors backpressure from downstream.
*
Scheduler:
*
{@code just} does not operate by default on a particular {@link Scheduler}.
*
- * + * * @param value * the item to emit * @param @@ -1672,16 +2176,18 @@ public static Observable interval(long initialDelay, long period, TimeUnit public static Observable just(final T value) { return ScalarSynchronousObservable.create(value); } - + /** * Converts two items into an Observable that emits those items. *

* *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream and signals each value on-demand (i.e., when requested).
*
Scheduler:
*
{@code just} does not operate by default on a particular {@link Scheduler}.
*
- * + * * @param t1 * first item * @param t2 @@ -1702,10 +2208,12 @@ public static Observable just(T t1, T t2) { *

* *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream and signals each value on-demand (i.e., when requested).
*
Scheduler:
*
{@code just} does not operate by default on a particular {@link Scheduler}.
*
- * + * * @param t1 * first item * @param t2 @@ -1728,10 +2236,12 @@ public static Observable just(T t1, T t2, T t3) { *

* *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream and signals each value on-demand (i.e., when requested).
*
Scheduler:
*
{@code just} does not operate by default on a particular {@link Scheduler}.
*
- * + * * @param t1 * first item * @param t2 @@ -1756,6 +2266,8 @@ public static Observable just(T t1, T t2, T t3, T t4) { *

* *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream and signals each value on-demand (i.e., when requested).
*
Scheduler:
*
{@code just} does not operate by default on a particular {@link Scheduler}.
*
@@ -1786,10 +2298,12 @@ public static Observable just(T t1, T t2, T t3, T t4, T t5) { *

* *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream and signals each value on-demand (i.e., when requested).
*
Scheduler:
*
{@code just} does not operate by default on a particular {@link Scheduler}.
*
- * + * * @param t1 * first item * @param t2 @@ -1818,10 +2332,12 @@ public static Observable just(T t1, T t2, T t3, T t4, T t5, T t6) { *

* *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream and signals each value on-demand (i.e., when requested).
*
Scheduler:
*
{@code just} does not operate by default on a particular {@link Scheduler}.
*
- * + * * @param t1 * first item * @param t2 @@ -1852,10 +2368,12 @@ public static Observable just(T t1, T t2, T t3, T t4, T t5, T t6, T t7) { *

* *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream and signals each value on-demand (i.e., when requested).
*
Scheduler:
*
{@code just} does not operate by default on a particular {@link Scheduler}.
*
- * + * * @param t1 * first item * @param t2 @@ -1888,10 +2406,12 @@ public static Observable just(T t1, T t2, T t3, T t4, T t5, T t6, T t7, T *

* *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream and signals each value on-demand (i.e., when requested).
*
Scheduler:
*
{@code just} does not operate by default on a particular {@link Scheduler}.
*
- * + * * @param t1 * first item * @param t2 @@ -1926,10 +2446,12 @@ public static Observable just(T t1, T t2, T t3, T t4, T t5, T t6, T t7, T *

* *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream and signals each value on-demand (i.e., when requested).
*
Scheduler:
*
{@code just} does not operate by default on a particular {@link Scheduler}.
*
- * + * * @param t1 * first item * @param t2 @@ -1960,7 +2482,7 @@ public static Observable just(T t1, T t2, T t3, T t4, T t5, T t6, T t7, T public static Observable just(T t1, T t2, T t3, T t4, T t5, T t6, T t7, T t8, T t9, T t10) { return from((T[])new Object[] { t1, t2, t3, t4, t5, t6, t7, t8, t9, t10 }); } - + /** * Flattens an Iterable of Observables into one Observable, without any transformation. *

@@ -1969,10 +2491,13 @@ public static Observable just(T t1, T t2, T t3, T t4, T t5, T t6, T t7, T * You can combine the items emitted by multiple Observables so that they appear as a single Observable, by * using the {@code merge} method. *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The source {@code Observable}s are expected to honor + * backpressure; if violated, the operator may signal {@code MissingBackpressureException}.
*
Scheduler:
*
{@code merge} does not operate by default on a particular {@link Scheduler}.
*
- * + * * @param the common element base type * @param sequences * the Iterable of Observables @@ -1993,10 +2518,13 @@ public static Observable merge(Iterable * You can combine the items emitted by multiple Observables so that they appear as a single Observable, by * using the {@code merge} method. *
+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The source {@code Observable}s are expected to honor + * backpressure; if violated, the operator may signal {@code MissingBackpressureException}.
*
Scheduler:
*
{@code merge} does not operate by default on a particular {@link Scheduler}.
*
- * + * * @param the common element base type * @param sequences * the Iterable of Observables @@ -2021,6 +2549,10 @@ public static Observable merge(Iterable * You can combine the items emitted by multiple Observables so that they appear as a single Observable, by * using the {@code merge} method. *
+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The outer {@code Observable} is consumed + * in unbounded mode (i.e., no backpressure is applied to it). The inner {@code Observable}s are expected to honor + * backpressure; if violated, the operator may signal {@code MissingBackpressureException}.
*
Scheduler:
*
{@code merge} does not operate by default on a particular {@link Scheduler}.
*
@@ -2050,10 +2582,13 @@ public static Observable merge(Observable + *
Backpressure:
+ *
The operator honors backpressure from downstream. Both the outer and inner {@code Observable}s are expected to honor + * backpressure; if violated, the operator may signal {@code MissingBackpressureException}.
*
Scheduler:
*
{@code merge} does not operate by default on a particular {@link Scheduler}.
* - * + * * @param the common element base type * @param source * an Observable that emits Observables @@ -2082,10 +2617,13 @@ public static Observable merge(Observable + *
Backpressure:
+ *
The operator honors backpressure from downstream. The source {@code Observable}s are expected to honor + * backpressure; if violated, the operator may signal {@code MissingBackpressureException}.
*
Scheduler:
*
{@code merge} does not operate by default on a particular {@link Scheduler}.
* - * + * * @param the common element base type * @param t1 * an Observable to be merged @@ -2107,10 +2645,13 @@ public static Observable merge(Observable t1, Observable + *
Backpressure:
+ *
The operator honors backpressure from downstream. The source {@code Observable}s are expected to honor + * backpressure; if violated, the operator may signal {@code MissingBackpressureException}.
*
Scheduler:
*
{@code merge} does not operate by default on a particular {@link Scheduler}.
* - * + * * @param the common element base type * @param t1 * an Observable to be merged @@ -2134,10 +2675,13 @@ public static Observable merge(Observable t1, Observable + *
Backpressure:
+ *
The operator honors backpressure from downstream. The source {@code Observable}s are expected to honor + * backpressure; if violated, the operator may signal {@code MissingBackpressureException}.
*
Scheduler:
*
{@code merge} does not operate by default on a particular {@link Scheduler}.
* - * + * * @param the common element base type * @param t1 * an Observable to be merged @@ -2163,10 +2707,13 @@ public static Observable merge(Observable t1, Observable + *
Backpressure:
+ *
The operator honors backpressure from downstream. The source {@code Observable}s are expected to honor + * backpressure; if violated, the operator may signal {@code MissingBackpressureException}.
*
Scheduler:
*
{@code merge} does not operate by default on a particular {@link Scheduler}.
* - * + * * @param the common element base type * @param t1 * an Observable to be merged @@ -2194,10 +2741,13 @@ public static Observable merge(Observable t1, Observable + *
Backpressure:
+ *
The operator honors backpressure from downstream. The source {@code Observable}s are expected to honor + * backpressure; if violated, the operator may signal {@code MissingBackpressureException}.
*
Scheduler:
*
{@code merge} does not operate by default on a particular {@link Scheduler}.
* - * + * * @param the common element base type * @param t1 * an Observable to be merged @@ -2227,10 +2777,13 @@ public static Observable merge(Observable t1, Observable + *
Backpressure:
+ *
The operator honors backpressure from downstream. The source {@code Observable}s are expected to honor + * backpressure; if violated, the operator may signal {@code MissingBackpressureException}.
*
Scheduler:
*
{@code merge} does not operate by default on a particular {@link Scheduler}.
* - * + * * @param the common element base type * @param t1 * an Observable to be merged @@ -2262,10 +2815,13 @@ public static Observable merge(Observable t1, Observable + *
Backpressure:
+ *
The operator honors backpressure from downstream. The source {@code Observable}s are expected to honor + * backpressure; if violated, the operator may signal {@code MissingBackpressureException}.
*
Scheduler:
*
{@code merge} does not operate by default on a particular {@link Scheduler}.
* - * + * * @param the common element base type * @param t1 * an Observable to be merged @@ -2299,10 +2855,13 @@ public static Observable merge(Observable t1, Observable + *
Backpressure:
+ *
The operator honors backpressure from downstream. The source {@code Observable}s are expected to honor + * backpressure; if violated, the operator may signal {@code MissingBackpressureException}.
*
Scheduler:
*
{@code merge} does not operate by default on a particular {@link Scheduler}.
* - * + * * @param the common element base type * @param t1 * an Observable to be merged @@ -2338,10 +2897,13 @@ public static Observable merge(Observable t1, Observable + *
Backpressure:
+ *
The operator honors backpressure from downstream. The source {@code Observable}s are expected to honor + * backpressure; if violated, the operator may signal {@code MissingBackpressureException}.
*
Scheduler:
*
{@code merge} does not operate by default on a particular {@link Scheduler}.
* - * + * * @param the common element base type * @param sequences * the Array of Observables @@ -2351,7 +2913,7 @@ public static Observable merge(Observable t1, Observable Observable merge(Observable[] sequences) { return merge(from(sequences)); } - + /** * Flattens an Array of Observables into one Observable, without any transformation, while limiting the * number of concurrent subscriptions to these Observables. @@ -2361,10 +2923,13 @@ public static Observable merge(Observable[] sequences) { * You can combine items emitted by multiple Observables so that they appear as a single Observable, by * using the {@code merge} method. *
+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The source {@code Observable}s are expected to honor + * backpressure; if violated, the operator may signal {@code MissingBackpressureException}.
*
Scheduler:
*
{@code merge} does not operate by default on a particular {@link Scheduler}.
*
- * + * * @param the common element base type * @param sequences * the Array of Observables @@ -2392,10 +2957,14 @@ public static Observable merge(Observable[] sequences, int m * Even if multiple merged Observables send {@code onError} notifications, {@code mergeDelayError} will only * invoke the {@code onError} method of its Observers once. *
+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The outer {@code Observable} is consumed + * in unbounded mode (i.e., no backpressure is applied to it). The inner {@code Observable}s are expected to honor + * backpressure; if violated, the operator may signal {@code MissingBackpressureException}.
*
Scheduler:
*
{@code mergeDelayError} does not operate by default on a particular {@link Scheduler}.
*
- * + * * @param the common element base type * @param source * an Observable that emits Observables @@ -2422,10 +2991,13 @@ public static Observable mergeDelayError(Observable + *
Backpressure:
+ *
The operator honors backpressure from downstream. Both the outer and inner {@code Observable}s are expected to honor + * backpressure; if violated, the operator may signal {@code MissingBackpressureException}.
*
Scheduler:
*
{@code mergeDelayError} does not operate by default on a particular {@link Scheduler}.
* - * + * * @param the common element base type * @param source * an Observable that emits Observables @@ -2434,9 +3006,8 @@ public static Observable mergeDelayError(ObservableReactiveX operators documentation: Merge - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Experimental public static Observable mergeDelayError(Observable> source, int maxConcurrent) { return source.lift(OperatorMerge.instance(true, maxConcurrent)); } @@ -2458,7 +3029,7 @@ public static Observable mergeDelayError(ObservableScheduler: *
{@code mergeDelayError} does not operate by default on a particular {@link Scheduler}.
* - * + * * @param the common element base type * @param sequences * the Iterable of Observables @@ -2487,7 +3058,7 @@ public static Observable mergeDelayError(IterableScheduler: *
{@code mergeDelayError} does not operate by default on a particular {@link Scheduler}.
* - * + * * @param the common element base type * @param sequences * the Iterable of Observables @@ -2516,10 +3087,13 @@ public static Observable mergeDelayError(Iterable + *
Backpressure:
+ *
The operator honors backpressure from downstream. The source {@code Observable}s are expected to honor + * backpressure; if violated, the operator may signal {@code MissingBackpressureException}.
*
Scheduler:
*
{@code mergeDelayError} does not operate by default on a particular {@link Scheduler}.
* - * + * * @param the common element base type * @param t1 * an Observable to be merged @@ -2547,10 +3121,13 @@ public static Observable mergeDelayError(Observable t1, Obse * Even if multiple merged Observables send {@code onError} notifications, {@code mergeDelayError} will only * invoke the {@code onError} method of its Observers once. *
+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The source {@code Observable}s are expected to honor + * backpressure; if violated, the operator may signal {@code MissingBackpressureException}.
*
Scheduler:
*
{@code mergeDelayError} does not operate by default on a particular {@link Scheduler}.
*
- * + * * @param the common element base type * @param t1 * an Observable to be merged @@ -2580,10 +3157,13 @@ public static Observable mergeDelayError(Observable t1, Obse * Even if multiple merged Observables send {@code onError} notifications, {@code mergeDelayError} will only * invoke the {@code onError} method of its Observers once. *
+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The source {@code Observable}s are expected to honor + * backpressure; if violated, the operator may signal {@code MissingBackpressureException}.
*
Scheduler:
*
{@code mergeDelayError} does not operate by default on a particular {@link Scheduler}.
*
- * + * * @param the common element base type * @param t1 * an Observable to be merged @@ -2615,10 +3195,13 @@ public static Observable mergeDelayError(Observable t1, Obse * Even if multiple merged Observables send {@code onError} notifications, {@code mergeDelayError} will only * invoke the {@code onError} method of its Observers once. *
+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The source {@code Observable}s are expected to honor + * backpressure; if violated, the operator may signal {@code MissingBackpressureException}.
*
Scheduler:
*
{@code mergeDelayError} does not operate by default on a particular {@link Scheduler}.
*
- * + * * @param the common element base type * @param t1 * an Observable to be merged @@ -2652,10 +3235,13 @@ public static Observable mergeDelayError(Observable t1, Obse * Even if multiple merged Observables send {@code onError} notifications, {@code mergeDelayError} will only * invoke the {@code onError} method of its Observers once. *
+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The source {@code Observable}s are expected to honor + * backpressure; if violated, the operator may signal {@code MissingBackpressureException}.
*
Scheduler:
*
{@code mergeDelayError} does not operate by default on a particular {@link Scheduler}.
*
- * + * * @param the common element base type * @param t1 * an Observable to be merged @@ -2692,10 +3278,13 @@ public static Observable mergeDelayError(Observable t1, Obse * Even if multiple merged Observables send {@code onError} notifications, {@code mergeDelayError} will only * invoke the {@code onError} method of its Observers once. *
+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The source {@code Observable}s are expected to honor + * backpressure; if violated, the operator may signal {@code MissingBackpressureException}.
*
Scheduler:
*
{@code mergeDelayError} does not operate by default on a particular {@link Scheduler}.
*
- * + * * @param the common element base type * @param t1 * an Observable to be merged @@ -2733,10 +3322,13 @@ public static Observable mergeDelayError(Observable t1, Obse * Even if multiple merged Observables send {@code onError} notifications, {@code mergeDelayError} will only * invoke the {@code onError} method of its Observers once. *
+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The source {@code Observable}s are expected to honor + * backpressure; if violated, the operator may signal {@code MissingBackpressureException}.
*
Scheduler:
*
{@code mergeDelayError} does not operate by default on a particular {@link Scheduler}.
*
- * + * * @param the common element base type * @param t1 * an Observable to be merged @@ -2776,10 +3368,13 @@ public static Observable mergeDelayError(Observable t1, Obse * Even if multiple merged Observables send {@code onError} notifications, {@code mergeDelayError} will only * invoke the {@code onError} method of its Observers once. *
+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The source {@code Observable}s are expected to honor + * backpressure; if violated, the operator may signal {@code MissingBackpressureException}.
*
Scheduler:
*
{@code mergeDelayError} does not operate by default on a particular {@link Scheduler}.
*
- * + * * @param the common element base type * @param t1 * an Observable to be merged @@ -2812,10 +3407,12 @@ public static Observable mergeDelayError(Observable t1, Obse *

* *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream.
*
Scheduler:
*
{@code nest} does not operate by default on a particular {@link Scheduler}.
*
- * + * * @return an Observable that emits a single item: the source Observable * @see ReactiveX operators documentation: To */ @@ -2830,10 +3427,12 @@ public final Observable> nest() { *

* This Observable is useful primarily for testing purposes. *

+ *
Backpressure:
+ *
This source doesn't produce any elements and effectively ignores downstream backpressure.
*
Scheduler:
*
{@code never} does not operate by default on a particular {@link Scheduler}.
*
- * + * * @param * the type of items (not) emitted by the Observable * @return an Observable that never emits any items or sends any notifications to an {@link Observer} @@ -2848,10 +3447,12 @@ public static Observable never() { *

* *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream and signals values on-demand (i.e., when requested).
*
Scheduler:
*
{@code range} does not operate by default on a particular {@link Scheduler}.
*
- * + * * @param start * the value of the first Integer in the sequence * @param count @@ -2872,10 +3473,10 @@ public static Observable range(int start, int count) { if (start > Integer.MAX_VALUE - count + 1) { throw new IllegalArgumentException("start + count can not exceed Integer.MAX_VALUE"); } - if(count == 1) { + if (count == 1) { return Observable.just(start); } - return Observable.create(new OnSubscribeRange(start, start + (count - 1))); + return Observable.unsafeCreate(new OnSubscribeRange(start, start + (count - 1))); } /** @@ -2884,10 +3485,12 @@ public static Observable range(int start, int count) { *

* *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream and signals values on-demand (i.e., when requested).
*
Scheduler:
*
you specify which {@link Scheduler} this operator will use
*
- * + * * @param start * the value of the first Integer in the sequence * @param count @@ -2910,7 +3513,7 @@ public static Observable range(int start, int count, Scheduler schedule *
Scheduler:
*
{@code sequenceEqual} does not operate by default on a particular {@link Scheduler}.
* - * + * * @param first * the first Observable to compare * @param second @@ -2923,7 +3526,7 @@ public static Observable range(int start, int count, Scheduler schedule public static Observable sequenceEqual(Observable first, Observable second) { return sequenceEqual(first, second, InternalObservableUtils.OBJECT_EQUALS); } - + /** * Returns an Observable that emits a Boolean value that indicates whether two Observable sequences are the * same by comparing the items emitted by each Observable pairwise based on the results of a specified @@ -2931,10 +3534,13 @@ public static Observable sequenceEqual(Observable firs *

* *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The source {@code Observable}s are expected to honor + * backpressure; if violated, the operator signals a {@code MissingBackpressureException}.
*
Scheduler:
*
{@code sequenceEqual} does not operate by default on a particular {@link Scheduler}.
*
- * + * * @param first * the first Observable to compare * @param second @@ -2965,10 +3571,15 @@ public static Observable sequenceEqual(Observable firs * The resulting Observable completes if both the outer Observable and the last inner Observable, if any, complete. * If the outer Observable signals an onError, the inner Observable is unsubscribed and the error delivered in-sequence. *
+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The outer {@code Observable} is consumed in an + * unbounded manner (i.e., without backpressure) and the inner {@code Observable}s are expected to honor + * backpressure but it is not enforced; the operator won't signal a {@code MissingBackpressureException} + * but the violation may lead to {@code OutOfMemoryError} due to internal buffer bloat.
*
Scheduler:
*
{@code switchOnNext} does not operate by default on a particular {@link Scheduler}.
*
- * + * * @param the item type * @param sequenceOfSequences * the source Observable that emits Observables @@ -2995,20 +3606,23 @@ public static Observable switchOnNext(Observable + *
Backpressure:
+ *
The operator honors backpressure from downstream. The outer {@code Observable} is consumed in an + * unbounded manner (i.e., without backpressure) and the inner {@code Observable}s are expected to honor + * backpressure but it is not enforced; the operator won't signal a {@code MissingBackpressureException} + * but the violation may lead to {@code OutOfMemoryError} due to internal buffer bloat.
*
Scheduler:
*
{@code switchOnNext} does not operate by default on a particular {@link Scheduler}.
* - * + * * @param the item type * @param sequenceOfSequences * the source Observable that emits Observables * @return an Observable that emits the items emitted by the Observable most recently emitted by the source * Observable * @see ReactiveX operators documentation: Switch - * @Experimental The behavior of this can change at any time. - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Experimental public static Observable switchOnNextDelayError(Observable> sequenceOfSequences) { return sequenceOfSequences.lift(OperatorSwitch.instance(true)); } @@ -3019,13 +3633,13 @@ public static Observable switchOnNextDelayError(Observable * *
- *
Backpressure Support:
+ *
Backpressure:
*
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}.
*
Scheduler:
*
{@code timer} operates by default on the {@code computation} {@link Scheduler}.
*
- * + * * @param initialDelay * the initial delay time to wait before emitting the first value of 0L * @param period @@ -3048,13 +3662,13 @@ public static Observable timer(long initialDelay, long period, TimeUnit un *

* *

- *
Backpressure Support:
+ *
Backpressure:
*
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}.
*
Scheduler:
*
you specify which {@link Scheduler} this operator will use
*
- * + * * @param initialDelay * the initial delay time to wait before emitting the first value of 0L * @param period @@ -3074,17 +3688,17 @@ public static Observable timer(long initialDelay, long period, TimeUnit un } /** - * Returns an Observable that emits one item after a specified delay, and then completes. + * Returns an Observable that emits {@code 0L} after a specified delay, and then completes. *

* *

- *
Backpressure Support:
+ *
Backpressure:
*
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}.
*
Scheduler:
*
{@code timer} operates by default on the {@code computation} {@link Scheduler}.
*
- * + * * @param delay * the initial delay before emitting a single {@code 0L} * @param unit @@ -3097,18 +3711,18 @@ public static Observable timer(long delay, TimeUnit unit) { } /** - * Returns an Observable that emits one item after a specified delay, on a specified Scheduler, and then + * Returns an Observable that emits {@code 0L} after a specified delay, on a specified Scheduler, and then * completes. *

* *

- *
Backpressure Support:
+ *
Backpressure:
*
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}.
*
Scheduler:
*
you specify which {@link Scheduler} this operator will use
*
- * + * * @param delay * the initial delay before emitting a single 0L * @param unit @@ -3120,7 +3734,7 @@ public static Observable timer(long delay, TimeUnit unit) { * @see ReactiveX operators documentation: Timer */ public static Observable timer(long delay, TimeUnit unit, Scheduler scheduler) { - return create(new OnSubscribeTimerOnce(delay, unit, scheduler)); + return unsafeCreate(new OnSubscribeTimerOnce(delay, unit, scheduler)); } /** @@ -3129,12 +3743,12 @@ public static Observable timer(long delay, TimeUnit unit, Scheduler schedu * *
*
Backpressure:
- *
The operator is a pass-through for backpressure and otherwise depends on the + *
The operator is a pass-through for backpressure and otherwise depends on the * backpressure support of the Observable returned by the {@code resourceFactory}.
*
Scheduler:
*
{@code using} does not operate by default on a particular {@link Scheduler}.
*
- * + * * @param the element type of the generated Observable * @param the type of the resource associated with the output sequence * @param resourceFactory @@ -3152,9 +3766,9 @@ public static Observable using( final Action1 disposeAction) { return using(resourceFactory, observableFactory, disposeAction, false); } - + /** - * Constructs an Observable that creates a dependent resource object which is disposed of just before + * Constructs an Observable that creates a dependent resource object which is disposed of just before * termination if you have set {@code disposeEagerly} to {@code true} and unsubscription does not occur * before termination. Otherwise resource disposal will occur on unsubscription. Eager disposal is * particularly appropriate for a synchronous Observable that reuses resources. {@code disposeAction} will @@ -3163,12 +3777,12 @@ public static Observable using( * *
*
Backpressure:
- *
The operator is a pass-through for backpressure and otherwise depends on the + *
The operator is a pass-through for backpressure and otherwise depends on the * backpressure support of the Observable returned by the {@code resourceFactory}.
*
Scheduler:
*
{@code using} does not operate by default on a particular {@link Scheduler}.
*
- * + * * @param the element type of the generated Observable * @param the type of the resource associated with the output sequence * @param resourceFactory @@ -3178,19 +3792,17 @@ public static Observable using( * @param disposeAction * the function that will dispose of the resource * @param disposeEagerly - * if {@code true} then disposal will happen either on unsubscription or just before emission of + * if {@code true} then disposal will happen either on unsubscription or just before emission of * a terminal event ({@code onComplete} or {@code onError}). * @return the Observable whose lifetime controls the lifetime of the dependent resource object * @see ReactiveX operators documentation: Using - * @Experimental The behavior of this can change at any time. - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Experimental public static Observable using( final Func0 resourceFactory, final Func1> observableFactory, final Action1 disposeAction, boolean disposeEagerly) { - return create(new OnSubscribeUsing(resourceFactory, observableFactory, disposeAction, disposeEagerly)); + return unsafeCreate(new OnSubscribeUsing(resourceFactory, observableFactory, disposeAction, disposeEagerly)); } /** @@ -3205,16 +3817,16 @@ public static Observable using( * The resulting {@code Observable} returned from {@code zip} will invoke {@code onNext} as many times as * the number of {@code onNext} invocations of the source Observable that emits the fewest items. *

- * 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 unsubscribing the other sources. Therefore, it - * is possible those other sources will never be able to run to completion (and thus not calling + * 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 unsubscribing the other sources. Therefore, it + * is possible those other sources will never be able to run to completion (and thus not calling * {@code doOnCompleted()}). 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 * be sending further values and it will unsubscribe B immediately. For example: *

zip(Arrays.asList(range(1, 5).doOnCompleted(action1), range(6, 5).doOnCompleted(action2)), (a) -> a)
* {@code action1} will be called but {@code action2} won't. *
To work around this termination property, - * use {@code doOnUnsubscribed()} as well or use {@code using()} to do cleanup in case of completion + * use {@code doOnUnsubscribed()} as well or use {@code using()} to do cleanup in case of completion * or unsubscription. *

* @@ -3226,7 +3838,7 @@ public static Observable using( *

Scheduler:
*
{@code zip} does not operate by default on a particular {@link Scheduler}.
* - * + * * @param the zipped result type * @param ws * an Iterable of source Observables @@ -3278,7 +3890,7 @@ public static Observable zip(Iterable> ws, FuncN< *
Scheduler:
*
{@code zip} does not operate by default on a particular {@link Scheduler}.
* - * + * * @param the result type * @param ws * an array of source Observables @@ -3287,8 +3899,8 @@ public static Observable zip(Iterable> ws, FuncN< * an item that will be emitted by the resulting Observable * @return an Observable that emits the zipped results * @see ReactiveX operators documentation: Zip + * @since 1.3 */ - @Experimental public static Observable zip(Observable[] ws, FuncN zipFunction) { return Observable.just(ws).lift(new OperatorZip(zipFunction)); } @@ -3305,16 +3917,16 @@ public static Observable zip(Observable[] ws, FuncN zipFu * The resulting {@code Observable} returned from {@code zip} will invoke {@code onNext} as many times as * the number of {@code onNext} invocations of the source Observable that emits the fewest items. *

- * 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 unsubscribing the other sources. Therefore, it - * is possible those other sources will never be able to run to completion (and thus not calling + * 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 unsubscribing the other sources. Therefore, it + * is possible those other sources will never be able to run to completion (and thus not calling * {@code doOnCompleted()}). 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 * be sending further values and it will unsubscribe B immediately. For example: *

zip(just(range(1, 5).doOnCompleted(action1), range(6, 5).doOnCompleted(action2)), (a) -> a)
* {@code action1} will be called but {@code action2} won't. *
To work around this termination property, - * use {@code doOnUnsubscribed()} as well or use {@code using()} to do cleanup in case of completion + * use {@code doOnUnsubscribed()} as well or use {@code using()} to do cleanup in case of completion * or unsubscription. *

* @@ -3326,7 +3938,7 @@ public static Observable zip(Observable[] ws, FuncN zipFu *

Scheduler:
*
{@code zip} does not operate by default on a particular {@link Scheduler}.
* - * + * * @param the zipped result type * @param ws * an Observable of source Observables @@ -3339,7 +3951,7 @@ public static Observable zip(Observable[] ws, FuncN zipFu public static Observable zip(Observable> ws, final FuncN zipFunction) { return ws.toList().map(InternalObservableUtils.TO_ARRAY).lift(new OperatorZip(zipFunction)); } - + /** * Returns an Observable that emits the results of a specified combiner function applied to combinations of * two items emitted, in sequence, by two other Observables. @@ -3355,16 +3967,16 @@ public static Observable zip(Observable> ws, fina * as many times as the number of {@code onNext} invocations of the source Observable that emits the fewest * items. *

- * 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 unsubscribing the other sources. Therefore, it - * is possible those other sources will never be able to run to completion (and thus not calling + * 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 unsubscribing the other sources. Therefore, it + * is possible those other sources will never be able to run to completion (and thus not calling * {@code doOnCompleted()}). 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 * be sending further values and it will unsubscribe B immediately. For example: *

zip(range(1, 5).doOnCompleted(action1), range(6, 5).doOnCompleted(action2), (a, b) -> a + b)
* {@code action1} will be called but {@code action2} won't. *
To work around this termination property, - * use {@code doOnUnsubscribed()} as well or use {@code using()} to do cleanup in case of completion + * use {@code doOnUnsubscribed()} as well or use {@code using()} to do cleanup in case of completion * or unsubscription. *
*
Backpressure:
@@ -3374,7 +3986,7 @@ public static Observable zip(Observable> ws, fina *
Scheduler:
*
{@code zip} does not operate by default on a particular {@link Scheduler}.
*
- * + * * @param the value type of the first source * @param the value type of the second source * @param the zipped result type @@ -3408,16 +4020,16 @@ public static Observable zip(Observable o1, Observa * as many times as the number of {@code onNext} invocations of the source Observable that emits the fewest * items. *

- * 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 unsubscribing the other sources. Therefore, it - * is possible those other sources will never be able to run to completion (and thus not calling + * 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 unsubscribing the other sources. Therefore, it + * is possible those other sources will never be able to run to completion (and thus not calling * {@code doOnCompleted()}). 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 * be sending further values and it will unsubscribe B immediately. For example: *

zip(range(1, 5).doOnCompleted(action1), range(6, 5).doOnCompleted(action2), ..., (a, b, c) -> a + b)
* {@code action1} will be called but {@code action2} won't. *
To work around this termination property, - * use {@code doOnUnsubscribed()} as well or use {@code using()} to do cleanup in case of completion + * use {@code doOnUnsubscribed()} as well or use {@code using()} to do cleanup in case of completion * or unsubscription. *
*
Backpressure:
@@ -3427,7 +4039,7 @@ public static Observable zip(Observable o1, Observa *
Scheduler:
*
{@code zip} does not operate by default on a particular {@link Scheduler}.
*
- * + * * @param the value type of the first source * @param the value type of the second source * @param the value type of the third source @@ -3464,16 +4076,16 @@ public static Observable zip(Observable o1, Obs * as many times as the number of {@code onNext} invocations of the source Observable that emits the fewest * items. *

- * 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 unsubscribing the other sources. Therefore, it - * is possible those other sources will never be able to run to completion (and thus not calling + * 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 unsubscribing the other sources. Therefore, it + * is possible those other sources will never be able to run to completion (and thus not calling * {@code doOnCompleted()}). 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 * be sending further values and it will unsubscribe B immediately. For example: *

zip(range(1, 5).doOnCompleted(action1), range(6, 5).doOnCompleted(action2), ..., (a, b, c, d) -> a + b)
* {@code action1} will be called but {@code action2} won't. *
To work around this termination property, - * use {@code doOnUnsubscribed()} as well or use {@code using()} to do cleanup in case of completion + * use {@code doOnUnsubscribed()} as well or use {@code using()} to do cleanup in case of completion * or unsubscription. *
*
Backpressure:
@@ -3483,7 +4095,7 @@ public static Observable zip(Observable o1, Obs *
Scheduler:
*
{@code zip} does not operate by default on a particular {@link Scheduler}.
*
- * + * * @param the value type of the first source * @param the value type of the second source * @param the value type of the third source @@ -3523,16 +4135,16 @@ public static Observable zip(Observable o1, * as many times as the number of {@code onNext} invocations of the source Observable that emits the fewest * items. *

- * 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 unsubscribing the other sources. Therefore, it - * is possible those other sources will never be able to run to completion (and thus not calling + * 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 unsubscribing the other sources. Therefore, it + * is possible those other sources will never be able to run to completion (and thus not calling * {@code doOnCompleted()}). 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 * be sending further values and it will unsubscribe B immediately. For example: *

zip(range(1, 5).doOnCompleted(action1), range(6, 5).doOnCompleted(action2), ..., (a, b, c, d, e) -> a + b)
* {@code action1} will be called but {@code action2} won't. *
To work around this termination property, - * use {@code doOnUnsubscribed()} as well or use {@code using()} to do cleanup in case of completion + * use {@code doOnUnsubscribed()} as well or use {@code using()} to do cleanup in case of completion * or unsubscription. *
*
Backpressure:
@@ -3542,7 +4154,7 @@ public static Observable zip(Observable o1, *
Scheduler:
*
{@code zip} does not operate by default on a particular {@link Scheduler}.
*
- * + * * @param the value type of the first source * @param the value type of the second source * @param the value type of the third source @@ -3584,16 +4196,16 @@ public static Observable zip(Observable * as many times as the number of {@code onNext} invocations of the source Observable that emits the fewest * items. *

- * 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 unsubscribing the other sources. Therefore, it - * is possible those other sources will never be able to run to completion (and thus not calling + * 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 unsubscribing the other sources. Therefore, it + * is possible those other sources will never be able to run to completion (and thus not calling * {@code doOnCompleted()}). 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 * be sending further values and it will unsubscribe B immediately. For example: *

zip(range(1, 5).doOnCompleted(action1), range(6, 5).doOnCompleted(action2), ..., (a, b, c, d, e, f) -> a + b)
* {@code action1} will be called but {@code action2} won't. *
To work around this termination property, - * use {@code doOnUnsubscribed()} as well or use {@code using()} to do cleanup in case of completion + * use {@code doOnUnsubscribed()} as well or use {@code using()} to do cleanup in case of completion * or unsubscription. *
*
Backpressure:
@@ -3603,7 +4215,7 @@ public static Observable zip(Observable *
Scheduler:
*
{@code zip} does not operate by default on a particular {@link Scheduler}.
*
- * + * * @param the value type of the first source * @param the value type of the second source * @param the value type of the third source @@ -3649,16 +4261,16 @@ public static Observable zip(Observable - * 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 unsubscribing the other sources. Therefore, it - * is possible those other sources will never be able to run to completion (and thus not calling + * 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 unsubscribing the other sources. Therefore, it + * is possible those other sources will never be able to run to completion (and thus not calling * {@code doOnCompleted()}). 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 * be sending further values and it will unsubscribe B immediately. For example: *
zip(range(1, 5).doOnCompleted(action1), range(6, 5).doOnCompleted(action2), ..., (a, b, c, d, e, f, g) -> a + b)
* {@code action1} will be called but {@code action2} won't. *
To work around this termination property, - * use {@code doOnUnsubscribed()} as well or use {@code using()} to do cleanup in case of completion + * use {@code doOnUnsubscribed()} as well or use {@code using()} to do cleanup in case of completion * or unsubscription. *
*
Backpressure:
@@ -3668,7 +4280,7 @@ public static Observable zip(ObservableScheduler:
*
{@code zip} does not operate by default on a particular {@link Scheduler}.
*
- * + * * @param the value type of the first source * @param the value type of the second source * @param the value type of the third source @@ -3717,16 +4329,16 @@ public static Observable zip(Observable - * 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 unsubscribing the other sources. Therefore, it - * is possible those other sources will never be able to run to completion (and thus not calling + * 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 unsubscribing the other sources. Therefore, it + * is possible those other sources will never be able to run to completion (and thus not calling * {@code doOnCompleted()}). 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 * be sending further values and it will unsubscribe B immediately. For example: *
zip(range(1, 5).doOnCompleted(action1), range(6, 5).doOnCompleted(action2), ..., (a, b, c, d, e, f, g, h) -> a + b)
* {@code action1} will be called but {@code action2} won't. *
To work around this termination property, - * use {@code doOnUnsubscribed()} as well or use {@code using()} to do cleanup in case of completion + * use {@code doOnUnsubscribed()} as well or use {@code using()} to do cleanup in case of completion * or unsubscription. *
*
Backpressure:
@@ -3736,7 +4348,7 @@ public static Observable zip(ObservableScheduler:
*
{@code zip} does not operate by default on a particular {@link Scheduler}.
*
- * + * * @param the value type of the first source * @param the value type of the second source * @param the value type of the third source @@ -3788,16 +4400,16 @@ public static Observable zip(Observable - * 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 unsubscribing the other sources. Therefore, it - * is possible those other sources will never be able to run to completion (and thus not calling + * 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 unsubscribing the other sources. Therefore, it + * is possible those other sources will never be able to run to completion (and thus not calling * {@code doOnCompleted()}). 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 * be sending further values and it will unsubscribe B immediately. For example: *
zip(range(1, 5).doOnCompleted(action1), range(6, 5).doOnCompleted(action2), ..., (a, b, c, d, e, f, g, h, i) -> a + b)
* {@code action1} will be called but {@code action2} won't. *
To work around this termination property, - * use {@code doOnUnsubscribed()} as well or use {@code using()} to do cleanup in case of completion + * use {@code doOnUnsubscribed()} as well or use {@code using()} to do cleanup in case of completion * or unsubscription. *
*
Backpressure:
@@ -3807,7 +4419,7 @@ public static Observable zip(ObservableScheduler:
*
{@code zip} does not operate by default on a particular {@link Scheduler}.
*
- * + * * @param the value type of the first source * @param the value type of the second source * @param the value type of the third source @@ -3853,10 +4465,13 @@ public static Observable zip(Observab *

* *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream and consumes the source {@code Observable} in an unbounded + * manner (i.e., without applying backpressure).
*
Scheduler:
*
{@code all} does not operate by default on a particular {@link Scheduler}.
*
- * + * * @param predicate * a function that evaluates an item and returns a Boolean * @return an Observable that emits {@code true} if all items emitted by the source Observable satisfy the @@ -3866,17 +4481,20 @@ public static Observable zip(Observab public final Observable all(Func1 predicate) { return lift(new OperatorAll(predicate)); } - + /** * Mirrors the Observable (current or provided) that first either emits an item or sends a termination * notification. *

* *

+ *
Backpressure:
+ *
The operator itself doesn't interfere with backpressure which is determined by the winning + * {@code Observable}'s backpressure behavior.
*
Scheduler:
*
{@code amb} does not operate by default on a particular {@link Scheduler}.
*
- * + * * @param t1 * an Observable competing to react first * @return an Observable that emits the same sequence as whichever of the source Observables first @@ -3892,10 +4510,13 @@ public final Observable ambWith(Observable t1) { * when you have an implementation of a subclass of Observable but you want to hide the properties and * methods of this subclass from whomever you are passing the Observable to. *
+ *
Backpressure:
+ *
The operator itself doesn't interfere with backpressure which is determined by this + * {@code Observable}'s backpressure behavior.
*
Scheduler:
*
{@code asObservable} does not operate by default on a particular {@link Scheduler}.
*
- * + * * @return an Observable that hides the identity of this Observable */ public final Observable asObservable() { @@ -3909,17 +4530,17 @@ public final Observable asObservable() { *

* *

- *
Backpressure Support:
+ *
Backpressure:
*
This operator does not support backpressure as it is instead controlled by the given Observables and * buffers data. It requests {@code Long.MAX_VALUE} upstream and does not obey downstream requests.
*
Scheduler:
*
This version of {@code buffer} does not operate by default on a particular {@link Scheduler}.
*
- * + * * @param the value type of the boundary-providing Observable * @param bufferClosingSelector * a {@link Func0} that produces an Observable that governs the boundary between buffers. - * Whenever this {@code Observable} emits an item, {@code buffer} emits the current buffer and + * Whenever the source {@code Observable} 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 Observable * each time the Observable created with the {@code bufferClosingSelector} argument emits an item @@ -3937,10 +4558,14 @@ public final Observable> buffer(Func0 * *
+ *
Backpressure:
+ *
The operator honors backpressure from downstream and expects the source {@code Observable} to honor it as + * well, although not enforced; violation may lead to {@code MissingBackpressureException} somewhere + * downstream.
*
Scheduler:
*
This version of {@code buffer} does not operate by default on a particular {@link Scheduler}.
*
- * + * * @param count * the maximum number of items in each buffer before it should be emitted * @return an Observable that emits connected, non-overlapping buffers, each containing at most @@ -3959,10 +4584,14 @@ public final Observable> buffer(int count) { *

* *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream and expects the source {@code Observable} to honor it as + * well, although not enforced; violation may lead to {@code MissingBackpressureException} somewhere + * downstream.
*
Scheduler:
*
This version of {@code buffer} does not operate by default on a particular {@link Scheduler}.
*
- * + * * @param count * the maximum size of each buffer before it should be emitted * @param skip @@ -3986,13 +4615,13 @@ public final Observable> buffer(int count, int skip) { *

* *

- *
Backpressure Support:
+ *
Backpressure:
*
This operator does not support backpressure as it uses time. It requests {@code Long.MAX_VALUE} * upstream and does not obey downstream requests.
*
Scheduler:
*
This version of {@code buffer} operates by default on the {@code computation} {@link Scheduler}.
*
- * + * * @param timespan * the period of time each buffer collects items before it is emitted * @param timeshift @@ -4016,13 +4645,13 @@ public final Observable> buffer(long timespan, long timeshift, TimeUnit *

* *

- *
Backpressure Support:
+ *
Backpressure:
*
This operator does not support backpressure as it uses time. It requests {@code Long.MAX_VALUE} * upstream and does not obey downstream requests.
*
Scheduler:
*
you specify which {@link Scheduler} this operator will use
*
- * + * * @param timespan * the period of time each buffer collects items before it is emitted * @param timeshift @@ -4047,13 +4676,13 @@ public final Observable> buffer(long timespan, long timeshift, TimeUnit *

* *

- *
Backpressure Support:
+ *
Backpressure:
*
This operator does not support backpressure as it uses time. It requests {@code Long.MAX_VALUE} * upstream and does not obey downstream requests.
*
Scheduler:
*
This version of {@code buffer} operates by default on the {@code computation} {@link Scheduler}.
*
- * + * * @param timespan * the period of time each buffer collects items before it is emitted and replaced with a new * buffer @@ -4076,13 +4705,13 @@ public final Observable> buffer(long timespan, TimeUnit unit) { *

* *

- *
Backpressure Support:
+ *
Backpressure:
*
This operator does not support backpressure as it uses time. It requests {@code Long.MAX_VALUE} * upstream and does not obey downstream requests.
*
Scheduler:
*
This version of {@code buffer} operates by default on the {@code computation} {@link Scheduler}.
*
- * + * * @param timespan * the period of time each buffer collects items before it is emitted and replaced with a new * buffer @@ -4109,13 +4738,13 @@ public final Observable> buffer(long timespan, TimeUnit unit, int count) *

* *

- *
Backpressure Support:
+ *
Backpressure:
*
This operator does not support backpressure as it uses time. It requests {@code Long.MAX_VALUE} * upstream and does not obey downstream requests.
*
Scheduler:
*
you specify which {@link Scheduler} this operator will use
*
- * + * * @param timespan * the period of time each buffer collects items before it is emitted and replaced with a new * buffer @@ -4143,13 +4772,13 @@ public final Observable> buffer(long timespan, TimeUnit unit, int count, *

* *

- *
Backpressure Support:
+ *
Backpressure:
*
This operator does not support backpressure as it uses time. It requests {@code Long.MAX_VALUE} * upstream and does not obey downstream requests.
*
Scheduler:
*
you specify which {@link Scheduler} this operator will use
*
- * + * * @param timespan * the period of time each buffer collects items before it is emitted and replaced with a new * buffer @@ -4172,13 +4801,13 @@ public final Observable> buffer(long timespan, TimeUnit unit, Scheduler *

* *

- *
Backpressure Support:
+ *
Backpressure:
*
This operator does not support backpressure as it is instead controlled by the given Observables and * buffers data. It requests {@code Long.MAX_VALUE} upstream and does not obey downstream requests.
*
Scheduler:
*
This version of {@code buffer} does not operate by default on a particular {@link Scheduler}.
*
- * + * * @param the element type of the buffer-opening Observable * @param the element type of the individual buffer-closing Observables * @param bufferOpenings @@ -4203,14 +4832,14 @@ public final Observable> buffer(Observable - *
Backpressure Support:
+ *
Backpressure:
*
This operator does not support backpressure as it is instead controlled by the {@code Observable} * {@code boundary} and buffers data. It requests {@code Long.MAX_VALUE} upstream and does not obey * downstream requests.
*
Scheduler:
*
This version of {@code buffer} does not operate by default on a particular {@link Scheduler}.
* - * + * * @param * the boundary value type (ignored) * @param boundary @@ -4233,14 +4862,14 @@ public final Observable> buffer(Observable boundary) { * Completion of either the source or the boundary Observable causes the returned Observable to emit the * latest buffer and complete. *
- *
Backpressure Support:
+ *
Backpressure:
*
This operator does not support backpressure as it is instead controlled by the {@code Observable} * {@code boundary} and buffers data. It requests {@code Long.MAX_VALUE} upstream and does not obey * downstream requests.
*
Scheduler:
*
This version of {@code buffer} does not operate by default on a particular {@link Scheduler}.
*
- * + * * @param * the boundary value type (ignored) * @param boundary @@ -4257,7 +4886,7 @@ public final Observable> buffer(Observable boundary, int initialC } /** - * Returns an Observable that subscribes to this Observable lazily, caches all of its events + * Returns an Observable that subscribes to this Observable lazily, caches all of its events * and replays them, in the same order as received, to all the downstream subscribers. *

* @@ -4267,16 +4896,16 @@ public final Observable> buffer(Observable boundary, int initialC *

* The operator subscribes only when the first downstream subscriber subscribes and maintains * a single subscription towards this Observable. In contrast, the operator family of {@link #replay()} - * that return a {@link ConnectableObservable} require an explicit call to {@link ConnectableObservable#connect()}. + * that return a {@link ConnectableObservable} require an explicit call to {@link ConnectableObservable#connect()}. *

* Note: You sacrifice the ability to unsubscribe from the origin when you use the {@code cache} * Observer so be careful not to use this Observer on Observables that emit an infinite or very large number - * of items that will use up memory. + * of items that will use up memory. * A possible workaround is to apply `takeUntil` with a predicate or * another source before (and perhaps after) the application of cache(). *


      * AtomicBoolean shouldStop = new AtomicBoolean();
-     * 
+     *
      * source.takeUntil(v -> shouldStop.get())
      *       .cache()
      *       .takeUntil(v -> shouldStop.get())
@@ -4284,10 +4913,10 @@ public final  Observable> buffer(Observable boundary, int initialC
      * 
* Since the operator doesn't allow clearing the cached values either, the possible workaround is * to forget all references to it via {@link #onTerminateDetach()} applied along with the previous - * workaround: + * workaround: *

      * AtomicBoolean shouldStop = new AtomicBoolean();
-     * 
+     *
      * source.takeUntil(v -> shouldStop.get())
      *       .onTerminateDetach()
      *       .cache()
@@ -4296,13 +4925,13 @@ public final  Observable> buffer(Observable boundary, int initialC
      *       .subscribe(...);
      * 
*
- *
Backpressure Support:
+ *
Backpressure:
*
The operator consumes this Observable in an unbounded fashion but respects the backpressure * of each downstream Subscriber individually.
*
Scheduler:
*
{@code cache} does not operate by default on a particular {@link Scheduler}.
*
- * + * * @return an Observable that, when first subscribed to, caches all of its items and notifications for the * benefit of subsequent subscribers * @see ReactiveX operators documentation: Replay @@ -4325,7 +4954,7 @@ public final Observable cache(int initialCapacity) { } /** - * Returns an Observable that subscribes to this Observable lazily, caches all of its events + * Returns an Observable that subscribes to this Observable lazily, caches all of its events * and replays them, in the same order as received, to all the downstream subscribers. *

* @@ -4335,7 +4964,7 @@ public final Observable cache(int initialCapacity) { *

* The operator subscribes only when the first downstream subscriber subscribes and maintains * a single subscription towards this Observable. In contrast, the operator family of {@link #replay()} - * that return a {@link ConnectableObservable} require an explicit call to {@link ConnectableObservable#connect()}. + * that return a {@link ConnectableObservable} require an explicit call to {@link ConnectableObservable#connect()}. *

* Note: You sacrifice the ability to unsubscribe from the origin when you use the {@code cache} * Observer so be careful not to use this Observer on Observables that emit an infinite or very large number @@ -4344,7 +4973,7 @@ public final Observable cache(int initialCapacity) { * another source before (and perhaps after) the application of cache(). *


      * AtomicBoolean shouldStop = new AtomicBoolean();
-     * 
+     *
      * source.takeUntil(v -> shouldStop.get())
      *       .cache()
      *       .takeUntil(v -> shouldStop.get())
@@ -4352,10 +4981,10 @@ public final Observable cache(int initialCapacity) {
      * 
* Since the operator doesn't allow clearing the cached values either, the possible workaround is * to forget all references to it via {@link #onTerminateDetach()} applied along with the previous - * workaround: + * workaround: *

      * AtomicBoolean shouldStop = new AtomicBoolean();
-     * 
+     *
      * source.takeUntil(v -> shouldStop.get())
      *       .onTerminateDetach()
      *       .cache()
@@ -4364,7 +4993,7 @@ public final Observable cache(int initialCapacity) {
      *       .subscribe(...);
      * 
*
- *
Backpressure Support:
+ *
Backpressure:
*
The operator consumes this Observable in an unbounded fashion but respects the backpressure * of each downstream Subscriber individually.
*
Scheduler:
@@ -4373,7 +5002,7 @@ public final Observable cache(int initialCapacity) { *

* Note: The capacity hint is not an upper bound on cache size. For that, consider * {@link #replay(int)} in combination with {@link ConnectableObservable#autoConnect()} or similar. - * + * * @param initialCapacity hint for number of items to cache (for optimizing underlying data structure) * @return an Observable that, when first subscribed to, caches all of its items and notifications for the * benefit of subsequent subscribers @@ -4389,10 +5018,13 @@ public final Observable cacheWithInitialCapacity(int initialCapacity) { *

* *

+ *
Backpressure:
+ *
The operator doesn't interfere with backpressure which is determined by the source {@code Observable}'s + * backpressure behavior.
*
Scheduler:
*
{@code cast} does not operate by default on a particular {@link Scheduler}.
*
- * + * * @param the output value type cast to * @param klass * the target class type that {@code cast} will cast the items emitted by the source Observable @@ -4413,13 +5045,13 @@ public final Observable cast(final Class klass) { *

* This is a simplified version of {@code reduce} that does not need to return the state on each pass. *

- *
Backpressure Support:
+ *
Backpressure:
*
This operator does not support backpressure because by intent it will receive all values and reduce * them to a single {@code onNext}.
*
Scheduler:
*
{@code collect} does not operate by default on a particular {@link Scheduler}.
*
- * + * * @param the accumulator and output type * @param stateFactory * the mutable data structure that will collect the items @@ -4431,15 +5063,13 @@ public final Observable cast(final Class klass) { * @see ReactiveX operators documentation: Reduce */ public final Observable collect(Func0 stateFactory, final Action2 collector) { - Func2 accumulator = InternalObservableUtils.createCollectorCaller(collector); - /* * Discussion and confirmation of implementation at * https://github.com/ReactiveX/RxJava/issues/423#issuecomment-27642532 - * + * * It should use last() not takeLast(1) since it needs to emit an error if the sequence is empty. */ - return lift(new OperatorScan(stateFactory, accumulator)).last(); + return unsafeCreate(new OnSubscribeCollect(this, stateFactory, collector)); } /** @@ -4449,11 +5079,17 @@ public final Observable collect(Func0 stateFactory, final Action2 * *
+ *
Backpressure:
+ *
The operator honors backpressure from downstream. Both this and the inner {@code Observable}s are + * expected to honor backpressure as well. If the source {@code Observable} violates the rule, the operator will + * signal a {@code MissingBackpressureException}. If any of the inner {@code Observable}s doesn't honor + * backpressure, that may throw an {@code IllegalStateException} when that + * {@code Observable} completes.
*
Scheduler:
*
{@code concatMap} does not operate by default on a particular {@link Scheduler}.
*
- * - * @param the type of the inner Observable sources and thus the ouput type + * + * @param the type of the inner Observable sources and thus the output type * @param func * a function that, when applied to an item emitted by the source Observable, returns an * Observable @@ -4466,45 +5102,53 @@ public final Observable concatMap(Func1 scalar = (ScalarSynchronousObservable) this; return scalar.scalarFlatMap(func); } - return create(new OnSubscribeConcatMap(this, func, 2, OnSubscribeConcatMap.IMMEDIATE)); + return unsafeCreate(new OnSubscribeConcatMap(this, func, 2, OnSubscribeConcatMap.IMMEDIATE)); } - + /** * Maps each of the items into an Observable, subscribes to them one after the other, * one at a time and emits their values in order * while delaying any error from either this or any of the inner Observables * till all of them terminate. - * + * *
*
Backpressure:
- *
{@code concatMapDelayError} fully supports backpressure.
+ *
The operator honors backpressure from downstream. Both this and the inner {@code Observable}s are + * expected to honor backpressure as well. If the source {@code Observable} violates the rule, the operator will + * signal a {@code MissingBackpressureException}. If any of the inner {@code Observable}s doesn't honor + * backpressure, that may throw an {@code IllegalStateException} when that + * {@code Observable} completes.
*
Scheduler:
*
{@code concatMapDelayError} does not operate by default on a particular {@link Scheduler}.
*
- * + * * @param the result value type * @param func the function that maps the items of this Observable into the inner Observables. * @return the new Observable instance with the concatenation behavior + * @since 1.3 */ - @Experimental public final Observable concatMapDelayError(Func1> func) { if (this instanceof ScalarSynchronousObservable) { ScalarSynchronousObservable scalar = (ScalarSynchronousObservable) this; return scalar.scalarFlatMap(func); } - return create(new OnSubscribeConcatMap(this, func, 2, OnSubscribeConcatMap.END)); + return unsafeCreate(new OnSubscribeConcatMap(this, func, 2, OnSubscribeConcatMap.END)); } - + /** * Returns an Observable that concatenate each item emitted by the source Observable with the values in an * Iterable corresponding to that item that is generated by a selector. *

- * + * *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The source {@code Observable}s is + * expected to honor backpressure as well. If the source {@code Observable} violates the rule, the operator will + * signal a {@code MissingBackpressureException}.
*
Scheduler:
*
{@code concatMapIterable} does not operate by default on a particular {@link Scheduler}.
*
- * + * * @param * the type of item emitted by the resulting Observable * @param collectionSelector @@ -4517,17 +5161,21 @@ public final Observable concatMapDelayError(Func1 Observable concatMapIterable(Func1> collectionSelector) { return OnSubscribeFlattenIterable.createFrom(this, collectionSelector, RxRingBuffer.SIZE); } - + /** * Returns an Observable that emits the items emitted from the current Observable, then the next, one after * the other, without interleaving them. *

* *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream. Both this and the {@code other} {@code Observable}s + * are expected to honor backpressure as well. If any of then violates this rule, it may throw an + * {@code IllegalStateException} when the source {@code Observable} completes.
*
Scheduler:
*
{@code concat} does not operate by default on a particular {@link Scheduler}.
*
- * + * * @param t1 * an Observable to be concatenated after the current * @return an Observable that emits items emitted by the two source Observables, one after the other, @@ -4544,10 +5192,13 @@ public final Observable concatWith(Observable t1) { *

* *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream and consumes the source {@code Observable} in an + * unbounded manner (i.e., without applying backpressure).
*
Scheduler:
*
{@code contains} does not operate by default on a particular {@link Scheduler}.
*
- * + * * @param element * the item to search for in the emissions from the source Observable * @return an Observable that emits {@code true} if the specified item is emitted by the source Observable, @@ -4563,13 +5214,13 @@ public final Observable contains(final Object element) { *

* *

- *
Backpressure Support:
- *
This operator does not support backpressure because by intent it will receive all values and reduce - * them to a single {@code onNext}.
+ *
Backpressure:
+ *
The operator honors backpressure from downstream and consumes the source {@code Observable} in an + * unbounded manner (i.e., without applying backpressure).
*
Scheduler:
*
{@code count} does not operate by default on a particular {@link Scheduler}.
*
- * + * * @return an Observable that emits a single item: the number of elements emitted by the source Observable * @see ReactiveX operators documentation: Count * @see #countLong() @@ -4577,20 +5228,20 @@ public final Observable contains(final Object element) { public final Observable count() { return reduce(0, InternalObservableUtils.COUNTER); } - + /** * Returns an Observable that counts the total number of items emitted by the source Observable and emits * this count as a 64-bit Long. *

* *

- *
Backpressure Support:
- *
This operator does not support backpressure because by intent it will receive all values and reduce - * them to a single {@code onNext}.
+ *
Backpressure:
+ *
The operator honors backpressure from downstream and consumes the source {@code Observable} in an + * unbounded manner (i.e., without applying backpressure).
*
Scheduler:
*
{@code countLong} does not operate by default on a particular {@link Scheduler}.
*
- * + * * @return an Observable that emits a single item: the number of items emitted by the source Observable as a * 64-bit Long item * @see ReactiveX operators documentation: Count @@ -4606,13 +5257,13 @@ public final Observable countLong() { *

* *

- *
Backpressure Support:
+ *
Backpressure:
*
This operator does not support backpressure as it uses the {@code debounceSelector} to mark * boundaries.
*
Scheduler:
*
This version of {@code debounce} does not operate by default on a particular {@link Scheduler}.
*
- * + * * @param * the debounce value type (ignored) * @param debounceSelector @@ -4644,12 +5295,12 @@ public final Observable debounce(Func1 *
  • Javascript - don't spam your server: debounce and throttle
  • * *
    - *
    Backpressure Support:
    + *
    Backpressure:
    *
    This operator does not support backpressure as it uses time to control data flow.
    *
    Scheduler:
    *
    This version of {@code debounce} operates by default on the {@code computation} {@link Scheduler}.
    *
    - * + * * @param timeout * the time each item has to be "the most recent" of those emitted by the source Observable to * ensure that it's not dropped @@ -4683,12 +5334,12 @@ public final Observable debounce(long timeout, TimeUnit unit) { *
  • Javascript - don't spam your server: debounce and throttle
  • * *
    - *
    Backpressure Support:
    + *
    Backpressure:
    *
    This operator does not support backpressure as it uses time to control data flow.
    *
    Scheduler:
    *
    you specify which {@link Scheduler} this operator will use
    *
    - * + * * @param timeout * the time each item has to be "the most recent" of those emitted by the source Observable to * ensure that it's not dropped @@ -4713,10 +5364,15 @@ public final Observable debounce(long timeout, TimeUnit unit, Scheduler sched *

    * *

    + *
    Backpressure:
    + *
    If the source {@code Observable} is empty, this operator is guaranteed to honor backpressure from downstream. + * If the source {@code Observable} is non-empty, it is expected to honor backpressure as well; if the rule is violated, + * a {@code MissingBackpressureException} may get signalled somewhere downstream. + *
    *
    Scheduler:
    *
    {@code defaultIfEmpty} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param defaultValue * the item to emit if the source Observable emits no items * @return an Observable that emits either the specified default item if the source Observable emits no @@ -4732,7 +5388,15 @@ public final Observable defaultIfEmpty(final T defaultValue) { * Returns an Observable that emits the items emitted by the source Observable or the items of an alternate * Observable if the source Observable is empty. *

    + *

    + * *

    + *
    Backpressure:
    + *
    If the source {@code Observable} is empty, the alternate {@code Observable} is expected to honor backpressure. + * If the source {@code Observable} is non-empty, it is expected to honor backpressure as instead. + * In either case, if violated, a {@code MissingBackpressureException} may get + * signalled somewhere downstream. + *
    *
    Scheduler:
    *
    {@code switchIfEmpty} does not operate by default on a particular {@link Scheduler}.
    *
    @@ -4741,10 +5405,15 @@ public final Observable defaultIfEmpty(final T defaultValue) { * the alternate Observable to subscribe to if the source does not emit any items * @return an Observable that emits the items emitted by the source Observable or the items of an * alternate Observable if the source Observable is empty. + * @throws NullPointerException + * if {@code alternate} is null * @since 1.1.0 */ public final Observable switchIfEmpty(Observable alternate) { - return lift(new OperatorSwitchIfEmpty(alternate)); + if (alternate == null) { + throw new NullPointerException("alternate is null"); + } + return unsafeCreate(new OnSubscribeSwitchIfEmpty(this, alternate)); } /** @@ -4756,10 +5425,14 @@ public final Observable switchIfEmpty(Observable alternate) { * Note: the resulting Observable will immediately propagate any {@code onError} notification * from the source Observable. *
    + *
    Backpressure:
    + *
    The operator doesn't interfere with the backpressure behavior which is determined by the source {@code Observable}. + * All of the other {@code Observable}s supplied by the functions are consumed + * in an unbounded manner (i.e., no backpressure applied to them).
    *
    Scheduler:
    *
    This version of {@code delay} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param * the subscription delay value type (ignored) * @param @@ -4790,10 +5463,14 @@ public final Observable delay( * Note: the resulting Observable will immediately propagate any {@code onError} notification * from the source Observable. *
    + *
    Backpressure:
    + *
    The operator doesn't interfere with the backpressure behavior which is determined by the source {@code Observable}. + * All of the other {@code Observable}s supplied by the function are consumed + * in an unbounded manner (i.e., no backpressure applied to them).
    *
    Scheduler:
    *
    This version of {@code delay} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param * the item delay value type (ignored) * @param itemDelay @@ -4814,10 +5491,12 @@ public final Observable delay(Func1> i *

    * *

    + *
    Backpressure:
    + *
    The operator doesn't interfere with the backpressure behavior which is determined by the source {@code Observable}.
    *
    Scheduler:
    *
    This version of {@code delay} operates by default on the {@code computation} {@link Scheduler}.
    *
    - * + * * @param delay * the delay to shift the source by * @param unit @@ -4835,10 +5514,12 @@ public final Observable delay(long delay, TimeUnit unit) { *

    * *

    + *
    Backpressure:
    + *
    The operator doesn't interfere with the backpressure behavior which is determined by the source {@code Observable}.
    *
    Scheduler:
    *
    you specify which {@link Scheduler} this operator will use
    *
    - * + * * @param delay * the delay to shift the source by * @param unit @@ -4857,10 +5538,12 @@ public final Observable delay(long delay, TimeUnit unit, Scheduler scheduler) *

    * *

    + *
    Backpressure:
    + *
    The operator doesn't interfere with the backpressure behavior which is determined by the source {@code Observable}.
    *
    Scheduler:
    *
    This version of {@code delay} operates by default on the {@code computation} {@link Scheduler}.
    *
    - * + * * @param delay * the time to delay the subscription * @param unit @@ -4878,10 +5561,12 @@ public final Observable delaySubscription(long delay, TimeUnit unit) { *

    * *

    + *
    Backpressure:
    + *
    The operator doesn't interfere with the backpressure behavior which is determined by the source {@code Observable}.
    *
    Scheduler:
    *
    you specify which {@link Scheduler} this operator will use
    *
    - * + * * @param delay * the time to delay the subscription * @param unit @@ -4893,19 +5578,23 @@ public final Observable delaySubscription(long delay, TimeUnit unit) { * @see ReactiveX operators documentation: Delay */ public final Observable delaySubscription(long delay, TimeUnit unit, Scheduler scheduler) { - return create(new OnSubscribeDelaySubscription(this, delay, unit, scheduler)); + return unsafeCreate(new OnSubscribeDelaySubscription(this, delay, unit, scheduler)); } - + /** * Returns an Observable that delays the subscription to the source Observable until a second Observable * emits an item. *

    * *

    + *
    Backpressure:
    + *
    The operator doesn't interfere with the backpressure behavior which is determined by the source {@code Observable}. + * The other {@code Observable}s supplied by the function is consumed in an unbounded manner + * (i.e., no backpressure applied to it).
    *
    Scheduler:
    *
    This method does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param the element type of the delaying Observable * @param subscriptionDelay * a function that returns an Observable that triggers the subscription to the source Observable @@ -4915,7 +5604,7 @@ public final Observable delaySubscription(long delay, TimeUnit unit, Schedule * @see ReactiveX operators documentation: Delay */ public final Observable delaySubscription(Func0> subscriptionDelay) { - return create(new OnSubscribeDelaySubscriptionWithSelector(this, subscriptionDelay)); + return unsafeCreate(new OnSubscribeDelaySubscriptionWithSelector(this, subscriptionDelay)); } /** @@ -4929,21 +5618,21 @@ public final Observable delaySubscription(Func0> *
    Scheduler:
    *
    This method does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param the value type of the other Observable, irrelevant * @param other the other Observable that should trigger the subscription * to this Observable. * @return an Observable that delays the subscription to this Observable * until the other Observable emits an element or completes normally. + * @since 1.3 */ - @Experimental public final Observable delaySubscription(Observable other) { if (other == null) { throw new NullPointerException(); } - return create(new OnSubscribeDelaySubscriptionOther(this, other)); + return unsafeCreate(new OnSubscribeDelaySubscriptionOther(this, other)); } - + /** * Returns an Observable that reverses the effect of {@link #materialize materialize} by transforming the * {@link Notification} objects emitted by the source Observable into the items or notifications they @@ -4951,6 +5640,9 @@ public final Observable delaySubscription(Observable other) { *

    * *

    + *
    Backpressure:
    + *
    The operator doesn't interfere with backpressure which is determined by the source {@code Observable}'s + * backpressure behavior.
    *
    Scheduler:
    *
    {@code dematerialize} does not operate by default on a particular {@link Scheduler}.
    *
    @@ -4972,10 +5664,13 @@ public final Observable dematerialize() { *

    * *

    + *
    Backpressure:
    + *
    The operator doesn't interfere with backpressure which is determined by the source {@code Observable}'s + * backpressure behavior.
    *
    Scheduler:
    *
    {@code distinct} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @return an Observable that emits only those items emitted by the source Observable that are distinct from * each other * @see ReactiveX operators documentation: Distinct @@ -4990,10 +5685,13 @@ public final Observable distinct() { *

    * *

    + *
    Backpressure:
    + *
    The operator doesn't interfere with backpressure which is determined by the source {@code Observable}'s + * backpressure behavior.
    *
    Scheduler:
    *
    {@code distinct} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param the key type * @param keySelector * a function that projects an emitted item to a key value that is used to decide whether an item @@ -5011,10 +5709,13 @@ public final Observable distinct(Func1 keySelecto *

    * *

    + *
    Backpressure:
    + *
    The operator doesn't interfere with backpressure which is determined by the source {@code Observable}'s + * backpressure behavior.
    *
    Scheduler:
    *
    {@code distinctUntilChanged} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @return an Observable that emits those items from the source Observable that are distinct from their * immediate predecessors * @see ReactiveX operators documentation: Distinct @@ -5029,10 +5730,13 @@ public final Observable distinctUntilChanged() { *

    * *

    + *
    Backpressure:
    + *
    The operator doesn't interfere with backpressure which is determined by the source {@code Observable}'s + * backpressure behavior.
    *
    Scheduler:
    *
    {@code distinctUntilChanged} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param the key type * @param keySelector * a function that projects an emitted item to a key value that is used to decide whether an item @@ -5051,6 +5755,9 @@ public final Observable distinctUntilChanged(Func1 * *
    + *
    Backpressure:
    + *
    The operator doesn't interfere with backpressure which is determined by the source {@code Observable}'s + * backpressure behavior.
    *
    Scheduler:
    *
    {@code distinctUntilChanged} does not operate by default on a particular {@link Scheduler}.
    *
    @@ -5060,10 +5767,8 @@ public final Observable distinctUntilChanged(Func1ReactiveX operators documentation: Distinct - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical - * with the release number) + * @since 1.3 */ - @Experimental public final Observable distinctUntilChanged(Func2 comparator) { return lift(new OperatorDistinctUntilChanged(comparator)); } @@ -5073,10 +5778,13 @@ public final Observable distinctUntilChanged(Func2 * *
    + *
    Backpressure:
    + *
    The operator doesn't interfere with backpressure which is determined by the source {@code Observable}'s + * backpressure behavior.
    *
    Scheduler:
    *
    {@code doOnCompleted} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param onCompleted * the action to invoke when the source Observable calls {@code onCompleted} * @return the source Observable with the side-effecting behavior applied @@ -5085,20 +5793,23 @@ public final Observable distinctUntilChanged(Func2 doOnCompleted(final Action0 onCompleted) { Action1 onNext = Actions.empty(); Action1 onError = Actions.empty(); - Observer observer = new ActionSubscriber(onNext, onError, onCompleted); + Observer observer = new ActionObserver(onNext, onError, onCompleted); - return lift(new OperatorDoOnEach(observer)); + return unsafeCreate(new OnSubscribeDoOnEach(this, observer)); } /** - * Modifies the source Observable so that it invokes an action for each item it emits. + * Modifies the source Observable so that it invokes an action for each item and terminal event it emits. *

    * *

    + *
    Backpressure:
    + *
    The operator doesn't interfere with backpressure which is determined by the source {@code Observable}'s + * backpressure behavior.
    *
    Scheduler:
    *
    {@code doOnEach} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param onNotification * the action to invoke for each item emitted by the source Observable * @return the source Observable with the side-effecting behavior applied @@ -5106,8 +5817,7 @@ public final Observable doOnCompleted(final Action0 onCompleted) { */ public final Observable doOnEach(final Action1> onNotification) { Observer observer = new ActionNotificationObserver(onNotification); - - return lift(new OperatorDoOnEach(observer)); + return unsafeCreate(new OnSubscribeDoOnEach(this, observer)); } /** @@ -5120,10 +5830,13 @@ public final Observable doOnEach(final Action1> onNot *

    * *

    + *
    Backpressure:
    + *
    The operator doesn't interfere with backpressure which is determined by the source {@code Observable}'s + * backpressure behavior.
    *
    Scheduler:
    *
    {@code doOnEach} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param observer * the observer to be notified about onNext, onError and onCompleted events on its * respective methods before the actual downstream Subscriber gets notified. @@ -5131,7 +5844,7 @@ public final Observable doOnEach(final Action1> onNot * @see ReactiveX operators documentation: Do */ public final Observable doOnEach(Observer observer) { - return lift(new OperatorDoOnEach(observer)); + return unsafeCreate(new OnSubscribeDoOnEach(this, observer)); } /** @@ -5142,21 +5855,24 @@ public final Observable doOnEach(Observer observer) { *

    * *

    + *
    Backpressure:
    + *
    The operator doesn't interfere with backpressure which is determined by the source {@code Observable}'s + * backpressure behavior.
    *
    Scheduler:
    *
    {@code doOnError} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param onError * the action to invoke if the source Observable calls {@code onError} * @return the source Observable with the side-effecting behavior applied * @see ReactiveX operators documentation: Do */ - public final Observable doOnError(final Action1 onError) { + public final Observable doOnError(final Action1 onError) { Action1 onNext = Actions.empty(); Action0 onCompleted = Actions.empty(); - Observer observer = new ActionSubscriber(onNext, onError, onCompleted); + Observer observer = new ActionObserver(onNext, onError, onCompleted); - return lift(new OperatorDoOnEach(observer)); + return unsafeCreate(new OnSubscribeDoOnEach(this, observer)); } /** @@ -5164,10 +5880,13 @@ public final Observable doOnError(final Action1 onError) { *

    * *

    + *
    Backpressure:
    + *
    The operator doesn't interfere with backpressure which is determined by the source {@code Observable}'s + * backpressure behavior.
    *
    Scheduler:
    *
    {@code doOnNext} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param onNext * the action to invoke when the source Observable calls {@code onNext} * @return the source Observable with the side-effecting behavior applied @@ -5176,9 +5895,9 @@ public final Observable doOnError(final Action1 onError) { public final Observable doOnNext(final Action1 onNext) { Action1 onError = Actions.empty(); Action0 onCompleted = Actions.empty(); - Observer observer = new ActionSubscriber(onNext, onError, onCompleted); + Observer observer = new ActionObserver(onNext, onError, onCompleted); - return lift(new OperatorDoOnEach(observer)); + return unsafeCreate(new OnSubscribeDoOnEach(this, observer)); } /** @@ -5188,6 +5907,9 @@ public final Observable doOnNext(final Action1 onNext) { * Note: This operator is for tracing the internal behavior of back-pressure request * patterns and generally intended for debugging use. *
    + *
    Backpressure:
    + *
    The operator doesn't interfere with backpressure which is determined by the source {@code Observable}'s + * backpressure behavior.
    *
    Scheduler:
    *
    {@code doOnRequest} does not operate by default on a particular {@link Scheduler}.
    *
    @@ -5198,11 +5920,9 @@ public final Observable doOnNext(final Action1 onNext) { * @return the source {@code Observable} modified so as to call this Action when appropriate * @see ReactiveX operators * documentation: Do - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical - * with the release number) + * @since 1.2 */ - @Beta - public final Observable doOnRequest(final Action1 onRequest) { + public final Observable doOnRequest(final Action1 onRequest) { return lift(new OperatorDoOnRequest(onRequest)); } @@ -5214,19 +5934,22 @@ public final Observable doOnRequest(final Action1 onRequest) { *

    * *

    + *
    Backpressure:
    + *
    The operator doesn't interfere with backpressure which is determined by the source {@code Observable}'s + * backpressure behavior.
    *
    Scheduler:
    *
    {@code doOnSubscribe} does not operate by default on a particular {@link Scheduler}.
    *
    * * @param subscribe - * the action that gets called when an observer subscribes to this {@code Observable} + * the action that gets called when an observer subscribes to the source {@code Observable} * @return the source {@code Observable} modified so as to call this Action when appropriate * @see ReactiveX operators documentation: Do */ public final Observable doOnSubscribe(final Action0 subscribe) { return lift(new OperatorDoOnSubscribe(subscribe)); } - + /** * Modifies the source Observable so that it invokes an action when it calls {@code onCompleted} or * {@code onError}. @@ -5236,10 +5959,13 @@ public final Observable doOnSubscribe(final Action0 subscribe) { * This differs from {@code finallyDo} in that this happens before the {@code onCompleted} or * {@code onError} notification. *
    + *
    Backpressure:
    + *
    The operator doesn't interfere with backpressure which is determined by the source {@code Observable}'s + * backpressure behavior.
    *
    Scheduler:
    *
    {@code doOnTerminate} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param onTerminate * the action to invoke when the source Observable calls {@code onCompleted} or {@code onError} * @return the source Observable with the side-effecting behavior applied @@ -5249,12 +5975,12 @@ public final Observable doOnSubscribe(final Action0 subscribe) { public final Observable doOnTerminate(final Action0 onTerminate) { Action1 onNext = Actions.empty(); Action1 onError = Actions.toAction1(onTerminate); - - Observer observer = new ActionSubscriber(onNext, onError, onTerminate); - return lift(new OperatorDoOnEach(observer)); + Observer observer = new ActionObserver(onNext, onError, onTerminate); + + return unsafeCreate(new OnSubscribeDoOnEach(this, observer)); } - + /** * Calls the unsubscribe {@code Action0} if the downstream unsubscribes the sequence. *

    @@ -5276,7 +6002,7 @@ public final Observable doOnTerminate(final Action0 onTerminate) { * * * @param unsubscribe - * the action that gets called when this {@code Observable} is unsubscribed + * the action that gets called when the source {@code Observable} is unsubscribed * @return the source {@code Observable} modified so as to call this Action when appropriate * @see ReactiveX operators documentation: Do */ @@ -5297,18 +6023,17 @@ public final Observable doOnUnsubscribe(final Action0 unsubscribe) { *

    Scheduler:
    *
    This method does not operate by default on a particular {@link Scheduler}.
    * - * @param the value type + * @param the value type * @param o1 the first source * @param o2 the second source * @return the new Observable instance with the specified concatenation behavior - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Experimental @SuppressWarnings("unchecked") public static Observable concatEager(Observable o1, Observable o2) { return concatEager(Arrays.asList(o1, o2)); } - + /** * Concatenates three sources eagerly into a single stream of values. *

    @@ -5327,9 +6052,8 @@ public static Observable concatEager(Observable o1, Observab * @param o2 the second source * @param o3 the third source * @return the new Observable instance with the specified concatenation behavior - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Experimental @SuppressWarnings("unchecked") public static Observable concatEager( Observable o1, Observable o2, @@ -5337,7 +6061,7 @@ public static Observable concatEager( ) { return concatEager(Arrays.asList(o1, o2, o3)); } - + /** * Concatenates four sources eagerly into a single stream of values. *

    @@ -5357,9 +6081,8 @@ public static Observable concatEager( * @param o3 the third source * @param o4 the fourth source * @return the new Observable instance with the specified concatenation behavior - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Experimental @SuppressWarnings("unchecked") public static Observable concatEager( Observable o1, Observable o2, @@ -5367,7 +6090,7 @@ public static Observable concatEager( ) { return concatEager(Arrays.asList(o1, o2, o3, o4)); } - + /** * Concatenates five sources eagerly into a single stream of values. *

    @@ -5388,9 +6111,8 @@ public static Observable concatEager( * @param o4 the fourth source * @param o5 the fifth source * @return the new Observable instance with the specified concatenation behavior - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Experimental @SuppressWarnings("unchecked") public static Observable concatEager( Observable o1, Observable o2, @@ -5421,9 +6143,8 @@ public static Observable concatEager( * @param o5 the fifth source * @param o6 the sixth source * @return the new Observable instance with the specified concatenation behavior - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Experimental @SuppressWarnings("unchecked") public static Observable concatEager( Observable o1, Observable o2, @@ -5455,9 +6176,8 @@ public static Observable concatEager( * @param o6 the sixth source * @param o7 the seventh source * @return the new Observable instance with the specified concatenation behavior - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Experimental @SuppressWarnings("unchecked") public static Observable concatEager( Observable o1, Observable o2, @@ -5467,7 +6187,7 @@ public static Observable concatEager( ) { return concatEager(Arrays.asList(o1, o2, o3, o4, o5, o6, o7)); } - + /** * Concatenates eight sources eagerly into a single stream of values. *

    @@ -5491,9 +6211,8 @@ public static Observable concatEager( * @param o7 the seventh source * @param o8 the eighth source * @return the new Observable instance with the specified concatenation behavior - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Experimental @SuppressWarnings("unchecked") public static Observable concatEager( Observable o1, Observable o2, @@ -5528,9 +6247,8 @@ public static Observable concatEager( * @param o8 the eighth source * @param o9 the ninth source * @return the new Observable instance with the specified concatenation behavior - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Experimental @SuppressWarnings("unchecked") public static Observable concatEager( Observable o1, Observable o2, @@ -5558,9 +6276,8 @@ public static Observable concatEager( * @param the value type * @param sources a sequence of Observables that need to be eagerly concatenated * @return the new Observable instance with the specified concatenation behavior - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Experimental @SuppressWarnings({ "unchecked", "rawtypes" }) public static Observable concatEager(Iterable> sources) { return Observable.from(sources).concatMapEager((Func1)UtilityFunctions.identity()); @@ -5583,14 +6300,13 @@ public static Observable concatEager(Iterable Observable concatEager(Iterable> sources, int capacityHint) { return Observable.from(sources).concatMapEager((Func1)UtilityFunctions.identity(), capacityHint); } - + /** * Concatenates an Observable sequence of Observables eagerly into a single stream of values. *

    @@ -5607,9 +6323,8 @@ public static Observable concatEager(Iterable the value type * @param sources a sequence of Observables that need to be eagerly concatenated * @return the new Observable instance with the specified concatenation behavior - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Experimental @SuppressWarnings({ "unchecked", "rawtypes" }) public static Observable concatEager(Observable> sources) { return sources.concatMapEager((Func1)UtilityFunctions.identity()); @@ -5632,14 +6347,13 @@ public static Observable concatEager(Observable Observable concatEager(Observable> sources, int capacityHint) { return sources.concatMapEager((Func1)UtilityFunctions.identity(), capacityHint); } - + /** * Maps a sequence of values into Observables and concatenates these Observables eagerly into a single * Observable. @@ -5658,9 +6372,8 @@ public static Observable concatEager(Observable Observable concatMapEager(Func1> mapper) { return concatMapEager(mapper, RxRingBuffer.SIZE); } @@ -5684,9 +6397,8 @@ public final Observable concatMapEager(Func1 Observable concatMapEager(Func1> mapper, int capacityHint) { if (capacityHint < 1) { throw new IllegalArgumentException("capacityHint > 0 required but it was " + capacityHint); @@ -5714,9 +6426,8 @@ public final Observable concatMapEager(Func1 Observable concatMapEager(Func1> mapper, int capacityHint, int maxConcurrent) { if (capacityHint < 1) { throw new IllegalArgumentException("capacityHint > 0 required but it was " + capacityHint); @@ -5726,17 +6437,20 @@ public final Observable concatMapEager(Func1(mapper, capacityHint, maxConcurrent)); } - + /** * Returns an Observable that emits the single item at a specified index in a sequence of emissions from a * source Observable. *

    * *

    + *
    Backpressure:
    + *
    The operator honors backpressure from downstream and consumes the source {@code Observable} in an unbounded manner + * (i.e., no backpressure applied to it).
    *
    Scheduler:
    *
    {@code elementAt} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param index * the zero-based index of the item to retrieve * @return an Observable that emits a single item: the item at the specified position in the sequence of @@ -5757,10 +6471,13 @@ public final Observable elementAt(int index) { *

    * *

    + *
    Backpressure:
    + *
    The operator honors backpressure from downstream and consumes the source {@code Observable} in an unbounded manner + * (i.e., no backpressure applied to it).
    *
    Scheduler:
    *
    {@code elementAtOrDefault} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param index * the zero-based index of the item to retrieve * @param defaultValue @@ -5785,10 +6502,13 @@ public final Observable elementAtOrDefault(int index, T defaultValue) { * In Rx.Net this is the {@code any} Observer but we renamed it in RxJava to better match Java naming * idioms. *
    + *
    Backpressure:
    + *
    The operator honors backpressure from downstream and consumes the source {@code Observable} in an unbounded manner + * (i.e., no backpressure applied to it).
    *
    Scheduler:
    *
    {@code exists} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param predicate * the condition to test items emitted by the source Observable * @return an Observable that emits a Boolean that indicates whether any item emitted by the source @@ -5804,10 +6524,13 @@ public final Observable exists(Func1 predicate) { *

    * *

    + *
    Backpressure:
    + *
    The operator doesn't interfere with backpressure which is determined by the source {@code Observable}'s backpressure + * behavior.
    *
    Scheduler:
    *
    {@code filter} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param predicate * a function that evaluates each item emitted by the source Observable, returning {@code true} * if it passes the filter @@ -5816,7 +6539,7 @@ public final Observable exists(Func1 predicate) { * @see ReactiveX operators documentation: Filter */ public final Observable filter(Func1 predicate) { - return lift(new OperatorFilter(predicate)); + return unsafeCreate(new OnSubscribeFilter(this, predicate)); } /** @@ -5825,10 +6548,13 @@ public final Observable filter(Func1 predicate) { *

    * *

    + *
    Backpressure:
    + *
    The operator doesn't interfere with backpressure which is determined by the source {@code Observable}'s backpressure + * behavior.
    *
    Scheduler:
    *
    {@code finallyDo} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param action * an {@link Action0} to be invoked when the source Observable finishes * @return an Observable that emits the same items as the source Observable, then invokes the @@ -5848,6 +6574,9 @@ public final Observable finallyDo(Action0 action) { *

    * *

    + *
    Backpressure:
    + *
    The operator doesn't interfere with backpressure which is determined by the source {@code Observable}'s backpressure + * behavior.
    *
    Scheduler:
    *
    {@code doAfterTerminate} does not operate by default on a particular {@link Scheduler}.
    *
    @@ -5869,10 +6598,13 @@ public final Observable doAfterTerminate(Action0 action) { *

    * *

    + *
    Backpressure:
    + *
    The operator honors backpressure from downstream and consumes the source {@code Observable} in an + * unbounded manner (i.e., without applying backpressure).
    *
    Scheduler:
    *
    {@code first} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @return an Observable that emits only the very first item emitted by the source Observable, or raises an * {@code NoSuchElementException} if the source Observable is empty * @see ReactiveX operators documentation: First @@ -5887,10 +6619,13 @@ public final Observable first() { *

    * *

    + *
    Backpressure:
    + *
    The operator honors backpressure from downstream and consumes the source {@code Observable} in an + * unbounded manner (i.e., without applying backpressure).
    *
    Scheduler:
    *
    {@code first} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param predicate * the condition that an item emitted by the source Observable has to satisfy * @return an Observable that emits only the very first item emitted by the source Observable that satisfies @@ -5907,10 +6642,13 @@ public final Observable first(Func1 predicate) { *

    * *

    + *
    Backpressure:
    + *
    The operator honors backpressure from downstream and consumes the source {@code Observable} in an + * unbounded manner (i.e., without applying backpressure).
    *
    Scheduler:
    *
    {@code firstOrDefault} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param defaultValue * the default item to emit if the source Observable doesn't emit anything * @return an Observable that emits only the very first item from the source, or a default item if the @@ -5927,10 +6665,13 @@ public final Observable firstOrDefault(T defaultValue) { *

    * *

    + *
    Backpressure:
    + *
    The operator honors backpressure from downstream and consumes the source {@code Observable} in an + * unbounded manner (i.e., without applying backpressure).
    *
    Scheduler:
    *
    {@code firstOrDefault} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param predicate * the condition any item emitted by the source Observable has to satisfy * @param defaultValue @@ -5951,10 +6692,14 @@ public final Observable firstOrDefault(T defaultValue, Func1 * *
    + *
    Backpressure:
    + *
    The operator honors backpressure from downstream. The outer {@code Observable} is consumed + * in unbounded mode (i.e., no backpressure is applied to it). The inner {@code Observable}s are expected to honor + * backpressure; if violated, the operator may signal {@code MissingBackpressureException}.
    *
    Scheduler:
    *
    {@code flatMap} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param the value type of the inner Observables and the output type * @param func * a function that, when applied to an item emitted by the source Observable, returns an @@ -5979,10 +6724,13 @@ public final Observable flatMap(Func1 * *
    + *
    Backpressure:
    + *
    The operator honors backpressure from downstream. Both the outer and inner {@code Observable}s are expected to honor + * backpressure; if violated, the operator may signal {@code MissingBackpressureException}.
    *
    Scheduler:
    *
    {@code flatMap} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param the value type of the inner Observables and the output type * @param func * a function that, when applied to an item emitted by the source Observable, returns an @@ -5993,9 +6741,8 @@ public final Observable flatMap(Func1ReactiveX operators documentation: FlatMap - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.2 */ - @Beta public final Observable flatMap(Func1> func, int maxConcurrent) { if (getClass() == ScalarSynchronousObservable.class) { return ((ScalarSynchronousObservable)this).scalarFlatMap(func); @@ -6009,10 +6756,14 @@ public final Observable flatMap(Func1 * *
    + *
    Backpressure:
    + *
    The operator honors backpressure from downstream. The outer {@code Observable} is consumed + * in unbounded mode (i.e., no backpressure is applied to it). The inner {@code Observable}s are expected to honor + * backpressure; if violated, the operator may signal {@code MissingBackpressureException}.
    *
    Scheduler:
    *
    {@code flatMap} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param * the result type * @param onNext @@ -6035,15 +6786,18 @@ public final Observable flatMap( } /** * Returns an Observable that applies a function to each item emitted or notification raised by the source - * Observable and then flattens the Observables returned from these functions and emits the resulting items, + * Observable and then flattens the Observables returned from these functions and emits the resulting items, * while limiting the maximum number of concurrent subscriptions to these Observables. *

    * *

    + *
    Backpressure:
    + *
    The operator honors backpressure from downstream. Both the outer and inner {@code Observable}s are expected to honor + * backpressure; if violated, the operator may signal {@code MissingBackpressureException}.
    *
    Scheduler:
    *
    {@code flatMap} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param * the result type * @param onNext @@ -6059,9 +6813,8 @@ public final Observable flatMap( * @return an Observable that emits the results of merging the Observables returned from applying the * specified functions to the emissions and notifications of the source Observable * @see ReactiveX operators documentation: FlatMap - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.2 */ - @Beta public final Observable flatMap( Func1> onNext, Func1> onError, @@ -6075,10 +6828,13 @@ public final Observable flatMap( *

    * *

    + *
    The operator honors backpressure from downstream. The outer {@code Observable} is consumed + * in unbounded mode (i.e., no backpressure is applied to it). The inner {@code Observable}s are expected to honor + * backpressure; if violated, the operator may signal {@code MissingBackpressureException}.
    *
    Scheduler:
    *
    {@code flatMap} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param * the type of items emitted by the collection Observable * @param @@ -6103,10 +6859,13 @@ public final Observable flatMap(final Func1 * *
    + *
    Backpressure:
    + *
    The operator honors backpressure from downstream. Both the outer and inner {@code Observable}s are expected to honor + * backpressure; if violated, the operator may signal {@code MissingBackpressureException}.
    *
    Scheduler:
    *
    {@code flatMap} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param * the type of items emitted by the collection Observable * @param @@ -6121,24 +6880,95 @@ public final Observable flatMap(final Func1ReactiveX operators documentation: FlatMap - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.2 */ - @Beta public final Observable flatMap(final Func1> collectionSelector, final Func2 resultSelector, int maxConcurrent) { return merge(lift(new OperatorMapPair(collectionSelector, resultSelector)), maxConcurrent); } + /** + * Maps all upstream values to Completables and runs them together until the upstream + * and all inner Completables complete normally. + *
    + *
    Backpressure:
    + *
    The operator consumes items from upstream in an unbounded manner and ignores downstream backpressure + * as it doesn't emit items but only terminal event.
    + *
    Scheduler:
    + *
    {@code flatMapCompletable} does not operate by default on a particular {@link Scheduler}.
    + *
    + *

    History: 1.2.7 - experimental + * @param mapper the function that receives an upstream value and turns it into a Completable + * to be merged. + * @return the new Observable instance + * @see #flatMapCompletable(Func1, boolean, int) + * @since 1.3 + */ + public final Observable flatMapCompletable(Func1 mapper) { + return flatMapCompletable(mapper, false, Integer.MAX_VALUE); + } + + /** + * Maps all upstream values to Completables and runs them together, optionally delaying any errors, until the upstream + * and all inner Completables terminate. + *

    + *
    Backpressure:
    + *
    The operator consumes items from upstream in an unbounded manner and ignores downstream backpressure + * as it doesn't emit items but only terminal event.
    + *
    Scheduler:
    + *
    {@code flatMapCompletable} does not operate by default on a particular {@link Scheduler}.
    + *
    + *

    History: 1.2.7 - experimental + * @param mapper the function that receives an upstream value and turns it into a Completable + * to be merged. + * @param delayErrors if true, errors from the upstream and from the inner Completables get delayed till + * the all of them terminate. + * @return the new Observable instance + * @since 1.3 + * @see #flatMapCompletable(Func1, boolean, int) + */ + public final Observable flatMapCompletable(Func1 mapper, boolean delayErrors) { + return flatMapCompletable(mapper, delayErrors, Integer.MAX_VALUE); + } + + /** + * Maps upstream values to Completables and runs up to the given number of them together at a time, + * optionally delaying any errors, until the upstream and all inner Completables terminate. + *

    + *
    Backpressure:
    + *
    The operator consumes at most maxConcurrent items from upstream and one-by-one after as the inner + * Completables terminate. The operator ignores downstream backpressure as it doesn't emit items but + * only the terminal event.
    + *
    Scheduler:
    + *
    {@code flatMapCompletable} does not operate by default on a particular {@link Scheduler}.
    + *
    + *

    History: 1.2.7 - experimental + * @param mapper the function that receives an upstream value and turns it into a Completable + * to be merged. + * @param delayErrors if true, errors from the upstream and from the inner Completables get delayed till + * the all of them terminate. + * @param maxConcurrency the maximum number of inner Completables to run at a time + * @return the new Observable instance + * @since 1.3 + */ + public final Observable flatMapCompletable(Func1 mapper, boolean delayErrors, int maxConcurrency) { + return unsafeCreate(new OnSubscribeFlatMapCompletable(this, mapper, delayErrors, maxConcurrency)); + } + /** * Returns an Observable that merges each item emitted by the source Observable with the values in an * Iterable corresponding to that item that is generated by a selector. *

    * *

    + *
    Backpressure:
    + *
    The operator honors backpressure from downstream. The source {@code Observable}s is + * expected to honor backpressure as well. If the source {@code Observable} violates the rule, the operator will + * signal a {@code MissingBackpressureException}.
    *
    Scheduler:
    *
    {@code flatMapIterable} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param * the type of item emitted by the resulting Observable * @param collectionSelector @@ -6159,6 +6989,10 @@ public final Observable flatMapIterable(Func1 * *
    + *
    Backpressure:
    + *
    The operator honors backpressure from downstream. The source {@code Observable}s is + * expected to honor backpressure as well. If the source {@code Observable} violates the rule, the operator will + * signal a {@code MissingBackpressureException}.
    *
    Scheduler:
    *
    {@code flatMapIterable} does not operate by default on a particular {@link Scheduler}.
    *
    @@ -6175,23 +7009,25 @@ public final Observable flatMapIterable(Func1ReactiveX operators documentation: FlatMap - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.2 */ - @Beta public final Observable flatMapIterable(Func1> collectionSelector, int maxConcurrent) { return OnSubscribeFlattenIterable.createFrom(this, collectionSelector, maxConcurrent); } - + /** * Returns an Observable that emits the results of applying a function to the pair of values from the source * Observable and an Iterable corresponding to that item that is generated by a selector. *

    * *

    + *
    Backpressure:
    + *
    The operator honors backpressure from downstream and the source {@code Observable}s is + * consumed in an unbounded manner (i.e., no backpressure is applied to it).
    *
    Scheduler:
    *
    {@code flatMapIterable} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param * the collection element type * @param @@ -6219,6 +7055,10 @@ public final Observable flatMapIterable(Func1 * *
    + *
    Backpressure:
    + *
    The operator honors backpressure from downstream. The source {@code Observable}s is + * expected to honor backpressure as well. If the source {@code Observable} violates the rule, the operator will + * signal a {@code MissingBackpressureException}.
    *
    Scheduler:
    *
    {@code flatMapIterable} does not operate by default on a particular {@link Scheduler}.
    *
    @@ -6240,13 +7080,80 @@ public final Observable flatMapIterable(Func1ReactiveX operators documentation: FlatMap - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.2 + */ + @SuppressWarnings("cast") + public final Observable flatMapIterable(Func1> collectionSelector, + Func2 resultSelector, int maxConcurrent) { + return (Observable)flatMap(OperatorMapPair.convertSelector(collectionSelector), resultSelector, maxConcurrent); + } + + /** + * Maps all upstream values to Singles and runs them together until the upstream + * and all inner Singles complete normally. + *
    + *
    Backpressure:
    + *
    The operator consumes items from upstream in an unbounded manner and honors downstream backpressure.
    + *
    Scheduler:
    + *
    {@code flatMapSingle} does not operate by default on a particular {@link Scheduler}.
    + *
    + *

    History: 1.2.7 - experimental + * @param the value type of the inner Singles and the resulting Observable + * @param mapper the function that receives an upstream value and turns it into a Single + * to be merged. + * @return the new Observable instance + * @see #flatMapSingle(Func1, boolean, int) + * @since 1.3 + */ + public final Observable flatMapSingle(Func1> mapper) { + return flatMapSingle(mapper, false, Integer.MAX_VALUE); + } + + /** + * Maps all upstream values to Singles and runs them together, optionally delaying any errors, until the upstream + * and all inner Singles terminate. + *

    + *
    Backpressure:
    + *
    The operator consumes items from upstream in an unbounded manner and honors downstream backpressure.
    + *
    Scheduler:
    + *
    {@code flatMapSingle} does not operate by default on a particular {@link Scheduler}.
    + *
    + *

    History: 1.2.7 - experimental + * @param the value type of the inner Singles and the resulting Observable + * @param mapper the function that receives an upstream value and turns it into a Single + * to be merged. + * @param delayErrors if true, errors from the upstream and from the inner Singles get delayed till + * the all of them terminate. + * @return the new Observable instance + * @since 1.3 + * @see #flatMapSingle(Func1, boolean, int) + */ + public final Observable flatMapSingle(Func1> mapper, boolean delayErrors) { + return flatMapSingle(mapper, delayErrors, Integer.MAX_VALUE); + } + + /** + * Maps upstream values to Singles and runs up to the given number of them together at a time, + * optionally delaying any errors, until the upstream and all inner Singles terminate. + *

    + *
    Backpressure:
    + *
    The operator consumes at most maxConcurrent items from upstream and one-by-one after as the inner + * Singles terminate. The operator honors downstream backpressure.
    + *
    Scheduler:
    + *
    {@code flatMapSingle} does not operate by default on a particular {@link Scheduler}.
    + *
    + *

    History: 1.2.7 - experimental + * @param the value type of the inner Singles and the resulting Observable + * @param mapper the function that receives an upstream value and turns it into a Single + * to be merged. + * @param delayErrors if true, errors from the upstream and from the inner Singles get delayed till + * the all of them terminate. + * @param maxConcurrency the maximum number of inner Singles to run at a time + * @return the new Observable instance + * @since 1.3 */ - @SuppressWarnings("cast") - @Beta - public final Observable flatMapIterable(Func1> collectionSelector, - Func2 resultSelector, int maxConcurrent) { - return (Observable)flatMap(OperatorMapPair.convertSelector(collectionSelector), resultSelector, maxConcurrent); + public final Observable flatMapSingle(Func1> mapper, boolean delayErrors, int maxConcurrency) { + return unsafeCreate(new OnSubscribeFlatMapSingle(this, mapper, delayErrors, maxConcurrency)); } /** @@ -6254,10 +7161,13 @@ public final Observable flatMapIterable(Func1 * Alias to {@link #subscribe(Action1)} *

    + *
    Backpressure:
    + *
    The operator consumes the source {@code Observable} in an unbounded manner (i.e., no + * backpressure is applied to it).
    *
    Scheduler:
    *
    {@code forEach} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param onNext * {@link Action1} to execute for each item. * @throws IllegalArgumentException @@ -6269,16 +7179,19 @@ public final Observable flatMapIterable(Func1 onNext) { subscribe(onNext); } - + /** * Subscribes to the {@link Observable} and receives notifications for each element and error events. *

    * Alias to {@link #subscribe(Action1, Action1)} *

    + *
    Backpressure:
    + *
    The operator consumes the source {@code Observable} in an unbounded manner (i.e., no + * backpressure is applied to it).
    *
    Scheduler:
    *
    {@code forEach} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param onNext * {@link Action1} to execute for each item. * @param onError @@ -6293,16 +7206,19 @@ public final void forEach(final Action1 onNext) { public final void forEach(final Action1 onNext, final Action1 onError) { subscribe(onNext, onError); } - + /** * Subscribes to the {@link Observable} and receives notifications for each element and the terminal events. *

    * Alias to {@link #subscribe(Action1, Action1, Action0)} *

    + *
    Backpressure:
    + *
    The operator consumes the source {@code Observable} in an unbounded manner (i.e., no + * backpressure is applied to it).
    *
    Scheduler:
    *
    {@code forEach} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param onNext * {@link Action1} to execute for each item. * @param onError @@ -6320,12 +7236,12 @@ public final void forEach(final Action1 onNext, final Action1 onNext, final Action1 onError, final Action0 onComplete) { subscribe(onNext, onError, onComplete); } - + /** * Groups the items emitted by an {@code Observable} according to a specified criterion, and emits these - * grouped items as {@link GroupedObservable}s. The emitted {@code GroupedObservable} allows only a single - * {@link Subscriber} during its lifetime and if this {@code Subscriber} unsubscribes before the - * source terminates, the next emission by the source having the same key will trigger a new + * grouped items as {@link GroupedObservable}s. The emitted {@code GroupedObservable} allows only a single + * {@link Subscriber} during its lifetime and if this {@code Subscriber} unsubscribes before the + * source terminates, the next emission by the source having the same key will trigger a new * {@code GroupedObservable} emission. *

    * @@ -6335,10 +7251,16 @@ public final void forEach(final Action1 onNext, final Action1 + *

    Backpressure:
    + *
    Both the returned and its inner {@code Observable}s honor backpressure and the source {@code Observable} + * 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 Observable}s use + * unbounded internal buffers and if the source {@code Observable} doesn't honor backpressure, that may + * lead to {@code OutOfMemoryError}.
    *
    Scheduler:
    *
    {@code groupBy} does not operate by default on a particular {@link Scheduler}.
    * - * + * * @param keySelector * a function that extracts the key for each item * @param elementSelector @@ -6353,7 +7275,149 @@ public final void forEach(final Action1 onNext, final Action1ReactiveX operators documentation: GroupBy */ public final Observable> groupBy(final Func1 keySelector, final Func1 elementSelector) { - return lift(new OperatorGroupBy(keySelector, elementSelector)); + return lift(new OperatorGroupByEvicting(keySelector, elementSelector)); + } + + /** + * Groups the items emitted by an {@code Observable} according to a specified criterion, and emits these + * grouped items as {@link GroupedObservable}s. The emitted {@code GroupedObservable} allows only a single + * {@link Subscriber} during its lifetime and if this {@code Subscriber} unsubscribes before the + * source terminates, the next emission by the source having the same key will trigger a new + * {@code GroupedObservable} emission. + *

    + * + *

    + * Note: A {@link GroupedObservable} 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 may + * discard their buffers by applying an operator like {@link #ignoreElements} to them. + *

    + *
    Backpressure:
    + *
    Both the returned and its inner {@code Observable}s honor backpressure and the source {@code Observable} + * 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 Observable}s use + * unbounded internal buffers and if the source {@code Observable} doesn't honor backpressure, that may + * lead to {@code OutOfMemoryError}.
    + *
    Scheduler:
    + *
    {@code groupBy} does not operate by default on a particular {@link Scheduler}.
    + *
    + * + * @param keySelector + * a function that extracts the key for each item + * @param elementSelector + * a function that extracts the return element for each item + * @param evictingMapFactory + * a function that given an eviction action returns a {@link Map} instance that will be used to assign + * items to the appropriate {@code GroupedObservable}s. The {@code Map} instance must be thread-safe + * and any eviction must trigger a call to the supplied action (synchronously or asynchronously). + * This can be used to limit the size of the map by evicting keys by maximum size or access time for + * instance. Here's an example using Guava's {@code CacheBuilder} from v19.0: + *
    +     *            {@code
    +     *            Func1, Map> mapFactory
    +     *              = action -> CacheBuilder.newBuilder()
    +     *                  .maximumSize(1000)
    +     *                  .expireAfterAccess(12, TimeUnit.HOURS)
    +     *                  .removalListener(notification -> action.call(notification.getKey()))
    +     *                  . build().asMap();
    +     *            }
    +     *            
    + * + * @param + * the key type + * @param + * the element type + * @return an {@code Observable} that emits {@link GroupedObservable}s, each of which corresponds to a + * unique key value and each of which emits those items from the source Observable that share that + * key value + * @throws NullPointerException + * if {@code evictingMapFactory} is null + * @see ReactiveX operators documentation: GroupBy + * @since 1.3 + * @deprecated since 1.3.7, use {@link #groupBy(Func1, Func1, int, boolean, Func1)} + * instead which uses much less memory. Please take note of the + * usage difference involving the evicting action which now expects + * the value from the map instead of the key. + */ + @Deprecated + public final Observable> groupBy(final Func1 keySelector, + final Func1 elementSelector, final Func1, Map> evictingMapFactory) { + if (evictingMapFactory == null) { + throw new NullPointerException("evictingMapFactory cannot be null"); + } + return lift(new OperatorGroupBy(keySelector, elementSelector, evictingMapFactory)); + } + + /** + * Groups the items emitted by an {@code Observable} according to a specified criterion, and emits these + * grouped items as {@link GroupedObservable}s. The emitted {@code GroupedObservable} allows only a single + * {@link Subscriber} during its lifetime and if this {@code Subscriber} unsubscribes before the + * source terminates, the next emission by the source having the same key will trigger a new + * {@code GroupedObservable} emission. + *

    + * + *

    + * Note: A {@link GroupedObservable} 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 may + * discard their buffers by applying an operator like {@link #ignoreElements} to them. + *

    + *
    Backpressure:
    + *
    Both the returned and its inner {@code Observable}s honor backpressure and the source {@code Observable} + * 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 Observable}s use + * unbounded internal buffers and if the source {@code Observable} doesn't honor backpressure, that may + * lead to {@code OutOfMemoryError}.
    + *
    Scheduler:
    + *
    {@code groupBy} does not operate by default on a particular {@link Scheduler}.
    + *
    + * + * @param keySelector + * a function that extracts the key for each item + * @param elementSelector + * a function that extracts the return element for each item + * @param bufferSize + * the size of the buffer ({@link RxRingBuffer.SIZE} may be suitable). + * @param delayError + * if and only if false then onError emissions can shortcut onNext emissions (emissions may be buffered) + * @param evictingMapFactory + * a function that given an eviction action returns a {@link Map} instance that will be used to assign + * items to the appropriate {@code GroupedObservable}s. The {@code Map} instance must be thread-safe + * and any eviction must trigger a call to the supplied action (synchronously or asynchronously). + * This can be used to limit the size of the map by evicting entries by map maximum size or access time for + * instance. Here's an example using Guava's {@code CacheBuilder} from v24.0: + *
    +     *            {@code
    +     *            Func1, Map> mapFactory 
    +     *              = action -> CacheBuilder.newBuilder()
    +     *                  .maximumSize(1000)
    +     *                  .expireAfterAccess(12, TimeUnit.HOURS)
    +     *                  .removalListener(entry -> action.call(entry.getValue()))
    +     *                  . build().asMap();
    +     *            }
    +     *            
    + * + * @param + * the key type + * @param + * the element type + * @return an {@code Observable} that emits {@link GroupedObservable}s, each of which corresponds to a + * unique key value and each of which emits those items from the source Observable that share that + * key value + * @throws NullPointerException + * if {@code evictingMapFactory} is null + * @see ReactiveX operators documentation: GroupBy + * @since 1.3.7 + */ + @Experimental + public final Observable> groupBy(final Func1 keySelector, + final Func1 elementSelector, int bufferSize, boolean delayError, + final Func1, Map> evictingMapFactory) { + if (evictingMapFactory == null) { + throw new NullPointerException("evictingMapFactory cannot be null"); + } + return lift(new OperatorGroupByEvicting( + keySelector, elementSelector, bufferSize, delayError, evictingMapFactory)); } /** @@ -6384,7 +7448,7 @@ public final Observable> groupBy(final Func1ReactiveX operators documentation: GroupBy */ public final Observable> groupBy(final Func1 keySelector) { - return lift(new OperatorGroupBy(keySelector)); + return lift(new OperatorGroupByEvicting(keySelector)); } /** @@ -6395,10 +7459,13 @@ public final Observable> groupBy(final Func1 * *
    + *
    Backpressure:
    + *
    The operator doesn't support backpressure and consumes all participating {@code Observable}s in + * an unbounded mode (i.e., not applying any backpressure to them).
    *
    Scheduler:
    *
    {@code groupJoin} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param the value type of the right Observable source * @param the element type of the left duration Observables * @param the element type of the right duration Observables @@ -6421,7 +7488,7 @@ public final Observable> groupBy(final Func1 Observable groupJoin(Observable right, Func1> leftDuration, Func1> rightDuration, Func2, ? extends R> resultSelector) { - return create(new OnSubscribeGroupJoin(this, right, leftDuration, rightDuration, resultSelector)); + return unsafeCreate(new OnSubscribeGroupJoin(this, right, leftDuration, rightDuration, resultSelector)); } /** @@ -6429,10 +7496,13 @@ public final Observable groupJoin(Observable right, Func1 *

    * *

    + *
    Backpressure:
    + *
    This operator ignores backpressure as it doesn't emit any elements and consumes the source {@code Observable} + * in an unbounded manner (i.e., no backpressure is applied to it).
    *
    Scheduler:
    *
    {@code ignoreElements} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @return an empty Observable that only calls {@code onCompleted} or {@code onError}, based on which one is * called by the source Observable * @see ReactiveX operators documentation: IgnoreElements @@ -6449,17 +7519,20 @@ public final Observable ignoreElements() { *

    * *

    + *
    Backpressure:
    + *
    The operator honors backpressure from downstream and consumes the source {@code Observable} in an + * unbounded manner (i.e., without applying backpressure).
    *
    Scheduler:
    *
    {@code isEmpty} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @return an Observable that emits a Boolean * @see ReactiveX operators documentation: Contains */ public final Observable isEmpty() { return lift(InternalObservableUtils.IS_EMPTY); } - + /** * Correlates the items emitted by two Observables based on overlapping durations. *

    @@ -6468,10 +7541,13 @@ public final Observable isEmpty() { *

    * *

    + *
    Backpressure:
    + *
    The operator doesn't support backpressure and consumes all participating {@code Observable}s in + * an unbounded mode (i.e., not applying any backpressure to them).
    *
    Scheduler:
    *
    {@code join} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param the value type of the right Observable source * @param the element type of the left duration Observables * @param the element type of the right duration Observables @@ -6494,7 +7570,7 @@ public final Observable isEmpty() { public final Observable join(Observable right, Func1> leftDurationSelector, Func1> rightDurationSelector, Func2 resultSelector) { - return create(new OnSubscribeJoin(this, right, leftDurationSelector, rightDurationSelector, resultSelector)); + return unsafeCreate(new OnSubscribeJoin(this, right, leftDurationSelector, rightDurationSelector, resultSelector)); } /** @@ -6503,10 +7579,13 @@ public final Observable join(Obser *

    * *

    + *
    Backpressure:
    + *
    The operator honors backpressure from downstream and consumes the source {@code Observable} in an + * unbounded manner (i.e., without applying backpressure).
    *
    Scheduler:
    *
    {@code last} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @return an Observable that emits the last item from the source Observable or notifies observers of an * error * @see ReactiveX operators documentation: Last @@ -6521,10 +7600,13 @@ public final Observable last() { *

    * *

    + *
    Backpressure:
    + *
    The operator honors backpressure from downstream and consumes the source {@code Observable} in an + * unbounded manner (i.e., without applying backpressure).
    *
    Scheduler:
    *
    {@code last} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param predicate * the condition any source emitted item has to satisfy * @return an Observable that emits only the last item satisfying the given condition from the source, or an @@ -6543,10 +7625,13 @@ public final Observable last(Func1 predicate) { *

    * *

    + *
    Backpressure:
    + *
    The operator honors backpressure from downstream and consumes the source {@code Observable} in an + * unbounded manner (i.e., without applying backpressure).
    *
    Scheduler:
    *
    {@code lastOrDefault} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param defaultValue * the default item to emit if the source Observable is empty * @return an Observable that emits only the last item emitted by the source Observable, or a default item @@ -6563,10 +7648,13 @@ public final Observable lastOrDefault(T defaultValue) { *

    * *

    + *
    Backpressure:
    + *
    The operator honors backpressure from downstream and consumes the source {@code Observable} in an + * unbounded manner (i.e., without applying backpressure to it).
    *
    Scheduler:
    *
    {@code lastOrDefault} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param defaultValue * the default item to emit if the source Observable doesn't emit anything that satisfies the * specified {@code predicate} @@ -6591,10 +7679,14 @@ public final Observable lastOrDefault(T defaultValue, Func1 + *
    Backpressure:
    + *
    The operator doesn't interfere with backpressure which is determined by the source {@code Observable}'s backpressure + * behavior in case the first request is smaller than the {@code count}. Otherwise, the source {@code Observable} + * is consumed in an unbounded manner (i.e., without applying backpressure to it).
    *
    Scheduler:
    *
    {@code limit} does not operate by default on a particular {@link Scheduler}.
    * - * + * * @param count * the maximum number of items to emit * @return an Observable that emits only the first {@code count} items emitted by the source Observable, or @@ -6604,17 +7696,20 @@ public final Observable lastOrDefault(T defaultValue, Func1 limit(int count) { return take(count); } - + /** * Returns an Observable that applies a specified function to each item emitted by the source Observable and * emits the results of these function applications. *

    * *

    + *
    Backpressure:
    + *
    The operator doesn't interfere with backpressure which is determined by the source {@code Observable}'s backpressure + * behavior.
    *
    Scheduler:
    *
    {@code map} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param the output type * @param func * a function to apply to each item emitted by the Observable @@ -6623,9 +7718,9 @@ public final Observable limit(int count) { * @see ReactiveX operators documentation: Map */ public final Observable map(Func1 func) { - return lift(new OperatorMap(func)); + return unsafeCreate(new OnSubscribeMap(this, func)); } - + private Observable mapNotification(Func1 onNext, Func1 onError, Func0 onCompleted) { return lift(new OperatorMapNotification(onNext, onError, onCompleted)); } @@ -6636,10 +7731,13 @@ private Observable mapNotification(Func1 onNext, *

    * *

    + *
    Backpressure:
    + *
    The operator honors backpressure from downstream and expects it from the source {@code Observable}. + * If this expectation is violated, the operator may throw an {@code IllegalStateException}.
    *
    Scheduler:
    *
    {@code materialize} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @return an Observable that emits items that are the result of materializing the items and notifications * of the source Observable * @see ReactiveX operators documentation: Materialize @@ -6656,10 +7754,13 @@ public final Observable> materialize() { * You can combine items emitted by multiple Observables so that they appear as a single Observable, by * using the {@code mergeWith} method. *
    + *
    Backpressure:
    + *
    The operator honors backpressure from downstream. This and the other {@code Observable}s are expected to honor + * backpressure; if violated, the operator may signal {@code MissingBackpressureException}.
    *
    Scheduler:
    *
    {@code mergeWith} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param t1 * an Observable to be merged * @return an Observable that emits all of the items emitted by the source Observables @@ -6668,7 +7769,7 @@ public final Observable> materialize() { public final Observable mergeWith(Observable t1) { return merge(this, t1); } - + /** * Modifies an Observable to perform its emissions and notifications on a specified {@link Scheduler}, * asynchronously with a bounded buffer of {@link rx.internal.util.RxRingBuffer#SIZE} slots. @@ -6678,10 +7779,16 @@ public final Observable mergeWith(Observable t1) { *

    * *

    + *
    Backpressure:
    + *
    This operator honors backpressure from downstream and expects it from the source {@code Observable}. Violating this + * expectation will lead to {@code MissingBackpressureException}. This is the most common operator where the exception + * pops up; look for sources up the chain that don't support backpressure, + * such as {@code interval}, {@code timer}, {code PublishSubject} or {@code BehaviorSubject} and apply any + * of the {@code onBackpressureXXX} operators before applying {@code observeOn} itself.
    *
    Scheduler:
    *
    you specify which {@link Scheduler} this operator will use
    *
    - * + * * @param scheduler * the {@link Scheduler} to notify {@link Observer}s on * @return the source Observable modified so that its {@link Observer}s are notified on the specified @@ -6706,6 +7813,12 @@ public final Observable observeOn(Scheduler scheduler) { *

    * *

    + *
    Backpressure:
    + *
    This operator honors backpressure from downstream and expects it from the source {@code Observable}. Violating this + * expectation will lead to {@code MissingBackpressureException}. This is the most common operator where the exception + * pops up; look for sources up the chain that don't support backpressure, + * such as {@code interval}, {@code timer}, {code PublishSubject} or {@code BehaviorSubject} and apply any + * of the {@code onBackpressureXXX} operators before applying {@code observeOn} itself.
    *
    Scheduler:
    *
    you specify which {@link Scheduler} this operator will use
    *
    @@ -6731,10 +7844,16 @@ public final Observable observeOn(Scheduler scheduler, int bufferSize) { *

    * *

    + *
    Backpressure:
    + *
    This operator honors backpressure from downstream and expects it from the source {@code Observable}. Violating this + * expectation will lead to {@code MissingBackpressureException}. This is the most common operator where the exception + * pops up; look for sources up the chain that don't support backpressure, + * such as {@code interval}, {@code timer}, {code PublishSubject} or {@code BehaviorSubject} and apply any + * of the {@code onBackpressureXXX} operators before applying {@code observeOn} itself.
    *
    Scheduler:
    *
    you specify which {@link Scheduler} this operator will use
    *
    - * + * * @param scheduler * the {@link Scheduler} to notify {@link Observer}s on * @param delayError @@ -6760,6 +7879,12 @@ public final Observable observeOn(Scheduler scheduler, boolean delayError) { *

    * *

    + *
    Backpressure:
    + *
    This operator honors backpressure from downstream and expects it from the source {@code Observable}. Violating this + * expectation will lead to {@code MissingBackpressureException}. This is the most common operator where the exception + * pops up; look for sources up the chain that don't support backpressure, + * such as {@code interval}, {@code timer}, {code PublishSubject} or {@code BehaviorSubject} and apply any + * of the {@code onBackpressureXXX} operators before applying {@code observeOn} itself.
    *
    Scheduler:
    *
    you specify which {@link Scheduler} this operator will use
    *
    @@ -6792,10 +7917,13 @@ public final Observable observeOn(Scheduler scheduler, boolean delayError, in *

    * *

    + *
    Backpressure:
    + *
    The operator doesn't interfere with backpressure which is determined by the source {@code Observable}'s backpressure + * behavior.
    *
    Scheduler:
    *
    {@code ofType} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param the output type * @param klass * the class type to filter the items emitted by the source Observable @@ -6812,6 +7940,9 @@ public final Observable ofType(final Class klass) { *

    * *

    + *
    Backpressure:
    + *
    The operator honors backpressure from downstream and consumes the source {@code Observable} in an unbounded + * manner (i.e., not applying backpressure to it).
    *
    Scheduler:
    *
    {@code onBackpressureBuffer} does not operate by default on a particular {@link Scheduler}.
    *
    @@ -6831,6 +7962,9 @@ public final Observable onBackpressureBuffer() { *

    * *

    + *
    Backpressure:
    + *
    The operator honors backpressure from downstream and consumes the source {@code Observable} in an unbounded + * manner (i.e., not applying backpressure to it).
    *
    Scheduler:
    *
    {@code onBackpressureBuffer} does not operate by default on a particular {@link Scheduler}.
    *
    @@ -6852,6 +7986,9 @@ public final Observable onBackpressureBuffer(long capacity) { *

    * *

    + *
    Backpressure:
    + *
    The operator honors backpressure from downstream and consumes the source {@code Observable} in an unbounded + * manner (i.e., not applying backpressure to it).
    *
    Scheduler:
    *
    {@code onBackpressureBuffer} does not operate by default on a particular {@link Scheduler}.
    *
    @@ -6885,6 +8022,9 @@ public final Observable onBackpressureBuffer(long capacity, Action0 onOverflo *

    * *

    + *
    Backpressure:
    + *
    The operator honors backpressure from downstream and consumes the source {@code Observable} in an unbounded + * manner (i.e., not applying backpressure to it).
    *
    Scheduler:
    *
    {@code onBackpressureBuffer} does not operate by default on a particular {@link Scheduler}.
    *
    @@ -6894,9 +8034,8 @@ public final Observable onBackpressureBuffer(long capacity, Action0 onOverflo * @param overflowStrategy how should the {@code Observable} react to buffer overflows. Null is not allowed. * @return the source {@code Observable} modified to buffer items up to the given capacity * @see ReactiveX operators documentation: backpressure operators - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Experimental public final Observable onBackpressureBuffer(long capacity, Action0 onOverflow, BackpressureOverflow.Strategy overflowStrategy) { return lift(new OperatorOnBackpressureBuffer(capacity, onOverflow, overflowStrategy)); } @@ -6910,6 +8049,9 @@ public final Observable onBackpressureBuffer(long capacity, Action0 onOverflo * If the downstream request count hits 0 then the Observable will refrain from calling {@code onNext} until * the observer invokes {@code request(n)} again to increase the request count. *
    + *
    Backpressure:
    + *
    The operator honors backpressure from downstream and consumes the source {@code Observable} in an unbounded + * manner (i.e., not applying backpressure to it).
    *
    Scheduler:
    *
    {@code onBackpressureDrop} does not operate by default on a particular {@link Scheduler}.
    *
    @@ -6917,7 +8059,6 @@ public final Observable onBackpressureBuffer(long capacity, Action0 onOverflo * @param onDrop the action to invoke for each item dropped. onDrop action should be fast and should never block. * @return the source Observable modified to drop {@code onNext} notifications on overflow * @see ReactiveX operators documentation: backpressure operators - * @Experimental The behavior of this can change at any time. * @since 1.1.0 */ public final Observable onBackpressureDrop(Action1 onDrop) { @@ -6933,19 +8074,22 @@ public final Observable onBackpressureDrop(Action1 onDrop) { * If the downstream request count hits 0 then the Observable will refrain from calling {@code onNext} until * the observer invokes {@code request(n)} again to increase the request count. *
    + *
    Backpressure:
    + *
    The operator honors backpressure from downstream and consumes the source {@code Observable} in an unbounded + * manner (i.e., not applying backpressure to it).
    *
    Scheduler:
    *
    {@code onBackpressureDrop} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @return the source Observable modified to drop {@code onNext} notifications on overflow * @see ReactiveX operators documentation: backpressure operators */ public final Observable onBackpressureDrop() { return lift(OperatorOnBackpressureDrop.instance()); } - + /** - * Instructs an Observable that is emitting items faster than its observer can consume them to + * Instructs an Observable that is emitting items faster than its observer can consume them to * hold onto the latest value and emit that on request. *

    * @@ -6958,6 +8102,14 @@ public final Observable onBackpressureDrop() { *

    * 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. + *

    + *

    + *
    Backpressure:
    + *
    The operator honors backpressure from downstream and consumes the source {@code Observable} in an unbounded + * manner (i.e., not applying backpressure to it).
    + *
    Scheduler:
    + *
    {@code onBackpressureLatest} does not operate by default on a particular {@link Scheduler}.
    + *
    * * @return the source Observable modified so that it emits the most recently-received item upon request * @since 1.1.0 @@ -6965,7 +8117,7 @@ public final Observable onBackpressureDrop() { public final Observable onBackpressureLatest() { return lift(OperatorOnBackpressureLatest.instance()); } - + /** * Instructs an Observable to pass control to another Observable rather than invoking * {@link Observer#onError onError} if it encounters an error. @@ -6985,17 +8137,23 @@ public final Observable onBackpressureLatest() { * You can use this to prevent errors from propagating or to supply fallback data should errors be * encountered. *
    + *
    Backpressure:
    + *
    The operator honors backpressure from downstream. This and the resuming {@code Observable}s + * are expected to honor backpressure as well. + * If any of them violate this expectation, the operator may throw an + * {@code IllegalStateException} when the source {@code Observable} completes or + * a {@code MissingBackpressureException} is signalled somewhere downstream.
    *
    Scheduler:
    *
    {@code onErrorResumeNext} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param resumeFunction * a function that returns an Observable that will take over if the source Observable encounters * an error * @return the original Observable, with appropriately modified behavior * @see ReactiveX operators documentation: Catch */ - public final Observable onErrorResumeNext(final Func1> resumeFunction) { + public final Observable onErrorResumeNext(final Func1> resumeFunction) { return lift(new OperatorOnErrorResumeNextViaFunction(resumeFunction)); } @@ -7018,10 +8176,16 @@ public final Observable onErrorResumeNext(final Func1 + *
    Backpressure:
    + *
    The operator honors backpressure from downstream. This and the resuming {@code Observable}s + * are expected to honor backpressure as well. + * If any of them violate this expectation, the operator may throw an + * {@code IllegalStateException} when the source {@code Observable} completes or + * {@code MissingBackpressureException} is signalled somewhere downstream.
    *
    Scheduler:
    *
    {@code onErrorResumeNext} does not operate by default on a particular {@link Scheduler}.
    * - * + * * @param resumeSequence * a function that returns an Observable that will take over if the source Observable encounters * an error @@ -7049,10 +8213,15 @@ public final Observable onErrorResumeNext(final Observable resum * You can use this to prevent errors from propagating or to supply fallback data should errors be * encountered. *
    + *
    Backpressure:
    + *
    The operator honors backpressure from downstream. The source {@code Observable}s is expected to honor + * backpressure as well. If it this expectation is violated, the operator may throw + * {@code IllegalStateException} when the source {@code Observable} completes or + * {@code MissingBackpressureException} is signalled somewhere downstream.
    *
    Scheduler:
    *
    {@code onErrorReturn} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param resumeFunction * a function that returns an item that the new Observable will emit if the source Observable * encounters an error @@ -7060,7 +8229,7 @@ public final Observable onErrorResumeNext(final Observable resum * @see ReactiveX operators documentation: Catch */ @SuppressWarnings("cast") - public final Observable onErrorReturn(Func1 resumeFunction) { + public final Observable onErrorReturn(Func1 resumeFunction) { return lift((Operator)OperatorOnErrorResumeNextViaFunction.withSingle(resumeFunction)); } @@ -7086,10 +8255,16 @@ public final Observable onErrorReturn(Func1 resumeFun * You can use this to prevent exceptions from propagating or to supply fallback data should exceptions be * encountered. *
    + *
    Backpressure:
    + *
    The operator honors backpressure from downstream. This and the resuming {@code Observable}s + * are expected to honor backpressure as well. + * If any of them violate this expectation, the operator may throw an + * {@code IllegalStateException} when the source {@code Observable} completes or + * {@code MissingBackpressureException} is signalled somewhere downstream.
    *
    Scheduler:
    *
    {@code onExceptionResumeNext} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param resumeSequence * a function that returns an Observable that will take over if the source Observable encounters * an exception @@ -7101,23 +8276,25 @@ public final Observable onExceptionResumeNext(final Observable r return lift((Operator)OperatorOnErrorResumeNextViaFunction.withException(resumeSequence)); } - + /** * Nulls out references to the upstream producer and downstream Subscriber if * the sequence is terminated or downstream unsubscribes. *
    + *
    Backpressure:
    + *
    The operator doesn't interfere with backpressure which is determined by the source {@code Observable}'s backpressure + * behavior.
    *
    Scheduler:
    *
    {@code onTerminateDetach} does not operate by default on a particular {@link Scheduler}.
    *
    * @return an Observable which out references to the upstream producer and downstream Subscriber if * the sequence is terminated or downstream unsubscribes - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Experimental public final Observable onTerminateDetach() { - return create(new OnSubscribeDetach(this)); + return unsafeCreate(new OnSubscribeDetach(this)); } - + /** * Returns a {@link ConnectableObservable}, which is a variety of Observable that waits until its * {@link ConnectableObservable#connect connect} method is called before it begins emitting items to those @@ -7125,10 +8302,14 @@ public final Observable onTerminateDetach() { *

    * *

    + *
    Backpressure:
    + *
    The returned {@code ConnectableObservable} honors backpressure for each of its {@code Subscriber}s + * and expects the source {@code Observable} to honor backpressure as well. If this expectation is violated, + * the operator will signal a {@code MissingBackpressureException} to its {@code Subscriber}s and disconnect.
    *
    Scheduler:
    *
    {@code publish} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @return a {@link ConnectableObservable} that upon connection causes the source Observable to emit items * to its {@link Observer}s * @see ReactiveX operators documentation: Publish @@ -7143,10 +8324,16 @@ public final ConnectableObservable publish() { *

    * *

    + *
    Backpressure:
    + *
    The operator expects the source {@code Observable} to honor backpressure and if this expectation is + * violated, the operator will signal a {@code MissingBackpressureException} through the {@code Observable} + * provided to the function. Since the {@code Observable} returned by the {@code selector} may be + * independent from the provided {@code Observable} to the function, the output's backpressure behavior + * is determined by this returned {@code Observable}.
    *
    Scheduler:
    *
    {@code publish} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param * the type of items emitted by the resulting Observable * @param selector @@ -7163,29 +8350,28 @@ public final Observable publish(Func1, ? extends Ob /** * Requests {@code n} initially from the upstream and then 75% of {@code n} subsequently * after 75% of {@code n} values have been emitted to the downstream. - * + * *

    This operator allows preventing the downstream to trigger unbounded mode via {@code request(Long.MAX_VALUE)} * or compensate for the per-item overhead of small and frequent requests. - * + * *

    *
    Backpressure:
    *
    The operator expects backpressure from upstream and honors backpressure from downstream.
    *
    Scheduler:
    *
    {@code rebatchRequests} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param n the initial request amount, further request will happen after 75% of this value * @return the Observable that rebatches request amounts from downstream - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Experimental public final Observable rebatchRequests(int n) { if (n <= 0) { throw new IllegalArgumentException("n > 0 required but it was " + n); } return lift(OperatorObserveOn.rebatch(n)); } - + /** * Returns an Observable that applies a specified accumulator function to the first item emitted by a source * Observable, then feeds the result of that function along with the second item emitted by the source @@ -7198,13 +8384,13 @@ public final Observable rebatchRequests(int n) { * "compress," or "inject" in other programming contexts. Groovy, for instance, has an {@code inject} method * that does a similar operation on lists. *
    - *
    Backpressure Support:
    + *
    Backpressure:
    *
    The operator honors backpressure of its downstream consumer and consumes the * upstream source in unbounded mode.
    *
    Scheduler:
    *
    {@code reduce} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param accumulator * an accumulator function to be invoked on each item emitted by the source Observable, whose * result will be used in the next accumulator call @@ -7219,10 +8405,10 @@ public final Observable reduce(Func2 accumulator) { /* * Discussion and confirmation of implementation at * https://github.com/ReactiveX/RxJava/issues/423#issuecomment-27642532 - * + * * It should use last() not takeLast(1) since it needs to emit an error if the sequence is empty. */ - return scan(accumulator).last(); + return unsafeCreate(new OnSubscribeReduce(this, accumulator)); } /** @@ -7243,21 +8429,21 @@ public final Observable reduce(Func2 accumulator) { *
    
          * Observable<T> source = ...
          * Observable.defer(() -> source.reduce(new ArrayList<>(), (list, item) -> list.add(item)));
    -     * 
    +     *
          * // alternatively, by using compose to stay fluent
    -     * 
    +     *
          * source.compose(o ->
          *     Observable.defer(() -> o.reduce(new ArrayList<>(), (list, item) -> list.add(item)))
          * );
          * 
    *
    - *
    Backpressure Support:
    + *
    Backpressure:
    *
    The operator honors backpressure of its downstream consumer and consumes the * upstream source in unbounded mode.
    *
    Scheduler:
    *
    {@code reduce} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param the accumulator and output value type * @param initialValue * the initial (seed) accumulator value @@ -7270,18 +8456,21 @@ public final Observable reduce(Func2 accumulator) { * @see Wikipedia: Fold (higher-order function) */ public final Observable reduce(R initialValue, Func2 accumulator) { - return scan(initialValue, accumulator).takeLast(1); + return unsafeCreate(new OnSubscribeReduceSeed(this, initialValue, accumulator)); } - + /** * Returns an Observable that repeats the sequence of items emitted by the source Observable indefinitely. *

    * *

    + *
    Backpressure:
    + *
    The operator honors downstream backpressure and expects the source {@code Observable} to honor backpressure as well. + * If this expectation is violated, the operator may throw an {@code IllegalStateException}.
    *
    Scheduler:
    *
    {@code repeat} operates by default on the {@code trampoline} {@link Scheduler}.
    *
    - * + * * @return an Observable that emits the items emitted by the source Observable repeatedly and in sequence * @see ReactiveX operators documentation: Repeat */ @@ -7295,10 +8484,13 @@ public final Observable repeat() { *

    * *

    + *
    Backpressure:
    + *
    The operator honors downstream backpressure and expects the source {@code Observable} to honor backpressure as well. + * If this expectation is violated, the operator may throw an {@code IllegalStateException}.
    *
    Scheduler:
    *
    you specify which {@link Scheduler} this operator will use
    *
    - * + * * @param scheduler * the Scheduler to emit the items on * @return an Observable that emits the items emitted by the source Observable repeatedly and in sequence @@ -7314,10 +8506,13 @@ public final Observable repeat(Scheduler scheduler) { *

    * *

    + *
    Backpressure:
    + *
    The operator honors downstream backpressure and expects the source {@code Observable} to honor backpressure as well. + * If this expectation is violated, the operator may throw an {@code IllegalStateException}.
    *
    Scheduler:
    *
    {@code repeat} operates by default on the {@code trampoline} {@link Scheduler}.
    *
    - * + * * @param count * the number of times the source Observable items are repeated, a count of 0 will yield an empty * sequence @@ -7337,10 +8532,13 @@ public final Observable repeat(final long count) { *

    * *

    + *
    Backpressure:
    + *
    The operator honors downstream backpressure and expects the source {@code Observable} to honor backpressure as well. + * If this expectation is violated, the operator may throw an {@code IllegalStateException}.
    *
    Scheduler:
    *
    you specify which {@link Scheduler} this operator will use
    *
    - * + * * @param count * the number of times the source Observable items are repeated, a count of 0 will yield an empty * sequence @@ -7364,10 +8562,13 @@ public final Observable repeat(final long count, Scheduler scheduler) { *

    * *

    + *
    Backpressure:
    + *
    The operator honors downstream backpressure and expects the source {@code Observable} to honor backpressure as well. + * If this expectation is violated, the operator may throw an {@code IllegalStateException}.
    *
    Scheduler:
    *
    you specify which {@link Scheduler} this operator will use
    *
    - * + * * @param notificationHandler * receives an Observable of notifications with which a user can complete or error, aborting the repeat. * @param scheduler @@ -7389,10 +8590,13 @@ public final Observable repeatWhen(final Func1 * *
    + *
    Backpressure:
    + *
    The operator honors downstream backpressure and expects the source {@code Observable} to honor backpressure as well. + * If this expectation is violated, the operator may throw an {@code IllegalStateException}.
    *
    Scheduler:
    *
    {@code repeatWhen} operates by default on the {@code trampoline} {@link Scheduler}.
    *
    - * + * * @param notificationHandler * receives an Observable of notifications with which a user can complete or error, aborting the repeat. * @return the source Observable modified with repeat logic @@ -7410,14 +8614,14 @@ public final Observable repeatWhen(final Func1 * *
    - *
    Backpressure Support:
    + *
    Backpressure:
    *
    This operator supports backpressure. Note that the upstream requests are determined by the child * Subscriber which requests the largest amount: i.e., two child Subscribers with requests of 10 and 100 will * request 100 elements from the underlying Observable sequence.
    *
    Scheduler:
    *
    This version of {@code replay} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @return a {@link ConnectableObservable} that upon connection causes the source Observable to emit its * items to its {@link Observer}s * @see ReactiveX operators documentation: Replay @@ -7432,14 +8636,14 @@ public final ConnectableObservable replay() { *

    * *

    - *
    Backpressure Support:
    + *
    Backpressure:
    *
    This operator supports backpressure. Note that the upstream requests are determined by the child * Subscriber which requests the largest amount: i.e., two child Subscribers with requests of 10 and 100 will * request 100 elements from the underlying Observable sequence.
    *
    Scheduler:
    *
    This version of {@code replay} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param * the type of items emitted by the resulting Observable * @param selector @@ -7460,14 +8664,14 @@ public final Observable replay(Func1, ? extends Obs *

    * *

    - *
    Backpressure Support:
    + *
    Backpressure:
    *
    This operator supports backpressure. Note that the upstream requests are determined by the child * Subscriber which requests the largest amount: i.e., two child Subscribers with requests of 10 and 100 will * request 100 elements from the underlying Observable sequence.
    *
    Scheduler:
    *
    This version of {@code replay} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param * the type of items emitted by the resulting Observable * @param selector @@ -7491,14 +8695,14 @@ public final Observable replay(Func1, ? extends Obs *

    * *

    - *
    Backpressure Support:
    + *
    Backpressure:
    *
    This operator supports backpressure. Note that the upstream requests are determined by the child * Subscriber which requests the largest amount: i.e., two child Subscribers with requests of 10 and 100 will * request 100 elements from the underlying Observable sequence.
    *
    Scheduler:
    *
    This version of {@code replay} operates by default on the {@code computation} {@link Scheduler}.
    *
    - * + * * @param * the type of items emitted by the resulting Observable * @param selector @@ -7527,14 +8731,14 @@ public final Observable replay(Func1, ? extends Obs *

    * *

    - *
    Backpressure Support:
    + *
    Backpressure:
    *
    This operator supports backpressure. Note that the upstream requests are determined by the child * Subscriber which requests the largest amount: i.e., two child Subscribers with requests of 10 and 100 will * request 100 elements from the underlying Observable sequence.
    *
    Scheduler:
    *
    you specify which {@link Scheduler} this operator will use
    *
    - * + * * @param * the type of items emitted by the resulting Observable * @param selector @@ -7571,14 +8775,14 @@ public final Observable replay(Func1, ? extends Obs *

    * *

    - *
    Backpressure Support:
    + *
    Backpressure:
    *
    This operator supports backpressure. Note that the upstream requests are determined by the child * Subscriber which requests the largest amount: i.e., two child Subscribers with requests of 10 and 100 will * request 100 elements from the underlying Observable sequence.
    *
    Scheduler:
    *
    you specify which {@link Scheduler} this operator will use
    *
    - * + * * @param * the type of items emitted by the resulting Observable * @param selector @@ -7594,7 +8798,7 @@ public final Observable replay(Func1, ? extends Obs * @see ReactiveX operators documentation: Replay */ public final Observable replay(final Func1, ? extends Observable> selector, final int bufferSize, final Scheduler scheduler) { - return OperatorReplay.multicastSelector(InternalObservableUtils.createReplaySupplier(this, bufferSize), + return OperatorReplay.multicastSelector(InternalObservableUtils.createReplaySupplier(this, bufferSize), InternalObservableUtils.createReplaySelectorAndObserveOn(selector, scheduler)); } @@ -7605,14 +8809,14 @@ public final Observable replay(final Func1, ? exten *

    * *

    - *
    Backpressure Support:
    + *
    Backpressure:
    *
    This operator supports backpressure. Note that the upstream requests are determined by the child * Subscriber which requests the largest amount: i.e., two child Subscribers with requests of 10 and 100 will * request 100 elements from the underlying Observable sequence.
    *
    Scheduler:
    *
    This version of {@code replay} operates by default on the {@code computation} {@link Scheduler}.
    *
    - * + * * @param * the type of items emitted by the resulting Observable * @param selector @@ -7638,14 +8842,14 @@ public final Observable replay(Func1, ? extends Obs *

    * *

    - *
    Backpressure Support:
    + *
    Backpressure:
    *
    This operator supports backpressure. Note that the upstream requests are determined by the child * Subscriber which requests the largest amount: i.e., two child Subscribers with requests of 10 and 100 will * request 100 elements from the underlying Observable sequence.
    *
    Scheduler:
    *
    you specify which {@link Scheduler} this operator will use
    *
    - * + * * @param * the type of items emitted by the resulting Observable * @param selector @@ -7673,14 +8877,14 @@ public final Observable replay(Func1, ? extends Obs *

    * *

    - *
    Backpressure Support:
    + *
    Backpressure:
    *
    This operator supports backpressure. Note that the upstream requests are determined by the child * Subscriber which requests the largest amount: i.e., two child Subscribers with requests of 10 and 100 will * request 100 elements from the underlying Observable sequence.
    *
    Scheduler:
    *
    you specify which {@link Scheduler} this operator will use
    *
    - * + * * @param * the type of items emitted by the resulting Observable * @param selector @@ -7695,7 +8899,7 @@ public final Observable replay(Func1, ? extends Obs */ public final Observable replay(final Func1, ? extends Observable> selector, final Scheduler scheduler) { return OperatorReplay.multicastSelector( - InternalObservableUtils.createReplaySupplier(this), + InternalObservableUtils.createReplaySupplier(this), InternalObservableUtils.createReplaySelectorAndObserveOn(selector, scheduler)); } @@ -7707,14 +8911,14 @@ public final Observable replay(final Func1, ? exten *

    * *

    - *
    Backpressure Support:
    + *
    Backpressure:
    *
    This operator supports backpressure. Note that the upstream requests are determined by the child * Subscriber which requests the largest amount: i.e., two child Subscribers with requests of 10 and 100 will * request 100 elements from the underlying Observable sequence.
    *
    Scheduler:
    *
    This version of {@code replay} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param bufferSize * the buffer size that limits the number of items that can be replayed * @return a {@link ConnectableObservable} that shares a single subscription to the source Observable and @@ -7729,18 +8933,18 @@ public final ConnectableObservable replay(final int bufferSize) { * Returns a {@link ConnectableObservable} that shares a single subscription to the source Observable and * replays at most {@code bufferSize} items that were emitted during a specified time window. A Connectable * Observable resembles an ordinary Observable, except that it does not begin emitting items when it is - * subscribed to, but only when its {@code connect} method is called. + * subscribed to, but only when its {@code connect} method is called. *

    * *

    - *
    Backpressure Support:
    + *
    Backpressure:
    *
    This operator supports backpressure. Note that the upstream requests are determined by the child * Subscriber which requests the largest amount: i.e., two child Subscribers with requests of 10 and 100 will * request 100 elements from the underlying Observable sequence.
    *
    Scheduler:
    *
    This version of {@code replay} operates by default on the {@code computation} {@link Scheduler}.
    *
    - * + * * @param bufferSize * the buffer size that limits the number of items that can be replayed * @param time @@ -7764,14 +8968,14 @@ public final ConnectableObservable replay(int bufferSize, long time, TimeUnit *

    * *

    - *
    Backpressure Support:
    + *
    Backpressure:
    *
    This operator supports backpressure. Note that the upstream requests are determined by the child * Subscriber which requests the largest amount: i.e., two child Subscribers with requests of 10 and 100 will * request 100 elements from the underlying Observable sequence.
    *
    Scheduler:
    *
    you specify which {@link Scheduler} this operator will use
    *
    - * + * * @param bufferSize * the buffer size that limits the number of items that can be replayed * @param time @@ -7798,18 +9002,18 @@ public final ConnectableObservable replay(final int bufferSize, final long ti * Returns a {@link ConnectableObservable} that shares a single subscription to the source Observable and * replays at most {@code bufferSize} items emitted by that 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 {@code connect} method is called. + * when its {@code connect} method is called. *

    * *

    - *
    Backpressure Support:
    + *
    Backpressure:
    *
    This operator supports backpressure. Note that the upstream requests are determined by the child * Subscriber which requests the largest amount: i.e., two child Subscribers with requests of 10 and 100 will * request 100 elements from the underlying Observable sequence.
    *
    Scheduler:
    *
    you specify which {@link Scheduler} this operator will use
    *
    - * + * * @param bufferSize * the buffer size that limits the number of items that can be replayed * @param scheduler @@ -7826,18 +9030,18 @@ public final ConnectableObservable replay(final int bufferSize, final Schedul * Returns a {@link ConnectableObservable} that shares a single subscription to the source Observable and * replays all items emitted by that Observable within a specified time window. A Connectable Observable * resembles an ordinary Observable, except that it does not begin emitting items when it is subscribed to, - * but only when its {@code connect} method is called. + * but only when its {@code connect} method is called. *

    * *

    - *
    Backpressure Support:
    + *
    Backpressure:
    *
    This operator supports backpressure. Note that the upstream requests are determined by the child * Subscriber which requests the largest amount: i.e., two child Subscribers with requests of 10 and 100 will * request 100 elements from the underlying Observable sequence.
    *
    Scheduler:
    *
    This version of {@code replay} operates by default on the {@code computation} {@link Scheduler}.
    *
    - * + * * @param time * the duration of the window in which the replayed items must have been emitted * @param unit @@ -7854,18 +9058,18 @@ public final ConnectableObservable replay(long time, TimeUnit unit) { * Returns a {@link ConnectableObservable} that shares a single subscription to the source Observable and * replays all items emitted by that Observable within a specified time window. A Connectable Observable * resembles an ordinary Observable, except that it does not begin emitting items when it is subscribed to, - * but only when its {@code connect} method is called. + * but only when its {@code connect} method is called. *

    * *

    - *
    Backpressure Support:
    + *
    Backpressure:
    *
    This operator supports backpressure. Note that the upstream requests are determined by the child * Subscriber which requests the largest amount: i.e., two child Subscribers with requests of 10 and 100 will * request 100 elements from the underlying Observable sequence.
    *
    Scheduler:
    *
    you specify which {@link Scheduler} this operator will use
    *
    - * + * * @param time * the duration of the window in which the replayed items must have been emitted * @param unit @@ -7888,14 +9092,14 @@ public final ConnectableObservable replay(final long time, final TimeUnit uni *

    * *

    - *
    Backpressure Support:
    + *
    Backpressure:
    *
    This operator supports backpressure. Note that the upstream requests are determined by the child * Subscriber which requests the largest amount: i.e., two child Subscribers with requests of 10 and 100 will * request 100 elements from the underlying Observable sequence.
    *
    Scheduler:
    *
    you specify which {@link Scheduler} this operator will use
    *
    - * + * * @param scheduler * the Scheduler on which the Observers will observe the emitted items * @return a {@link ConnectableObservable} that shares a single subscription to the source Observable that @@ -7921,10 +9125,13 @@ public final ConnectableObservable replay(final Scheduler scheduler) { * {@code [1, 2]} then succeeds the second time and emits {@code [1, 2, 3, 4, 5]} then the complete sequence * of emissions and notifications would be {@code [1, 2, 1, 2, 3, 4, 5, onCompleted]}. *
    + *
    Backpressure:
    + *
    The operator honors downstream backpressure and expects the source {@code Observable} to honor backpressure as well. + * If this expectation is violated, the operator may throw an {@code IllegalStateException}.
    *
    Scheduler:
    *
    {@code retry} operates by default on the {@code trampoline} {@link Scheduler}.
    *
    - * + * * @return the source Observable modified with retry logic * @see ReactiveX operators documentation: Retry */ @@ -7947,10 +9154,13 @@ public final Observable retry() { * {@code [1, 2]} then succeeds the second time and emits {@code [1, 2, 3, 4, 5]} then the complete sequence * of emissions and notifications would be {@code [1, 2, 1, 2, 3, 4, 5, onCompleted]}. *
    + *
    Backpressure:
    + *
    The operator honors downstream backpressure and expects the source {@code Observable} to honor backpressure as well. + * If this expectation is violated, the operator may throw an {@code IllegalStateException}.
    *
    Scheduler:
    *
    {@code retry} operates by default on the {@code trampoline} {@link Scheduler}.
    *
    - * + * * @param count * number of retry attempts before failing * @return the source Observable modified with retry logic @@ -7966,8 +9176,9 @@ public final Observable retry(final long count) { *

    * *

    - *
    Backpressure Support:
    - *
    This operator honors backpressure. + *
    Backpressure:
    + *
    The operator honors downstream backpressure and expects the source {@code Observable} to honor backpressure as well. + * If this expectation is violated, the operator may throw an {@code IllegalStateException}.
    *
    Scheduler:
    *
    {@code retry} operates by default on the {@code trampoline} {@link Scheduler}.
    *
    @@ -7989,14 +9200,14 @@ public final Observable retry(Func2 predicate) { * {@link Throwable} item to the Observable provided as an argument to the {@code notificationHandler} * function. If that Observable calls {@code onComplete} or {@code onError} then {@code retry} will call * {@code onCompleted} or {@code onError} on the child subscription. Otherwise, this Observable will - * resubscribe to the source Observable. + * resubscribe to the source Observable. *

    * - * + * * Example: - * + * * This retries 3 times, each time incrementing the number of seconds it waits. - * + * *

    
          *  Observable.create((Subscriber s) -> {
          *      System.out.println("subscribing");
    @@ -8008,7 +9219,7 @@ public final Observable retry(Func2 predicate) {
          *      });
          *  }).toBlocking().forEach(System.out::println);
          * 
    - * + * * Output is: * *
     {@code
    @@ -8021,6 +9232,9 @@ public final Observable retry(Func2 predicate) {
          * subscribing
          * } 
    *
    + *
    Backpressure:
    + *
    The operator honors downstream backpressure and expects the source {@code Observable} to honor backpressure as well. + * If this expectation is violated, the operator may throw an {@code IllegalStateException}.
    *
    Scheduler:
    *
    {@code retryWhen} operates by default on the {@code trampoline} {@link Scheduler}.
    *
    @@ -8041,11 +9255,14 @@ public final Observable retryWhen(final Func1 * *

    *

    + *
    Backpressure:
    + *
    The operator honors downstream backpressure and expects the source {@code Observable} to honor backpressure as well. + * If this expectation is violated, the operator may throw an {@code IllegalStateException}.
    *
    Scheduler:
    *
    you specify which {@link Scheduler} this operator will use
    *
    @@ -8066,14 +9283,14 @@ public final Observable retryWhen(final Func1 - * + * *
    - *
    Backpressure Support:
    + *
    Backpressure:
    *
    This operator does not support backpressure as it uses time to control data flow.
    *
    Scheduler:
    *
    {@code sample} operates by default on the {@code computation} {@link Scheduler}.
    *
    - * + * * @param period * the sampling rate * @param unit @@ -8092,14 +9309,14 @@ public final Observable sample(long period, TimeUnit unit) { * Returns an Observable that emits the most recently emitted item (if any) emitted by the source Observable * within periodic time intervals, where the intervals are defined on a particular Scheduler. *

    - * + * *

    - *
    Backpressure Support:
    + *
    Backpressure:
    *
    This operator does not support backpressure as it uses time to control data flow.
    *
    Scheduler:
    *
    you specify which {@link Scheduler} this operator will use
    *
    - * + * * @param period * the sampling rate * @param unit @@ -8123,13 +9340,13 @@ public final Observable sample(long period, TimeUnit unit, Scheduler schedule *

    * *

    - *
    Backpressure Support:
    + *
    Backpressure:
    *
    This operator does not support backpressure as it uses the emissions of the {@code sampler} * Observable to control data flow.
    *
    Scheduler:
    *
    This version of {@code sample} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param the element type of the sampler Observable * @param sampler * the Observable to use for sampling the source Observable @@ -8152,10 +9369,13 @@ public final Observable sample(Observable sampler) { *

    * This sort of function is sometimes called an accumulator. *

    + *
    Backpressure:
    + *
    The operator honors downstream backpressure and expects the source {@code Observable} to honor backpressure as well. + * Violating this expectation, a {@code MissingBackpressureException} may get signalled somewhere downstream.
    *
    Scheduler:
    *
    {@code scan} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param accumulator * an accumulator function to be invoked on each item emitted by the source Observable, whose * result will be emitted to {@link Observer}s via {@link Observer#onNext onNext} and used in the @@ -8186,20 +9406,21 @@ public final Observable scan(Func2 accumulator) { *
    
          * Observable<T> source = ...
          * Observable.defer(() -> source.scan(new ArrayList<>(), (list, item) -> list.add(item)));
    -     * 
    +     *
          * // alternatively, by using compose to stay fluent
    -     * 
    +     *
          * source.compose(o ->
          *     Observable.defer(() -> o.scan(new ArrayList<>(), (list, item) -> list.add(item)))
          * );
          * 
    *
    - *
    Backpressure:
    - *
    The operator honors backpressure.
    + *
    Backpressure:
    + *
    The operator honors downstream backpressure and expects the source {@code Observable} to honor backpressure as well. + * Violating this expectation, a {@code MissingBackpressureException} may get signalled somewhere downstream.
    *
    Scheduler:
    *
    {@code scan} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param the initial, accumulator and result type * @param initialValue * the initial (seed) accumulator item @@ -8227,6 +9448,9 @@ public final Observable scan(R initialValue, Func2 accum *

    * *

    + *
    Backpressure:
    + *
    The operator doesn't interfere with backpressure which is determined by the source {@code Observable}'s backpressure + * behavior.
    *
    Scheduler:
    *
    {@code serialize} does not operate by default on a particular {@link Scheduler}.
    *
    @@ -8241,21 +9465,21 @@ public final Observable serialize() { /** * Returns a new {@link Observable} that multicasts (shares) the original {@link Observable}. As long as - * there is at least one {@link Subscriber} this {@link Observable} will be subscribed and emitting data. - * When all subscribers have unsubscribed it will unsubscribe from the source {@link Observable}. + * there is at least one {@link Subscriber} this {@link Observable} will be subscribed and emitting data. + * When all subscribers have unsubscribed it will unsubscribe from the source {@link Observable}. *

    * This is an alias for {@link #publish()}.{@link ConnectableObservable#refCount()}. *

    * *

    - *
    Backpressure Support:
    - *
    This operator does not support backpressure because multicasting means the stream is "hot" with - * multiple subscribers. Each child will need to manage backpressure independently using operators such - * as {@link #onBackpressureDrop} and {@link #onBackpressureBuffer}.
    + *
    Backpressure:
    + *
    The operator honors backpressure and and expects the source {@code Observable} to honor backpressure as well. + * If this expectation is violated, the operator will signal a {@code MissingBackpressureException} to + * its {@code Subscriber}s.
    *
    Scheduler:
    *
    {@code share} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @return an {@code Observable} that upon connection causes the source {@code Observable} to emit items * to its {@link Observer}s * @see ReactiveX operators documentation: RefCount @@ -8263,7 +9487,7 @@ public final Observable serialize() { public final Observable share() { return publish().refCount(); } - + /** * Returns an Observable that emits the single item emitted by the source Observable, if that Observable * emits only a single item. If the source Observable emits more than one item or no items, notify of an @@ -8271,10 +9495,13 @@ public final Observable share() { *

    * *

    + *
    Backpressure:
    + *
    The operator honors backpressure from downstream and consumes the source {@code Observable} in an + * unbounded manner (i.e., without applying backpressure).
    *
    Scheduler:
    *
    {@code single} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @return an Observable that emits the single item emitted by the source Observable * @throws IllegalArgumentException * if the source emits more than one item @@ -8294,10 +9521,13 @@ public final Observable single() { *

    * *

    + *
    Backpressure:
    + *
    The operator honors backpressure from downstream and consumes the source {@code Observable} in an + * unbounded manner (i.e., without applying backpressure).
    *
    Scheduler:
    *
    {@code single} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param predicate * a predicate function to evaluate items emitted by the source Observable * @return an Observable that emits the single item emitted by the source Observable that matches the @@ -8319,10 +9549,13 @@ public final Observable single(Func1 predicate) { *

    * *

    + *
    Backpressure:
    + *
    The operator honors backpressure from downstream and consumes the source {@code Observable} in an + * unbounded manner (i.e., without applying backpressure).
    *
    Scheduler:
    *
    {@code singleOrDefault} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param defaultValue * a default value to emit if the source Observable emits no item * @return an Observable that emits the single item emitted by the source Observable, or a default item if @@ -8343,10 +9576,13 @@ public final Observable singleOrDefault(T defaultValue) { *

    * *

    + *
    Backpressure:
    + *
    The operator honors backpressure from downstream and consumes the source {@code Observable} in an + * unbounded manner (i.e., without applying backpressure).
    *
    Scheduler:
    *
    {@code singleOrDefault} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param defaultValue * a default item to emit if the source Observable emits no matching items * @param predicate @@ -8367,10 +9603,13 @@ public final Observable singleOrDefault(T defaultValue, Func1 * *
    + *
    Backpressure:
    + *
    The operator doesn't interfere with backpressure which is determined by the source {@code Observable}'s backpressure + * behavior.
    *
    Scheduler:
    *
    This version of {@code skip} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param count * the number of items to skip * @return an Observable that is identical to the source Observable except that it does not emit the first @@ -8387,10 +9626,13 @@ public final Observable skip(int count) { *

    * *

    + *
    Backpressure:
    + *
    The operator doesn't support backpressure as it uses time to skip arbitrary number of elements and + * thus has to consume the source {@code Observable} in an unbounded manner (i.e., no backpressure applied to it).
    *
    Scheduler:
    *
    This version of {@code skip} operates by default on the {@code computation} {@link Scheduler}.
    *
    - * + * * @param time * the length of the time window to skip * @param unit @@ -8409,10 +9651,13 @@ public final Observable skip(long time, TimeUnit unit) { *

    * *

    + *
    Backpressure:
    + *
    The operator doesn't support backpressure as it uses time to skip arbitrary number of elements and + * thus has to consume the source {@code Observable} in an unbounded manner (i.e., no backpressure applied to it).
    *
    Scheduler:
    *
    you specify which {@link Scheduler} this operator will use
    *
    - * + * * @param time * the length of the time window to skip * @param unit @@ -8424,7 +9669,7 @@ public final Observable skip(long time, TimeUnit unit) { * @see ReactiveX operators documentation: Skip */ public final Observable skip(long time, TimeUnit unit, Scheduler scheduler) { - return lift(new OperatorSkipTimed(time, unit, scheduler)); + return unsafeCreate(new OnSubscribeSkipTimed(this, time, unit, scheduler)); } /** @@ -8437,10 +9682,13 @@ public final Observable skip(long time, TimeUnit unit, Scheduler scheduler) { * received, items are taken from the front of the queue and emitted by the returned Observable. This causes * such items to be delayed. *
    + *
    Backpressure:
    + *
    The operator doesn't interfere with backpressure which is determined by the source {@code Observable}'s backpressure + * behavior.
    *
    Scheduler:
    *
    This version of {@code skipLast} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param count * number of items to drop from the end of the source sequence * @return an Observable that emits the items emitted by the source Observable except for the dropped ones @@ -8461,10 +9709,13 @@ public final Observable skipLast(int count) { *

    * Note: this action will cache the latest items arriving in the specified time window. *

    + *
    Backpressure:
    + *
    The operator doesn't support backpressure as it uses time to skip arbitrary number of elements and + * thus has to consume the source {@code Observable} in an unbounded manner (i.e., no backpressure applied to it).
    *
    Scheduler:
    *
    This version of {@code skipLast} operates by default on the {@code computation} {@link Scheduler}.
    *
    - * + * * @param time * the length of the time window * @param unit @@ -8485,6 +9736,9 @@ public final Observable skipLast(long time, TimeUnit unit) { *

    * Note: this action will cache the latest items arriving in the specified time window. *

    + *
    Backpressure:
    + *
    The operator doesn't support backpressure as it uses time to skip arbitrary number of elements and + * thus has to consume the source {@code Observable} in an unbounded manner (i.e., no backpressure applied to it).
    *
    Scheduler:
    *
    you specify which {@link Scheduler} this operator will use
    *
    @@ -8509,10 +9763,13 @@ public final Observable skipLast(long time, TimeUnit unit, Scheduler schedule *

    * *

    + *
    Backpressure:
    + *
    The operator doesn't interfere with backpressure which is determined by the source {@code Observable}'s backpressure + * behavior.
    *
    Scheduler:
    *
    {@code skipUntil} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param the element type of the other Observable * @param other * the second Observable that has to emit an item before the source Observable's elements begin @@ -8531,10 +9788,13 @@ public final Observable skipUntil(Observable other) { *

    * *

    + *
    Backpressure:
    + *
    The operator doesn't interfere with backpressure which is determined by the source {@code Observable}'s backpressure + * behavior.
    *
    Scheduler:
    *
    {@code skipWhile} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param predicate * a function to test each item emitted from the source Observable * @return an Observable that begins emitting items emitted by the source Observable when the specified @@ -8551,10 +9811,14 @@ public final Observable skipWhile(Func1 predicate) { *

    * *

    + *
    Backpressure:
    + *
    The operator honors backpressure from downstream. Both this and the {@code other} {@code Observable}s + * are expected to honor backpressure as well. If any of then violates this rule, it may throw an + * {@code IllegalStateException} when the source {@code Observable} completes.
    *
    Scheduler:
    *
    {@code startWith} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param values * an Observable that contains the items you want the modified Observable to emit first * @return an Observable that emits the items in the specified {@link Observable} and then emits the items @@ -8571,10 +9835,14 @@ public final Observable startWith(Observable values) { *

    * *

    + *
    Backpressure:
    + *
    The operator honors backpressure from downstream. The source {@code Observable} + * is expected to honor backpressure as well. If it violates this rule, it may throw an + * {@code IllegalStateException} when the source {@code Observable} completes.
    *
    Scheduler:
    *
    {@code startWith} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param values * an Iterable that contains the items you want the modified Observable to emit first * @return an Observable that emits the items in the specified {@link Iterable} and then emits the items @@ -8591,10 +9859,14 @@ public final Observable startWith(Iterable values) { *

    * *

    + *
    Backpressure:
    + *
    The operator honors backpressure from downstream. The source {@code Observable} + * is expected to honor backpressure as well. If it violates this rule, it may throw an + * {@code IllegalStateException} when the source {@code Observable} completes.
    *
    Scheduler:
    *
    {@code startWith} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param t1 * the item to emit * @return an Observable that emits the specified item before it begins to emit items emitted by the source @@ -8611,10 +9883,14 @@ public final Observable startWith(T t1) { *

    * *

    + *
    Backpressure:
    + *
    The operator honors backpressure from downstream. The source {@code Observable} + * is expected to honor backpressure as well. If it violates this rule, it may throw an + * {@code IllegalStateException} when the source {@code Observable} completes.
    *
    Scheduler:
    *
    {@code startWith} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param t1 * the first item to emit * @param t2 @@ -8633,10 +9909,14 @@ public final Observable startWith(T t1, T t2) { *

    * *

    + *
    Backpressure:
    + *
    The operator honors backpressure from downstream. The source {@code Observable} + * is expected to honor backpressure as well. If it violates this rule, it may throw an + * {@code IllegalStateException} when the source {@code Observable} completes.
    *
    Scheduler:
    *
    {@code startWith} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param t1 * the first item to emit * @param t2 @@ -8657,10 +9937,14 @@ public final Observable startWith(T t1, T t2, T t3) { *

    * *

    + *
    Backpressure:
    + *
    The operator honors backpressure from downstream. The source {@code Observable} + * is expected to honor backpressure as well. If it violates this rule, it may throw an + * {@code IllegalStateException} when the source {@code Observable} completes.
    *
    Scheduler:
    *
    {@code startWith} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param t1 * the first item to emit * @param t2 @@ -8683,10 +9967,14 @@ public final Observable startWith(T t1, T t2, T t3, T t4) { *

    * *

    + *
    Backpressure:
    + *
    The operator honors backpressure from downstream. The source {@code Observable} + * is expected to honor backpressure as well. If it violates this rule, it may throw an + * {@code IllegalStateException} when the source {@code Observable} completes.
    *
    Scheduler:
    *
    {@code startWith} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param t1 * the first item to emit * @param t2 @@ -8711,10 +9999,14 @@ public final Observable startWith(T t1, T t2, T t3, T t4, T t5) { *

    * *

    + *
    Backpressure:
    + *
    The operator honors backpressure from downstream. The source {@code Observable} + * is expected to honor backpressure as well. If it violates this rule, it may throw an + * {@code IllegalStateException} when the source {@code Observable} completes.
    *
    Scheduler:
    *
    {@code startWith} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param t1 * the first item to emit * @param t2 @@ -8741,10 +10033,14 @@ public final Observable startWith(T t1, T t2, T t3, T t4, T t5, T t6) { *

    * *

    + *
    Backpressure:
    + *
    The operator honors backpressure from downstream. The source {@code Observable} + * is expected to honor backpressure as well. If it violates this rule, it may throw an + * {@code IllegalStateException} when the source {@code Observable} completes.
    *
    Scheduler:
    *
    {@code startWith} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param t1 * the first item to emit * @param t2 @@ -8773,10 +10069,14 @@ public final Observable startWith(T t1, T t2, T t3, T t4, T t5, T t6, T t7) { *

    * *

    + *
    Backpressure:
    + *
    The operator honors backpressure from downstream. The source {@code Observable} + * is expected to honor backpressure as well. If it violates this rule, it may throw an + * {@code IllegalStateException} when the source {@code Observable} completes.
    *
    Scheduler:
    *
    {@code startWith} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param t1 * the first item to emit * @param t2 @@ -8807,10 +10107,14 @@ public final Observable startWith(T t1, T t2, T t3, T t4, T t5, T t6, T t7, T *

    * *

    + *
    Backpressure:
    + *
    The operator honors backpressure from downstream. The source {@code Observable} + * is expected to honor backpressure as well. If it violates this rule, it may throw an + * {@code IllegalStateException} when the source {@code Observable} completes.
    *
    Scheduler:
    *
    {@code startWith} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param t1 * the first item to emit * @param t2 @@ -8838,13 +10142,16 @@ public final Observable startWith(T t1, T t2, T t3, T t4, T t5, T t6, T t7, T } /** - * Subscribes to an Observable and ignores {@code onNext} and {@code onCompleted} emissions. If an {@code onError} emission arrives then - * {@link OnErrorNotImplementedException} is thrown. + * Subscribes to an Observable and ignores {@code onNext} and {@code onCompleted} emissions. If an {@code onError} emission arrives then + * {@link OnErrorNotImplementedException} is thrown. *
    + *
    Backpressure:
    + *
    The operator consumes the source {@code Observable} in an unbounded manner (i.e., no + * backpressure is applied to it).
    *
    Scheduler:
    *
    {@code subscribe} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @return a {@link Subscription} reference with which the {@link Observer} can stop receiving items before * the Observable has finished sending them * @throws OnErrorNotImplementedException @@ -8861,10 +10168,13 @@ public final Subscription subscribe() { /** * Subscribes to an Observable and provides a callback to handle the items it emits. *
    + *
    Backpressure:
    + *
    The operator consumes the source {@code Observable} in an unbounded manner (i.e., no + * backpressure is applied to it).
    *
    Scheduler:
    *
    {@code subscribe} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param onNext * the {@code Action1} you have designed to accept emissions from the Observable * @return a {@link Subscription} reference with which the {@link Observer} can stop receiving items before @@ -8889,10 +10199,13 @@ public final Subscription subscribe(final Action1 onNext) { * Subscribes to an Observable and provides callbacks to handle the items it emits and any error * notification it issues. *
    + *
    Backpressure:
    + *
    The operator consumes the source {@code Observable} in an unbounded manner (i.e., no + * backpressure is applied to it).
    *
    Scheduler:
    *
    {@code subscribe} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param onNext * the {@code Action1} you have designed to accept emissions from the Observable * @param onError @@ -8921,10 +10234,13 @@ public final Subscription subscribe(final Action1 onNext, final Actio * Subscribes to an Observable and provides callbacks to handle the items it emits and any error or * completion notification it issues. *
    + *
    Backpressure:
    + *
    The operator consumes the source {@code Observable} in an unbounded manner (i.e., no + * backpressure is applied to it).
    *
    Scheduler:
    *
    {@code subscribe} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param onNext * the {@code Action1} you have designed to accept emissions from the Observable * @param onError @@ -8959,6 +10275,9 @@ public final Subscription subscribe(final Action1 onNext, final Actio * Subscribes to an Observable and provides an Observer that implements functions to handle the items the * Observable emits and any error or completion notification it issues. *
    + *
    Backpressure:
    + *
    The operator consumes the source {@code Observable} in an unbounded manner (i.e., no + * backpressure is applied to it).
    *
    Scheduler:
    *
    {@code subscribe} does not operate by default on a particular {@link Scheduler}.
    *
    @@ -8973,6 +10292,9 @@ public final Subscription subscribe(final Observer observer) { if (observer instanceof Subscriber) { return subscribe((Subscriber)observer); } + if (observer == null) { + throw new NullPointerException("observer is null"); + } return subscribe(new ObserverSubscriber(observer)); } @@ -8985,10 +10307,13 @@ public final Subscription subscribe(final Observer observer) { * the Observable contract and other * functionality. *
    + *
    Backpressure:
    + *
    The operator doesn't interfere with backpressure which is determined by the source {@code Observable}'s backpressure + * behavior.
    *
    Scheduler:
    *
    {@code unsafeSubscribe} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param subscriber * the Subscriber that will handle emissions and notifications from the Observable * @return a {@link Subscription} reference with which the {@link Subscriber} can stop receiving items @@ -9011,11 +10336,11 @@ public final Subscription unsafeSubscribe(Subscriber subscriber) { Exceptions.throwIfFatal(e2); // if this happens it means the onError itself failed (perhaps an invalid function implementation) // so we are unable to propagate the error correctly and will just throw - RuntimeException r = new RuntimeException("Error occurred attempting to subscribe [" + e.getMessage() + "] and then again while trying to pass to onError.", e2); + RuntimeException r = new OnErrorFailedException("Error occurred attempting to subscribe [" + e.getMessage() + "] and then again while trying to pass to onError.", e2); // TODO could the hook be the cause of the error in the on error handling. RxJavaHooks.onObservableError(r); // TODO why aren't we throwing the hook's return value. - throw r; + throw r; // NOPMD } return Subscriptions.unsubscribed(); } @@ -9040,10 +10365,13 @@ public final Subscription unsafeSubscribe(Subscriber subscriber) { * For more information see the * ReactiveX documentation. *
    + *
    Backpressure:
    + *
    The operator doesn't interfere with backpressure which is determined by the source {@code Observable}'s backpressure + * behavior.
    *
    Scheduler:
    *
    {@code subscribe} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param subscriber * the {@link Subscriber} that will handle emissions and notifications from the Observable * @return a {@link Subscription} reference with which Subscribers that are {@link Observer}s can @@ -9061,7 +10389,7 @@ public final Subscription unsafeSubscribe(Subscriber subscriber) { public final Subscription subscribe(Subscriber subscriber) { return Observable.subscribe(subscriber, this); } - + static Subscription subscribe(Subscriber subscriber, Observable observable) { // validate and proceed if (subscriber == null) { @@ -9074,10 +10402,10 @@ static Subscription subscribe(Subscriber subscriber, Observable Subscription subscribe(Subscriber subscriber, Observable(subscriber); } - // The code below is exactly the same an unsafeSubscribe but not used because it would + // The code below is exactly the same an unsafeSubscribe but not used because it would // add a significant depth to already huge call stacks. try { // allow the hook to intercept and/or decorate @@ -9099,7 +10427,7 @@ static Subscription subscribe(Subscriber subscriber, Observable Subscription subscribe(Subscriber subscriber, Observable Subscription subscribe(Subscriber subscriber, Observable + * If there is a {@link #create(Action1, rx.Emitter.BackpressureMode)} type source up in the + * chain, it is recommended to use {@code subscribeOn(scheduler, false)} instead + * to avoid same-pool deadlock because requests pile up behind a eager/blocking emitter. + *

    * *

    + *
    Backpressure:
    + *
    The operator doesn't interfere with backpressure amount which is determined by the source {@code Observable}'s backpressure + * behavior. However, the upstream is requested from the given scheduler thread.
    *
    Scheduler:
    *
    you specify which {@link Scheduler} this operator will use
    *
    - * + * * @param scheduler * the {@link Scheduler} to perform subscription actions on * @return the source Observable modified so that its subscriptions happen on the @@ -9135,12 +10470,47 @@ static Subscription subscribe(Subscriber subscriber, ObservableReactiveX operators documentation: SubscribeOn * @see RxJava Threading Examples * @see #observeOn + * @see #subscribeOn(Scheduler, boolean) */ public final Observable subscribeOn(Scheduler scheduler) { + return subscribeOn(scheduler, !(this.onSubscribe instanceof OnSubscribeCreate)); + } + + /** + * Asynchronously subscribes Observers to this Observable on the specified {@link Scheduler} and + * optionally reroutes requests from other threads to the same {@link Scheduler} thread. + *

    + * If there is a {@link #create(Action1, rx.Emitter.BackpressureMode)} type source up in the + * chain, it is recommended to have {@code requestOn} false to avoid same-pool deadlock + * because requests pile up behind a eager/blocking emitter. + *

    + * + *

    + *
    Backpressure:
    + *
    The operator doesn't interfere with backpressure amount which is determined by the source {@code Observable}'s backpressure + * behavior. However, the upstream is requested from the given scheduler if requestOn is true.
    + *
    Scheduler:
    + *
    you specify which {@link Scheduler} this operator will use
    + *
    + *

    History: 1.2.7 - 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) + * if false, requests coming from any thread are simply forwarded to + * the upstream on the same thread (weak pipelining) + * @return the source Observable modified so that its subscriptions happen on the + * specified {@link Scheduler} + * @see ReactiveX operators documentation: SubscribeOn + * @see RxJava Threading Examples + * @see #observeOn + * @see #subscribeOn(Scheduler) + * @since 1.3 + */ + public final Observable subscribeOn(Scheduler scheduler, boolean requestOn) { if (this instanceof ScalarSynchronousObservable) { return ((ScalarSynchronousObservable)this).scalarScheduleOn(scheduler); } - return create(new OperatorSubscribeOn(this, scheduler)); + return unsafeCreate(new OperatorSubscribeOn(this, scheduler, requestOn)); } /** @@ -9153,10 +10523,15 @@ public final Observable subscribeOn(Scheduler scheduler) { *

    * *

    + *
    Backpressure:
    + *
    The operator honors backpressure from downstream. The outer {@code Observable} is consumed in an + * unbounded manner (i.e., without backpressure) and the inner {@code Observable}s are expected to honor + * backpressure but it is not enforced; the operator won't signal a {@code MissingBackpressureException} + * but the violation may lead to {@code OutOfMemoryError} due to internal buffer bloat.
    *
    Scheduler:
    *
    {@code switchMap} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param the element type of the inner Observables and the output * @param func * a function that, when applied to an item emitted by the source Observable, returns an @@ -9179,26 +10554,29 @@ public final Observable switchMap(Func1 * *
    + *
    Backpressure:
    + *
    The operator honors backpressure from downstream. The outer {@code Observable} is consumed in an + * unbounded manner (i.e., without backpressure) and the inner {@code Observable}s are expected to honor + * backpressure but it is not enforced; the operator won't signal a {@code MissingBackpressureException} + * but the violation may lead to {@code OutOfMemoryError} due to internal buffer bloat.
    *
    Scheduler:
    *
    {@code switchMap} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param the element type of the inner Observables and the output * @param func * a function that, when applied to an item emitted by the source Observable, returns an * Observable * @return an Observable that emits the items emitted by the Observable returned from applying {@code func} to the most recently emitted item emitted by the source Observable * @see ReactiveX operators documentation: FlatMap - * @Experimental The behavior of this can change at any time. - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Experimental public final Observable switchMapDelayError(Func1> func) { return switchOnNextDelayError(map(func)); } /** - * Returns an Observable that emits only the first {@code count} items emitted by the source Observable. If the source emits fewer than + * Returns an Observable that emits only the first {@code count} items emitted by the source Observable. If the source emits fewer than * {@code count} items then all of its items are emitted. *

    * @@ -9207,10 +10585,14 @@ public final Observable switchMapDelayError(Func1 + *

    Backpressure:
    + *
    The operator doesn't interfere with backpressure which is determined by the source {@code Observable}'s backpressure + * behavior in case the first request is smaller than the {@code count}. Otherwise, the source {@code Observable} + * is consumed in an unbounded manner (i.e., without applying backpressure to it).
    *
    Scheduler:
    *
    This version of {@code take} does not operate by default on a particular {@link Scheduler}.
    * - * + * * @param count * the maximum number of items to emit * @return an Observable that emits only the first {@code count} items emitted by the source Observable, or @@ -9225,12 +10607,18 @@ public final Observable take(final int count) { * Returns an Observable that emits those items emitted by source Observable before a specified time runs * out. *

    + * If time runs out before the {@code Observable} completes normally, the {@code onComplete} event will be + * signaled on the default {@code computation} {@link Scheduler}. + *

    * *

    + *
    Backpressure:
    + *
    The operator doesn't interfere with backpressure which is determined by the source {@code Observable}'s backpressure + * behavior.
    *
    Scheduler:
    *
    This version of {@code take} operates by default on the {@code computation} {@link Scheduler}.
    *
    - * + * * @param time * the length of the time window * @param unit @@ -9246,12 +10634,18 @@ public final Observable take(long time, TimeUnit unit) { * Returns an Observable that emits those items emitted by source Observable before a specified time (on a * specified Scheduler) runs out. *

    + * If time runs out before the {@code Observable} completes normally, the {@code onComplete} event will be + * signaled on the provided {@link Scheduler}. + *

    * *

    + *
    Backpressure:
    + *
    The operator doesn't interfere with backpressure which is determined by the source {@code Observable}'s backpressure + * behavior.
    *
    Scheduler:
    *
    you specify which {@link Scheduler} this operator will use
    *
    - * + * * @param time * the length of the time window * @param unit @@ -9272,10 +10666,13 @@ public final Observable take(long time, TimeUnit unit, Scheduler scheduler) { *

    * *

    + *
    Backpressure:
    + *
    The operator doesn't interfere with backpressure which is determined by the source {@code Observable}'s backpressure + * behavior.
    *
    Scheduler:
    *
    {@code takeFirst} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param predicate * the condition any item emitted by the source Observable has to satisfy * @return an Observable that emits only the very first item emitted by the source Observable that satisfies @@ -9288,15 +10685,18 @@ public final Observable takeFirst(Func1 predicate) { } /** - * Returns an Observable that emits at most the last {@code count} items emitted by the source Observable. If the source emits fewer than + * Returns an Observable that emits at most the last {@code count} items emitted by the source Observable. If the source emits fewer than * {@code count} items then all of its items are emitted. *

    * *

    + *
    Backpressure:
    + *
    The operator honors backpressure from downstream if the {@code count} is non-zero; ignores + * backpressure if the {@code count} is zero as it doesn't signal any values.
    *
    Scheduler:
    *
    This version of {@code takeLast} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param count * the maximum number of items to emit from the end of the sequence of items emitted by the source * Observable @@ -9306,24 +10706,28 @@ public final Observable takeFirst(Func1 predicate) { * @see ReactiveX operators documentation: TakeLast */ public final Observable takeLast(final int count) { - if (count == 0) + if (count == 0) { return ignoreElements(); - else if (count == 1 ) - return lift(OperatorTakeLastOne.instance()); - else + } else if (count == 1) { + return unsafeCreate(new OnSubscribeTakeLastOne(this)); + } else { return lift(new OperatorTakeLast(count)); + } } /** * Returns an Observable that emits at most a specified number of items from the source Observable that were - * emitted in a specified window of time before the Observable completed. + * emitted in a specified window of time before the Observable completed. *

    * *

    + *
    Backpressure:
    + *
    The operator honors backpressure from downstream and consumes the source {@code Observable} in an + * unbounded manner (i.e., no backpressure is applied to it).
    *
    Scheduler:
    *
    This version of {@code takeLast} operates by default on the {@code computation} {@link Scheduler}.
    *
    - * + * * @param count * the maximum number of items to emit * @param time @@ -9345,10 +10749,13 @@ public final Observable takeLast(int count, long time, TimeUnit unit) { *

    * *

    + *
    Backpressure:
    + *
    The operator honors backpressure from downstream and consumes the source {@code Observable} in an + * unbounded manner (i.e., no backpressure is applied to it).
    *
    Scheduler:
    *
    you specify which {@link Scheduler} this operator will use
    *
    - * + * * @param count * the maximum number of items to emit * @param time @@ -9374,10 +10781,16 @@ public final Observable takeLast(int count, long time, TimeUnit unit, Schedul *

    * *

    + *
    Backpressure:
    + *
    The operator honors backpressure from downstream and consumes the source {@code Observable} in an + * unbounded manner (i.e., no backpressure is applied to it) but note that this may + * lead to {@code OutOfMemoryError} due to internal buffer bloat. + * Consider using {@link #takeLast(int, long, TimeUnit)} in this case.
    + * behavior. *
    Scheduler:
    *
    This version of {@code takeLast} operates by default on the {@code computation} {@link Scheduler}.
    *
    - * + * * @param time * the length of the time window * @param unit @@ -9397,10 +10810,15 @@ public final Observable takeLast(long time, TimeUnit unit) { *

    * *

    + *
    Backpressure:
    + *
    The operator honors backpressure from downstream and consumes the source {@code Observable} in an + * unbounded manner (i.e., no backpressure is applied to it) but note that this may + * lead to {@code OutOfMemoryError} due to internal buffer bloat. + * Consider using {@link #takeLast(int, long, TimeUnit, Scheduler)} in this case.
    *
    Scheduler:
    *
    you specify which {@link Scheduler} this operator will use
    *
    - * + * * @param time * the length of the time window * @param unit @@ -9422,10 +10840,13 @@ public final Observable takeLast(long time, TimeUnit unit, Scheduler schedule *

    * *

    + *
    Backpressure:
    + *
    The operator honors backpressure from downstream and consumes the source {@code Observable} in an + * unbounded manner (i.e., without applying backpressure to it).
    *
    Scheduler:
    *
    This version of {@code takeLastBuffer} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param count * the maximum number of items to emit in the list * @return an Observable that emits a single list containing at most the last {@code count} elements emitted by the @@ -9442,10 +10863,13 @@ public final Observable> takeLastBuffer(int count) { *

    * *

    + *
    Backpressure:
    + *
    The operator honors backpressure from downstream and consumes the source {@code Observable} in an + * unbounded manner (i.e., without applying backpressure to it).
    *
    Scheduler:
    *
    This version of {@code takeLastBuffer} operates by default on the {@code computation} {@link Scheduler}.
    *
    - * + * * @param count * the maximum number of items to emit * @param time @@ -9468,10 +10892,13 @@ public final Observable> takeLastBuffer(int count, long time, TimeUnit u *

    * *

    + *
    Backpressure:
    + *
    The operator honors backpressure from downstream and consumes the source {@code Observable} in an + * unbounded manner (i.e., without applying backpressure to it).
    *
    Scheduler:
    *
    you specify which {@link Scheduler} this operator will use
    *
    - * + * * @param count * the maximum number of items to emit * @param time @@ -9495,10 +10922,13 @@ public final Observable> takeLastBuffer(int count, long time, TimeUnit u *

    * *

    + *
    Backpressure:
    + *
    The operator honors backpressure from downstream and consumes the source {@code Observable} in an + * unbounded manner (i.e., without applying backpressure to it).
    *
    Scheduler:
    *
    This version of {@code takeLastBuffer} operates by default on the {@code computation} {@link Scheduler}.
    *
    - * + * * @param time * the length of the time window * @param unit @@ -9518,10 +10948,13 @@ public final Observable> takeLastBuffer(long time, TimeUnit unit) { *

    * *

    + *
    Backpressure:
    + *
    The operator honors backpressure from downstream and consumes the source {@code Observable} in an + * unbounded manner (i.e., without applying backpressure to it).
    *
    Scheduler:
    *
    you specify which {@link Scheduler} this operator will use
    *
    - * + * * @param time * the length of the time window * @param unit @@ -9543,10 +10976,13 @@ public final Observable> takeLastBuffer(long time, TimeUnit unit, Schedu *

    * *

    + *
    Backpressure:
    + *
    The operator doesn't interfere with backpressure which is determined by the source {@code Observable}'s backpressure + * behavior.
    *
    Scheduler:
    *
    {@code takeUntil} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param other * the Observable whose first emitted item will cause {@code takeUntil} to stop emitting items * from the source Observable @@ -9565,10 +11001,13 @@ public final Observable takeUntil(Observable other) { *

    * *

    + *
    Backpressure:
    + *
    The operator doesn't interfere with backpressure which is determined by the source {@code Observable}'s backpressure + * behavior.
    *
    Scheduler:
    *
    {@code takeWhile} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param predicate * a function that evaluates an item emitted by the source Observable and returns a Boolean * @return an Observable that emits the items from the source Observable so long as each item satisfies the @@ -9582,13 +11021,13 @@ public final Observable takeWhile(final Func1 predicate) /** * Returns an Observable that emits items emitted by the source Observable, checks the specified predicate - * for each item, and then completes if the condition is satisfied. + * for each item, and then completes when the condition is satisfied. *

    * *

    * The difference between this operator and {@link #takeWhile(Func1)} is that here, the condition is * evaluated after the item is emitted. - * + * *

    *
    Backpressure:
    *
    The operator is a pass-through for backpressure; the backpressure behavior is determined by the upstream @@ -9596,11 +11035,11 @@ public final Observable takeWhile(final Func1 predicate) *
    Scheduler:
    *
    {@code takeWhile} does not operate by default on a particular {@link Scheduler}.
    *
    - * - * @param stopPredicate + * + * @param stopPredicate * a function that evaluates an item emitted by the source Observable and returns a Boolean * @return an Observable that first emits items emitted by the source Observable, checks the specified - * condition after each item, and then completes if the condition is satisfied. + * condition after each item, and then completes when the condition is satisfied. * @see ReactiveX operators documentation: TakeUntil * @see Observable#takeWhile(Func1) * @since 1.1.0 @@ -9608,7 +11047,7 @@ public final Observable takeWhile(final Func1 predicate) public final Observable takeUntil(final Func1 stopPredicate) { return lift(new OperatorTakeUntilPredicate(stopPredicate)); } - + /** * Returns an Observable that emits only the first item emitted by the source Observable during sequential * time windows of a specified duration. @@ -9618,12 +11057,12 @@ public final Observable takeUntil(final Func1 stopPredica *

    * *

    - *
    Backpressure Support:
    + *
    Backpressure:
    *
    This operator does not support backpressure as it uses time to control data flow.
    *
    Scheduler:
    *
    {@code throttleFirst} operates by default on the {@code computation} {@link Scheduler}.
    *
    - * + * * @param windowDuration * time to wait before emitting another item after emitting the last item * @param unit @@ -9645,12 +11084,12 @@ public final Observable throttleFirst(long windowDuration, TimeUnit unit) { *

    * *

    - *
    Backpressure Support:
    + *
    Backpressure:
    *
    This operator does not support backpressure as it uses time to control data flow.
    *
    Scheduler:
    *
    you specify which {@link Scheduler} this operator will use
    *
    - * + * * @param skipDuration * time to wait before emitting another item after emitting the last item * @param unit @@ -9675,12 +11114,12 @@ public final Observable throttleFirst(long skipDuration, TimeUnit unit, Sched *

    * *

    - *
    Backpressure Support:
    + *
    Backpressure:
    *
    This operator does not support backpressure as it uses time to control data flow.
    *
    Scheduler:
    *
    {@code throttleLast} operates by default on the {@code computation} {@link Scheduler}.
    *
    - * + * * @param intervalDuration * duration of windows within which the last item emitted by the source Observable will be * emitted @@ -9704,12 +11143,12 @@ public final Observable throttleLast(long intervalDuration, TimeUnit unit) { *

    * *

    - *
    Backpressure Support:
    + *
    Backpressure:
    *
    This operator does not support backpressure as it uses time to control data flow.
    *
    Scheduler:
    *
    you specify which {@link Scheduler} this operator will use
    *
    - * + * * @param intervalDuration * duration of windows within which the last item emitted by the source Observable will be * emitted @@ -9744,12 +11183,12 @@ public final Observable throttleLast(long intervalDuration, TimeUnit unit, Sc *
  • Javascript - don't spam your server: debounce and throttle
  • * *
    - *
    Backpressure Support:
    + *
    Backpressure:
    *
    This operator does not support backpressure as it uses time to control data flow.
    *
    Scheduler:
    *
    {@code throttleWithTimeout} operates by default on the {@code computation} {@link Scheduler}.
    *
    - * + * * @param timeout * the length of the window of time that must pass after the emission of an item from the source * Observable in which that Observable emits no items in order for the item to be emitted by the @@ -9783,12 +11222,12 @@ public final Observable throttleWithTimeout(long timeout, TimeUnit unit) { *
  • Javascript - don't spam your server: debounce and throttle
  • * *
    - *
    Backpressure Support:
    + *
    Backpressure:
    *
    This operator does not support backpressure as it uses time to control data flow.
    *
    Scheduler:
    *
    you specify which {@link Scheduler} this operator will use
    *
    - * + * * @param timeout * the length of the window of time that must pass after the emission of an item from the source * Observable in which that Observable emits no items in order for the item to be emitted by the @@ -9813,15 +11252,19 @@ public final Observable throttleWithTimeout(long timeout, TimeUnit unit, Sche *

    * *

    + *
    Backpressure:
    + *
    The operator doesn't interfere with backpressure which is determined by the source {@code Observable}'s backpressure + * behavior.
    *
    Scheduler:
    - *
    {@code timeInterval} operates by default on the {@code immediate} {@link Scheduler}.
    + *
    {@code timeInterval} does not operate on any particular scheduler but uses the current time + * from the {@code computation} {@link Scheduler}.
    *
    - * + * * @return an Observable that emits time interval information items * @see ReactiveX operators documentation: TimeInterval */ public final Observable> timeInterval() { - return timeInterval(Schedulers.immediate()); + return timeInterval(Schedulers.computation()); } /** @@ -9830,10 +11273,14 @@ public final Observable> timeInterval() { *

    * *

    + *
    Backpressure:
    + *
    The operator doesn't interfere with backpressure which is determined by the source {@code Observable}'s backpressure + * behavior.
    *
    Scheduler:
    - *
    you specify which {@link Scheduler} this operator will use
    + *
    The operator does not operate on any particular scheduler but uses the current time + * from the specified {@link Scheduler}.
    *
    - * + * * @param scheduler * the {@link Scheduler} used to compute time intervals * @return an Observable that emits time interval information items @@ -9850,10 +11297,14 @@ public final Observable> timeInterval(Scheduler scheduler) { *

    * *

    + *
    Backpressure:
    + *
    The operator honors backpressure from downstream. Both this and the returned {@code Observable}s + * are expected to honor backpressure as well. If any of then violates this rule, it may throw an + * {@code IllegalStateException} when the {@code Observable} completes.
    *
    Scheduler:
    *
    This version of {@code timeout} operates by default on the {@code immediate} {@link Scheduler}.
    *
    - * + * * @param * the first timeout value type (ignored) * @param @@ -9881,10 +11332,15 @@ public final Observable timeout(Func0> firstTi *

    * *

    + *
    Backpressure:
    + *
    The operator honors backpressure from downstream. The {@code Observable} + * sources are expected to honor backpressure as well. + * If any of the source {@code Observable}s violate this, it may throw an + * {@code IllegalStateException} when the source {@code Observable} completes.
    *
    Scheduler:
    *
    This version of {@code timeout} operates by default on the {@code immediate} {@link Scheduler}.
    *
    - * + * * @param * the first timeout value type (ignored) * @param @@ -9905,11 +11361,13 @@ public final Observable timeout(Func0> firstTi * if {@code timeoutSelector} is null * @see ReactiveX operators documentation: Timeout */ + @SuppressWarnings("unchecked") public final Observable timeout(Func0> firstTimeoutSelector, Func1> timeoutSelector, Observable other) { if (timeoutSelector == null) { throw new NullPointerException("timeoutSelector is null"); } - return lift(new OperatorTimeoutWithSelector(firstTimeoutSelector, timeoutSelector, other)); + return unsafeCreate(new OnSubscribeTimeoutSelectorWithFallback(this, + firstTimeoutSelector != null ? defer((Func0>)firstTimeoutSelector) : null, timeoutSelector, other)); } /** @@ -9922,10 +11380,15 @@ public final Observable timeout(Func0> firstTi *

    * Note: The arrival of the first source item is never timed out. *

    + *
    Backpressure:
    + *
    The operator honors backpressure from downstream. The {@code Observable} + * sources are expected to honor backpressure as well. + * If any of the source {@code Observable}s violate this, it may throw an + * {@code IllegalStateException} when the source {@code Observable} completes.
    *
    Scheduler:
    *
    This version of {@code timeout} operates by default on the {@code immediate} {@link Scheduler}.
    *
    - * + * * @param * the timeout value type (ignored) * @param timeoutSelector @@ -9950,10 +11413,15 @@ public final Observable timeout(Func1> *

    * Note: The arrival of the first source item is never timed out. *

    + *
    Backpressure:
    + *
    The operator honors backpressure from downstream. The {@code Observable} + * sources are expected to honor backpressure as well. + * If any of the source {@code Observable}s violate this, it may throw an + * {@code IllegalStateException} when the source {@code Observable} completes.
    *
    Scheduler:
    *
    This version of {@code timeout} operates by default on the {@code immediate} {@link Scheduler}.
    *
    - * + * * @param * the timeout value type (ignored) * @param timeoutSelector @@ -9977,10 +11445,13 @@ public final Observable timeout(Func1> *

    * *

    + *
    Backpressure:
    + *
    The operator doesn't interfere with backpressure which is determined by the source {@code Observable}'s backpressure + * behavior.
    *
    Scheduler:
    *
    This version of {@code timeout} operates by default on the {@code computation} {@link Scheduler}.
    *
    - * + * * @param timeout * maximum duration between emitted items before a timeout occurs * @param timeUnit @@ -10000,10 +11471,15 @@ public final Observable timeout(long timeout, TimeUnit timeUnit) { *

    * *

    + *
    Backpressure:
    + *
    The operator honors backpressure from downstream. The {@code Observable} + * sources are expected to honor backpressure as well. + * If any of the source {@code Observable}s violate this, it may throw an + * {@code IllegalStateException} when the source {@code Observable} completes.
    *
    Scheduler:
    *
    This version of {@code timeout} operates by default on the {@code computation} {@link Scheduler}.
    *
    - * + * * @param timeout * maximum duration between items before a timeout occurs * @param timeUnit @@ -10024,10 +11500,15 @@ public final Observable timeout(long timeout, TimeUnit timeUnit, Observable * *
    + *
    Backpressure:
    + *
    The operator honors backpressure from downstream. The {@code Observable} + * sources are expected to honor backpressure as well. + * If any of the source {@code Observable}s violate this, it may throw an + * {@code IllegalStateException} when the source {@code Observable} completes.
    *
    Scheduler:
    *
    you specify which {@link Scheduler} this operator will use
    *
    - * + * * @param timeout * maximum duration between items before a timeout occurs * @param timeUnit @@ -10041,7 +11522,7 @@ public final Observable timeout(long timeout, TimeUnit timeUnit, ObservableReactiveX operators documentation: Timeout */ public final Observable timeout(long timeout, TimeUnit timeUnit, Observable other, Scheduler scheduler) { - return lift(new OperatorTimeout(timeout, timeUnit, other, scheduler)); + return unsafeCreate(new OnSubscribeTimeoutTimedWithFallback(this, timeout, timeUnit, scheduler, other)); } /** @@ -10052,10 +11533,13 @@ public final Observable timeout(long timeout, TimeUnit timeUnit, Observable * *
    + *
    Backpressure:
    + *
    The operator doesn't interfere with backpressure which is determined by the source {@code Observable}'s backpressure + * behavior.
    *
    Scheduler:
    *
    you specify which {@link Scheduler} this operator will use
    *
    - * + * * @param timeout * maximum duration between items before a timeout occurs * @param timeUnit @@ -10076,15 +11560,19 @@ public final Observable timeout(long timeout, TimeUnit timeUnit, Scheduler sc *

    * *

    + *
    Backpressure:
    + *
    The operator doesn't interfere with backpressure which is determined by the source {@code Observable}'s backpressure + * behavior.
    *
    Scheduler:
    - *
    {@code timestamp} operates by default on the {@code immediate} {@link Scheduler}.
    + *
    {@code timestamp} does not operate on any particular scheduler but uses the current time + * from the {@code computation} {@link Scheduler}.
    *
    - * + * * @return an Observable that emits timestamped items from the source Observable * @see ReactiveX operators documentation: Timestamp */ public final Observable> timestamp() { - return timestamp(Schedulers.immediate()); + return timestamp(Schedulers.computation()); } /** @@ -10093,10 +11581,14 @@ public final Observable> timestamp() { *

    * *

    + *
    Backpressure:
    + *
    The operator doesn't interfere with backpressure which is determined by the source {@code Observable}'s backpressure + * behavior.
    *
    Scheduler:
    - *
    you specify which {@link Scheduler} this operator will use
    + *
    The operator does not operate on any particular scheduler but uses the current time + * from the specified {@link Scheduler}.
    *
    - * + * * @param scheduler * the {@link Scheduler} to use as a time source * @return an Observable that emits timestamped items from the source Observable with timestamps provided by @@ -10110,6 +11602,9 @@ public final Observable> timestamp(Scheduler scheduler) { /** * Converts an Observable into a {@link BlockingObservable} (an Observable with blocking operators). *
    + *
    Backpressure:
    + *
    The operator doesn't interfere with backpressure which is determined by the source {@code Observable}'s backpressure + * behavior.
    *
    Scheduler:
    *
    {@code toBlocking} does not operate by default on a particular {@link Scheduler}.
    *
    @@ -10136,12 +11631,13 @@ public final BlockingObservable toBlocking() { * Be careful not to use this operator on Observables that emit infinite or very large numbers of items, as * you do not have the option to unsubscribe. *
    - *
    Backpressure Support:
    - *
    The operator buffers everything from its upstream but it only emits the aggregated list when the downstream requests at least one item.
    + *
    Backpressure:
    + *
    The operator honors backpressure from downstream and consumes the source {@code Observable} in an + * unbounded manner (i.e., without applying backpressure to it).
    *
    Scheduler:
    *
    {@code toList} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @return an Observable that emits a single item: a List containing all of the items emitted by the source * Observable * @see ReactiveX operators documentation: To @@ -10158,12 +11654,13 @@ public final Observable> toList() { *

    * If more than one source item maps to the same key, the HashMap will contain the latest of those items. *

    - *
    Backpressure Support:
    - *
    This operator does not support backpressure as by intent it is requesting and buffering everything.
    + *
    Backpressure:
    + *
    The operator honors backpressure from downstream and consumes the source {@code Observable} in an + * unbounded manner (i.e., without applying backpressure to it).
    *
    Scheduler:
    *
    {@code toMap} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param the key type of the Map * @param keySelector * the function that extracts the key from a source item to be used in the HashMap @@ -10172,7 +11669,7 @@ public final Observable> toList() { * @see ReactiveX operators documentation: To */ public final Observable> toMap(Func1 keySelector) { - return lift(new OperatorToMap(keySelector, UtilityFunctions.identity())); + return unsafeCreate(new OnSubscribeToMap(this, keySelector, UtilityFunctions.identity())); } /** @@ -10184,12 +11681,13 @@ public final Observable> toMap(Func1 keySe * 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. *
    - *
    Backpressure Support:
    - *
    This operator does not support backpressure as by intent it is requesting and buffering everything.
    + *
    Backpressure:
    + *
    The operator honors backpressure from downstream and consumes the source {@code Observable} in an + * unbounded manner (i.e., without applying backpressure to it).
    *
    Scheduler:
    *
    {@code toMap} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param the key type of the Map * @param the value type of the Map * @param keySelector @@ -10201,7 +11699,7 @@ public final Observable> toMap(Func1 keySe * @see ReactiveX operators documentation: To */ public final Observable> toMap(Func1 keySelector, Func1 valueSelector) { - return lift(new OperatorToMap(keySelector, valueSelector)); + return unsafeCreate(new OnSubscribeToMap(this, keySelector, valueSelector)); } /** @@ -10210,12 +11708,13 @@ public final Observable> toMap(Func1 ke *

    * *

    - *
    Backpressure Support:
    - *
    This operator does not support backpressure as by intent it is requesting and buffering everything.
    + *
    Backpressure:
    + *
    The operator honors backpressure from downstream and consumes the source {@code Observable} in an + * unbounded manner (i.e., without applying backpressure to it).
    *
    Scheduler:
    *
    {@code toMap} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param the key type of the Map * @param the value type of the Map * @param keySelector @@ -10229,7 +11728,7 @@ public final Observable> toMap(Func1 ke * @see ReactiveX operators documentation: To */ public final Observable> toMap(Func1 keySelector, Func1 valueSelector, Func0> mapFactory) { - return lift(new OperatorToMap(keySelector, valueSelector, mapFactory)); + return unsafeCreate(new OnSubscribeToMap(this, keySelector, valueSelector, mapFactory)); } /** @@ -10238,12 +11737,12 @@ public final Observable> toMap(Func1 ke *

    * *

    - *
    Backpressure Support:
    + *
    Backpressure:
    *
    This operator does not support backpressure as by intent it is requesting and buffering everything.
    *
    Scheduler:
    *
    {@code toMultiMap} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param the key type of the Map * @param keySelector * the function that extracts the key from the source items to be used as key in the HashMap @@ -10252,7 +11751,7 @@ public final Observable> toMap(Func1 ke * @see ReactiveX operators documentation: To */ public final Observable>> toMultimap(Func1 keySelector) { - return lift(new OperatorToMultimap(keySelector, UtilityFunctions.identity())); + return unsafeCreate(new OnSubscribeToMultimap(this, keySelector, UtilityFunctions.identity())); } /** @@ -10262,12 +11761,13 @@ public final Observable>> toMultimap(Func1 * *
    - *
    Backpressure Support:
    - *
    This operator does not support backpressure as by intent it is requesting and buffering everything.
    + *
    Backpressure:
    + *
    The operator honors backpressure from downstream and consumes the source {@code Observable} in an + * unbounded manner (i.e., without applying backpressure to it).
    *
    Scheduler:
    *
    {@code toMultiMap} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param the key type of the Map * @param the value type of the Map * @param keySelector @@ -10279,7 +11779,7 @@ public final Observable>> toMultimap(Func1ReactiveX operators documentation: To */ public final Observable>> toMultimap(Func1 keySelector, Func1 valueSelector) { - return lift(new OperatorToMultimap(keySelector, valueSelector)); + return unsafeCreate(new OnSubscribeToMultimap(this, keySelector, valueSelector)); } /** @@ -10289,12 +11789,13 @@ public final Observable>> toMultimap(Func1 * *
    - *
    Backpressure Support:
    - *
    This operator does not support backpressure as by intent it is requesting and buffering everything.
    + *
    Backpressure:
    + *
    The operator honors backpressure from downstream and consumes the source {@code Observable} in an + * unbounded manner (i.e., without applying backpressure to it).
    *
    Scheduler:
    *
    {@code toMultiMap} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param the key type of the Map * @param the value type of the Map * @param keySelector @@ -10308,7 +11809,7 @@ public final Observable>> toMultimap(Func1ReactiveX operators documentation: To */ public final Observable>> toMultimap(Func1 keySelector, Func1 valueSelector, Func0>> mapFactory) { - return lift(new OperatorToMultimap(keySelector, valueSelector, mapFactory)); + return unsafeCreate(new OnSubscribeToMultimap(this, keySelector, valueSelector, mapFactory)); } /** @@ -10318,12 +11819,13 @@ public final Observable>> toMultimap(Func1 * *
    - *
    Backpressure Support:
    - *
    This operator does not support backpressure as by intent it is requesting and buffering everything.
    + *
    Backpressure:
    + *
    The operator honors backpressure from downstream and consumes the source {@code Observable} in an + * unbounded manner (i.e., without applying backpressure to it).
    *
    Scheduler:
    *
    {@code toMultiMap} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param the key type of the Map * @param the value type of the Map * @param keySelector @@ -10339,7 +11841,7 @@ public final Observable>> toMultimap(Func1ReactiveX operators documentation: To */ public final Observable>> toMultimap(Func1 keySelector, Func1 valueSelector, Func0>> mapFactory, Func1> collectionFactory) { - return lift(new OperatorToMultimap(keySelector, valueSelector, mapFactory, collectionFactory)); + return unsafeCreate(new OnSubscribeToMultimap(this, keySelector, valueSelector, mapFactory, collectionFactory)); } /** @@ -10349,12 +11851,13 @@ public final Observable>> toMultimap(Func1 * *
    - *
    Backpressure Support:
    - *
    The operator buffers everything from its upstream but it only emits the sorted list when the downstream requests at least one item.
    + *
    Backpressure:
    + *
    The operator honors backpressure from downstream and consumes the source {@code Observable} in an + * unbounded manner (i.e., without applying backpressure to it).
    *
    Scheduler:
    *
    {@code toSortedList} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @throws ClassCastException * if any item emitted by the Observable does not implement {@link Comparable} with respect to * all other items emitted by the Observable @@ -10372,12 +11875,13 @@ public final Observable> toSortedList() { *

    * *

    - *
    Backpressure Support:
    - *
    The operator buffers everything from its upstream but it only emits the sorted list when the downstream requests at least one item.
    + *
    Backpressure:
    + *
    The operator honors backpressure from downstream and consumes the source {@code Observable} in an + * unbounded manner (i.e., without applying backpressure to it).
    *
    Scheduler:
    *
    {@code toSortedList} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param sortFunction * a function that compares two items emitted by the source Observable and returns an Integer * that indicates their sort order @@ -10396,13 +11900,14 @@ public final Observable> toSortedList(Func2 * *
    - *
    Backpressure Support:
    - *
    The operator buffers everything from its upstream but it only emits the sorted list when the downstream requests at least one item.
    + *
    Backpressure:
    + *
    The operator honors backpressure from downstream and consumes the source {@code Observable} in an + * unbounded manner (i.e., without applying backpressure to it).
    *
    Scheduler:
    *
    {@code toSortedList} does not operate by default on a particular {@link Scheduler}.
    *
    - * - * @param initialCapacity + * + * @param initialCapacity * the initial capacity of the ArrayList used to accumulate items before sorting * @return an Observable that emits a list that contains the items emitted by the source Observable in * sorted order @@ -10410,9 +11915,8 @@ public final Observable> toSortedList(Func2ReactiveX operators documentation: To - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Experimental public final Observable> toSortedList(int initialCapacity) { return lift(new OperatorToObservableSortedList(initialCapacity)); } @@ -10423,35 +11927,89 @@ public final Observable> toSortedList(int initialCapacity) { *

    * *

    - *
    Backpressure Support:
    - *
    The operator buffers everything from its upstream but it only emits the sorted list when the downstream requests at least one item.
    + *
    Backpressure:
    + *
    The operator honors backpressure from downstream and consumes the source {@code Observable} in an + * unbounded manner (i.e., without applying backpressure to it).
    *
    Scheduler:
    *
    {@code toSortedList} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param sortFunction * a function that compares two items emitted by the source Observable and returns an Integer * that indicates their sort order - * @param initialCapacity + * @param initialCapacity * the initial capacity of the ArrayList used to accumulate items before sorting * @return an Observable that emits a list that contains the items emitted by the source Observable in * sorted order * @see ReactiveX operators documentation: To - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Experimental public final Observable> toSortedList(Func2 sortFunction, int initialCapacity) { return lift(new OperatorToObservableSortedList(sortFunction, initialCapacity)); } + /** + * Returns an Observable that emits the events emitted by source Observable, in a + * sorted order. Each item emitted by the Observable must implement {@link Comparable} with respect to all + * other items in the sequence. + * + *

    Note that calling {@code sorted} with long, non-terminating or infinite sources + * might cause {@link OutOfMemoryError} + * + *

    + *
    Backpressure:
    + *
    The operator honors backpressure from downstream and consumes the source {@code Observable} in an + * unbounded manner (i.e., without applying backpressure to it).
    + *
    Scheduler:
    + *
    {@code sorted} does not operate by default on a particular {@link Scheduler}.
    + *
    + * + * @throws ClassCastException + * if any item emitted by the Observable does not implement {@link Comparable} with respect to + * all other items emitted by the Observable + * @return an Observable that emits the items emitted by the source Observable in sorted order + * @since 1.3 + */ + public final Observable sorted() { + return toSortedList().flatMapIterable(UtilityFunctions.>identity()); + } + + /** + * Returns an Observable that emits the events emitted by source Observable, in a + * sorted order based on a specified comparison function. + * + *

    Note that calling {@code sorted} with long, non-terminating or infinite sources + * might cause {@link OutOfMemoryError} + * + *

    + *
    Backpressure:
    + *
    The operator honors backpressure from downstream and consumes the source {@code Observable} in an + * unbounded manner (i.e., without applying backpressure to it).
    + *
    Scheduler:
    + *
    {@code sorted} does not operate by default on a particular {@link Scheduler}.
    + *
    + * + * @param sortFunction + * a function that compares two items emitted by the source Observable and returns an Integer + * that indicates their sort order + * @return an Observable that emits the items emitted by the source Observable in sorted order + * @since 1.3 + */ + public final Observable sorted(Func2 sortFunction) { + return toSortedList(sortFunction).flatMapIterable(UtilityFunctions.>identity()); + } + /** * Modifies the source Observable so that subscribers will unsubscribe from it on a specified * {@link Scheduler}. *
    + *
    Backpressure:
    + *
    The operator doesn't interfere with backpressure which is determined by the source {@code Observable}'s backpressure + * behavior.
    *
    Scheduler:
    *
    you specify which {@link Scheduler} this operator will use
    *
    - * + * * @param scheduler * the {@link Scheduler} to perform unsubscription actions on * @return the source Observable modified so that its unsubscriptions happen on the specified @@ -10467,7 +12025,7 @@ public final Observable unsubscribeOn(Scheduler scheduler) { * function only when the source Observable (this instance) emits an item. *

    * - * + * *

    *
    Backpressure:
    *
    The operator is a pass-through for backpressure: the backpressure support @@ -10476,7 +12034,7 @@ public final Observable unsubscribeOn(Scheduler scheduler) { *
    Scheduler:
    *
    This operator, by default, doesn't run any particular {@link Scheduler}.
    *
    - * + * * @param the element type of the other Observable * @param the result type of the combination * @param other @@ -10487,11 +12045,9 @@ public final Observable unsubscribeOn(Scheduler scheduler) { * @return an Observable that merges the specified Observable into this Observable by using the * {@code resultSelector} function only when the source Observable sequence (this instance) emits an * item - * @Experimental The behavior of this can change at any time. - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 * @see ReactiveX operators documentation: CombineLatest */ - @Experimental public final Observable withLatestFrom(Observable other, Func2 resultSelector) { return lift(new OperatorWithLatestFrom(other, resultSelector)); } @@ -10499,15 +12055,15 @@ public final Observable withLatestFrom(Observable other, /** * Combines the value emission from this Observable with the latest emissions from the * other Observables via a function to produce the output item. - * + * *

    Note that this operator doesn't emit anything until all other sources have produced at * least one value. The resulting emission only happens when this Observable emits (and - * not when any of the other sources emit, unlike combineLatest). + * not when any of the other sources emit, unlike combineLatest). * If a source doesn't produce any value and just completes, the sequence is completed immediately. - * + * *

    - *
    Backpressure Support:
    - *
    This operator is a pass-through for backpressure behavior between this {@code Observable} + *
    Backpressure:
    + *
    This operator is a pass-through for backpressure behavior between the source {@code Observable} * and the downstream Subscriber. The other {@code Observable}s are consumed in an unbounded manner.
    *
    Scheduler:
    *
    This operator does not operate by default on a particular {@link Scheduler}.
    @@ -10520,26 +12076,24 @@ public final Observable withLatestFrom(Observable other, * @param o2 the second other Observable * @param combiner the function called with an array of values from each participating observable * @return the new Observable instance - * @Experimental The behavior of this can change at any time. - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Experimental public final Observable withLatestFrom(Observable o1, Observable o2, Func3 combiner) { - return create(new OperatorWithLatestFromMany(this, new Observable[] { o1, o2 }, null, Functions.fromFunc(combiner))); + return unsafeCreate(new OperatorWithLatestFromMany(this, new Observable[] { o1, o2 }, null, Functions.fromFunc(combiner))); } /** * Combines the value emission from this Observable with the latest emissions from the * other Observables via a function to produce the output item. - * + * *

    Note that this operator doesn't emit anything until all other sources have produced at * least one value. The resulting emission only happens when this Observable emits (and - * not when any of the other sources emit, unlike combineLatest). + * not when any of the other sources emit, unlike combineLatest). * If a source doesn't produce any value and just completes, the sequence is completed immediately. - * + * *

    - *
    Backpressure Support:
    - *
    This operator is a pass-through for backpressure behavior between this {@code Observable} + *
    Backpressure:
    + *
    This operator is a pass-through for backpressure behavior between the source {@code Observable} * and the downstream Subscriber. The other {@code Observable}s are consumed in an unbounded manner.
    *
    Scheduler:
    *
    This operator does not operate by default on a particular {@link Scheduler}.
    @@ -10554,30 +12108,28 @@ public final Observable withLatestFrom(Observable o1, Observa * @param o3 the third other Observable * @param combiner the function called with an array of values from each participating observable * @return the new Observable instance - * @Experimental The behavior of this can change at any time. - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Experimental public final Observable withLatestFrom( - Observable o1, Observable o2, - Observable o3, + Observable o1, Observable o2, + Observable o3, Func4 combiner) { - return create(new OperatorWithLatestFromMany(this, + return unsafeCreate(new OperatorWithLatestFromMany(this, new Observable[] { o1, o2, o3 }, null, Functions.fromFunc(combiner))); } /** * Combines the value emission from this Observable with the latest emissions from the * other Observables via a function to produce the output item. - * + * *

    Note that this operator doesn't emit anything until all other sources have produced at * least one value. The resulting emission only happens when this Observable emits (and - * not when any of the other sources emit, unlike combineLatest). + * not when any of the other sources emit, unlike combineLatest). * If a source doesn't produce any value and just completes, the sequence is completed immediately. - * + * *

    - *
    Backpressure Support:
    - *
    This operator is a pass-through for backpressure behavior between this {@code Observable} + *
    Backpressure:
    + *
    This operator is a pass-through for backpressure behavior between the source {@code Observable} * and the downstream Subscriber. The other {@code Observable}s are consumed in an unbounded manner.
    *
    Scheduler:
    *
    This operator does not operate by default on a particular {@link Scheduler}.
    @@ -10594,29 +12146,27 @@ public final Observable withLatestFrom( * @param o4 the fourth other Observable * @param combiner the function called with an array of values from each participating observable * @return the new Observable instance - * @Experimental The behavior of this can change at any time. - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Experimental public final Observable withLatestFrom( - Observable o1, Observable o2, - Observable o3, Observable o4, + Observable o1, Observable o2, + Observable o3, Observable o4, Func5 combiner) { - return create(new OperatorWithLatestFromMany(this, + return unsafeCreate(new OperatorWithLatestFromMany(this, new Observable[] { o1, o2, o3, o4 }, null, Functions.fromFunc(combiner))); } /** * Combines the value emission from this Observable with the latest emissions from the * other Observables via a function to produce the output item. - * + * *

    Note that this operator doesn't emit anything until all other sources have produced at * least one value. The resulting emission only happens when this Observable emits (and - * not when any of the other sources emit, unlike combineLatest). + * not when any of the other sources emit, unlike combineLatest). * If a source doesn't produce any value and just completes, the sequence is completed immediately. - * + * *

    - *
    Backpressure Support:
    - *
    This operator is a pass-through for backpressure behavior between this {@code Observable} + *
    Backpressure:
    + *
    This operator is a pass-through for backpressure behavior between the source {@code Observable} * and the downstream Subscriber. The other {@code Observable}s are consumed in an unbounded manner.
    *
    Scheduler:
    *
    This operator does not operate by default on a particular {@link Scheduler}.
    @@ -10635,31 +12185,29 @@ public final Observable withLatestFrom( * @param o5 the fifth other Observable * @param combiner the function called with an array of values from each participating observable * @return the new Observable instance - * @Experimental The behavior of this can change at any time. - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Experimental public final Observable withLatestFrom( - Observable o1, Observable o2, - Observable o3, Observable o4, - Observable o5, + Observable o1, Observable o2, + Observable o3, Observable o4, + Observable o5, Func6 combiner) { - return create(new OperatorWithLatestFromMany(this, + return unsafeCreate(new OperatorWithLatestFromMany(this, new Observable[] { o1, o2, o3, o4, o5 }, null, Functions.fromFunc(combiner))); } /** * Combines the value emission from this Observable with the latest emissions from the * other Observables via a function to produce the output item. - * + * *

    Note that this operator doesn't emit anything until all other sources have produced at * least one value. The resulting emission only happens when this Observable emits (and - * not when any of the other sources emit, unlike combineLatest). + * not when any of the other sources emit, unlike combineLatest). * If a source doesn't produce any value and just completes, the sequence is completed immediately. - * + * *

    - *
    Backpressure Support:
    - *
    This operator is a pass-through for backpressure behavior between this {@code Observable} + *
    Backpressure:
    + *
    This operator is a pass-through for backpressure behavior between the source {@code Observable} * and the downstream Subscriber. The other {@code Observable}s are consumed in an unbounded manner.
    *
    Scheduler:
    *
    This operator does not operate by default on a particular {@link Scheduler}.
    @@ -10680,31 +12228,29 @@ public final Observable withLatestFrom( * @param o6 the sixth other Observable * @param combiner the function called with an array of values from each participating observable * @return the new Observable instance - * @Experimental The behavior of this can change at any time. - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Experimental public final Observable withLatestFrom( - Observable o1, Observable o2, - Observable o3, Observable o4, - Observable o5, Observable o6, + Observable o1, Observable o2, + Observable o3, Observable o4, + Observable o5, Observable o6, Func7 combiner) { - return create(new OperatorWithLatestFromMany(this, + return unsafeCreate(new OperatorWithLatestFromMany(this, new Observable[] { o1, o2, o3, o4, o5, o6 }, null, Functions.fromFunc(combiner))); } /** * Combines the value emission from this Observable with the latest emissions from the * other Observables via a function to produce the output item. - * + * *

    Note that this operator doesn't emit anything until all other sources have produced at * least one value. The resulting emission only happens when this Observable emits (and - * not when any of the other sources emit, unlike combineLatest). + * not when any of the other sources emit, unlike combineLatest). * If a source doesn't produce any value and just completes, the sequence is completed immediately. - * + * *

    - *
    Backpressure Support:
    - *
    This operator is a pass-through for backpressure behavior between this {@code Observable} + *
    Backpressure:
    + *
    This operator is a pass-through for backpressure behavior between the source {@code Observable} * and the downstream Subscriber. The other {@code Observable}s are consumed in an unbounded manner.
    *
    Scheduler:
    *
    This operator does not operate by default on a particular {@link Scheduler}.
    @@ -10727,32 +12273,30 @@ public final Observable withLatestFrom( * @param o7 the seventh other Observable * @param combiner the function called with an array of values from each participating observable * @return the new Observable instance - * @Experimental The behavior of this can change at any time. - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Experimental public final Observable withLatestFrom( - Observable o1, Observable o2, - Observable o3, Observable o4, - Observable o5, Observable o6, - Observable o7, + Observable o1, Observable o2, + Observable o3, Observable o4, + Observable o5, Observable o6, + Observable o7, Func8 combiner) { - return create(new OperatorWithLatestFromMany(this, + return unsafeCreate(new OperatorWithLatestFromMany(this, new Observable[] { o1, o2, o3, o4, o5, o6, o7 }, null, Functions.fromFunc(combiner))); } /** * Combines the value emission from this Observable with the latest emissions from the * other Observables via a function to produce the output item. - * + * *

    Note that this operator doesn't emit anything until all other sources have produced at * least one value. The resulting emission only happens when this Observable emits (and - * not when any of the other sources emit, unlike combineLatest). + * not when any of the other sources emit, unlike combineLatest). * If a source doesn't produce any value and just completes, the sequence is completed immediately. - * + * *

    - *
    Backpressure Support:
    - *
    This operator is a pass-through for backpressure behavior between this {@code Observable} + *
    Backpressure:
    + *
    This operator is a pass-through for backpressure behavior between the source {@code Observable} * and the downstream Subscriber. The other {@code Observable}s are consumed in an unbounded manner.
    *
    Scheduler:
    *
    This operator does not operate by default on a particular {@link Scheduler}.
    @@ -10765,7 +12309,7 @@ public final Observable withLatestFrom( * @param the fifth other source's value type * @param the sixth other source's value type * @param the seventh other source's value type - * @param the eigth other source's value type + * @param the eighth other source's value type * @param the result value type * @param o1 the first other Observable * @param o2 the second other Observable @@ -10777,76 +12321,70 @@ public final Observable withLatestFrom( * @param o8 the eighth other Observable * @param combiner the function called with an array of values from each participating observable * @return the new Observable instance - * @Experimental The behavior of this can change at any time. - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Experimental public final Observable withLatestFrom( - Observable o1, Observable o2, - Observable o3, Observable o4, - Observable o5, Observable o6, - Observable o7, Observable o8, + Observable o1, Observable o2, + Observable o3, Observable o4, + Observable o5, Observable o6, + Observable o7, Observable o8, Func9 combiner) { - return create(new OperatorWithLatestFromMany(this, + return unsafeCreate(new OperatorWithLatestFromMany(this, new Observable[] { o1, o2, o3, o4, o5, o6, o7, o8 }, null, Functions.fromFunc(combiner))); } /** * Combines the value emission from this Observable with the latest emissions from the * other Observables via a function to produce the output item. - * + * *

    Note that this operator doesn't emit anything until all other sources have produced at * least one value. The resulting emission only happens when this Observable emits (and - * not when any of the other sources emit, unlike combineLatest). + * not when any of the other sources emit, unlike combineLatest). * If a source doesn't produce any value and just completes, the sequence is completed immediately. - * + * *

    - *
    Backpressure Support:
    - *
    This operator is a pass-through for backpressure behavior between this {@code Observable} + *
    Backpressure:
    + *
    This operator is a pass-through for backpressure behavior between the source {@code Observable} * and the downstream Subscriber. The other {@code Observable}s are consumed in an unbounded manner.
    *
    Scheduler:
    *
    This operator does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param the result value type * @param others the array of other sources * @param combiner the function called with an array of values from each participating observable * @return the new Observable instance - * @Experimental The behavior of this can change at any time. - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Experimental public final Observable withLatestFrom(Observable[] others, FuncN combiner) { - return create(new OperatorWithLatestFromMany(this, others, null, combiner)); + return unsafeCreate(new OperatorWithLatestFromMany(this, others, null, combiner)); } /** * Combines the value emission from this Observable with the latest emissions from the * other Observables via a function to produce the output item. - * + * *

    Note that this operator doesn't emit anything until all other sources have produced at * least one value. The resulting emission only happens when this Observable emits (and - * not when any of the other sources emit, unlike combineLatest). + * not when any of the other sources emit, unlike combineLatest). * If a source doesn't produce any value and just completes, the sequence is completed immediately. - * + * *

    - *
    Backpressure Support:
    - *
    This operator is a pass-through for backpressure behavior between this {@code Observable} + *
    Backpressure:
    + *
    This operator is a pass-through for backpressure behavior between the source {@code Observable} * and the downstream Subscriber. The other {@code Observable}s are consumed in an unbounded manner.
    *
    Scheduler:
    *
    This operator does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param the result value type * @param others the iterable of other sources * @param combiner the function called with an array of values from each participating observable * @return the new Observable instance - * @Experimental The behavior of this can change at any time. - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Experimental public final Observable withLatestFrom(Iterable> others, FuncN combiner) { - return create(new OperatorWithLatestFromMany(this, null, others, combiner)); + return unsafeCreate(new OperatorWithLatestFromMany(this, null, others, combiner)); } /** @@ -10856,17 +12394,20 @@ public final Observable withLatestFrom(Iterable> others, Fu *

    * *

    - *
    Backpressure Support:
    - *
    This operator does not support backpressure as it uses the {@code closingSelector} to control data - * flow.
    + *
    Backpressure:
    + *
    The operator consumes the source {@code Observable} in an unbounded manner. + * The returned {@code Observable} doesn't support backpressure as it uses + * the {@code closingSelector} to control the creation of windows. The returned inner {@code Observable}s honor + * backpressure but have an unbounded inner buffer that may lead to {@code OutOfMemoryError} + * if left unconsumed.
    *
    Scheduler:
    *
    This version of {@code window} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param the element type of the boundary Observable * @param closingSelector * a {@link Func0} that returns an {@code Observable} that governs the boundary between windows. - * When this {@code Observable} emits an item, {@code window} emits the current window and begins + * When the source {@code Observable} emits an item, {@code window} emits the current window and begins * a new one. * @return an Observable that emits connected, non-overlapping windows of items from the source Observable * whenever {@code closingSelector} emits an item @@ -10884,13 +12425,13 @@ public final Observable> window(Func0 * *
    - *
    Backpressure Support:
    + *
    Backpressure:
    *
    The operator honors backpressure of its inner and outer subscribers, however, the inner Observable uses an * unbounded buffer that may hold at most {@code count} elements.
    *
    Scheduler:
    *
    This version of {@code window} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param count * the maximum size of each window before it should be emitted * @return an Observable that emits connected, non-overlapping windows, each containing at most @@ -10910,13 +12451,13 @@ public final Observable> window(int count) { *

    * *

    - *
    Backpressure Support:
    + *
    Backpressure:
    *
    The operator honors backpressure of its inner and outer subscribers, however, the inner Observable uses an * unbounded buffer that may hold at most {@code count} elements.
    *
    Scheduler:
    *
    This version of {@code window} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param count * the maximum size of each window before it should be emitted * @param skip @@ -10946,12 +12487,16 @@ public final Observable> window(int count, int skip) { *

    * *

    - *
    Backpressure Support:
    - *
    This operator does not support backpressure as it uses time to control data flow.
    + *
    Backpressure:
    + *
    The operator consumes the source {@code Observable} in an unbounded manner. + * The returned {@code Observable} doesn't support backpressure as it uses + * time to control the creation of windows. The returned inner {@code Observable}s honor + * backpressure but have an unbounded inner buffer that may lead to {@code OutOfMemoryError} + * if left unconsumed.
    *
    Scheduler:
    *
    This version of {@code window} operates by default on the {@code computation} {@link Scheduler}.
    *
    - * + * * @param timespan * the period of time each window collects items before it should be emitted * @param timeshift @@ -10974,12 +12519,16 @@ public final Observable> window(long timespan, long timeshift, Tim *

    * *

    - *
    Backpressure Support:
    - *
    This operator does not support backpressure as it uses time to control data flow.
    + *
    Backpressure:
    + *
    The operator consumes the source {@code Observable} in an unbounded manner. + * The returned {@code Observable} doesn't support backpressure as it uses + * time to control the creation of windows. The returned inner {@code Observable}s honor + * backpressure but have an unbounded inner buffer that may lead to {@code OutOfMemoryError} + * if left unconsumed.
    *
    Scheduler:
    *
    you specify which {@link Scheduler} this operator will use
    *
    - * + * * @param timespan * the period of time each window collects items before it should be emitted * @param timeshift @@ -10994,7 +12543,7 @@ public final Observable> window(long timespan, long timeshift, Tim public final Observable> window(long timespan, long timeshift, TimeUnit unit, Scheduler scheduler) { return window(timespan, timeshift, unit, Integer.MAX_VALUE, scheduler); } - + /** * Returns an Observable that emits windows of items it collects from the source Observable. The resulting * Observable starts a new window periodically, as determined by the {@code timeshift} argument or a maximum @@ -11005,12 +12554,15 @@ public final Observable> window(long timespan, long timeshift, Tim *

    * *

    - *
    Backpressure Support:
    - *
    This operator does not support backpressure as it uses time to control data flow.
    + *
    Backpressure:
    + *
    The operator consumes the source {@code Observable} in an unbounded manner. + * The returned {@code Observable} doesn't support backpressure as it uses + * time to control the creation of windows. The returned inner {@code Observable}s honor + * backpressure and may hold up to {@code count} elements at most.
    *
    Scheduler:
    *
    you specify which {@link Scheduler} this operator will use
    *
    - * + * * @param timespan * the period of time each window collects items before it should be emitted * @param timeshift @@ -11036,12 +12588,15 @@ public final Observable> window(long timespan, long timeshift, Tim *

    * *

    - *
    Backpressure Support:
    - *
    This operator does not support backpressure as it uses time to control data flow.
    + *
    Backpressure:
    + *
    The operator consumes the source {@code Observable} in an unbounded manner. + * The returned {@code Observable} doesn't support backpressure as it uses + * time to control the creation of windows. The returned inner {@code Observable}s honor + * backpressure and may hold up to {@code count} elements at most.
    *
    Scheduler:
    *
    This version of {@code window} operates by default on the {@code computation} {@link Scheduler}.
    *
    - * + * * @param timespan * the period of time each window collects items before it should be emitted and replaced with a * new window @@ -11064,12 +12619,15 @@ public final Observable> window(long timespan, TimeUnit unit) { *

    * *

    - *
    Backpressure Support:
    - *
    This operator does not support backpressure as it uses time to control data flow.
    + *
    Backpressure:
    + *
    The operator consumes the source {@code Observable} in an unbounded manner. + * The returned {@code Observable} doesn't support backpressure as it uses + * time to control the creation of windows. The returned inner {@code Observable}s honor + * backpressure and may hold up to {@code count} elements at most.
    *
    Scheduler:
    *
    This version of {@code window} operates by default on the {@code computation} {@link Scheduler}.
    *
    - * + * * @param timespan * the period of time each window collects items before it should be emitted and replaced with a * new window @@ -11095,12 +12653,15 @@ public final Observable> window(long timespan, TimeUnit unit, int *

    * *

    - *
    Backpressure Support:
    - *
    This operator does not support backpressure as it uses time to control data flow.
    + *
    Backpressure:
    + *
    The operator consumes the source {@code Observable} in an unbounded manner. + * The returned {@code Observable} doesn't support backpressure as it uses + * time to control the creation of windows. The returned inner {@code Observable}s honor + * backpressure and may hold up to {@code count} elements at most.
    *
    Scheduler:
    *
    you specify which {@link Scheduler} this operator will use
    *
    - * + * * @param timespan * the period of time each window collects items before it should be emitted and replaced with a * new window @@ -11127,12 +12688,16 @@ public final Observable> window(long timespan, TimeUnit unit, int *

    * *

    - *
    Backpressure Support:
    - *
    This operator does not support backpressure as it uses time to control data flow.
    + *
    Backpressure:
    + *
    The operator consumes the source {@code Observable} in an unbounded manner. + * The returned {@code Observable} doesn't support backpressure as it uses + * time to control the creation of windows. The returned inner {@code Observable}s honor + * backpressure but have an unbounded inner buffer that may lead to {@code OutOfMemoryError} + * if left unconsumed.
    *
    Scheduler:
    *
    you specify which {@link Scheduler} this operator will use
    *
    - * + * * @param timespan * the period of time each window collects items before it should be emitted and replaced with a * new window @@ -11156,15 +12721,15 @@ public final Observable> window(long timespan, TimeUnit unit, Sche *

    * *

    - *
    Backpressure Support:
    + *
    Backpressure:
    *
    The outer Observable of this operator doesn't support backpressure because the emission of new - * inner Observables are controlled by the {@code windowOpenings} Observable. + * inner Observables are controlled by the {@code windowOpenings} Observable. * The inner Observables honor backpressure and buffer everything until the associated closing * Observable signals or completes.
    *
    Scheduler:
    *
    This version of {@code window} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param the element type of the window-opening Observable * @param the element type of the window-closing Observables * @param windowOpenings @@ -11187,13 +12752,13 @@ public final Observable> window(Observable * *
    - *
    Backpressure Support:
    + *
    Backpressure:
    *
    The outer Observable of this operator does not support backpressure as it uses a {@code boundary} Observable to control data * flow. The inner Observables honor backpressure and buffer everything until the boundary signals the next element.
    *
    Scheduler:
    *
    This version of {@code window} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param * the window element type (ignored) * @param boundary @@ -11223,7 +12788,7 @@ public final Observable> window(Observable boundary) { *
    Scheduler:
    *
    {@code zipWith} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param * the type of items in the {@code other} Iterable * @param @@ -11246,18 +12811,18 @@ public final Observable zipWith(Iterable other, Func2 *

    - * 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 unsubscribing the other sources. Therefore, it - * is possible those other sources will never be able to run to completion (and thus not calling + * 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 unsubscribing the other sources. Therefore, it + * is possible those other sources will never be able to run to completion (and thus not calling * {@code doOnCompleted()}). 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 * be sending further values and it will unsubscribe B immediately. For example: *

    range(1, 5).doOnCompleted(action1).zipWith(range(6, 5).doOnCompleted(action2), (a, b) -> a + b)
    * {@code action1} will be called but {@code action2} won't. *
    To work around this termination property, - * use {@code doOnUnsubscribed()} as well or use {@code using()} to do cleanup in case of completion + * use {@code doOnUnsubscribed()} as well or use {@code using()} to do cleanup in case of completion * or unsubscription. - * + * * *
    *
    Backpressure:
    @@ -11267,7 +12832,7 @@ public final Observable zipWith(Iterable other, Func2Scheduler:
    *
    {@code zipWith} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param * the type of items emitted by the {@code other} Observable * @param @@ -11285,4 +12850,46 @@ public final Observable zipWith(Iterable other, Func2 Observable zipWith(Observable other, Func2 zipFunction) { return (Observable)zip(this, other, zipFunction); } + + // ------------------------------------------------------------------------- + // Fluent test support, super handy and reduces test preparation boilerplate + // ------------------------------------------------------------------------- + /** + * Creates a AssertableSubscriber that requests {@code Long.MAX_VALUE} and subscribes + * it to this Observable. + *
    + *
    Backpressure:
    + *
    The returned AssertableSubscriber consumes this Observable in an unbounded fashion.
    + *
    Scheduler:
    + *
    {@code test} does not operate by default on a particular {@link Scheduler}.
    + *
    + *

    History: 1.2.3 - experimental + * @return the new AssertableSubscriber instance + * @since 1.3 + */ + public final AssertableSubscriber test() { + AssertableSubscriber ts = AssertableSubscriberObservable.create(Long.MAX_VALUE); + subscribe(ts); + return ts; + } + + /** + * Creates an AssertableSubscriber with the initial request amount and subscribes + * it to this Observable. + *

    + *
    Backpressure:
    + *
    The returned AssertableSubscriber requests the given {@code initialRequest} amount upfront.
    + *
    Scheduler:
    + *
    {@code test} does not operate by default on a particular {@link Scheduler}.
    + *
    + *

    History: 1.2.3 - experimental + * @return the new AssertableSubscriber instance + * @param initialRequestAmount the amount to request from upstream upfront, non-negative (not verified) + * @since 1.3 + */ + public final AssertableSubscriber test(long initialRequestAmount) { + AssertableSubscriber ts = AssertableSubscriberObservable.create(initialRequestAmount); + subscribe(ts); + return ts; + } } diff --git a/src/main/java/rx/Observer.java b/src/main/java/rx/Observer.java index 8c7eca5c77..28d29e53fa 100644 --- a/src/main/java/rx/Observer.java +++ b/src/main/java/rx/Observer.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -22,7 +22,7 @@ * {@code Observable} calls the Observer's {@link #onNext} method to provide notifications. A well-behaved * {@code Observable} will call an Observer's {@link #onCompleted} method exactly once or the Observer's * {@link #onError} method exactly once. - * + * * @see ReactiveX documentation: Observable * @param * the type of item the Observer expects to observe @@ -41,7 +41,7 @@ public interface Observer { *

    * If the {@link Observable} calls this method, it will not thereafter call {@link #onNext} or * {@link #onCompleted}. - * + * * @param e * the exception encountered by the Observable */ @@ -54,7 +54,7 @@ public interface Observer { *

    * The {@code Observable} will not call this method again after it calls either {@link #onCompleted} or * {@link #onError}. - * + * * @param t * the item emitted by the Observable */ diff --git a/src/main/java/rx/Producer.java b/src/main/java/rx/Producer.java index fb7f211e7c..7fe1d83663 100644 --- a/src/main/java/rx/Producer.java +++ b/src/main/java/rx/Producer.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -19,10 +19,10 @@ * Interface that establishes a request-channel between an Observable and a Subscriber and allows * the Subscriber to request a certain amount of items from the Observable (otherwise known as * backpressure). - * + * *

    The request amount only affects calls to {@link Subscriber#onNext(Object)}; onError and onCompleted may appear without * requests. - * + * *

    However, backpressure is somewhat optional in RxJava 1.x and Subscribers may not * receive a Producer via their {@link Subscriber#setProducer(Producer)} method and will run * in unbounded mode. Depending on the chain of operators, this can lead to {@link rx.exceptions.MissingBackpressureException}. @@ -33,15 +33,15 @@ public interface Producer { * Request a certain maximum number of items from this Producer. This is a way of requesting backpressure. * To disable backpressure, pass {@code Long.MAX_VALUE} to this method. *

    - * Requests are additive but if a sequence of requests totals more than {@code Long.MAX_VALUE} then - * {@code Long.MAX_VALUE} requests will be actioned and the extras may be ignored. Arriving at - * {@code Long.MAX_VALUE} by addition of requests cannot be assumed to disable backpressure. For example, + * Requests are additive but if a sequence of requests totals more than {@code Long.MAX_VALUE} then + * {@code Long.MAX_VALUE} requests will be actioned and the extras may be ignored. Arriving at + * {@code Long.MAX_VALUE} by addition of requests cannot be assumed to disable backpressure. For example, * the code below may result in {@code Long.MAX_VALUE} requests being actioned only. - * + * *

          * request(100);
          * request(Long.MAX_VALUE-1);
    -     * 
    + * * * @param n the maximum number of items you want this Producer to produce, or {@code Long.MAX_VALUE} if you * want the Producer to produce items at its own pace diff --git a/src/main/java/rx/Scheduler.java b/src/main/java/rx/Scheduler.java index 8437396e56..89259b178b 100644 --- a/src/main/java/rx/Scheduler.java +++ b/src/main/java/rx/Scheduler.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -17,9 +17,9 @@ import java.util.concurrent.TimeUnit; -import rx.functions.Action0; +import rx.functions.*; +import rx.internal.schedulers.*; import rx.schedulers.Schedulers; -import rx.subscriptions.MultipleAssignmentSubscription; /** * A {@code Scheduler} is an object that schedules units of work. You can find common implementations of this @@ -42,25 +42,13 @@ public abstract class Scheduler { * : Without virtual extension methods even additive changes are breaking and thus severely impede library * maintenance. */ - - /** - * The tolerance for a clock drift in nanoseconds where the periodic scheduler will rebase. - *

    - * The associated system parameter, {@code rx.scheduler.drift-tolerance}, expects its value in minutes. - */ - static final long CLOCK_DRIFT_TOLERANCE_NANOS; - static { - CLOCK_DRIFT_TOLERANCE_NANOS = TimeUnit.MINUTES.toNanos( - Long.getLong("rx.scheduler.drift-tolerance", 15)); - } - /** * Retrieves or creates a new {@link Scheduler.Worker} that represents serial execution of actions. *

    * When work is completed it should be unsubscribed using {@link Scheduler.Worker#unsubscribe()}. *

    * Work on a {@link Scheduler.Worker} is guaranteed to be sequential. - * + * * @return a Worker representing a serial queue of actions to be executed */ public abstract Worker createWorker(); @@ -68,33 +56,33 @@ public abstract class Scheduler { /** * Sequential Scheduler for executing actions on a single thread or event loop. *

    - * Unsubscribing the {@link Worker} unschedules all outstanding work and allows resources cleanup. + * Unsubscribing the {@link Worker} cancels all outstanding work and allows resources cleanup. */ public abstract static class Worker implements Subscription { /** * Schedules an Action for execution. - * + * * @param action * Action to schedule - * @return a subscription to be able to unsubscribe the action (unschedule it if not executed) + * @return a subscription to be able to prevent or cancel the execution of the action */ public abstract Subscription schedule(Action0 action); /** * Schedules an Action for execution at some point in the future. *

    - * Note to implementors: non-positive {@code delayTime} should be regarded as undelayed schedule, i.e., + * Note to implementors: non-positive {@code delayTime} should be regarded as non-delayed schedule, i.e., * as if the {@link #schedule(rx.functions.Action0)} was called. * * @param action * the Action to schedule * @param delayTime - * time to wait before executing the action; non-positive values indicate an undelayed + * time to wait before executing the action; non-positive values indicate an non-delayed * schedule * @param unit * the time unit of {@code delayTime} - * @return a subscription to be able to unsubscribe the action (unschedule it if not executed) + * @return a subscription to be able to prevent or cancel the execution of the action */ public abstract Subscription schedule(final Action0 action, final long delayTime, final TimeUnit unit); @@ -104,63 +92,23 @@ public abstract static class Worker implements Subscription { * concurrently). Each scheduler that can do periodic scheduling in a better way should override this. *

    * Note to implementors: non-positive {@code initialTime} and {@code period} should be regarded as - * undelayed scheduling of the first and any subsequent executions. - * + * non-delayed scheduling of the first and any subsequent executions. + * * @param action * the Action to execute periodically * @param initialDelay * time to wait before executing the action for the first time; non-positive values indicate - * an undelayed schedule + * an non-delayed schedule * @param period * the time interval to wait each time in between executing the action; non-positive values * indicate no delay between repeated schedules * @param unit * the time unit of {@code period} - * @return a subscription to be able to unsubscribe the action (unschedule it if not executed) + * @return a subscription to be able to prevent or cancel the execution of the action */ public Subscription schedulePeriodically(final Action0 action, long initialDelay, long period, TimeUnit unit) { - final long periodInNanos = unit.toNanos(period); - final long firstNowNanos = TimeUnit.MILLISECONDS.toNanos(now()); - final long firstStartInNanos = firstNowNanos + unit.toNanos(initialDelay); - - final MultipleAssignmentSubscription mas = new MultipleAssignmentSubscription(); - final Action0 recursiveAction = new Action0() { - long count; - long lastNowNanos = firstNowNanos; - long startInNanos = firstStartInNanos; - @Override - public void call() { - action.call(); - - if (!mas.isUnsubscribed()) { - - long nextTick; - - long nowNanos = TimeUnit.MILLISECONDS.toNanos(now()); - // If the clock moved in a direction quite a bit, rebase the repetition period - if (nowNanos + CLOCK_DRIFT_TOLERANCE_NANOS < lastNowNanos - || nowNanos >= lastNowNanos + periodInNanos + CLOCK_DRIFT_TOLERANCE_NANOS) { - nextTick = nowNanos + periodInNanos; - /* - * Shift the start point back by the drift as if the whole thing - * started count periods ago. - */ - startInNanos = nextTick - (periodInNanos * (++count)); - } else { - nextTick = startInNanos + (++count * periodInNanos); - } - lastNowNanos = nowNanos; - - long delay = nextTick - nowNanos; - mas.set(schedule(this, delay, TimeUnit.NANOSECONDS)); - } - } - }; - MultipleAssignmentSubscription s = new MultipleAssignmentSubscription(); - // Should call `mas.set` before `schedule`, or the new Subscription may replace the old one. - mas.set(s); - s.set(schedule(recursiveAction, initialDelay, unit)); - return mas; + return SchedulePeriodicHelper.schedulePeriodically(this, action, + initialDelay, period, unit, null); } /** @@ -182,4 +130,82 @@ public long now() { return System.currentTimeMillis(); } + /** + * Allows the use of operators for controlling the timing around when + * actions scheduled on workers are actually done. This makes it possible to + * layer additional behavior on this {@link Scheduler}. The only parameter + * is a function that flattens an {@link Observable} of {@link Observable} + * of {@link Completable}s into just one {@link Completable}. There must be + * a chain of operators connecting the returned value to the source + * {@link Observable} otherwise any work scheduled on the returned + * {@link Scheduler} will not be executed. + *

    + * When {@link Scheduler#createWorker()} is invoked a {@link Observable} of + * {@link Completable}s is onNext'd to the combinator to be flattened. If + * the inner {@link Observable} is not immediately subscribed to an calls to + * {@link Worker#schedule} are buffered. Once the {@link Observable} is + * subscribed to actions are then onNext'd as {@link Completable}s. + *

    + * Finally the actions scheduled on the parent {@link Scheduler} when the + * inner most {@link Completable}s are subscribed to. + *

    + * When the {@link Worker} is unsubscribed the {@link Completable} emits an + * onComplete and triggers any behavior in the flattening operator. The + * {@link Observable} and all {@link Completable}s give to the flattening + * function never onError. + *

    + * Limit the amount concurrency two at a time without creating a new fix + * size thread pool: + * + *

    +     * Scheduler limitScheduler = Schedulers.computation().when(workers -> {
    +     *     // use merge max concurrent to limit the number of concurrent
    +     *     // callbacks two at a time
    +     *     return Completable.merge(Observable.merge(workers), 2);
    +     * });
    +     * 
    + *

    + * This is a slightly different way to limit the concurrency but it has some + * interesting benefits and drawbacks to the method above. It works by + * limited the number of concurrent {@link Worker}s rather than individual + * actions. Generally each {@link Observable} uses its own {@link Worker}. + * This means that this will essentially limit the number of concurrent + * subscribes. The danger comes from using operators like + * {@link Observable#zip(Observable, Observable, rx.functions.Func2)} where + * subscribing to the first {@link Observable} could deadlock the + * subscription to the second. + * + *

    +     * Scheduler limitScheduler = Schedulers.computation().when(workers -> {
    +     *     // use merge max concurrent to limit the number of concurrent
    +     *     // Observables two at a time
    +     *     return Completable.merge(Observable.merge(workers, 2));
    +     * });
    +     * 
    + * + * 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 Observable} + * operator that limits the rate without dropping the values (aka leaky + * bucket algorithm). + * + *
    +     * Scheduler slowScheduler = Schedulers.computation().when(workers -> {
    +     *     // use concatenate to make each worker happen one at a time.
    +     *     return Completable.concat(workers.map(actions -> {
    +     *         // delay the starting of the next worker by 1 second.
    +     *         return Completable.merge(actions.delaySubscription(1, TimeUnit.SECONDS));
    +     *    }));
    +     * });
    +     * 
    + * + * @param a Scheduler and a Subscription + * @param combine the function that takes a two-level nested Observable sequence of a Completable and returns + * the Completable that will be subscribed to and should trigger the execution of the scheduled Actions. + * @return the Scheduler with the customized execution behavior + * @since 1.3 + */ + @SuppressWarnings("unchecked") + public S when(Func1>, Completable> combine) { + return (S) new SchedulerWhen(combine, this); + } } diff --git a/src/main/java/rx/Single.java b/src/main/java/rx/Single.java index 183f869fd0..0cf0d5f06f 100644 --- a/src/main/java/rx/Single.java +++ b/src/main/java/rx/Single.java @@ -1,11 +1,11 @@ /** * Copyright 2015 Netflix, Inc. - * + * * 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. @@ -19,9 +19,10 @@ import rx.annotations.*; import rx.exceptions.*; import rx.functions.*; +import rx.internal.observers.AssertableSubscriberObservable; import rx.internal.operators.*; -import rx.internal.producers.SingleDelayedProducer; import rx.internal.util.*; +import rx.observables.ConnectableObservable; import rx.observers.*; import rx.plugins.RxJavaHooks; import rx.schedulers.Schedulers; @@ -44,57 +45,44 @@ *

    * For more information see the ReactiveX * documentation. - * + * * @param * the type of the item emitted by the Single - * @since (If this class graduates from "Experimental" replace this parenthetical with the release number) + * @since 1.2 */ -@Beta public class Single { - final Observable.OnSubscribe onSubscribe; + final OnSubscribe onSubscribe; /** * Creates a Single with a Function to execute when it is subscribed to (executed). *

    * Note: Use {@link #create(OnSubscribe)} to create a Single, instead of this constructor, * unless you specifically have a need for inheritance. - * + * * @param f - * {@code OnExecute} to be executed when {@code execute(SingleSubscriber)} or + * {@code f} to be executed when {@code execute(SingleSubscriber)} or * {@code subscribe(Subscriber)} is called */ protected Single(OnSubscribe f) { - final OnSubscribe g = RxJavaHooks.onCreate(f); - // bridge between OnSubscribe (which all Operators and Observables use) and OnExecute (for Single) - this.onSubscribe = new Observable.OnSubscribe() { - - @Override - public void call(final Subscriber child) { - final SingleDelayedProducer producer = new SingleDelayedProducer(child); - child.setProducer(producer); - SingleSubscriber ss = new SingleSubscriber() { - - @Override - public void onSuccess(T value) { - producer.setValue(value); - } - - @Override - public void onError(Throwable error) { - child.onError(error); - } - - }; - child.add(ss); - g.call(ss); - } - - }; + this.onSubscribe = RxJavaHooks.onCreate(f); } - private Single(final Observable.OnSubscribe f) { - this.onSubscribe = RxJavaHooks.onCreate(f); + /** + * Creates a Single with a Function to execute when it is subscribed to (executed). + *

    + * Note: Use {@link #create(OnSubscribe)} to create a Single, instead of this constructor, + * unless you specifically have a need for inheritance. + * + * @param f + * {@code f} to be executed when {@code execute(SingleSubscriber)} or + * {@code subscribe(Subscriber)} is called + * @deprecated 1.2.1: Not recommended, use {@link #Single(OnSubscribe)} to avoid wrapping and + * conversion between the Observable and Single protocols. + */ + @Deprecated + protected Single(final Observable.OnSubscribe f) { + onSubscribe = RxJavaHooks.onCreate(new SingleFromObservable(f)); } /** @@ -114,7 +102,7 @@ private Single(final Observable.OnSubscribe f) { *

    Scheduler:
    *
    {@code create} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param * the type of the item that this Single emits * @param f @@ -151,37 +139,16 @@ public interface OnSubscribe extends Action1> { *
    Scheduler:
    *
    {@code lift} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param 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 RxJava wiki: Implementing Your Own Operators + * @since 1.3 */ - @Experimental public final Single lift(final Operator lift) { - return new Single(new Observable.OnSubscribe() { - @Override - public void call(Subscriber o) { - try { - final Subscriber st = RxJavaHooks.onSingleLift(lift).call(o); - try { - // new Subscriber created and being subscribed with so 'onStart' it - st.onStart(); - onSubscribe.call(st); - } catch (Throwable e) { - // localized capture of errors rather than it skipping all operators - // and ending up in the try/catch of the subscribe method which then - // prevents onErrorResumeNext and other similar approaches to error handling - Exceptions.throwOrReport(e, st); - } - } catch (Throwable e) { - // if the lift function failed all we can do is pass the error to the final Subscriber - // as we don't have the operator available to us - Exceptions.throwOrReport(e, o); - } - } - }); + return create(new SingleLiftObservableOperator(this.onSubscribe, lift)); } /** @@ -197,7 +164,7 @@ public void call(Subscriber o) { *
    Scheduler:
    *
    {@code compose} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param the value type of the single returned by the transformer function * @param transformer * implements the function that transforms the source Single @@ -210,9 +177,18 @@ public Single compose(Transformer transformer) { } /** - * Transformer function used by {@link #compose}. - * - * @warn more complete description needed + * Convenience type that allows a function to fluently transform a + * Single into another Single via {@link #compose}. + *
    +     *     Transformer<Integer, Integer> transformer = s ->
    +     *         s.subscribeOn(Schedulers.io())
    +     *          .observeOn(AndroidSchedulers.mainThread());
    +     *
    +     *     Single.just(1)
    +     *     .compose(transformer)
    +     *     .subscribe(System.out::println);
    +     * 
    + * * @param the source Single's value type * @param the transformed Single's value type */ @@ -221,40 +197,36 @@ public interface Transformer extends Func1, Single> { } /** + * Hides the identity of this Single. * - * - * @warn more complete description needed */ private static Observable asObservable(Single t) { // is this sufficient, or do I need to keep the outer Single and subscribe to it? - return Observable.create(t.onSubscribe); + return Observable.unsafeCreate(new SingleToObservable(t.onSubscribe)); } + /* ********************************************************************************************************* + * Operators Below Here + * ********************************************************************************************************* + */ + /** - * INTERNAL: Used with lift and operators. - * - * Converts the source {@code Single} into an {@code Single>} that emits an Observable - * that emits the same emission as the source Single. - *

    - * + * Casts the success value of the current Single into the target type or signals a + * ClassCastException if not compatible. *

    *
    Scheduler:
    - *
    {@code nest} does not operate by default on a particular {@link Scheduler}.
    + *
    {@code cast} does not operate by default on a particular {@link Scheduler}.
    *
    - * - * @return a Single that emits an Observable that emits the same item as the source Single - * @see ReactiveX operators documentation: To + * @param the target type + * @param klass the type token to use for casting the success result from the current Single + * @return the new Single instance + * @since 1.3.1 - experimental */ - @SuppressWarnings("unused") - private Single> nest() { - return Single.just(asObservable(this)); + @Experimental + public final Single cast(final Class klass) { + return map(new SingleOperatorCast(klass)); } - /* ********************************************************************************************************* - * Operators Below Here - * ********************************************************************************************************* - */ - /** * Returns an Observable that emits the items emitted by two Singles, one after the other. *

    @@ -263,12 +235,12 @@ private Single> nest() { *

    Scheduler:
    *
    {@code concat} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param the common value type * @param t1 - * an Single to be concatenated + * a Single to be concatenated * @param t2 - * an Single to be concatenated + * a Single to be concatenated * @return an Observable that emits items emitted by the two source Singles, one after the other. * @see ReactiveX operators documentation: Concat */ @@ -307,7 +279,7 @@ public static Observable concat(Single t1, SingleScheduler: *
    {@code concat} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param the common value type * @param t1 * a Single to be concatenated @@ -419,7 +391,7 @@ public static Observable concat(Single t1, SingleScheduler: *
    {@code concat} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param the common value type * @param t1 * a Single to be concatenated @@ -452,7 +424,7 @@ public static Observable concat(Single t1, SingleScheduler: *
    {@code concat} does not operate by default on a particular {@link Scheduler}.
    * - * + * * @param the common value type * @param t1 * a Single to be concatenated @@ -488,7 +460,7 @@ public static Observable concat(Single t1, SingleScheduler: *
    {@code error} does not operate by default on a particular {@link Scheduler}.
    * - * + * * @param exception * the particular Throwable to pass to {@link SingleSubscriber#onError onError} * @param @@ -522,7 +494,7 @@ public void call(SingleSubscriber te) { *
    Scheduler:
    *
    {@code from} does not operate by default on a particular {@link Scheduler}.
    * - * + * * @param future * the source {@link Future} * @param @@ -531,9 +503,8 @@ public void call(SingleSubscriber te) { * @return a {@code Single} that emits the item from the source {@link Future} * @see ReactiveX operators documentation: From */ - @SuppressWarnings("cast") public static Single from(Future future) { - return new Single((Observable.OnSubscribe)OnSubscribeToObservableFuture.toObservableFuture(future)); + return create(new SingleFromFuture(future, 0, null)); } /** @@ -550,7 +521,7 @@ public static Single from(Future future) { *
    Scheduler:
    *
    {@code from} does not operate by default on a particular {@link Scheduler}.
    * - * + * * @param future * the source {@link Future} * @param timeout @@ -563,9 +534,11 @@ public static Single from(Future future) { * @return a {@code Single} that emits the item from the source {@link Future} * @see ReactiveX operators documentation: From */ - @SuppressWarnings("cast") public static Single from(Future future, long timeout, TimeUnit unit) { - return new Single((Observable.OnSubscribe)OnSubscribeToObservableFuture.toObservableFuture(future, timeout, unit)); + if (unit == null) { + throw new NullPointerException("unit is null"); + } + return create(new SingleFromFuture(future, timeout, unit)); } /** @@ -580,7 +553,7 @@ public static Single from(Future future, long timeout, TimeU *
    Scheduler:
    *
    you specify which {@link Scheduler} this operator will use
    * - * + * * @param future * the source {@link Future} * @param scheduler @@ -592,9 +565,8 @@ public static Single from(Future future, long timeout, TimeU * @return a {@code Single} that emits the item from the source {@link Future} * @see ReactiveX operators documentation: From */ - @SuppressWarnings("cast") public static Single from(Future future, Scheduler scheduler) { - return new Single((Observable.OnSubscribe)OnSubscribeToObservableFuture.toObservableFuture(future)).subscribeOn(scheduler); + return from(future).subscribeOn(scheduler); } /** @@ -614,24 +586,48 @@ public static Single from(Future future, Scheduler scheduler * the type of the item emitted by the {@link Single}. * @return a {@link Single} whose {@link Observer}s' subscriptions trigger an invocation of the given function. */ - @Beta public static Single fromCallable(final Callable func) { - return create(new OnSubscribe() { - @Override - public void call(SingleSubscriber singleSubscriber) { - final T value; - - try { - value = func.call(); - } catch (Throwable t) { - Exceptions.throwIfFatal(t); - singleSubscriber.onError(t); - return; - } + return create(new SingleFromCallable(func)); + } - singleSubscriber.onSuccess(value); - } - }); + /** + * Provides an API (in a cold Single) that bridges the Single-reactive world + * with the callback-based world. + *

    The {@link SingleEmitter} allows registering a callback for + * cancellation/unsubscription of a resource. + *

    + * Example: + *

    
    +     * Single.fromEmitter(emitter -> {
    +     *     Callback listener = new Callback() {
    +     *         @Override
    +     *         public void onEvent(Event e) {
    +     *             emitter.onSuccess(e.getData());
    +     *         }
    +     *
    +     *         @Override
    +     *         public void onFailure(Exception e) {
    +     *             emitter.onError(e);
    +     *         }
    +     *     };
    +     *
    +     *     AutoCloseable c = api.someMethod(listener);
    +     *
    +     *     emitter.setCancellation(c::close);
    +     *
    +     * });
    +     * 
    + *

    All of the SingleEmitter's methods are thread-safe and ensure the + * Single's protocol are held. + *

    History: 1.2.3 - experimental + * @param the success value type + * @param producer the callback invoked for each incoming SingleSubscriber + * @return the new Single instance + * @since 1.3 + */ + public static Single fromEmitter(Action1> producer) { + if (producer == null) { throw new NullPointerException("producer is null"); } + return create(new SingleFromEmitter(producer)); } /** @@ -645,7 +641,7 @@ public void call(SingleSubscriber singleSubscriber) { *

    Scheduler:
    *
    {@code just} does not operate by default on a particular {@link Scheduler}.
    * - * + * * @param value * the item to emit * @param @@ -714,7 +710,7 @@ public void onError(Throwable error) { *
    Scheduler:
    *
    {@code merge} does not operate by default on a particular {@link Scheduler}.
    * - * + * * @param the common value type * @param t1 * a Single to be merged @@ -738,7 +734,7 @@ public static Observable merge(Single t1, SingleScheduler: *
    {@code merge} does not operate by default on a particular {@link Scheduler}.
    * - * + * * @param the common value type * @param t1 * a Single to be merged @@ -764,7 +760,7 @@ public static Observable merge(Single t1, SingleScheduler: *
    {@code merge} does not operate by default on a particular {@link Scheduler}.
    * - * + * * @param the common value type * @param t1 * a Single to be merged @@ -792,7 +788,7 @@ public static Observable merge(Single t1, SingleScheduler: *
    {@code merge} does not operate by default on a particular {@link Scheduler}.
    * - * + * * @param the common value type * @param t1 * a Single to be merged @@ -822,7 +818,7 @@ public static Observable merge(Single t1, SingleScheduler: *
    {@code merge} does not operate by default on a particular {@link Scheduler}.
    * - * + * * @param the common value type * @param t1 * a Single to be merged @@ -854,7 +850,7 @@ public static Observable merge(Single t1, SingleScheduler: *
    {@code merge} does not operate by default on a particular {@link Scheduler}.
    * - * + * * @param the common value type * @param t1 * a Single to be merged @@ -888,7 +884,7 @@ public static Observable merge(Single t1, SingleScheduler: *
    {@code merge} does not operate by default on a particular {@link Scheduler}.
    * - * + * * @param the common value type * @param t1 * a Single to be merged @@ -924,7 +920,7 @@ public static Observable merge(Single t1, SingleScheduler: *
    {@code merge} does not operate by default on a particular {@link Scheduler}.
    * - * + * * @param the common value type * @param t1 * a Single to be merged @@ -951,6 +947,97 @@ public static Observable merge(Single t1, Single + *
    Backpressure:
    + *
    The operator consumes items from the Observable in an unbounded manner and honors downstream backpressure.
    + *
    Scheduler:
    + *
    {@code merge} does not operate by default on a particular {@link Scheduler}.
    + * + *

    History: 1.2.7 - experimental + * @param the value type of the inner Singles and the resulting Observable + * @param sources the Observable that emits Singles to be merged + * @return the new Observable instance + * @see #merge(Observable, int) + * @see #mergeDelayError(Observable) + * @see #mergeDelayError(Observable, int) + * @since 1.3 + */ + public static Observable merge(Observable> sources) { + return merge(sources, Integer.MAX_VALUE); + } + + /** + * Merges the Singles emitted by the Observable and runs up to the given number of them together at a time, + * until the Observable and all inner Singles terminate. + *

    + *
    Backpressure:
    + *
    The operator consumes at most maxConcurrent items from the Observable and one-by-one after as the inner + * Singles terminate. The operator ignores downstream backpressure as it doesn't emit items but + * only the terminal event.
    + *
    Scheduler:
    + *
    {@code flatMapSingle} does not operate by default on a particular {@link Scheduler}.
    + *
    + *

    History: 1.2.7 - experimental + * @param the value type of the inner Singles and the resulting Observable + * @param sources the Observable that emits Singles to be merged + * @param maxConcurrency the maximum number of inner Singles to run at a time + * @return the new Observable instance + * @since 1.3 + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + public static Observable merge(Observable> sources, int maxConcurrency) { + return sources.flatMapSingle((Func1)UtilityFunctions.identity(), false, maxConcurrency); + } + + /** + * Merges all Singles emitted by the Observable and runs them together, + * delaying errors from them and the Observable, until the source + * Observable and all inner Singles complete normally. + *

    + *
    Backpressure:
    + *
    The operator consumes items from the Observable in an unbounded manner and honors downstream backpressure.
    + *
    Scheduler:
    + *
    {@code merge} does not operate by default on a particular {@link Scheduler}.
    + *
    + *

    History: 1.2.7 - experimental + * @param the value type of the inner Singles and the resulting Observable + * @param sources the Observable that emits Singles to be merged + * @return the new Observable instance + * @see #mergeDelayError(Observable, int) + * @see #merge(Observable) + * @see #merge(Observable, int) + * @since 1.3 + */ + public static Observable mergeDelayError(Observable> sources) { + return merge(sources, Integer.MAX_VALUE); + } + + /** + * Merges the Singles emitted by the Observable and runs up to the given number of them together at a time, + * delaying errors from them and the Observable, until the Observable and all inner Singles terminate. + *

    + *
    Backpressure:
    + *
    The operator consumes at most maxConcurrent items from the Observable and one-by-one after as the inner + * Singles terminate. The operator ignores downstream backpressure as it doesn't emit items but + * only the terminal event.
    + *
    Scheduler:
    + *
    {@code flatMapSingle} does not operate by default on a particular {@link Scheduler}.
    + *
    + *

    History: 1.2.7 - experimental + * @param the value type of the inner Singles and the resulting Observable + * @param sources the Observable that emits Singles to be merged + * @param maxConcurrency the maximum number of inner Singles to run at a time + * @return the new Observable instance + * @since 1.3 + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + public static Observable mergeDelayError(Observable> sources, int maxConcurrency) { + return sources.flatMapSingle((Func1)UtilityFunctions.identity(), true, maxConcurrency); + } + /** * Returns a Single that emits the results of a specified combiner function applied to two items emitted by * two other Singles. @@ -960,7 +1047,7 @@ public static Observable merge(Single t1, SingleScheduler: *

    {@code zip} does not operate by default on a particular {@link Scheduler}.
    * - * + * * @param the first source Single's value type * @param the second source Single's value type * @param the result value type @@ -993,7 +1080,7 @@ public R call(Object... args) { *
    Scheduler:
    *
    {@code zip} does not operate by default on a particular {@link Scheduler}.
    * - * + * * @param the first source Single's value type * @param the second source Single's value type * @param the third source Single's value type @@ -1029,7 +1116,7 @@ public R call(Object... args) { *
    Scheduler:
    *
    {@code zip} does not operate by default on a particular {@link Scheduler}.
    * - * + * * @param the first source Single's value type * @param the second source Single's value type * @param the third source Single's value type @@ -1068,7 +1155,7 @@ public R call(Object... args) { *
    Scheduler:
    *
    {@code zip} does not operate by default on a particular {@link Scheduler}.
    * - * + * * @param the first source Single's value type * @param the second source Single's value type * @param the third source Single's value type @@ -1110,7 +1197,7 @@ public R call(Object... args) { *
    Scheduler:
    *
    {@code zip} does not operate by default on a particular {@link Scheduler}.
    * - * + * * @param the first source Single's value type * @param the second source Single's value type * @param the third source Single's value type @@ -1156,7 +1243,7 @@ public R call(Object... args) { *
    Scheduler:
    *
    {@code zip} does not operate by default on a particular {@link Scheduler}.
    * - * + * * @param the first source Single's value type * @param the second source Single's value type * @param the third source Single's value type @@ -1205,7 +1292,7 @@ public R call(Object... args) { *
    Scheduler:
    *
    {@code zip} does not operate by default on a particular {@link Scheduler}.
    * - * + * * @param the first source Single's value type * @param the second source Single's value type * @param the third source Single's value type @@ -1213,7 +1300,7 @@ public R call(Object... args) { * @param the fifth source Single's value type * @param the sixth source Single's value type * @param the seventh source Single's value type - * @param the eigth source Single's value type + * @param the eighth source Single's value type * @param the result value type * @param s1 * the first source Single @@ -1257,7 +1344,7 @@ public R call(Object... args) { *
    Scheduler:
    *
    {@code zip} does not operate by default on a particular {@link Scheduler}.
    * - * + * * @param the first source Single's value type * @param the second source Single's value type * @param the third source Single's value type @@ -1265,7 +1352,7 @@ public R call(Object... args) { * @param the fifth source Single's value type * @param the sixth source Single's value type * @param the seventh source Single's value type - * @param the eigth source Single's value type + * @param the eighth source Single's value type * @param the ninth source Single's value type * @param the result value type * @param s1 @@ -1332,6 +1419,62 @@ public static Single zip(Iterable> singles, FuncN + * + *

    + * This is useful when you want a Single to cache its response and you can't control the + * subscribe/unsubscribe behavior of all the {@link Subscriber}s. + *

    + * The operator subscribes only when the first downstream subscriber subscribes and maintains + * a single subscription towards this Single. In contrast, the operator family of {@link Observable#replay()} + * that return a {@link ConnectableObservable} require an explicit call to {@link ConnectableObservable#connect()}. + *

    + * Note: You sacrifice the ability to unsubscribe from the origin when you use the {@code cache} + * Observer so be careful not to use this Observer on Observables that emit an infinite or very large number + * of items that will use up memory. + * A possible workaround is to apply `takeUntil` with a predicate or + * another source before (and perhaps after) the application of cache(). + *

    
    +     * AtomicBoolean shouldStop = new AtomicBoolean();
    +     *
    +     * source.takeUntil(v -> shouldStop.get())
    +     *       .cache()
    +     *       .takeUntil(v -> shouldStop.get())
    +     *       .subscribe(...);
    +     * 
    + * Since the operator doesn't allow clearing the cached values either, the possible workaround is + * to forget all references to it via {@link Observable#onTerminateDetach()} applied along with the previous + * workaround: + *
    
    +     * AtomicBoolean shouldStop = new AtomicBoolean();
    +     *
    +     * source.takeUntil(v -> shouldStop.get())
    +     *       .onTerminateDetach()
    +     *       .cache()
    +     *       .takeUntil(v -> shouldStop.get())
    +     *       .onTerminateDetach()
    +     *       .subscribe(...);
    +     * 
    + *
    + *
    Backpressure:
    + *
    The operator consumes this Single in an unbounded fashion but respects the backpressure + * of each downstream Subscriber individually.
    + *
    Scheduler:
    + *
    {@code cache} does not operate by default on a particular {@link Scheduler}.
    + *
    + * + * @return a Single that, when first subscribed to, caches its response for the + * benefit of subsequent subscribers + * @see ReactiveX operators documentation: Replay + * @since 1.3 + */ + public final Single cache() { + return toObservable().cacheWithInitialCapacity(1).toSingle(); + } + /** * Returns an Observable that emits the item emitted by the source Single, then the item emitted by the * specified Single. @@ -1341,7 +1484,7 @@ public static Single zip(Iterable> singles, FuncNScheduler: *
    {@code concat} does not operate by default on a particular {@link Scheduler}.
    * - * + * * @param t1 * a Single to be concatenated after the current * @return an Observable that emits the item emitted by the source Single, followed by the item emitted by @@ -1361,7 +1504,7 @@ public final Observable concatWith(Single t1) { *
    Scheduler:
    *
    {@code flatMap} does not operate by default on a particular {@link Scheduler}.
    * - * + * * @param the result value type * @param func * a function that, when applied to the item emitted by the source Single, returns a Single @@ -1384,7 +1527,7 @@ public final Single flatMap(final Func1Scheduler: *
    {@code flatMapObservable} does not operate by default on a particular {@link Scheduler}.
    * - * + * * @param the result value type * @param func * a function that, when applied to the item emitted by the source Single, returns an @@ -1396,6 +1539,27 @@ public final Observable flatMapObservable(Func1 + * + *
    + *
    Scheduler:
    + *
    {@code flatMapCompletable} does not operate by default on a particular {@link Scheduler}.
    + *
    + * + * @param func + * a function that, when applied to the item emitted by the source Single, returns a + * Completable + * @return the Completable returned from {@code func} when applied to the item emitted by the source Single + * @see ReactiveX operators documentation: FlatMap + * @since 1.3 + */ + public final Completable flatMapCompletable(final Func1 func) { + return Completable.create(new CompletableFlatMapSingleToCompletable(this, func)); + } + /** * Returns a Single that applies a specified function to the item emitted by the source Single and * emits the result of this function application. @@ -1413,7 +1577,7 @@ public final Observable flatMapObservable(Func1ReactiveX operators documentation: Map */ public final Single map(Func1 func) { - return lift(new OperatorMap(func)); + return create(new SingleOnSubscribeMap(this, func)); } /** @@ -1427,7 +1591,7 @@ public final Single map(Func1 func) { *
    Scheduler:
    *
    {@code mergeWith} does not operate by default on a particular {@link Scheduler}.
    * - * + * * @param t1 * a Single to be merged * @return an Observable that emits all of the items emitted by the source Singles @@ -1446,7 +1610,7 @@ public final Observable mergeWith(Single t1) { *
    Scheduler:
    *
    you specify which {@link Scheduler} this operator will use
    * - * + * * @param scheduler * the {@link Scheduler} to notify subscribers on * @return the source Single modified so that its subscribers are notified on the specified @@ -1459,9 +1623,10 @@ public final Single observeOn(Scheduler scheduler) { if (this instanceof ScalarSynchronousSingle) { return ((ScalarSynchronousSingle)this).scalarScheduleOn(scheduler); } - // Note that since Single emits onSuccess xor onError, - // there is no cut-ahead possible like with regular Observable sequences. - return lift(new OperatorObserveOn(scheduler, false)); + if (scheduler == null) { + throw new NullPointerException("scheduler is null"); + } + return create(new SingleObserveOn(onSubscribe, scheduler)); } /** @@ -1483,16 +1648,15 @@ public final Single observeOn(Scheduler scheduler) { *
    Scheduler:
    *
    {@code onErrorReturn} does not operate by default on a particular {@link Scheduler}.
    * - * + * * @param resumeFunction * a function that returns an item that the new Single will emit if the source Single encounters * an error * @return the original Single with appropriately modified behavior * @see ReactiveX operators documentation: Catch */ - @SuppressWarnings("cast") public final Single onErrorReturn(Func1 resumeFunction) { - return lift((Operator)OperatorOnErrorResumeNextViaFunction.withSingle(resumeFunction)); + return create(new SingleOnErrorReturn(onSubscribe, resumeFunction)); } /** @@ -1504,7 +1668,7 @@ public final Single onErrorReturn(Func1 resumeFunctio * By default, when a Single encounters an error that prevents it from emitting the expected item to * its {@link Observer}, the Single invokes its Observer's {@code onError} method, and then quits * without invoking any more of its Observer's methods. The {@code onErrorResumeNext} method changes this - * behavior. If you pass another Single ({@code resumeSingleInCaseOfError}) to an Single's + * behavior. If you pass another Single ({@code resumeSingleInCaseOfError}) to a Single's * {@code onErrorResumeNext} method, if the original Single encounters an error, instead of invoking its * Observer's {@code onError} method, it will instead relinquish control to {@code resumeSingleInCaseOfError} which * will invoke the Observer's {@link Observer#onNext onNext} method if it is able to do so. In such a case, @@ -1521,10 +1685,8 @@ public final Single onErrorReturn(Func1 resumeFunctio * @param resumeSingleInCaseOfError a Single that will take control if source Single encounters an error. * @return the original Single, with appropriately modified behavior. * @see ReactiveX operators documentation: Catch - * @Experimental The behavior of this can change at any time. - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Experimental public final Single onErrorResumeNext(Single resumeSingleInCaseOfError) { return new Single(SingleOperatorOnErrorResumeNext.withOther(this, resumeSingleInCaseOfError)); } @@ -1538,7 +1700,7 @@ public final Single onErrorResumeNext(Single resumeSingleInCaseO * By default, when a Single encounters an error that prevents it from emitting the expected item to * its {@link Observer}, the Single invokes its Observer's {@code onError} method, and then quits * without invoking any more of its Observer's methods. The {@code onErrorResumeNext} method changes this - * behavior. If you pass a function that will return another Single ({@code resumeFunctionInCaseOfError}) to an Single's + * behavior. If you pass a function that will return another Single ({@code resumeFunctionInCaseOfError}) to a Single's * {@code onErrorResumeNext} method, if the original Single encounters an error, instead of invoking its * Observer's {@code onError} method, it will instead relinquish control to {@code resumeSingleInCaseOfError} which * will invoke the Observer's {@link Observer#onNext onNext} method if it is able to do so. In such a case, @@ -1555,10 +1717,8 @@ public final Single onErrorResumeNext(Single resumeSingleInCaseO * @param resumeFunctionInCaseOfError a function that returns a Single that will take control if source Single encounters an error. * @return the original Single, with appropriately modified behavior. * @see ReactiveX operators documentation: Catch - * @Experimental The behavior of this can change at any time. - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Experimental public final Single onErrorResumeNext(final Func1> resumeFunctionInCaseOfError) { return new Single(SingleOperatorOnErrorResumeNext.withFunction(this, resumeFunctionInCaseOfError)); } @@ -1569,31 +1729,14 @@ public final Single onErrorResumeNext(final Func1Scheduler: *
    {@code subscribe} does not operate by default on a particular {@link Scheduler}.
    * - * + * * @return a {@link Subscription} reference can request the {@link Single} stop work. * @throws OnErrorNotImplementedException * if the Single tries to call {@link Subscriber#onError} * @see ReactiveX operators documentation: Subscribe */ public final Subscription subscribe() { - return subscribe(new Subscriber() { - - @Override - public final void onCompleted() { - // do nothing - } - - @Override - public final void onError(Throwable e) { - throw new OnErrorNotImplementedException(e); - } - - @Override - public final void onNext(T args) { - // do nothing - } - - }); + return subscribe(Actions.empty(), Actions.errorNotImplemented()); } /** @@ -1602,7 +1745,7 @@ public final void onNext(T args) { *
    Scheduler:
    *
    {@code subscribe} does not operate by default on a particular {@link Scheduler}.
    * - * + * * @param onSuccess * the {@code Action1} you have designed to accept the emission from the Single * @return a {@link Subscription} reference can request the {@link Single} stop work. @@ -1613,28 +1756,7 @@ public final void onNext(T args) { * @see ReactiveX operators documentation: Subscribe */ public final Subscription subscribe(final Action1 onSuccess) { - if (onSuccess == null) { - throw new IllegalArgumentException("onSuccess can not be null"); - } - - return subscribe(new Subscriber() { - - @Override - public final void onCompleted() { - // do nothing - } - - @Override - public final void onError(Throwable e) { - throw new OnErrorNotImplementedException(e); - } - - @Override - public final void onNext(T args) { - onSuccess.call(args); - } - - }); + return subscribe(onSuccess, Actions.errorNotImplemented()); } /** @@ -1644,7 +1766,7 @@ public final void onNext(T args) { *
    Scheduler:
    *
    {@code subscribe} does not operate by default on a particular {@link Scheduler}.
    * - * + * * @param onSuccess * the {@code Action1} you have designed to accept the emission from the Single * @param onError @@ -1664,21 +1786,24 @@ public final Subscription subscribe(final Action1 onSuccess, final Ac throw new IllegalArgumentException("onError can not be null"); } - return subscribe(new Subscriber() { - - @Override - public final void onCompleted() { - // do nothing - } + return subscribe(new SingleSubscriber() { @Override public final void onError(Throwable e) { - onError.call(e); + try { + onError.call(e); + } finally { + unsubscribe(); + } } @Override - public final void onNext(T args) { - onSuccess.call(args); + public final void onSuccess(T args) { + try { + onSuccess.call(args); + } finally { + unsubscribe(); + } } }); @@ -1694,16 +1819,22 @@ public final void onNext(T args) { *
    Scheduler:
    *
    {@code unsafeSubscribe} does not operate by default on a particular {@link Scheduler}.
    * - * + * * @param subscriber * the Subscriber that will handle the emission or notification from the Single * @return the subscription that allows unsubscribing */ public final Subscription unsafeSubscribe(Subscriber subscriber) { + return unsafeSubscribe(subscriber, true); + } + + private Subscription unsafeSubscribe(Subscriber subscriber, boolean start) { try { - // new Subscriber so onStart it - subscriber.onStart(); - RxJavaHooks.onSingleStart(this, onSubscribe).call(subscriber); + if (start) { + // new Subscriber so onStart it + subscriber.onStart(); + } + RxJavaHooks.onSingleStart(this, onSubscribe).call(SingleLiftObservableOperator.wrap(subscriber)); return RxJavaHooks.onSingleReturn(subscriber); } catch (Throwable e) { // special handling for certain Throwable/Error/Exception types @@ -1719,7 +1850,7 @@ public final Subscription unsafeSubscribe(Subscriber subscriber) { // TODO could the hook be the cause of the error in the on error handling. RxJavaHooks.onSingleError(r); // TODO why aren't we throwing the hook's return value. - throw r; + throw r; // NOPMD } return Subscriptions.unsubscribed(); } @@ -1728,9 +1859,9 @@ public final Subscription unsafeSubscribe(Subscriber subscriber) { /** * Subscribes an Observer to this single and returns a Subscription that allows * unsubscription. - * + * * @param observer the Observer to subscribe - * @return the Subscription that allows unsubscription + * @return the Subscription that allows unsubscription */ public final Subscription subscribe(final Observer observer) { if (observer == null) { @@ -1748,7 +1879,7 @@ public void onError(Throwable error) { } }); } - + /** * Subscribes to a Single and provides a Subscriber that implements functions to handle the item the Single * emits or any error notification it issues. @@ -1770,7 +1901,7 @@ public void onError(Throwable error) { *
    Scheduler:
    *
    {@code subscribe} does not operate by default on a particular {@link Scheduler}.
    * - * + * * @param subscriber * the {@link Subscriber} that will handle the emission or notification from the Single * @return a {@link Subscription} reference can request the {@link Single} stop work. @@ -1789,13 +1920,6 @@ public final Subscription subscribe(Subscriber subscriber) { if (subscriber == null) { throw new IllegalArgumentException("observer can not be null"); } - if (onSubscribe == null) { - throw new IllegalStateException("onSubscribe function can not be null."); - /* - * the subscribe function can also be overridden but generally that's not the appropriate approach - * so I won't mention that in the exception - */ - } // new Subscriber so onStart it subscriber.onStart(); @@ -1807,32 +1931,9 @@ public final Subscription subscribe(Subscriber subscriber) { // if not already wrapped if (!(subscriber instanceof SafeSubscriber)) { // assign to `observer` so we return the protected version - subscriber = new SafeSubscriber(subscriber); - } - - // The code below is exactly the same an unsafeSubscribe but not used because it would add a significant depth to already huge call stacks. - try { - // allow the hook to intercept and/or decorate - RxJavaHooks.onSingleStart(this, onSubscribe).call(subscriber); - return RxJavaHooks.onSingleReturn(subscriber); - } catch (Throwable e) { - // special handling for certain Throwable/Error/Exception types - Exceptions.throwIfFatal(e); - // if an unhandled error occurs executing the onSubscribe we will propagate it - try { - subscriber.onError(RxJavaHooks.onSingleError(e)); - } catch (Throwable e2) { - Exceptions.throwIfFatal(e2); - // if this happens it means the onError itself failed (perhaps an invalid function implementation) - // so we are unable to propagate the error correctly and will just throw - RuntimeException r = new RuntimeException("Error occurred attempting to subscribe [" + e.getMessage() + "] and then again while trying to pass to onError.", e2); - // TODO could the hook be the cause of the error in the on error handling. - RxJavaHooks.onSingleError(r); - // TODO why aren't we throwing the hook's return value. - throw r; - } - return Subscriptions.empty(); + return unsafeSubscribe(new SafeSubscriber(subscriber), false); } + return unsafeSubscribe(subscriber, true); } /** @@ -1856,7 +1957,7 @@ public final Subscription subscribe(Subscriber subscriber) { *
    Scheduler:
    *
    {@code subscribe} does not operate by default on a particular {@link Scheduler}.
    * - * + * * @param te * the {@link SingleSubscriber} that will handle the emission or notification from the Single * @return a {@link Subscription} reference can request the {@link Single} stop work. @@ -1871,27 +1972,29 @@ public final Subscription subscribe(Subscriber subscriber) { * @see ReactiveX operators documentation: Subscribe */ public final Subscription subscribe(final SingleSubscriber te) { - Subscriber s = new Subscriber() { - - @Override - public void onCompleted() { - - } - - @Override - public void onError(Throwable e) { - te.onError(e); - } - - @Override - public void onNext(T t) { - te.onSuccess(t); + if (te == null) { + throw new IllegalArgumentException("te is null"); + } + try { + RxJavaHooks.onSingleStart(this, onSubscribe).call(te); + return RxJavaHooks.onSingleReturn(te); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + // if an unhandled error occurs executing the onSubscribe we will propagate it + try { + te.onError(RxJavaHooks.onSingleError(ex)); + } catch (Throwable e2) { + Exceptions.throwIfFatal(e2); + // if this happens it means the onError itself failed (perhaps an invalid function implementation) + // so we are unable to propagate the error correctly and will just throw + RuntimeException r = new RuntimeException("Error occurred attempting to subscribe [" + ex.getMessage() + "] and then again while trying to pass to onError.", e2); + // TODO could the hook be the cause of the error in the on error handling. + RxJavaHooks.onSingleError(r); + // TODO why aren't we throwing the hook's return value. + throw r; // NOPMD } - - }; - te.add(s); - subscribe(s); - return s; + return Subscriptions.empty(); + } } /** @@ -1902,7 +2005,7 @@ public void onNext(T t) { *
    Scheduler:
    *
    you specify which {@link Scheduler} this operator will use
    * - * + * * @param scheduler * the {@link Scheduler} to perform subscription actions on * @return the source Single modified so that its subscriptions happen on the specified {@link Scheduler} @@ -1923,7 +2026,7 @@ public void call(final SingleSubscriber t) { w.schedule(new Action0() { @Override public void call() { - SingleSubscriber ssub = new SingleSubscriber() { + SingleSubscriber single = new SingleSubscriber() { @Override public void onSuccess(T value) { try { @@ -1943,9 +2046,9 @@ public void onError(Throwable error) { } }; - t.add(ssub); + t.add(single); - Single.this.subscribe(ssub); + Single.this.subscribe(single); } }); } @@ -1970,59 +2073,7 @@ public void onError(Throwable error) { * @see ReactiveX operators documentation: TakeUntil */ public final Single takeUntil(final Completable other) { - return lift(new Operator() { - @Override - public Subscriber call(Subscriber child) { - final Subscriber serial = new SerializedSubscriber(child, false); - - final Subscriber main = new Subscriber(serial, false) { - @Override - public void onNext(T t) { - serial.onNext(t); - } - @Override - public void onError(Throwable e) { - try { - serial.onError(e); - } finally { - serial.unsubscribe(); - } - } - @Override - public void onCompleted() { - try { - serial.onCompleted(); - } finally { - serial.unsubscribe(); - } - } - }; - - final Completable.CompletableSubscriber so = new Completable.CompletableSubscriber() { - @Override - public void onCompleted() { - onError(new CancellationException("Stream was canceled before emitting a terminal event.")); - } - - @Override - public void onError(Throwable e) { - main.onError(e); - } - - @Override - public void onSubscribe(Subscription d) { - serial.add(d); - } - }; - - serial.add(main); - child.add(serial); - - other.unsafeSubscribe(so); - - return main; - } - }); + return create(new SingleTakeUntilCompletable(onSubscribe, other)); } /** @@ -2046,62 +2097,7 @@ public void onSubscribe(Subscription d) { * @see ReactiveX operators documentation: TakeUntil */ public final Single takeUntil(final Observable other) { - return lift(new Operator() { - @Override - public Subscriber call(Subscriber child) { - final Subscriber serial = new SerializedSubscriber(child, false); - - final Subscriber main = new Subscriber(serial, false) { - @Override - public void onNext(T t) { - serial.onNext(t); - } - @Override - public void onError(Throwable e) { - try { - serial.onError(e); - } finally { - serial.unsubscribe(); - } - } - @Override - public void onCompleted() { - try { - serial.onCompleted(); - } finally { - serial.unsubscribe(); - } - } - }; - - final Subscriber so = new Subscriber() { - - @Override - public void onCompleted() { - onError(new CancellationException("Stream was canceled before emitting a terminal event.")); - } - - @Override - public void onError(Throwable e) { - main.onError(e); - } - - @Override - public void onNext(E e) { - onError(new CancellationException("Stream was canceled before emitting a terminal event.")); - } - }; - - serial.add(main); - serial.add(so); - - child.add(serial); - - other.unsafeSubscribe(so); - - return main; - } - }); + return create(new SingleTakeUntilObservable(onSubscribe, other)); } /** @@ -2123,63 +2119,27 @@ public void onNext(E e) { * @see ReactiveX operators documentation: TakeUntil */ public final Single takeUntil(final Single other) { - return lift(new Operator() { - @Override - public Subscriber call(Subscriber child) { - final Subscriber serial = new SerializedSubscriber(child, false); - - final Subscriber main = new Subscriber(serial, false) { - @Override - public void onNext(T t) { - serial.onNext(t); - } - @Override - public void onError(Throwable e) { - try { - serial.onError(e); - } finally { - serial.unsubscribe(); - } - } - @Override - public void onCompleted() { - try { - serial.onCompleted(); - } finally { - serial.unsubscribe(); - } - } - }; - - final SingleSubscriber so = new SingleSubscriber() { - @Override - public void onSuccess(E value) { - onError(new CancellationException("Stream was canceled before emitting a terminal event.")); - } - - @Override - public void onError(Throwable e) { - main.onError(e); - } - }; - - serial.add(main); - serial.add(so); - - child.add(serial); - - other.subscribe(so); + return create(new SingleTakeUntilSingle(onSubscribe, other)); + } - return main; - } - }); + /** + * Calls the specified converter function during assembly time and returns its resulting value. + *

    + * This allows fluent conversion to any other type. + * @param the resulting object type + * @param converter the function that receives the current Single instance and returns a value + * @return the value returned by the function + * @since 1.3 + */ + public final R to(Func1, R> converter) { + return converter.call(this); } - + /** * Converts this Single into an {@link Observable}. *

    * - * + * * @return an {@link Observable} that emits a single item T. */ public final Observable toObservable() { @@ -2202,10 +2162,8 @@ public final Observable toObservable() { * @return a {@link Completable} that calls {@code onCompleted} on it's subscriber when the source {@link Single} * calls {@code onSuccess}. * @see ReactiveX documentation: Completable - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical - * with the release number). + * @since 1.3 */ - @Experimental public final Completable toCompletable() { return Completable.fromSingle(this); } @@ -2220,7 +2178,7 @@ public final Completable toCompletable() { *

    Scheduler:
    *
    This version of {@code timeout} operates by default on the {@code computation} {@link Scheduler}.
    * - * + * * @param timeout * maximum duration before the Single times out * @param timeUnit @@ -2243,7 +2201,7 @@ public final Single timeout(long timeout, TimeUnit timeUnit) { *
    Scheduler:
    *
    you specify which {@link Scheduler} this operator will use
    * - * + * * @param timeout * maximum duration before the Single times out * @param timeUnit @@ -2268,7 +2226,7 @@ public final Single timeout(long timeout, TimeUnit timeUnit, Scheduler schedu *
    Scheduler:
    *
    This version of {@code timeout} operates by default on the {@code computation} {@link Scheduler}.
    * - * + * * @param timeout * maximum time before a timeout occurs * @param timeUnit @@ -2292,7 +2250,7 @@ public final Single timeout(long timeout, TimeUnit timeUnit, SingleScheduler: *
    you specify which {@link Scheduler} this operator will use
    * - * + * * @param timeout * maximum duration before a timeout occurs * @param timeUnit @@ -2306,9 +2264,17 @@ public final Single timeout(long timeout, TimeUnit timeUnit, Single timeout(long timeout, TimeUnit timeUnit, Single other, Scheduler scheduler) { if (other == null) { - other = Single. error(new TimeoutException()); + // Use a defer instead of simply other = Single.error(new TimeoutException()) + // since instantiating an exception will cause the current stack trace to be inspected + // and we only want to incur that overhead when a timeout actually happens. + other = Single.defer(new Func0>() { + @Override + public Single call() { + return Single.error(new TimeoutException()); + } + }); } - return lift(new OperatorTimeout(timeout, timeUnit, asObservable(other), scheduler)); + return create(new SingleTimeout(onSubscribe, timeout, timeUnit, scheduler, other.onSubscribe)); } /** @@ -2320,8 +2286,8 @@ public final Single timeout(long timeout, TimeUnit timeUnit, SingleReactiveX operators documentation: To + * @since 1.3 */ - @Experimental public final BlockingSingle toBlocking() { return BlockingSingle.from(this); } @@ -2335,7 +2301,7 @@ public final BlockingSingle toBlocking() { *
    Scheduler:
    *
    {@code zipWith} does not operate by default on a particular {@link Scheduler}.
    * - * + * * @param * the type of items emitted by the {@code other} Single * @param @@ -2370,27 +2336,54 @@ public final Single zipWith(Single other, Func2ReactiveX operators documentation: Do + * @since 1.3 */ - @Experimental public final Single doOnError(final Action1 onError) { - Observer observer = new Observer() { + if (onError == null) { + throw new IllegalArgumentException("onError is null"); + } + + return Single.create(new SingleDoOnEvent(this, Actions.empty(), new Action1() { @Override - public void onCompleted() { + public void call(final Throwable throwable) { + onError.call(throwable); } + })); + } + /** + * Modifies the source {@link Single} so that it invokes an action when it calls {@code onSuccess} or {@code onError}. + *

    + * + *

    + *
    Scheduler:
    + *
    {@code doOnEach} does not operate by default on a particular {@link Scheduler}.
    + *
    + * + * @param onNotification + * the action to invoke when the source {@link Single} calls {@code onSuccess} or {@code onError}. + * @return the source {@link Single} with the side-effecting behavior applied + * @see ReactiveX operators documentation: Do + * @since 1.3 + */ + public final Single doOnEach(final Action1> onNotification) { + if (onNotification == null) { + throw new IllegalArgumentException("onNotification is null"); + } + + return Single.create(new SingleDoOnEvent(this, new Action1() { @Override - public void onError(Throwable e) { - onError.call(e); + public void call(final T t) { + onNotification.call(Notification.createOnNext(t)); } - + }, new Action1() { @Override - public void onNext(T t) { + public void call(final Throwable throwable) { + onNotification.call(Notification.createOnError(throwable)); } - }; - - return lift(new OperatorDoOnEach(observer)); + })); } - + /** * Modifies the source {@link Single} so that it invokes an action when it calls {@code onSuccess}. *

    @@ -2404,25 +2397,15 @@ public void onNext(T t) { * the action to invoke when the source {@link Single} calls {@code onSuccess} * @return the source {@link Single} with the side-effecting behavior applied * @see ReactiveX operators documentation: Do + * @since 1.3 */ - @Experimental public final Single doOnSuccess(final Action1 onSuccess) { - Observer observer = new Observer() { - @Override - public void onCompleted() { - } - - @Override - public void onError(Throwable e) { - } - - @Override - public void onNext(T t) { - onSuccess.call(t); - } - }; + if (onSuccess == null) { + throw new IllegalArgumentException("onSuccess is null"); + } - return lift(new OperatorDoOnEach(observer)); + Action1 empty = Actions.empty(); + return Single.create(new SingleDoOnEvent(this, onSuccess, empty)); } /** @@ -2441,14 +2424,14 @@ public void onNext(T t) { * the action that gets called when an observer subscribes to this {@code Single} * @return the source {@code Single} modified so as to call this Action when appropriate * @see ReactiveX operators documentation: Do + * @since 1.3 */ - @Experimental public final Single doOnSubscribe(final Action0 subscribe) { - return lift(new OperatorDoOnSubscribe(subscribe)); + return create(new SingleDoOnSubscribe(onSubscribe, subscribe)); } /** - * Returns an Single that emits the items emitted by the source Single shifted forward in time by a + * Returns a Single that emits the items emitted by the source Single shifted forward in time by a * specified delay. Error notifications from the source Single are not delayed. *

    * @@ -2465,14 +2448,14 @@ public final Single doOnSubscribe(final Action0 subscribe) { * the {@link Scheduler} to use for delaying * @return the source Single shifted in time by the specified delay * @see ReactiveX operators documentation: Delay + * @since 1.3 */ - @Experimental public final Single delay(long delay, TimeUnit unit, Scheduler scheduler) { - return lift(new OperatorDelay(delay, unit, scheduler)); + return create(new SingleDelay(onSubscribe, delay, unit, scheduler)); } /** - * Returns an Single that emits the items emitted by the source Single shifted forward in time by a + * Returns a Single that emits the items emitted by the source Single shifted forward in time by a * specified delay. Error notifications from the source Observable are not delayed. *

    * @@ -2487,8 +2470,8 @@ public final Single delay(long delay, TimeUnit unit, Scheduler scheduler) { * the {@link TimeUnit} in which {@code period} is defined * @return the source Single shifted in time by the specified delay * @see ReactiveX operators documentation: Delay + * @since 1.3 */ - @Experimental public final Single delay(long delay, TimeUnit unit) { return delay(delay, unit, Schedulers.computation()); } @@ -2516,8 +2499,8 @@ public final Single delay(long delay, TimeUnit unit) { * @return a {@link Single} whose {@link Observer}s' subscriptions trigger an invocation of the given * {@link Single} factory function. * @see ReactiveX operators documentation: Defer + * @since 1.3 */ - @Experimental public static Single defer(final Callable> singleFactory) { return create(new OnSubscribe() { @Override @@ -2551,10 +2534,10 @@ public void call(SingleSubscriber singleSubscriber) { * the action that gets called when this {@link Single} is unsubscribed. * @return the source {@link Single} modified so as to call this Action when appropriate. * @see ReactiveX operators documentation: Do + * @since 1.3 */ - @Experimental public final Single doOnUnsubscribe(final Action0 action) { - return lift(new OperatorDoOnUnsubscribe(action)); + return create(new SingleDoOnUnsubscribe(onSubscribe, action)); } /** @@ -2572,8 +2555,8 @@ public final Single doOnUnsubscribe(final Action0 action) { * @return a {@link Single} that emits the same item or error as the source {@link Single}, then invokes the * {@link Action0} * @see ReactiveX operators documentation: Do + * @since 1.3 */ - @Experimental public final Single doAfterTerminate(Action0 action) { return create(new SingleDoAfterTerminate(this, action)); } @@ -2589,7 +2572,7 @@ public final Single doAfterTerminate(Action0 action) { */ @SuppressWarnings("unchecked") static Single[] iterableToArray(final Iterable> singlesIterable) { - final Single[] singlesArray; + Single[] singlesArray; int count; if (singlesIterable instanceof Collection) { @@ -2642,7 +2625,7 @@ public final Single retry() { } /** - * Returns an Single that mirrors the source Single, resubscribing to it if it calls {@code onError} + * Returns a Single that mirrors the source Single, resubscribing to it if it calls {@code onError} * up to a specified number of retries. * * @@ -2667,7 +2650,7 @@ public final Single retry(final long count) { } /** - * Returns an Single that mirrors the source Single, resubscribing to it if it calls {@code onError} + * Returns a Single that mirrors the source Single, resubscribing to it if it calls {@code onError} * and the predicate returns true for that specific exception and retry count. * * @@ -2694,13 +2677,13 @@ public final Single retry(Func2 predicate) { * Returns a Single that emits the same values as the source Single with the exception of an * {@code onError}. An {@code onError} notification from the source will result in the emission of a * {@link Throwable} item to the Observable provided as an argument to the {@code notificationHandler} - * function. + * function. *

    Emissions from the handler {@code Observable} is treated as follows: *

      *
    • If the handler {@code Observable} emits an {@code onCompleted} the {@code retryWhen} will call {@code onError} - * with {@code NoSuchElementException} on the child subscription.
    • + * with {@code NoSuchElementException} on the child subscription. *
    • If the handler {@code Observable} emits an {@code onError} the {@code retryWhen} will call - * {@code onError} with the same Throwable instance on the child subscription. + * {@code onError} with the same Throwable instance on the child subscription. *
    • Otherwise, the operator will resubscribe to the source Single.
    • *
    *

    The {@code notificationHandler} function is called for each subscriber individually. This allows per-Subscriber @@ -2712,10 +2695,10 @@ public final Single retry(Func2 predicate) { * }).subscribe(...); * *

    - * Note that you must compose over the input {@code Observable} provided in the function call because {@retryWhen} expects + * Note that you must compose over the input {@code Observable} provided in the function call because {@link #retryWhen} expects * an emission of the exception to be matched by an event from the handler Observable. *

    - * + * * * *

    @@ -2735,14 +2718,14 @@ public final Single retryWhen(final Func1, ? } /** - * Constructs an Single that creates a dependent resource object which is disposed of on unsubscription. + * Constructs a Single that creates a dependent resource object which is disposed of on unsubscription. *

    * *

    *
    Scheduler:
    *
    {@code using} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param the value type of the generated source * @param the type of the per-subscriber resource * @param resourceFactory @@ -2753,17 +2736,17 @@ public final Single retryWhen(final Func1, ? * the function that will dispose of the resource * @return the Single whose lifetime controls the lifetime of the dependent resource object * @see ReactiveX operators documentation: Using + * @since 1.3 */ - @Experimental public static Single using( final Func0 resourceFactory, final Func1> singleFactory, final Action1 disposeAction) { return using(resourceFactory, singleFactory, disposeAction, false); } - + /** - * Constructs an Single that creates a dependent resource object which is disposed of just before + * Constructs a Single that creates a dependent resource object which is disposed of just before * termination if you have set {@code disposeEagerly} to {@code true} and unsubscription does not occur * before termination. Otherwise resource disposal will occur on unsubscription. Eager disposal is * particularly appropriate for a synchronous Single that reuses resources. {@code disposeAction} will @@ -2774,7 +2757,7 @@ public static Single using( *
    Scheduler:
    *
    {@code using} does not operate by default on a particular {@link Scheduler}.
    *
    - * + * * @param the value type of the generated source * @param the type of the per-subscriber resource * @param resourceFactory @@ -2784,14 +2767,12 @@ public static Single using( * @param disposeAction * the function that will dispose of the resource * @param disposeEagerly - * if {@code true} then disposal will happen either on unsubscription or just before emission of + * if {@code true} then disposal will happen either on unsubscription or just before emission of * a terminal event ({@code onComplete} or {@code onError}). * @return the Single whose lifetime controls the lifetime of the dependent resource object * @see ReactiveX operators documentation: Using - * @Experimental The behavior of this can change at any time. - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Experimental public static Single using( final Func0 resourceFactory, final Func1> singleFactory, @@ -2824,12 +2805,80 @@ public static Single using( * to this Single. * @return a Single that delays the subscription to this Single * until the Observable emits an element or completes normally. + * @since 1.3 */ - @Experimental public final Single delaySubscription(Observable other) { if (other == null) { throw new NullPointerException(); } return create(new SingleOnSubscribeDelaySubscriptionOther(this, other)); } + + /** + * Returns a Single which makes sure when a subscriber cancels the subscription, + * the dispose is called on the specified scheduler + * @param scheduler the target scheduler where to execute the cancellation + * @return the new Single instance + * @since 1.2.8 - experimental + */ + @Experimental + public final Single unsubscribeOn(final Scheduler scheduler) { + return create(new OnSubscribe() { + @Override + public void call(final SingleSubscriber t) { + final SingleSubscriber single = new SingleSubscriber() { + @Override + public void onSuccess(T value) { + t.onSuccess(value); + } + + @Override + public void onError(Throwable error) { + t.onError(error); + } + }; + + t.add(Subscriptions.create(new Action0() { + @Override + public void call() { + final Scheduler.Worker w = scheduler.createWorker(); + w.schedule(new Action0() { + @Override + public void call() { + try { + single.unsubscribe(); + } finally { + w.unsubscribe(); + } + } + }); + } + })); + + Single.this.subscribe(single); + } + }); + } + + // ------------------------------------------------------------------------- + // Fluent test support, super handy and reduces test preparation boilerplate + // ------------------------------------------------------------------------- + /** + * Creates an AssertableSubscriber that requests {@code Long.MAX_VALUE} and subscribes + * it to this Observable. + *
    + *
    Backpressure:
    + *
    The returned AssertableSubscriber consumes this Observable in an unbounded fashion.
    + *
    Scheduler:
    + *
    {@code test} does not operate by default on a particular {@link Scheduler}.
    + *
    + *

    History: 1.2.3 - experimental + * @return the new AssertableSubscriber instance + * @since 1.3 + */ + public final AssertableSubscriber test() { + AssertableSubscriberObservable ts = AssertableSubscriberObservable.create(Long.MAX_VALUE); + subscribe(ts); + return ts; + } } diff --git a/src/main/java/rx/SingleEmitter.java b/src/main/java/rx/SingleEmitter.java new file mode 100644 index 0000000000..53d034a076 --- /dev/null +++ b/src/main/java/rx/SingleEmitter.java @@ -0,0 +1,68 @@ +/* + * Copyright 2016 Netflix, Inc. + * + * 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 rx; + +import rx.functions.Cancellable; + +/** + * Abstraction over a {@link SingleSubscriber} that gets either an onSuccess or onError + * signal and allows registering an cancellation/unsubscription callback. + *

    + * All methods are thread-safe; calling onSuccess or onError twice or one after the other has + * no effect. + *

    History: 1.2.3 - experimental + * @param the success value type + * @since 1.3 + */ +public interface SingleEmitter { + + /** + * Notifies the SingleSubscriber that the {@link Single} has completed successfully with + * the given value. + *

    + * If the {@link Single} calls this method, it will not thereafter call + * {@link #onError}. + * + * @param t the success value + */ + void onSuccess(T t); + + /** + * Notifies the SingleSubscriber that the {@link Single} has experienced an error condition. + *

    + * If the {@link Single} calls this method, it will not thereafter call + * {@link #onSuccess}. + * + * @param t + * the exception encountered by the Observable + */ + void onError(Throwable t); + + /** + * Sets a Subscription on this emitter; any previous Subscription + * or Cancellation will be unsubscribed/cancelled. + * @param s the subscription, null is allowed + */ + void setSubscription(Subscription s); + + /** + * Sets a Cancellable on this emitter; any previous Subscription + * or Cancellation will be unsubscribed/cancelled. + * @param c the cancellable resource, null is allowed + */ + void setCancellation(Cancellable c); + +} diff --git a/src/main/java/rx/SingleSubscriber.java b/src/main/java/rx/SingleSubscriber.java index 7ab135e8ab..0860dd331f 100644 --- a/src/main/java/rx/SingleSubscriber.java +++ b/src/main/java/rx/SingleSubscriber.java @@ -1,12 +1,12 @@ /** * Copyright 2015 Netflix, Inc. - * + * * 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. @@ -15,7 +15,6 @@ */ package rx; -import rx.annotations.Beta; import rx.internal.util.SubscriptionList; /** @@ -25,38 +24,40 @@ * {@code Single} calls the SingleSubscriber's {@link #onSuccess} and {@link #onError} methods to provide * notifications. A well-behaved {@code Single} will call a SingleSubscriber's {@link #onSuccess} method exactly * once or the SingleSubscriber's {@link #onError} method exactly once. - * + *

    + * Note, that if you want {@link #isUnsubscribed} to return {@code true} after {@link #onSuccess} or {@link #onError} + * invocation, you need to invoke {@link #unsubscribe} in these methods. + * * @see ReactiveX documentation: Observable * @param * the type of item the SingleSubscriber expects to observe - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.2 */ -@Beta public abstract class SingleSubscriber implements Subscription { private final SubscriptionList cs = new SubscriptionList(); - + /** * Notifies the SingleSubscriber with a single item and that the {@link Single} has finished sending * push-based notifications. *

    * The {@link Single} will not call this method if it calls {@link #onError}. - * - * @param value + * + * @param t * the item emitted by the Single */ - public abstract void onSuccess(T value); + public abstract void onSuccess(T t); /** * Notifies the SingleSubscriber that the {@link Single} has experienced an error condition. *

    * If the {@link Single} calls this method, it will not thereafter call {@link #onSuccess}. - * + * * @param error * the exception encountered by the Single */ public abstract void onError(Throwable error); - + /** * Adds a {@link Subscription} to this Subscriber's list of subscriptions if this list is not marked as * unsubscribed. If the list is marked as unsubscribed, {@code add} will indicate this by @@ -76,7 +77,7 @@ public final void unsubscribe() { /** * Indicates whether this Subscriber has unsubscribed from its list of subscriptions. - * + * * @return {@code true} if this Subscriber has unsubscribed from its subscriptions, {@code false} otherwise */ @Override diff --git a/src/main/java/rx/Subscriber.java b/src/main/java/rx/Subscriber.java index 3b8913a9f0..6eb197cba8 100644 --- a/src/main/java/rx/Subscriber.java +++ b/src/main/java/rx/Subscriber.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -25,13 +25,13 @@ * {@link Observable} calls the Subscriber's {@link #onNext} method to emit items. A well-behaved * {@link Observable} will call a Subscriber's {@link #onCompleted} method exactly once or the Subscriber's * {@link #onError} method exactly once. - * + * * @see ReactiveX documentation: Observable * @param * the type of items the Subscriber expects to observe */ public abstract class Subscriber implements Observer, Subscription { - + // represents requested not set yet private static final long NOT_SET = Long.MIN_VALUE; @@ -50,7 +50,7 @@ protected Subscriber() { * Construct a Subscriber by using another Subscriber for backpressure and * for holding the subscription list (when this.add(sub) is * called this will in fact call subscriber.add(sub)). - * + * * @param subscriber * the other Subscriber */ @@ -68,7 +68,7 @@ protected Subscriber(Subscriber subscriber) { * To retain the chaining of subscribers when setting * shareSubscriptions to false, add the created * instance to {@code subscriber} via {@link #add}. - * + * * @param subscriber * the other Subscriber * @param shareSubscriptions @@ -100,7 +100,7 @@ public final void unsubscribe() { /** * Indicates whether this Subscriber has unsubscribed from its list of subscriptions. - * + * * @return {@code true} if this Subscriber has unsubscribed from its subscriptions, {@code false} otherwise */ @Override @@ -116,22 +116,22 @@ public final boolean isUnsubscribed() { public void onStart() { // do nothing by default } - + /** * Request a certain maximum number of emitted items from the Observable this Subscriber is subscribed to. * This is a way of requesting backpressure. To disable backpressure, pass {@code Long.MAX_VALUE} to this * method. *

    - * Requests are additive but if a sequence of requests totals more than {@code Long.MAX_VALUE} then - * {@code Long.MAX_VALUE} requests will be actioned and the extras may be ignored. Arriving at - * {@code Long.MAX_VALUE} by addition of requests cannot be assumed to disable backpressure. For example, + * Requests are additive but if a sequence of requests totals more than {@code Long.MAX_VALUE} then + * {@code Long.MAX_VALUE} requests will be actioned and the extras may be ignored. Arriving at + * {@code Long.MAX_VALUE} by addition of requests cannot be assumed to disable backpressure. For example, * the code below may result in {@code Long.MAX_VALUE} requests being actioned only. - * + * *

          * request(100);
          * request(Long.MAX_VALUE-1);
          * 
    - * + * * @param n the maximum number of items you want the Observable to emit to the Subscriber at this time, or * {@code Long.MAX_VALUE} if you want the Observable to emit items at its own pace * @throws IllegalArgumentException @@ -140,11 +140,11 @@ public void onStart() { protected final void request(long n) { if (n < 0) { throw new IllegalArgumentException("number requested cannot be negative: " + n); - } - + } + // if producer is set then we will request from it // otherwise we increase the requested count by n - Producer producerToRequestFrom = null; + Producer producerToRequestFrom; synchronized (this) { if (producer != null) { producerToRequestFrom = producer; @@ -160,7 +160,7 @@ protected final void request(long n) { private void addToRequested(long n) { if (requested == NOT_SET) { requested = n; - } else { + } else { final long total = requested + n; // check if overflow occurred if (total < 0) { @@ -170,7 +170,7 @@ private void addToRequested(long n) { } } } - + /** * If other subscriber is set (by calling constructor * {@link #Subscriber(Subscriber)} or @@ -181,7 +181,7 @@ private void addToRequested(long n) { * is not set and some requests have been made to this subscriber then * p.request(n) is called where n is the accumulated requests * to this subscriber. - * + * * @param p * producer to be used by this subscriber or the other subscriber * (or recursively its other subscriber) to make requests from diff --git a/src/main/java/rx/Subscription.java b/src/main/java/rx/Subscription.java index 00358903c4..ab0f03cf34 100644 --- a/src/main/java/rx/Subscription.java +++ b/src/main/java/rx/Subscription.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -30,7 +30,7 @@ public interface Subscription { * Stops the receipt of notifications on the {@link Subscriber} that was registered when this Subscription * was received. *

    - * This allows unregistering an {@link Subscriber} before it has finished receiving all events (i.e. before + * This allows deregistering an {@link Subscriber} before it has finished receiving all events (i.e. before * onCompleted is called). */ void unsubscribe(); diff --git a/src/main/java/rx/annotations/Beta.java b/src/main/java/rx/annotations/Beta.java index 0a117d6d84..8625beaaa5 100644 --- a/src/main/java/rx/annotations/Beta.java +++ b/src/main/java/rx/annotations/Beta.java @@ -2,19 +2,19 @@ /* * Copyright (C) 2010 The Guava Authors - * + * * 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. - * + * * Originally from https://code.google.com/p/guava-libraries/source/browse/guava/src/com/google/common/annotations/Beta.java */ @@ -34,7 +34,7 @@ * *

    It is generally safe for applications to depend on beta APIs, at * the cost of some extra work during upgrades. However it is generally - * inadvisable for libraries (which get included on users' CLASSPATHs, + * inadvisable for libraries (which get included on users' {@code CLASSPATH}s, * outside the library developers' control) to do so. * **/ @@ -46,6 +46,5 @@ ElementType.METHOD, ElementType.TYPE }) @Documented -@Beta public @interface Beta { } \ No newline at end of file diff --git a/src/main/java/rx/annotations/Experimental.java b/src/main/java/rx/annotations/Experimental.java index 52619a56a7..dbcaaa3fe6 100644 --- a/src/main/java/rx/annotations/Experimental.java +++ b/src/main/java/rx/annotations/Experimental.java @@ -4,15 +4,15 @@ * 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. - * + * * Inspired from https://code.google.com/p/guava-libraries/source/browse/guava/src/com/google/common/annotations/Beta.java */ @@ -24,9 +24,9 @@ /** * Signifies that a public API (public class, method or field) is will almost certainly - * be changed or removed in a future release. An API bearing this annotation should not + * be changed or removed in a future release. An API bearing this annotation should not * be used or relied upon in production code. APIs exposed with this annotation exist - * to allow broad testing and feedback on experimental features. + * to allow broad testing and feedback on experimental features. **/ @Retention(RetentionPolicy.CLASS) @Target({ @@ -36,6 +36,5 @@ ElementType.METHOD, ElementType.TYPE }) @Documented -@Experimental public @interface Experimental { } \ No newline at end of file diff --git a/src/main/java/rx/exceptions/AssemblyStackTraceException.java b/src/main/java/rx/exceptions/AssemblyStackTraceException.java index ee6a8be6a9..c21cb94510 100644 --- a/src/main/java/rx/exceptions/AssemblyStackTraceException.java +++ b/src/main/java/rx/exceptions/AssemblyStackTraceException.java @@ -1,12 +1,12 @@ /** * Copyright 2016 Netflix, Inc. - * + * * 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. @@ -15,28 +15,20 @@ */ package rx.exceptions; -import rx.annotations.Experimental; +import java.util.*; + +import rx.plugins.RxJavaHooks; /** * A RuntimeException that is stackless but holds onto a textual * stacktrace from tracking the assembly location of operators. + * @since 1.3 */ -@Experimental public final class AssemblyStackTraceException extends RuntimeException { /** */ private static final long serialVersionUID = 2038859767182585852L; - /** - * Constructs an AssemblyStackTraceException with the given message and - * a cause. - * @param message the message - * @param cause the cause - */ - public AssemblyStackTraceException(String message, Throwable cause) { - super(message, cause); - } - /** * Constructs an AssemblyStackTraceException with the given message. * @param message the message @@ -46,7 +38,59 @@ public AssemblyStackTraceException(String message) { } @Override - public synchronized Throwable fillInStackTrace() { + public synchronized Throwable fillInStackTrace() { // NOPMD return this; } + + /** + * Finds an empty cause slot and assigns itself to it. + * @param exception the exception to start from + */ + public void attachTo(Throwable exception) { + Set memory = new HashSet(); + + for (;;) { + if (exception.getCause() == null) { + try { + exception.initCause(this); + } catch (IllegalStateException e) { + RxJavaHooks.onError(new RuntimeException( + "Received an exception with a cause set to null, instead of being unset." + + " To fix this, look down the chain of causes. The last exception had" + + " a cause explicitly set to null. It should be unset instead.", + exception)); + } + return; + } + + exception = exception.getCause(); + if (!memory.add(exception)) { + // in case we run into a cycle, give up and report this to the hooks + RxJavaHooks.onError(this); + return; + } + } + } + + /** + * Locate the first AssemblyStackTraceException in the causal chain of the + * given Throwable (or it if it's one). + * @param e the input throwable + * @return the AssemblyStackTraceException located or null if not found + */ + public static AssemblyStackTraceException find(Throwable e) { + Set memory = new HashSet(); + for (;;) { + if (e instanceof AssemblyStackTraceException) { + return (AssemblyStackTraceException)e; + } + if (e == null || e.getCause() == null) { + return null; + } + e = e.getCause(); + if (!memory.add(e)) { + return null; + } + } + } } diff --git a/src/main/java/rx/exceptions/CompositeException.java b/src/main/java/rx/exceptions/CompositeException.java index 310cfab8ae..855d720e49 100644 --- a/src/main/java/rx/exceptions/CompositeException.java +++ b/src/main/java/rx/exceptions/CompositeException.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -18,8 +18,6 @@ import java.io.*; import java.util.*; -import rx.annotations.Experimental; - /** * Represents an exception that is a composite of one or more other exceptions. A {@code CompositeException} * does not modify the structure of any exception it wraps, but at print-time it iterates through the list of @@ -27,10 +25,10 @@ * * Its invariant is to contain an immutable, ordered (by insertion order), unique list of non-composite * exceptions. You can retrieve individual exceptions in this list with {@link #getExceptions()}. - * + * * The {@link #printStackTrace()} implementation handles the StackTrace in a customized way instead of using * {@code getCause()} so that it can avoid circular references. - * + * * If you invoke {@link #getCause()}, it will lazily create the causal chain but will stop if it finds any * Throwable in the chain that it has already seen. */ @@ -41,20 +39,22 @@ public final class CompositeException extends RuntimeException { private final List exceptions; private final String message; - /** + private Throwable cause; + + /** * Constructs a CompositeException with the given prefix and error collection. * @param messagePrefix the prefix to use (actually unused) * @param errors the collection of errors * @deprecated please use {@link #CompositeException(Collection)} */ @Deprecated - public CompositeException(String messagePrefix, Collection errors) { + public CompositeException(String messagePrefix, Collection errors) { // NOPMD Set deDupedExceptions = new LinkedHashSet(); - List _exceptions = new ArrayList(); + List localExceptions = new ArrayList(); if (errors != null) { for (Throwable ex : errors) { if (ex instanceof CompositeException) { deDupedExceptions.addAll(((CompositeException) ex).getExceptions()); - } else + } else if (ex != null) { deDupedExceptions.add(ex); } else { @@ -65,11 +65,17 @@ public CompositeException(String messagePrefix, Collection deDupedExceptions.add(new NullPointerException()); } - _exceptions.addAll(deDupedExceptions); - this.exceptions = Collections.unmodifiableList(_exceptions); + localExceptions.addAll(deDupedExceptions); + this.exceptions = Collections.unmodifiableList(localExceptions); this.message = exceptions.size() + " exceptions occurred. "; } + /** + * Constructs a CompositeException instance with the Throwable elements + * of the supplied Collection. + *

    Null values are replaced by {@link NullPointerException}. + * @param errors the collection of errors + */ public CompositeException(Collection errors) { this(null, errors); } @@ -77,16 +83,16 @@ public CompositeException(Collection errors) { /** * Constructs a CompositeException instance with the supplied initial Throwables. * @param errors the array of Throwables + * @since 1.3 */ - @Experimental public CompositeException(Throwable... errors) { Set deDupedExceptions = new LinkedHashSet(); - List _exceptions = new ArrayList(); + List localExceptions = new ArrayList(); if (errors != null) { for (Throwable ex : errors) { if (ex instanceof CompositeException) { deDupedExceptions.addAll(((CompositeException) ex).getExceptions()); - } else + } else if (ex != null) { deDupedExceptions.add(ex); } else { @@ -97,8 +103,8 @@ public CompositeException(Throwable... errors) { deDupedExceptions.add(new NullPointerException()); } - _exceptions.addAll(deDupedExceptions); - this.exceptions = Collections.unmodifiableList(_exceptions); + localExceptions.addAll(deDupedExceptions); + this.exceptions = Collections.unmodifiableList(localExceptions); this.message = exceptions.size() + " exceptions occurred. "; } @@ -116,26 +122,24 @@ public String getMessage() { return message; } - private Throwable cause = null; - @Override - public synchronized Throwable getCause() { + public synchronized Throwable getCause() { // NOPMD if (cause == null) { // we lazily generate this causal chain if this is called - CompositeExceptionCausalChain _cause = new CompositeExceptionCausalChain(); + CompositeExceptionCausalChain localCause = new CompositeExceptionCausalChain(); Set seenCauses = new HashSet(); - Throwable chain = _cause; + Throwable chain = localCause; for (Throwable e : exceptions) { if (seenCauses.contains(e)) { // already seen this outer Throwable so skip continue; } seenCauses.add(e); - + List listOfCauses = getListOfCauses(e); // check if any of them have been seen before - for(Throwable child : listOfCauses) { + for (Throwable child : listOfCauses) { if (seenCauses.contains(child)) { // already seen this outer Throwable so skip e = new RuntimeException("Duplicate found in causal chain so cropping to prevent loop ..."); @@ -147,14 +151,14 @@ public synchronized Throwable getCause() { // we now have 'e' as the last in the chain try { chain.initCause(e); - } catch (Throwable t) { + } catch (Throwable t) { // NOPMD // ignore // the javadocs say that some Throwables (depending on how they're made) will never // let me call initCause without blowing up even if it returns null } chain = getRootCause(chain); } - cause = _cause; + cause = localCause; } return cause; } @@ -187,39 +191,39 @@ public void printStackTrace(PrintWriter s) { /** * Special handling for printing out a {@code CompositeException}. * Loops through all inner exceptions and prints them out. - * + * * @param s * stream to print to */ private void printStackTrace(PrintStreamOrWriter s) { - StringBuilder bldr = new StringBuilder(); - bldr.append(this).append("\n"); + StringBuilder b = new StringBuilder(128); + b.append(this).append('\n'); for (StackTraceElement myStackElement : getStackTrace()) { - bldr.append("\tat ").append(myStackElement).append("\n"); + b.append("\tat ").append(myStackElement).append('\n'); } int i = 1; for (Throwable ex : exceptions) { - bldr.append(" ComposedException ").append(i).append(" :").append("\n"); - appendStackTrace(bldr, ex, "\t"); + b.append(" ComposedException ").append(i).append(" :\n"); + appendStackTrace(b, ex, "\t"); i++; } synchronized (s.lock()) { - s.println(bldr.toString()); + s.println(b.toString()); } } - private void appendStackTrace(StringBuilder bldr, Throwable ex, String prefix) { - bldr.append(prefix).append(ex).append("\n"); + private void appendStackTrace(StringBuilder b, Throwable ex, String prefix) { + b.append(prefix).append(ex).append('\n'); for (StackTraceElement stackElement : ex.getStackTrace()) { - bldr.append("\t\tat ").append(stackElement).append("\n"); + b.append("\t\tat ").append(stackElement).append('\n'); } if (ex.getCause() != null) { - bldr.append("\tCaused by: "); - appendStackTrace(bldr, ex.getCause(), ""); + b.append("\tCaused by: "); + appendStackTrace(b, ex.getCause(), ""); } } - private abstract static class PrintStreamOrWriter { + abstract static class PrintStreamOrWriter { /** Returns the object to be locked when using this StreamOrWriter */ abstract Object lock(); @@ -230,7 +234,7 @@ private abstract static class PrintStreamOrWriter { /** * Same abstraction and implementation as in JDK to allow PrintStream and PrintWriter to share implementation */ - private static class WrappedPrintStream extends PrintStreamOrWriter { + static final class WrappedPrintStream extends PrintStreamOrWriter { private final PrintStream printStream; WrappedPrintStream(PrintStream printStream) { @@ -248,7 +252,7 @@ void println(Object o) { } } - private static class WrappedPrintWriter extends PrintStreamOrWriter { + static final class WrappedPrintWriter extends PrintStreamOrWriter { private final PrintWriter printWriter; WrappedPrintWriter(PrintWriter printWriter) { @@ -268,7 +272,7 @@ void println(Object o) { /* package-private */final static class CompositeExceptionCausalChain extends RuntimeException { private static final long serialVersionUID = 3875212506787802066L; - /* package-private */static String MESSAGE = "Chain of Causes for CompositeException In Order Received =>"; + /* package-private */static final String MESSAGE = "Chain of Causes for CompositeException In Order Received =>"; @Override public String getMessage() { @@ -282,7 +286,7 @@ private List getListOfCauses(Throwable ex) { if (root == null || root == ex) { return list; } else { - while(true) { + while (true) { list.add(root); Throwable cause = root.getCause(); if (cause == null || cause == root) { @@ -295,17 +299,17 @@ private List getListOfCauses(Throwable ex) { } /** - * Returns the root cause of {@code e}. If {@code e.getCause()} returns {@null} or {@code e}, just return {@code e} itself. + * Returns the root cause of {@code e}. If {@code e.getCause()} returns {@code null} or {@code e}, just return {@code e} itself. * * @param e the {@link Throwable} {@code e}. - * @return The root cause of {@code e}. If {@code e.getCause()} returns {@null} or {@code e}, just return {@code e} itself. + * @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) { Throwable root = e.getCause(); if (root == null || root == e) { return e; } else { - while(true) { + while (true) { Throwable cause = root.getCause(); if (cause == null || cause == root) { return root; diff --git a/src/main/java/rx/exceptions/Exceptions.java b/src/main/java/rx/exceptions/Exceptions.java index f427018f53..9e5f25393d 100644 --- a/src/main/java/rx/exceptions/Exceptions.java +++ b/src/main/java/rx/exceptions/Exceptions.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -19,13 +19,15 @@ import rx.Observer; import rx.SingleSubscriber; -import rx.annotations.Experimental; /** * Utility class with methods to wrap checked exceptions and * manage fatal and regular exception delivery. */ public final class Exceptions { + + private static final int MAX_DEPTH = 25; + /** Utility class, no instances. */ private Exceptions() { throw new IllegalStateException("No instances!"); @@ -41,9 +43,9 @@ private Exceptions() { public static RuntimeException propagate(Throwable t) { /* * The return type of RuntimeException is a trick for code to be like this: - * + * * throw Exceptions.propagate(e); - * + * * Even though nothing will return and throw via that 'throw', it allows the code to look like it * so it's easy to read and understand that it will always result in a throw. */ @@ -52,7 +54,7 @@ public static RuntimeException propagate(Throwable t) { } else if (t instanceof Error) { throw (Error) t; } else { - throw new RuntimeException(t); + throw new RuntimeException(t); // NOPMD } } /** @@ -62,7 +64,6 @@ public static RuntimeException propagate(Throwable t) { *

  • {@link OnErrorNotImplementedException}
  • *
  • {@link OnErrorFailedException}
  • *
  • {@link OnCompletedFailedException}
  • - *
  • {@code StackOverflowError}
  • *
  • {@code VirtualMachineError}
  • *
  • {@code ThreadDeath}
  • *
  • {@code LinkageError}
  • @@ -85,9 +86,7 @@ public static void throwIfFatal(Throwable t) { throw (OnCompletedFailedException) t; } // values here derived from https://github.com/ReactiveX/RxJava/issues/748#issuecomment-32471495 - else if (t instanceof StackOverflowError) { - throw (StackOverflowError) t; - } else if (t instanceof VirtualMachineError) { + else if (t instanceof VirtualMachineError) { throw (VirtualMachineError) t; } else if (t instanceof ThreadDeath) { throw (ThreadDeath) t; @@ -96,8 +95,6 @@ else if (t instanceof StackOverflowError) { } } - private static final int MAX_DEPTH = 25; - /** * Adds a {@code Throwable} to a causality-chain of Throwables, as an additional cause (if it does not * already appear in the chain among the causes). @@ -126,7 +123,7 @@ public static void addCause(Throwable e, Throwable cause) { // we now have 'e' as the last in the chain try { e.initCause(cause); - } catch (Throwable t) { + } catch (Throwable t) { // NOPMD // ignore // the javadocs say that some Throwables (depending on how they're made) will never // let me call initCause without blowing up even if it returns null @@ -164,41 +161,52 @@ public static void throwIfAny(List exceptions) { if (exceptions != null && !exceptions.isEmpty()) { if (exceptions.size() == 1) { Throwable t = exceptions.get(0); - // had to manually inline propagate because some tests attempt StackOverflowError + // had to manually inline propagate because some tests attempt StackOverflowError // and can't handle it with the stack space remaining if (t instanceof RuntimeException) { throw (RuntimeException) t; } else if (t instanceof Error) { throw (Error) t; } else { - throw new RuntimeException(t); + throw new RuntimeException(t); // NOPMD } } throw new CompositeException(exceptions); } } - + /** * Forwards a fatal exception or reports it along with the value * caused it to the given Observer. * @param t the exception * @param o the observer to report to * @param value the value that caused the exception - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Experimental public static void throwOrReport(Throwable t, Observer o, Object value) { Exceptions.throwIfFatal(t); o.onError(OnErrorThrowable.addValueAsLastCause(t, value)); } + /** + * Forwards a fatal exception or reports it along with the value + * caused it to the given SingleSubscriber. + * @param t the exception + * @param o the observer to report to + * @param value the value that caused the exception + * @since 1.3 + */ + public static void throwOrReport(Throwable t, SingleSubscriber o, Object value) { + Exceptions.throwIfFatal(t); + o.onError(OnErrorThrowable.addValueAsLastCause(t, value)); + } + /** * Forwards a fatal exception or reports it to the given Observer. * @param t the exception * @param o the observer to report to - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Experimental public static void throwOrReport(Throwable t, Observer o) { Exceptions.throwIfFatal(t); o.onError(t); @@ -209,9 +217,8 @@ public static void throwOrReport(Throwable t, Observer o) { * * @param throwable the exception. * @param subscriber the subscriber to report to. - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number). + * @since 1.3 */ - @Experimental public static void throwOrReport(Throwable throwable, SingleSubscriber subscriber) { Exceptions.throwIfFatal(throwable); subscriber.onError(throwable); diff --git a/src/main/java/rx/exceptions/MissingBackpressureException.java b/src/main/java/rx/exceptions/MissingBackpressureException.java index b113d6536c..221b628bb1 100644 --- a/src/main/java/rx/exceptions/MissingBackpressureException.java +++ b/src/main/java/rx/exceptions/MissingBackpressureException.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -52,7 +52,7 @@ public class MissingBackpressureException extends Exception { * Constructs the exception without any custom message. */ public MissingBackpressureException() { - + super(); } /** diff --git a/src/main/java/rx/exceptions/OnCompletedFailedException.java b/src/main/java/rx/exceptions/OnCompletedFailedException.java index e6ae90c5ff..c474f009eb 100644 --- a/src/main/java/rx/exceptions/OnCompletedFailedException.java +++ b/src/main/java/rx/exceptions/OnCompletedFailedException.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -33,7 +33,7 @@ public final class OnCompletedFailedException extends RuntimeException { public OnCompletedFailedException(Throwable throwable) { super(throwable != null ? throwable : new NullPointerException()); } - + /** * Customizes the {@code Throwable} with a custom message and wraps it before it is to be re-thrown as an * {@code OnCompletedFailedException}. diff --git a/src/main/java/rx/exceptions/OnErrorFailedException.java b/src/main/java/rx/exceptions/OnErrorFailedException.java index a79000c21d..8d297c077b 100644 --- a/src/main/java/rx/exceptions/OnErrorFailedException.java +++ b/src/main/java/rx/exceptions/OnErrorFailedException.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. diff --git a/src/main/java/rx/exceptions/OnErrorNotImplementedException.java b/src/main/java/rx/exceptions/OnErrorNotImplementedException.java index d707a791fa..1763d912e9 100644 --- a/src/main/java/rx/exceptions/OnErrorNotImplementedException.java +++ b/src/main/java/rx/exceptions/OnErrorNotImplementedException.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. diff --git a/src/main/java/rx/exceptions/OnErrorThrowable.java b/src/main/java/rx/exceptions/OnErrorThrowable.java index 2ef465141b..b8c0c3555f 100644 --- a/src/main/java/rx/exceptions/OnErrorThrowable.java +++ b/src/main/java/rx/exceptions/OnErrorThrowable.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -15,11 +15,10 @@ */ package rx.exceptions; -import java.util.HashSet; -import java.util.Set; +import java.io.*; +import java.util.*; -import rx.plugins.RxJavaErrorHandler; -import rx.plugins.RxJavaPlugins; +import rx.plugins.*; /** * Represents a {@code Throwable} that an {@code Observable} might notify its subscribers of, but that then can @@ -43,7 +42,17 @@ private OnErrorThrowable(Throwable exception) { private OnErrorThrowable(Throwable exception, Object value) { super(exception); hasValue = true; - this.value = value; + Object v; + if (value instanceof Serializable) { + v = value; + } else { + try { + v = String.valueOf(value); + } catch (Throwable ex) { + v = ex.getMessage(); + } + } + this.value = v; } /** @@ -86,7 +95,7 @@ public static OnErrorThrowable from(Throwable t) { /** * Adds the given item as the final cause of the given {@code Throwable}, wrapped in {@code OnNextValue} * (which extends {@code RuntimeException}). - * + * * @param e * the {@link Throwable} to which you want to add a cause * @param value @@ -99,7 +108,7 @@ public static Throwable addValueAsLastCause(Throwable e, Object value) { e = new NullPointerException(); } Throwable lastCause = Exceptions.getFinalCause(e); - if (lastCause != null && lastCause instanceof OnNextValue) { + if (lastCause instanceof OnNextValue) { // purposefully using == for object reference check if (((OnNextValue) lastCause).getValue() == value) { // don't add another @@ -117,10 +126,12 @@ public static Throwable addValueAsLastCause(Throwable e, Object value) { public static class OnNextValue extends RuntimeException { private static final long serialVersionUID = -3454462756050397899L; - - // Lazy loaded singleton - private static final class Primitives { - + + private final Object value; + + // Lazy loaded singleton + static final class Primitives { + static final Set> INSTANCE = create(); private static Set> create() { @@ -133,14 +144,12 @@ private static Set> create() { set.add(Long.class); set.add(Float.class); set.add(Double.class); - // Void is another primitive but cannot be instantiated + // Void is another primitive but cannot be instantiated // and is caught by the null check in renderValue return set; } } - private final Object value; - /** * Create an {@code OnNextValue} exception and include in its error message a string representation of * the item that was intended to be emitted at the time the exception was handled. @@ -150,7 +159,17 @@ private static Set> create() { */ public OnNextValue(Object value) { super("OnError while emitting onNext value: " + renderValue(value)); - this.value = value; + Object v; + if (value instanceof Serializable) { + v = value; + } else { + try { + v = String.valueOf(value); + } catch (Throwable ex) { + v = ex.getMessage(); + } + } + this.value = v; } /** @@ -168,16 +187,16 @@ public Object getValue() { * * If a specific behavior has been defined in the {@link RxJavaErrorHandler} plugin, some types * may also have a specific rendering. Non-primitive types not managed by the plugin are rendered - * as the classname of the object. + * as the class name of the object. *

    * See PR #1401 and Issue #2468 for details. * * @param value * the item that the Observable was trying to emit at the time of the exception * @return a string version of the object if primitive or managed through error plugin, - * otherwise the classname of the object + * otherwise the class name of the object */ - static String renderValue(Object value){ + static String renderValue(Object value) { if (value == null) { return "null"; } diff --git a/src/main/java/rx/exceptions/UnsubscribeFailedException.java b/src/main/java/rx/exceptions/UnsubscribeFailedException.java index 69eb260ea2..0daba61fd4 100644 --- a/src/main/java/rx/exceptions/UnsubscribeFailedException.java +++ b/src/main/java/rx/exceptions/UnsubscribeFailedException.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -33,7 +33,7 @@ public final class UnsubscribeFailedException extends RuntimeException { public UnsubscribeFailedException(Throwable throwable) { super(throwable != null ? throwable : new NullPointerException()); } - + /** * Customizes the {@code Throwable} with a custom message and wraps it before it is to be re-thrown as an * {@code UnsubscribeFailedException}. @@ -46,5 +46,5 @@ public UnsubscribeFailedException(Throwable throwable) { public UnsubscribeFailedException(String message, Throwable throwable) { super(message, throwable != null ? throwable : new NullPointerException()); } - + } diff --git a/src/main/java/rx/exceptions/package-info.java b/src/main/java/rx/exceptions/package-info.java index 3cd8ac94dc..613c7416b9 100644 --- a/src/main/java/rx/exceptions/package-info.java +++ b/src/main/java/rx/exceptions/package-info.java @@ -15,7 +15,7 @@ */ /** - * Exception handling utilities, safe subscriber exception classes, + * Exception handling utilities, safe subscriber exception classes, * lifecycle exception classes. */ package rx.exceptions; \ No newline at end of file diff --git a/src/main/java/rx/functions/Action.java b/src/main/java/rx/functions/Action.java index 277533e325..8ea4cbf6be 100644 --- a/src/main/java/rx/functions/Action.java +++ b/src/main/java/rx/functions/Action.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. diff --git a/src/main/java/rx/functions/Action0.java b/src/main/java/rx/functions/Action0.java index cf11288d28..0c85ef33ca 100644 --- a/src/main/java/rx/functions/Action0.java +++ b/src/main/java/rx/functions/Action0.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. diff --git a/src/main/java/rx/functions/Action1.java b/src/main/java/rx/functions/Action1.java index ce9da099e7..c2bc25db99 100644 --- a/src/main/java/rx/functions/Action1.java +++ b/src/main/java/rx/functions/Action1.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. diff --git a/src/main/java/rx/functions/Action2.java b/src/main/java/rx/functions/Action2.java index c46b957f18..75e19ff936 100644 --- a/src/main/java/rx/functions/Action2.java +++ b/src/main/java/rx/functions/Action2.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. diff --git a/src/main/java/rx/functions/Action3.java b/src/main/java/rx/functions/Action3.java index e961358703..4b195d9965 100644 --- a/src/main/java/rx/functions/Action3.java +++ b/src/main/java/rx/functions/Action3.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. diff --git a/src/main/java/rx/functions/Action4.java b/src/main/java/rx/functions/Action4.java index 565fc51cbb..93bbdf74d0 100644 --- a/src/main/java/rx/functions/Action4.java +++ b/src/main/java/rx/functions/Action4.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. diff --git a/src/main/java/rx/functions/Action5.java b/src/main/java/rx/functions/Action5.java index dcc3971c4e..fe8fe5a52f 100644 --- a/src/main/java/rx/functions/Action5.java +++ b/src/main/java/rx/functions/Action5.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. diff --git a/src/main/java/rx/functions/Action6.java b/src/main/java/rx/functions/Action6.java index 0d298c4ab1..ec4a7eff23 100644 --- a/src/main/java/rx/functions/Action6.java +++ b/src/main/java/rx/functions/Action6.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. diff --git a/src/main/java/rx/functions/Action7.java b/src/main/java/rx/functions/Action7.java index 513844ae19..5978c18e95 100644 --- a/src/main/java/rx/functions/Action7.java +++ b/src/main/java/rx/functions/Action7.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. diff --git a/src/main/java/rx/functions/Action8.java b/src/main/java/rx/functions/Action8.java index 86a09a3c97..3fe35985e5 100644 --- a/src/main/java/rx/functions/Action8.java +++ b/src/main/java/rx/functions/Action8.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -24,7 +24,7 @@ * @param the fifth argument type * @param the sixth argument type * @param the seventh argument type - * @param the eigth argument type + * @param the eighth argument type */ public interface Action8 extends Action { void call(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7, T8 t8); diff --git a/src/main/java/rx/functions/Action9.java b/src/main/java/rx/functions/Action9.java index acb0c83c26..1296d14022 100644 --- a/src/main/java/rx/functions/Action9.java +++ b/src/main/java/rx/functions/Action9.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -24,7 +24,7 @@ * @param the fifth argument type * @param the sixth argument type * @param the seventh argument type - * @param the eigth argument type + * @param the eighth argument type * @param the ninth argument type */ public interface Action9 extends Action { diff --git a/src/main/java/rx/functions/ActionN.java b/src/main/java/rx/functions/ActionN.java index ccc714c341..d669f26ebd 100644 --- a/src/main/java/rx/functions/ActionN.java +++ b/src/main/java/rx/functions/ActionN.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. diff --git a/src/main/java/rx/functions/Actions.java b/src/main/java/rx/functions/Actions.java index 877c1adbda..91a8c4db0f 100644 --- a/src/main/java/rx/functions/Actions.java +++ b/src/main/java/rx/functions/Actions.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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 @@ -15,10 +15,15 @@ */ package rx.functions; +import rx.exceptions.OnErrorNotImplementedException; + /** * Utility class for the Action interfaces. */ public final class Actions { + @SuppressWarnings("rawtypes") + private static final EmptyAction EMPTY_ACTION = new EmptyAction(); + private Actions() { throw new IllegalStateException("No instances!"); } @@ -28,10 +33,7 @@ public static EmptyAction implements + static final class EmptyAction implements Action0, Action1, Action2, @@ -43,57 +45,66 @@ private static final class EmptyAction imple Action8, Action9, ActionN { - EmptyAction() { - } @Override public void call() { + // deliberately no op } @Override public void call(T0 t1) { + // deliberately no op } @Override public void call(T0 t1, T1 t2) { + // deliberately no op } @Override public void call(T0 t1, T1 t2, T2 t3) { + // deliberately no op } @Override public void call(T0 t1, T1 t2, T2 t3, T3 t4) { + // deliberately no op } @Override public void call(T0 t1, T1 t2, T2 t3, T3 t4, T4 t5) { + // deliberately no op } @Override public void call(T0 t1, T1 t2, T2 t3, T3 t4, T4 t5, T5 t6) { + // deliberately no op } @Override public void call(T0 t1, T1 t2, T2 t3, T3 t4, T4 t5, T5 t6, T6 t7) { + // deliberately no op } @Override public void call(T0 t1, T1 t2, T2 t3, T3 t4, T4 t5, T5 t6, T6 t7, T7 t8) { + // deliberately no op } @Override public void call(T0 t1, T1 t2, T2 t3, T3 t4, T4 t5, T5 t6, T6 t7, T7 t8, T8 t9) { + // deliberately no op } @Override public void call(Object... args) { + // deliberately no op } } - + /** * Converts an {@link Action0} to a function that calls the action and returns {@code null}. - * + * * @param action * the {@link Action0} to convert * @return a {@link Func0} that calls {@code action} and returns {@code null} @@ -104,7 +115,7 @@ public static Func0 toFunc(final Action0 action) { /** * Converts an {@link Action1} to a function that calls the action and returns {@code null}. - * + * * @param the first argument type * @param action * the {@link Action1} to convert @@ -116,7 +127,7 @@ public static Func1 toFunc(final Action1 action) { /** * Converts an {@link Action2} to a function that calls the action and returns {@code null}. - * + * * @param the first argument type * @param the second argument type * @param action @@ -129,7 +140,7 @@ public static Func2 toFunc(final Action2 action) /** * Converts an {@link Action3} to a function that calls the action and returns {@code null}. - * + * * @param the first argument type * @param the second argument type * @param the third argument type @@ -143,7 +154,7 @@ public static Func3 toFunc(final Action3 the first argument type * @param the second argument type * @param the third argument type @@ -158,7 +169,7 @@ public static Func4 toFunc(final Action4< /** * Converts an {@link Action5} to a function that calls the action and returns {@code null}. - * + * * @param the first argument type * @param the second argument type * @param the third argument type @@ -175,7 +186,7 @@ public static Func5 toFunc( /** * Converts an {@link Action6} to a function that calls the action and returns {@code null}. - * + * * @param the first argument type * @param the second argument type * @param the third argument type @@ -193,7 +204,7 @@ public static Func6 toFun /** * Converts an {@link Action7} to a function that calls the action and returns {@code null}. - * + * * @param the first argument type * @param the second argument type * @param the third argument type @@ -212,7 +223,7 @@ public static Func7 the first argument type * @param the second argument type * @param the third argument type @@ -220,7 +231,7 @@ public static Func7 the fifth argument type * @param the sixth argument type * @param the seventh argument type - * @param the eigth argument type + * @param the eighth argument type * @param action * the {@link Action8} to convert * @return a {@link Func8} that calls {@code action} and returns {@code null} @@ -232,7 +243,7 @@ public static Func8 the first argument type * @param the second argument type * @param the third argument type @@ -240,7 +251,7 @@ public static Func8 the fifth argument type * @param the sixth argument type * @param the seventh argument type - * @param the eigth argument type + * @param the eighth argument type * @param the ninth argument type * @param action * the {@link Action9} to convert @@ -253,7 +264,7 @@ public static Func9 toFunc( /** * Converts an {@link Action0} to a function that calls the action and returns a specified value. - * + * * @param the result type * @param action * the {@link Action0} to convert @@ -285,7 +296,7 @@ public R call() { /** * Converts an {@link Action1} to a function that calls the action and returns a specified value. - * + * * @param the first argument type * @param the result type * @param action @@ -306,7 +317,7 @@ public R call(T1 t1) { /** * Converts an {@link Action2} to a function that calls the action and returns a specified value. - * + * * @param the first argument type * @param the second argument type * @param the result type @@ -328,7 +339,7 @@ public R call(T1 t1, T2 t2) { /** * Converts an {@link Action3} to a function that calls the action and returns a specified value. - * + * * @param the first argument type * @param the second argument type * @param the third argument type @@ -351,7 +362,7 @@ public R call(T1 t1, T2 t2, T3 t3) { /** * Converts an {@link Action4} to a function that calls the action and returns a specified value. - * + * * @param the first argument type * @param the second argument type * @param the third argument type @@ -375,7 +386,7 @@ public R call(T1 t1, T2 t2, T3 t3, T4 t4) { /** * Converts an {@link Action5} to a function that calls the action and returns a specified value. - * + * * @param the first argument type * @param the second argument type * @param the third argument type @@ -401,7 +412,7 @@ public R call(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5) { /** * Converts an {@link Action6} to a function that calls the action and returns a specified value. - * + * * @param the first argument type * @param the second argument type * @param the third argument type @@ -428,7 +439,7 @@ public R call(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6) { /** * Converts an {@link Action7} to a function that calls the action and returns a specified value. - * + * * @param the first argument type * @param the second argument type * @param the third argument type @@ -456,7 +467,7 @@ public R call(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7) { /** * Converts an {@link Action8} to a function that calls the action and returns a specified value. - * + * * @param the first argument type * @param the second argument type * @param the third argument type @@ -464,7 +475,7 @@ public R call(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7) { * @param the fifth argument type * @param the sixth argument type * @param the seventh argument type - * @param the eigth argument type + * @param the eighth argument type * @param the result type * @param action * the {@link Action8} to convert @@ -485,7 +496,7 @@ public R call(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7, T8 t8) { /** * Converts an {@link Action9} to a function that calls the action and returns a specified value. - * + * * @param the first argument type * @param the second argument type * @param the third argument type @@ -493,7 +504,7 @@ public R call(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7, T8 t8) { * @param the fifth argument type * @param the sixth argument type * @param the seventh argument type - * @param the eigth argument type + * @param the eighth argument type * @param the ninth argument type * @param the result type * @param action @@ -515,7 +526,7 @@ public R call(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7, T8 t8, T9 t9) { /** * Converts an {@link ActionN} to a function that calls the action and returns a specified value. - * + * * @param the result type * @param action * the {@link ActionN} to convert @@ -533,7 +544,7 @@ public R call(Object... args) { } }; } - + /** * Wraps an Action0 instance into an Action1 instance where the latter calls * the former. @@ -544,17 +555,33 @@ public R call(Object... args) { public static Action1 toAction1(Action0 action) { return new Action1CallsAction0(action); } - + static final class Action1CallsAction0 implements Action1 { final Action0 action; - + public Action1CallsAction0(Action0 action) { this.action = action; } - + @Override public void call(T t) { action.call(); } } + + enum NotImplemented implements Action1 { + INSTANCE; + @Override + public void call(Throwable t) { + throw new OnErrorNotImplementedException(t); + } + } + + /** + * Returns an action which throws OnErrorNotImplementedException. + * @return the the shared action + */ + public static Action1 errorNotImplemented() { + return NotImplemented.INSTANCE; + } } diff --git a/src/main/java/rx/functions/Cancellable.java b/src/main/java/rx/functions/Cancellable.java new file mode 100644 index 0000000000..7b92b71884 --- /dev/null +++ b/src/main/java/rx/functions/Cancellable.java @@ -0,0 +1,32 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * 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 rx.functions; + +/** + * A functional interface that has a single close method that can throw. + * @since 1.3 + */ +public interface Cancellable { + + /** + * Cancel the action or free a resource. + * + * @throws Exception + * on error + */ + void cancel() throws Exception; +} \ No newline at end of file diff --git a/src/main/java/rx/functions/Func0.java b/src/main/java/rx/functions/Func0.java index fc9e10981b..b0179bce90 100644 --- a/src/main/java/rx/functions/Func0.java +++ b/src/main/java/rx/functions/Func0.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. diff --git a/src/main/java/rx/functions/Func1.java b/src/main/java/rx/functions/Func1.java index 64ffae0c60..2edc94d2fe 100644 --- a/src/main/java/rx/functions/Func1.java +++ b/src/main/java/rx/functions/Func1.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. diff --git a/src/main/java/rx/functions/Func2.java b/src/main/java/rx/functions/Func2.java index 660a11b79c..5222aecdf0 100644 --- a/src/main/java/rx/functions/Func2.java +++ b/src/main/java/rx/functions/Func2.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. diff --git a/src/main/java/rx/functions/Func3.java b/src/main/java/rx/functions/Func3.java index 0029ceebfb..f3b82bc729 100644 --- a/src/main/java/rx/functions/Func3.java +++ b/src/main/java/rx/functions/Func3.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. diff --git a/src/main/java/rx/functions/Func4.java b/src/main/java/rx/functions/Func4.java index 0daca118ee..229a5348ba 100644 --- a/src/main/java/rx/functions/Func4.java +++ b/src/main/java/rx/functions/Func4.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. diff --git a/src/main/java/rx/functions/Func5.java b/src/main/java/rx/functions/Func5.java index 7385065452..2e35bd2fa2 100644 --- a/src/main/java/rx/functions/Func5.java +++ b/src/main/java/rx/functions/Func5.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. diff --git a/src/main/java/rx/functions/Func6.java b/src/main/java/rx/functions/Func6.java index 9a8e2a436b..32c10b64d2 100644 --- a/src/main/java/rx/functions/Func6.java +++ b/src/main/java/rx/functions/Func6.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. diff --git a/src/main/java/rx/functions/Func7.java b/src/main/java/rx/functions/Func7.java index 79d23a829d..bc92638242 100644 --- a/src/main/java/rx/functions/Func7.java +++ b/src/main/java/rx/functions/Func7.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. diff --git a/src/main/java/rx/functions/Func8.java b/src/main/java/rx/functions/Func8.java index a64df6806e..2ba2894f86 100644 --- a/src/main/java/rx/functions/Func8.java +++ b/src/main/java/rx/functions/Func8.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -24,7 +24,7 @@ * @param the fifth argument type * @param the sixth argument type * @param the seventh argument type - * @param the eigth argument type + * @param the eighth argument type * @param the result type */ public interface Func8 extends Function { diff --git a/src/main/java/rx/functions/Func9.java b/src/main/java/rx/functions/Func9.java index 35c96f93f3..8e0c449f1d 100644 --- a/src/main/java/rx/functions/Func9.java +++ b/src/main/java/rx/functions/Func9.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -24,7 +24,7 @@ * @param the fifth argument type * @param the sixth argument type * @param the seventh argument type - * @param the eigth argument type + * @param the eighth argument type * @param the ninth argument type * @param the result type */ diff --git a/src/main/java/rx/functions/FuncN.java b/src/main/java/rx/functions/FuncN.java index e78e57a42d..8854e78a0e 100644 --- a/src/main/java/rx/functions/FuncN.java +++ b/src/main/java/rx/functions/FuncN.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. diff --git a/src/main/java/rx/functions/Function.java b/src/main/java/rx/functions/Function.java index cbe7bd1a37..8064b79304 100644 --- a/src/main/java/rx/functions/Function.java +++ b/src/main/java/rx/functions/Function.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. diff --git a/src/main/java/rx/functions/Functions.java b/src/main/java/rx/functions/Functions.java index b22df29204..5beab3ab65 100644 --- a/src/main/java/rx/functions/Functions.java +++ b/src/main/java/rx/functions/Functions.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -22,7 +22,7 @@ private Functions() { /** * Converts a {@link Func0} to a {@link FuncN} to allow heterogeneous handling of functions with different arities. - * + * * @param the result type * @param f * the {@code Func0} to convert @@ -34,7 +34,7 @@ public static FuncN fromFunc(final Func0 f) { @Override public R call(Object... args) { if (args.length != 0) { - throw new RuntimeException("Func0 expecting 0 arguments."); + throw new IllegalArgumentException("Func0 expecting 0 arguments."); } return f.call(); } @@ -44,7 +44,7 @@ public R call(Object... args) { /** * Converts a {@link Func1} to a {@link FuncN} to allow heterogeneous handling of functions with different arities. - * + * * @param the first argument type * @param the result type * @param f @@ -58,7 +58,7 @@ public static FuncN fromFunc(final Func1 f) @Override public R call(Object... args) { if (args.length != 1) { - throw new RuntimeException("Func1 expecting 1 argument."); + throw new IllegalArgumentException("Func1 expecting 1 argument."); } return f.call((T0) args[0]); } @@ -68,7 +68,7 @@ public R call(Object... args) { /** * Converts a {@link Func2} to a {@link FuncN} to allow heterogeneous handling of functions with different arities. - * + * * @param the first argument type * @param the second argument type * @param the result type @@ -83,7 +83,7 @@ public static FuncN fromFunc(final Func2 the first argument type * @param the second argument type * @param the third argument type @@ -109,7 +109,7 @@ public static FuncN fromFunc(final Func3 the first argument type * @param the second argument type * @param the third argument type @@ -136,7 +136,7 @@ public static FuncN fromFunc(final Func4 the first argument type * @param the second argument type * @param the third argument type @@ -164,7 +164,7 @@ public static FuncN fromFunc(final Func5 the first argument type * @param the second argument type * @param the third argument type @@ -193,7 +193,7 @@ public static FuncN fromFunc(final Func6 the first argument type * @param the second argument type * @param the third argument type @@ -223,7 +223,7 @@ public static FuncN fromFunc(final Func7 the first argument type * @param the second argument type * @param the third argument type @@ -241,7 +241,7 @@ public R call(Object... args) { * @param the fifth argument type * @param the sixth argument type * @param the seventh argument type - * @param the eigth argument type + * @param the eighth argument type * @param the result type * @param f * the {@code Func8} to convert @@ -254,7 +254,7 @@ public static FuncN fromFunc(final Func8< @Override public R call(Object... args) { if (args.length != 8) { - throw new RuntimeException("Func8 expecting 8 arguments."); + throw new IllegalArgumentException("Func8 expecting 8 arguments."); } return f.call((T0) args[0], (T1) args[1], (T2) args[2], (T3) args[3], (T4) args[4], (T5) args[5], (T6) args[6], (T7) args[7]); } @@ -264,7 +264,7 @@ public R call(Object... args) { /** * Converts a {@link Func9} to a {@link FuncN} to allow heterogeneous handling of functions with different arities. - * + * * @param the first argument type * @param the second argument type * @param the third argument type @@ -272,7 +272,7 @@ public R call(Object... args) { * @param the fifth argument type * @param the sixth argument type * @param the seventh argument type - * @param the eigth argument type + * @param the eighth argument type * @param the ninth argument type * @param the result type * @param f @@ -286,7 +286,7 @@ public static FuncN fromFunc(final Fu @Override public R call(Object... args) { if (args.length != 9) { - throw new RuntimeException("Func9 expecting 9 arguments."); + throw new IllegalArgumentException("Func9 expecting 9 arguments."); } return f.call((T0) args[0], (T1) args[1], (T2) args[2], (T3) args[3], (T4) args[4], (T5) args[5], (T6) args[6], (T7) args[7], (T8) args[8]); } @@ -296,7 +296,7 @@ public R call(Object... args) { /** * Converts an {@link Action0} to a {@link FuncN} to allow heterogeneous handling of functions with different arities. - * + * * @param f * the {@code Action0} to convert * @return a {@link FuncN} representation of {@code f} @@ -307,7 +307,7 @@ public static FuncN fromAction(final Action0 f) { @Override public Void call(Object... args) { if (args.length != 0) { - throw new RuntimeException("Action0 expecting 0 arguments."); + throw new IllegalArgumentException("Action0 expecting 0 arguments."); } f.call(); return null; @@ -318,7 +318,7 @@ public Void call(Object... args) { /** * Converts an {@link Action1} to a {@link FuncN} to allow heterogeneous handling of functions with different arities. - * + * * @param the first argument type * @param f * the {@code Action1} to convert @@ -331,7 +331,7 @@ public static FuncN fromAction(final Action1 f) { @Override public Void call(Object... args) { if (args.length != 1) { - throw new RuntimeException("Action1 expecting 1 argument."); + throw new IllegalArgumentException("Action1 expecting 1 argument."); } f.call((T0) args[0]); return null; @@ -342,7 +342,7 @@ public Void call(Object... args) { /** * Converts an {@link Action2} to a {@link FuncN} to allow heterogeneous handling of functions with different arities. - * + * * @param the first argument type * @param the second argument type * @param f @@ -356,7 +356,7 @@ public static FuncN fromAction(final Action2 the first argument type * @param the second argument type * @param the third argument type @@ -382,7 +382,7 @@ public static FuncN fromAction(final Action3 + * the value type + * @since 1.3 + */ +public class AssertableSubscriberObservable extends Subscriber implements AssertableSubscriber { + + private final TestSubscriber ts; + + public AssertableSubscriberObservable(TestSubscriber ts) { + this.ts = ts; + } + + public static AssertableSubscriberObservable create(long initialRequest) { + TestSubscriber t1 = new TestSubscriber(initialRequest); + AssertableSubscriberObservable t2 = new AssertableSubscriberObservable(t1); + t2.add(t1); + return t2; + } + + /* (non-Javadoc) + * @see rx.observers.AssertableSubscriber#onStart() + */ + @Override + public void onStart() { + ts.onStart(); + } + + @Override + public void onCompleted() { + ts.onCompleted(); + } + + /* (non-Javadoc) + * @see rx.observers.AssertableSubscriber#setProducer(rx.Producer) + */ + @Override + public void setProducer(Producer p) { + ts.setProducer(p); + } + + /* (non-Javadoc) + * @see rx.observers.AssertableSubscriber#getCompletions() + */ + @Override + public final int getCompletions() { + return ts.getCompletions(); + } + + @Override + public void onError(Throwable e) { + ts.onError(e); + } + + /* (non-Javadoc) + * @see rx.observers.AssertableSubscriber#getOnErrorEvents() + */ + @Override + public List getOnErrorEvents() { + return ts.getOnErrorEvents(); + } + + @Override + public void onNext(T t) { + ts.onNext(t); + } + + /* (non-Javadoc) + * @see rx.observers.AssertableSubscriber#getValueCount() + */ + @Override + public final int getValueCount() { + return ts.getValueCount(); + } + + /* (non-Javadoc) + * @see rx.observers.AssertableSubscriber#requestMore(long) + */ + @Override + public AssertableSubscriber requestMore(long n) { + ts.requestMore(n); + return this; + } + + /* (non-Javadoc) + * @see rx.observers.AssertableSubscriber#getOnNextEvents() + */ + @Override + public List getOnNextEvents() { + return ts.getOnNextEvents(); + } + + /* (non-Javadoc) + * @see rx.observers.AssertableSubscriber#assertReceivedOnNext(java.util.List) + */ + @Override + public AssertableSubscriber assertReceivedOnNext(List items) { + ts.assertReceivedOnNext(items); + return this; + } + + /* (non-Javadoc) + * @see rx.observers.AssertableSubscriber#awaitValueCount(int, long, java.util.concurrent.TimeUnit) + */ + @Override + public final AssertableSubscriber awaitValueCount(int expected, long timeout, TimeUnit unit) { + if (!ts.awaitValueCount(expected, timeout, unit)) { + throw new AssertionError("Did not receive enough values in time. Expected: " + expected + ", Actual: " + ts.getValueCount()); + } + return this; + } + + /* (non-Javadoc) + * @see rx.observers.AssertableSubscriber#assertTerminalEvent() + */ + @Override + public AssertableSubscriber assertTerminalEvent() { + ts.assertTerminalEvent(); + return this; + } + + /* (non-Javadoc) + * @see rx.observers.AssertableSubscriber#assertUnsubscribed() + */ + @Override + public AssertableSubscriber assertUnsubscribed() { + ts.assertUnsubscribed(); + return this; + } + + /* (non-Javadoc) + * @see rx.observers.AssertableSubscriber#assertNoErrors() + */ + @Override + public AssertableSubscriber assertNoErrors() { + ts.assertNoErrors(); + return this; + } + + /* (non-Javadoc) + * @see rx.observers.AssertableSubscriber#awaitTerminalEvent() + */ + @Override + public AssertableSubscriber awaitTerminalEvent() { + ts.awaitTerminalEvent(); + return this; + } + + /* (non-Javadoc) + * @see rx.observers.AssertableSubscriber#awaitTerminalEvent(long, java.util.concurrent.TimeUnit) + */ + @Override + public AssertableSubscriber awaitTerminalEvent(long timeout, TimeUnit unit) { + ts.awaitTerminalEvent(timeout, unit); + return this; + } + + /* (non-Javadoc) + * @see rx.observers.AssertableSubscriber#awaitTerminalEventAndUnsubscribeOnTimeout(long, java.util.concurrent.TimeUnit) + */ + @Override + public AssertableSubscriber awaitTerminalEventAndUnsubscribeOnTimeout(long timeout, + TimeUnit unit) { + ts.awaitTerminalEventAndUnsubscribeOnTimeout(timeout, unit); + return this; + } + + /* (non-Javadoc) + * @see rx.observers.AssertableSubscriber#getLastSeenThread() + */ + @Override + public Thread getLastSeenThread() { + return ts.getLastSeenThread(); + } + + /* (non-Javadoc) + * @see rx.observers.AssertableSubscriber#assertCompleted() + */ + @Override + public AssertableSubscriber assertCompleted() { + ts.assertCompleted(); + return this; + } + + /* (non-Javadoc) + * @see rx.observers.AssertableSubscriber#assertNotCompleted() + */ + @Override + public AssertableSubscriber assertNotCompleted() { + ts.assertNotCompleted(); + return this; + } + + /* (non-Javadoc) + * @see rx.observers.AssertableSubscriber#assertError(java.lang.Class) + */ + @Override + public AssertableSubscriber assertError(Class clazz) { + ts.assertError(clazz); + return this; + } + + /* (non-Javadoc) + * @see rx.observers.AssertableSubscriber#assertError(java.lang.Throwable) + */ + @Override + public AssertableSubscriber assertError(Throwable throwable) { + ts.assertError(throwable); + return this; + } + + /* (non-Javadoc) + * @see rx.observers.AssertableSubscriber#assertNoTerminalEvent() + */ + @Override + public AssertableSubscriber assertNoTerminalEvent() { + ts.assertNoTerminalEvent(); + return this; + } + + /* (non-Javadoc) + * @see rx.observers.AssertableSubscriber#assertNoValues() + */ + @Override + public AssertableSubscriber assertNoValues() { + ts.assertNoValues(); + return this; + } + + /* (non-Javadoc) + * @see rx.observers.AssertableSubscriber#assertValueCount(int) + */ + @Override + public AssertableSubscriber assertValueCount(int count) { + ts.assertValueCount(count); + return this; + } + + /* (non-Javadoc) + * @see rx.observers.AssertableSubscriber#assertValues(T) + */ + @Override + public AssertableSubscriber assertValues(T... values) { + ts.assertValues(values); + return this; + } + + /* (non-Javadoc) + * @see rx.observers.AssertableSubscriber#assertValue(T) + */ + @Override + public AssertableSubscriber assertValue(T value) { + ts.assertValue(value); + return this; + } + + /* (non-Javadoc) + * @see rx.observers.AssertableSubscriber#assertValuesAndClear(T, T) + */ + @Override + public final AssertableSubscriber assertValuesAndClear(T expectedFirstValue, + T... expectedRestValues) { + ts.assertValuesAndClear(expectedFirstValue, expectedRestValues); + return this; + } + + /* (non-Javadoc) + * @see rx.observers.AssertableSubscriber#perform(rx.functions.Action0) + */ + @Override + public final AssertableSubscriber perform(Action0 action) { + action.call(); + return this; + } + + @Override + public String toString() { + return ts.toString(); + } + + @Override + public final AssertableSubscriber assertResult(T... values) { + ts.assertValues(values); + ts.assertNoErrors(); + ts.assertCompleted(); + return this; + } + + @Override + public final AssertableSubscriber assertFailure(Class errorClass, T... values) { + ts.assertValues(values); + ts.assertError(errorClass); + ts.assertNotCompleted(); + return this; + } + + @Override + public final AssertableSubscriber assertFailureAndMessage(Class errorClass, String message, + T... values) { + ts.assertValues(values); + ts.assertError(errorClass); + ts.assertNotCompleted(); + + String actualMessage = ts.getOnErrorEvents().get(0).getMessage(); + if (!(actualMessage == message || (message != null && message.equals(actualMessage)))) { + throw new AssertionError("Error message differs. Expected: \'" + message + "\', Received: \'" + actualMessage + "\'"); + } + + return this; + } +} diff --git a/src/main/java/rx/internal/operators/BackpressureUtils.java b/src/main/java/rx/internal/operators/BackpressureUtils.java index 6a13709649..11cb441efa 100644 --- a/src/main/java/rx/internal/operators/BackpressureUtils.java +++ b/src/main/java/rx/internal/operators/BackpressureUtils.java @@ -27,47 +27,29 @@ * */ public final class BackpressureUtils { + /** + * Masks the most significant bit, i.e., 0x8000_0000_0000_0000L. + */ + static final long COMPLETED_MASK = Long.MIN_VALUE; + /** + * Masks the request amount bits, i.e., 0x7FFF_FFFF_FFFF_FFFF. + */ + static final long REQUESTED_MASK = Long.MAX_VALUE; + /** Utility class, no instances. */ private BackpressureUtils() { throw new IllegalStateException("No instances!"); } - /** - * Adds {@code n} to {@code requested} field and returns the value prior to - * addition once the addition is successful (uses CAS semantics). If - * overflows then sets {@code requested} field to {@code Long.MAX_VALUE}. - * - * @param the type of the target object on which the field updater operates - * - * @param requested - * atomic field updater for a request count - * @param object - * contains the field updated by the updater - * @param n - * the number of requests to add to the requested count - * @return requested value just prior to successful addition - * @deprecated Android has issues with reflection-based atomics - */ - @Deprecated - public static long getAndAddRequest(AtomicLongFieldUpdater requested, T object, long n) { - // add n to field but check for overflow - while (true) { - long current = requested.get(object); - long next = addCap(current, n); - if (requested.compareAndSet(object, current, next)) { - return current; - } - } - } /** - * Adds {@code n} to {@code requested} and returns the value prior to addition once the + * Adds {@code n} (not validated) to {@code requested} and returns the value prior to addition once the * addition is successful (uses CAS semantics). If overflows then sets * {@code requested} field to {@code Long.MAX_VALUE}. - * + * * @param requested * atomic long that should be updated * @param n - * the number of requests to add to the requested count + * the number of requests to add to the requested count, positive (not validated) * @return requested value just prior to successful addition */ public static long getAndAddRequest(AtomicLong requested, long n) { @@ -80,7 +62,7 @@ public static long getAndAddRequest(AtomicLong requested, long n) { } } } - + /** * Multiplies two positive longs and caps the result at Long.MAX_VALUE. * @param a the first value @@ -96,7 +78,7 @@ public static long multiplyCap(long a, long b) { } return u; } - + /** * Adds two positive longs and caps the result at Long.MAX_VALUE. * @param a the first value @@ -110,22 +92,13 @@ public static long addCap(long a, long b) { } return u; } - - /** - * Masks the most significant bit, i.e., 0x8000_0000_0000_0000L. - */ - static final long COMPLETED_MASK = Long.MIN_VALUE; - /** - * Masks the request amount bits, i.e., 0x7FFF_FFFF_FFFF_FFFF. - */ - static final long REQUESTED_MASK = Long.MAX_VALUE; - + /** * Signals the completion of the main sequence and switches to post-completion replay mode. - * + * *

    * Don't modify the queue after calling this method! - * + * *

    * Post-completion backpressure handles the case when a source produces values based on * requests when it is active but more values are available even after its completion. @@ -137,7 +110,7 @@ public static long addCap(long a, long b) { * The algorithm utilizes the most significant bit (bit 63) of a long value (AtomicLong) since * request amount only goes up to Long.MAX_VALUE (bits 0-62) and negative values aren't * allowed. - * + * * @param the value type to emit * @param requested the holder of current requested amount * @param queue the queue holding values to be emitted after completion @@ -146,10 +119,10 @@ public static long addCap(long a, long b) { public static void postCompleteDone(AtomicLong requested, Queue queue, Subscriber actual) { postCompleteDone(requested, queue, actual, UtilityFunctions.identity()); } - + /** * Accumulates requests (validated) and handles the completed mode draining of the queue based on the requests. - * + * *

    * Post-completion backpressure handles the case when a source produces values based on * requests when it is active but more values are available even after its completion. @@ -157,7 +130,7 @@ public static void postCompleteDone(AtomicLong requested, Queue queue, Su * coordinate with the requested amounts. This requires two distinct modes: active and * completed. In active mode, requests flow through and the queue is not accessed but * in completed mode, requests no-longer reach the upstream but help in draining the queue. - * + * * @param the value type to emit * @param requested the holder of current requested amount * @param n the value requested; @@ -169,14 +142,14 @@ public static void postCompleteDone(AtomicLong requested, Queue queue, Su public static boolean postCompleteRequest(AtomicLong requested, long n, Queue queue, Subscriber actual) { return postCompleteRequest(requested, n, queue, actual, UtilityFunctions.identity()); } - + /** * Signals the completion of the main sequence and switches to post-completion replay mode * and allows exit transformation on the queued values. - * + * *

    * Don't modify the queue after calling this method! - * + * *

    * Post-completion backpressure handles the case when a source produces values based on * requests when it is active but more values are available even after its completion. @@ -188,7 +161,7 @@ public static boolean postCompleteRequest(AtomicLong requested, long n, Queu * The algorithm utilizes the most significant bit (bit 63) of a long value (AtomicLong) since * request amount only goes up to Long.MAX_VALUE (bits 0-62) and negative values aren't * allowed. - * + * * @param the value type in the queue * @param the value type to emit * @param requested the holder of current requested amount @@ -199,17 +172,17 @@ public static boolean postCompleteRequest(AtomicLong requested, long n, Queu public static void postCompleteDone(AtomicLong requested, Queue queue, Subscriber actual, Func1 exitTransform) { for (;;) { long r = requested.get(); - + // switch to completed mode only once if ((r & COMPLETED_MASK) != 0L) { return; } - + // long u = r | COMPLETED_MASK; - + if (requested.compareAndSet(r, u)) { - // if we successfully switched to post-complete mode and there + // if we successfully switched to post-complete mode and there // are requests available start draining the queue if (r != 0L) { // if the switch happened when there was outstanding requests, start draining @@ -219,11 +192,11 @@ public static void postCompleteDone(AtomicLong requested, Queue queue, } } } - + /** * Accumulates requests (validated) and handles the completed mode draining of the queue based on the requests * and allows exit transformation on the queued values. - * + * *

    * Post-completion backpressure handles the case when a source produces values based on * requests when it is active but more values are available even after its completion. @@ -231,7 +204,7 @@ public static void postCompleteDone(AtomicLong requested, Queue queue, * coordinate with the requested amounts. This requires two distinct modes: active and * completed. In active mode, requests flow through and the queue is not accessed but * in completed mode, requests no-longer reach the upstream but help in draining the queue. - * + * * @param the value type in the queue * @param the value type to emit * @param requested the holder of current requested amount @@ -249,22 +222,22 @@ public static boolean postCompleteRequest(AtomicLong requested, long n, Q if (n == 0) { return (requested.get() & COMPLETED_MASK) == 0; } - + for (;;) { long r = requested.get(); - + // mask of the completed flag long c = r & COMPLETED_MASK; // mask of the requested amount long u = r & REQUESTED_MASK; - + // add the current requested amount and the new requested amount // cap at Long.MAX_VALUE; long v = addCap(u, n); - + // restore the completed flag v |= c; - + if (requested.compareAndSet(r, v)) { // if there was no outstanding request before and in // the post-completed state, start draining @@ -277,11 +250,11 @@ public static boolean postCompleteRequest(AtomicLong requested, long n, Q } } } - + /** * Drains the queue based on the outstanding requests in post-completed mode (only!) * and allows exit transformation on the queued values. - * + * * @param the value type in the queue * @param the value type to emit * @param requested the holder of current requested amount @@ -290,66 +263,66 @@ public static boolean postCompleteRequest(AtomicLong requested, long n, Q * @param exitTransform the transformation to apply on the dequeued value to get the value to be emitted */ static void postCompleteDrain(AtomicLong requested, Queue queue, Subscriber subscriber, Func1 exitTransform) { - + long r = requested.get(); - + // Run on a fast-path if the downstream is unbounded if (r == Long.MAX_VALUE) { for (;;) { if (subscriber.isUnsubscribed()) { return; } - + T v = queue.poll(); - + if (v == null) { subscriber.onCompleted(); return; } - + subscriber.onNext(exitTransform.call(v)); } } /* - * Since we are supposed to be in the post-complete state, + * Since we are supposed to be in the post-complete state, * requested will have its top bit set. * To allow direct comparison, we start with an emission value which has also * this flag set, then increment it as usual. - * Since COMPLETED_MASK is essentially Long.MIN_VALUE, + * Since COMPLETED_MASK is essentially Long.MIN_VALUE, * there won't be any overflow or sign flip. */ long e = COMPLETED_MASK; - + for (;;) { - + /* - * This is an improved queue-drain algorithm with a specialization + * This is an improved queue-drain algorithm with a specialization * in which we know the queue won't change anymore (i.e., done is always true * when looking at the classical algorithm and there is no error). - * + * * Note that we don't check for cancellation or emptiness upfront for two reasons: * 1) if e != r, the loop will do this and we quit appropriately * 2) if e == r, then either there was no outstanding requests or we emitted the requested amount * and the execution simply falls to the e == r check below which checks for emptiness anyway. */ - + while (e != r) { if (subscriber.isUnsubscribed()) { return; } - + T v = queue.poll(); - + if (v == null) { subscriber.onCompleted(); return; } - + subscriber.onNext(exitTransform.call(v)); - + e++; } - + /* * If the emission count reaches the requested amount the same time the queue becomes empty * this will make sure the subscriber is completed immediately instead of on the next request. @@ -365,15 +338,15 @@ static void postCompleteDrain(AtomicLong requested, Queue queue, Subsc return; } } - + /* * Fast flow: see if more requests have arrived in the meantime. * This avoids an atomic add (~40 cycles) and resumes the emission immediately. */ r = requested.get(); - + if (r == e) { - /* + /* * Atomically decrement the requested amount by the emission amount. * We can't use the full emission value because of the completed flag, * however, due to two's complement representation, the flag on requested @@ -413,4 +386,17 @@ public static long produced(AtomicLong requested, long n) { } } } + + /** + * Validates the requested amount and returns true if it is positive. + * @param n the requested amount + * @return true if n is positive + * @throws IllegalArgumentException if n is negative + */ + public static boolean validate(long n) { + if (n < 0) { + throw new IllegalArgumentException("n >= 0 required but it was " + n); + } + return n != 0L; + } } diff --git a/src/main/java/rx/internal/operators/BlockingOperatorLatest.java b/src/main/java/rx/internal/operators/BlockingOperatorLatest.java index 2a75eceb43..0e5097f78a 100644 --- a/src/main/java/rx/internal/operators/BlockingOperatorLatest.java +++ b/src/main/java/rx/internal/operators/BlockingOperatorLatest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -15,14 +15,12 @@ */ package rx.internal.operators; -import java.util.Iterator; -import java.util.NoSuchElementException; +import java.util.*; import java.util.concurrent.Semaphore; import java.util.concurrent.atomic.AtomicReference; -import rx.Notification; +import rx.*; import rx.Observable; -import rx.Subscriber; import rx.exceptions.Exceptions; /** @@ -62,11 +60,13 @@ static final class LatestObserverIterator extends Subscriber> value = new AtomicReference>(); + // iterator's notification + Notification iteratorNotification; @Override public void onNext(Notification args) { - boolean wasntAvailable = value.getAndSet(args) == null; - if (wasntAvailable) { + boolean wasNotAvailable = value.getAndSet(args) == null; + if (wasNotAvailable) { notify.release(); } } @@ -81,41 +81,38 @@ public void onCompleted() { // not expected } - // iterator's notification - Notification iNotif; - @Override public boolean hasNext() { - if (iNotif != null && iNotif.isOnError()) { - throw Exceptions.propagate(iNotif.getThrowable()); + if (iteratorNotification != null && iteratorNotification.isOnError()) { + throw Exceptions.propagate(iteratorNotification.getThrowable()); } - if (iNotif == null || !iNotif.isOnCompleted()) { - if (iNotif == null) { + if (iteratorNotification == null || !iteratorNotification.isOnCompleted()) { + if (iteratorNotification == null) { try { notify.acquire(); } catch (InterruptedException ex) { unsubscribe(); Thread.currentThread().interrupt(); - iNotif = Notification.createOnError(ex); + iteratorNotification = Notification.createOnError(ex); throw Exceptions.propagate(ex); } Notification n = value.getAndSet(null); - iNotif = n; - if (iNotif.isOnError()) { - throw Exceptions.propagate(iNotif.getThrowable()); + iteratorNotification = n; + if (iteratorNotification.isOnError()) { + throw Exceptions.propagate(iteratorNotification.getThrowable()); } } } - return !iNotif.isOnCompleted(); + return !iteratorNotification.isOnCompleted(); } @Override public T next() { if (hasNext()) { - if (iNotif.isOnNext()) { - T v = iNotif.getValue(); - iNotif = null; + if (iteratorNotification.isOnNext()) { + T v = iteratorNotification.getValue(); + iteratorNotification = null; return v; } } diff --git a/src/main/java/rx/internal/operators/BlockingOperatorMostRecent.java b/src/main/java/rx/internal/operators/BlockingOperatorMostRecent.java index 3a55cf0cb7..4ba6336f8e 100644 --- a/src/main/java/rx/internal/operators/BlockingOperatorMostRecent.java +++ b/src/main/java/rx/internal/operators/BlockingOperatorMostRecent.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -15,8 +15,7 @@ */ package rx.internal.operators; -import java.util.Iterator; -import java.util.NoSuchElementException; +import java.util.*; import rx.Observable; import rx.Subscriber; @@ -62,27 +61,26 @@ public Iterator iterator() { }; } - private static final class MostRecentObserver extends Subscriber { - final NotificationLite nl = NotificationLite.instance(); + static final class MostRecentObserver extends Subscriber { volatile Object value; MostRecentObserver(T value) { - this.value = nl.next(value); + this.value = NotificationLite.next(value); } @Override public void onCompleted() { - value = nl.completed(); + value = NotificationLite.completed(); } @Override public void onError(Throwable e) { - value = nl.error(e); + value = NotificationLite.error(e); } @Override public void onNext(T args) { - value = nl.next(args); + value = NotificationLite.next(args); } /** @@ -95,26 +93,28 @@ public Iterator getIterable() { /** * buffer to make sure that the state of the iterator doesn't change between calling hasNext() and next(). */ - private Object buf = null; + private Object buf; @Override public boolean hasNext() { buf = value; - return !nl.isCompleted(buf); + return !NotificationLite.isCompleted(buf); } @Override public T next() { try { // if hasNext wasn't called before calling next. - if (buf == null) + if (buf == null) { buf = value; - if (nl.isCompleted(buf)) + } + if (NotificationLite.isCompleted(buf)) { throw new NoSuchElementException(); - if (nl.isError(buf)) { - throw Exceptions.propagate(nl.getError(buf)); } - return nl.getValue(buf); + if (NotificationLite.isError(buf)) { + throw Exceptions.propagate(NotificationLite.getError(buf)); + } + return NotificationLite.getValue(buf); } finally { buf = null; diff --git a/src/main/java/rx/internal/operators/BlockingOperatorNext.java b/src/main/java/rx/internal/operators/BlockingOperatorNext.java index 94820b4acf..e9935dafee 100644 --- a/src/main/java/rx/internal/operators/BlockingOperatorNext.java +++ b/src/main/java/rx/internal/operators/BlockingOperatorNext.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -15,15 +15,12 @@ */ package rx.internal.operators; -import java.util.Iterator; -import java.util.NoSuchElementException; -import java.util.concurrent.ArrayBlockingQueue; -import java.util.concurrent.BlockingQueue; +import java.util.*; +import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; -import rx.Notification; +import rx.*; import rx.Observable; -import rx.Subscriber; import rx.exceptions.Exceptions; /** @@ -56,7 +53,7 @@ public Iterator iterator() { } - // test needs to access the observer.waiting flag non-blockingly. + // test needs to access the observer.waiting flag in a non-blocking fashion. /* private */static final class NextIterator implements Iterator { private final NextObserver observer; @@ -64,8 +61,8 @@ public Iterator iterator() { private T next; private boolean hasNext = true; private boolean isNextConsumed = true; - private Throwable error = null; - private boolean started = false; + private Throwable error; + private boolean started; NextIterator(Observable items, NextObserver observer) { this.items = items; @@ -84,11 +81,8 @@ public boolean hasNext() { // the iterator has reached the end. return false; } - if (!isNextConsumed) { - // next has not been used yet. - return true; - } - return moveToNext(); + // next has not been used yet. + return !isNextConsumed || moveToNext(); } @SuppressWarnings("unchecked") @@ -100,7 +94,7 @@ private boolean moveToNext() { observer.setWaiting(1); ((Observable)items).materialize().subscribe(observer); } - + Notification nextNotification = observer.takeNext(); if (nextNotification.isOnNext()) { isNextConsumed = false; @@ -122,7 +116,7 @@ private boolean moveToNext() { observer.unsubscribe(); Thread.currentThread().interrupt(); error = e; - throw Exceptions.propagate(error); + throw Exceptions.propagate(e); } } @@ -147,13 +141,10 @@ public void remove() { } } - private static class NextObserver extends Subscriber> { + static final class NextObserver extends Subscriber> { private final BlockingQueue> buf = new ArrayBlockingQueue>(1); final AtomicInteger waiting = new AtomicInteger(); - NextObserver() { - } - @Override public void onCompleted() { // ignore diff --git a/src/main/java/rx/internal/operators/BlockingOperatorToFuture.java b/src/main/java/rx/internal/operators/BlockingOperatorToFuture.java index 20a2377472..d6211530cb 100644 --- a/src/main/java/rx/internal/operators/BlockingOperatorToFuture.java +++ b/src/main/java/rx/internal/operators/BlockingOperatorToFuture.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -15,17 +15,10 @@ */ package rx.internal.operators; -import java.util.concurrent.CancellationException; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; +import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicReference; -import rx.Observable; -import rx.Subscriber; -import rx.Subscription; +import rx.*; /** * Returns a Future representing the single value emitted by an Observable. @@ -41,7 +34,7 @@ private BlockingOperatorToFuture() { } /** * Returns a Future that expects a single item from the observable. - * + * * @param that * an observable sequence to get a Future for. * @param @@ -77,7 +70,7 @@ public void onNext(T v) { return new Future() { - private volatile boolean cancelled = false; + private volatile boolean cancelled; @Override public boolean cancel(boolean mayInterruptIfRunning) { diff --git a/src/main/java/rx/internal/operators/BlockingOperatorToIterator.java b/src/main/java/rx/internal/operators/BlockingOperatorToIterator.java index 60de1157c9..cf12fa834e 100644 --- a/src/main/java/rx/internal/operators/BlockingOperatorToIterator.java +++ b/src/main/java/rx/internal/operators/BlockingOperatorToIterator.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -15,14 +15,11 @@ */ package rx.internal.operators; -import java.util.Iterator; -import java.util.NoSuchElementException; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.LinkedBlockingQueue; +import java.util.*; +import java.util.concurrent.*; -import rx.Notification; +import rx.*; import rx.Observable; -import rx.Subscriber; import rx.exceptions.Exceptions; import rx.internal.util.RxRingBuffer; @@ -31,7 +28,7 @@ *

    * *

    - * + * * @see Issue #50 */ public final class BlockingOperatorToIterator { @@ -41,7 +38,7 @@ private BlockingOperatorToIterator() { /** * Returns an iterator that iterates all values of the observable. - * + * * @param * the type of source. * @param source the source Observable diff --git a/src/main/java/rx/internal/operators/BufferUntilSubscriber.java b/src/main/java/rx/internal/operators/BufferUntilSubscriber.java index 2fcf60cf45..7855bdc5d9 100644 --- a/src/main/java/rx/internal/operators/BufferUntilSubscriber.java +++ b/src/main/java/rx/internal/operators/BufferUntilSubscriber.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -18,8 +18,7 @@ import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.atomic.AtomicReference; -import rx.Observer; -import rx.Subscriber; +import rx.*; import rx.functions.Action0; import rx.subjects.Subject; import rx.subscriptions.Subscriptions; @@ -48,6 +47,9 @@ * the type of the items to be buffered */ public final class BufferUntilSubscriber extends Subject { + final State state; + + private boolean forward; /** * Creates a default, unbounded buffering Subject instance. @@ -63,18 +65,18 @@ public static BufferUntilSubscriber create() { static final class State extends AtomicReference> { /** */ private static final long serialVersionUID = 8026705089538090368L; - boolean casObserverRef(Observer expected, Observer next) { - return compareAndSet(expected, next); - } final Object guard = new Object(); /* protected by guard */ - boolean emitting = false; + boolean emitting; final ConcurrentLinkedQueue buffer = new ConcurrentLinkedQueue(); - final NotificationLite nl = NotificationLite.instance(); + + boolean casObserverRef(Observer expected, Observer next) { + return compareAndSet(expected, next); + } } - + static final class OnSubscribeAction implements OnSubscribe { final State state; @@ -100,11 +102,10 @@ public void call() { } } if (win) { - final NotificationLite nl = NotificationLite.instance(); - while(true) { + while (true) { Object o; while ((o = state.buffer.poll()) != null) { - nl.accept(state.get(), o); + NotificationLite.accept(state.get(), o); } synchronized (state.guard) { if (state.buffer.isEmpty()) { @@ -121,11 +122,8 @@ public void call() { s.onError(new IllegalStateException("Only one subscriber allowed!")); } } - - } - final State state; - private boolean forward = false; + } private BufferUntilSubscriber(State state) { super(new OnSubscribeAction(state)); @@ -145,7 +143,7 @@ private void emit(Object v) { if (forward) { Object o; while ((o = state.buffer.poll()) != null) { - state.nl.accept(state.get(), o); + NotificationLite.accept(state.get(), o); } // Because `emit(Object v)` will be called in sequence, // no event will be put into `buffer` after we drain it. @@ -158,7 +156,7 @@ public void onCompleted() { state.get().onCompleted(); } else { - emit(state.nl.completed()); + emit(NotificationLite.completed()); } } @@ -168,7 +166,7 @@ public void onError(Throwable e) { state.get().onError(e); } else { - emit(state.nl.error(e)); + emit(NotificationLite.error(e)); } } @@ -178,7 +176,7 @@ public void onNext(T t) { state.get().onNext(t); } else { - emit(state.nl.next(t)); + emit(NotificationLite.next(t)); } } @@ -194,19 +192,19 @@ public boolean hasObservers() { @Override public void onCompleted() { - + // deliberately no op } @Override public void onError(Throwable e) { - + // deliberately no op } @Override public void onNext(Object t) { - + // deliberately no op } - + }; - + } diff --git a/src/main/java/rx/internal/operators/CachedObservable.java b/src/main/java/rx/internal/operators/CachedObservable.java index c9b6c47cda..f3b08676dd 100644 --- a/src/main/java/rx/internal/operators/CachedObservable.java +++ b/src/main/java/rx/internal/operators/CachedObservable.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -43,7 +43,7 @@ public final class CachedObservable extends Observable { public static CachedObservable from(Observable source) { return (CachedObservable)from(source, 16); } - + /** * Creates a cached Observable with the given capacity hint. * @param the value type @@ -59,12 +59,12 @@ public static CachedObservable from(Observable source, int c CachedSubscribe onSubscribe = new CachedSubscribe(state); return new CachedObservable(onSubscribe, state); } - + /** * Private constructor because state needs to be shared between the Observable body and * the onSubscribe function. - * @param onSubscribe - * @param state + * @param onSubscribe the shared OnSubscribe implementation + * @param state the cache state object */ private CachedObservable(OnSubscribe onSubscribe, CacheState state) { super(onSubscribe); @@ -78,10 +78,10 @@ private CachedObservable(OnSubscribe onSubscribe, CacheState state) { /* public */boolean isConnected() { return state.isConnected; } - + /** * Returns true if there are observers subscribed to this observable. - * @return + * @return true if this CachedObservable has observers */ /* public */ boolean hasObservers() { return state.producers.length != 0; @@ -101,27 +101,24 @@ static final class CacheState extends LinkedArrayList implements Observer volatile ReplayProducer[] producers; /** The default empty array of producers. */ static final ReplayProducer[] EMPTY = new ReplayProducer[0]; - - final NotificationLite nl; - + /** Set to true after connection. */ volatile boolean isConnected; - /** + /** * Indicates that the source has completed emitting values or the * Observable was forcefully terminated. */ boolean sourceDone; - + public CacheState(Observable source, int capacityHint) { super(capacityHint); this.source = source; this.producers = EMPTY; - this.nl = NotificationLite.instance(); this.connection = new SerialSubscription(); } /** * Adds a ReplayProducer to the producers array atomically. - * @param p + * @param p the downstream consumer's associated Producer instance */ public void addProducer(ReplayProducer p) { // guarding by connection to save on allocating another object @@ -137,7 +134,7 @@ public void addProducer(ReplayProducer p) { } /** * Removes the ReplayProducer (if present) from the producers array atomically. - * @param p + * @param p the downstream consumer's associated Producer instance */ public void removeProducer(ReplayProducer p) { synchronized (connection) { @@ -189,7 +186,7 @@ public void onCompleted() { @Override public void onNext(T t) { if (!sourceDone) { - Object o = nl.next(t); + Object o = NotificationLite.next(t); add(o); dispatch(); } @@ -198,7 +195,7 @@ public void onNext(T t) { public void onError(Throwable e) { if (!sourceDone) { sourceDone = true; - Object o = nl.error(e); + Object o = NotificationLite.error(e); add(o); connection.unsubscribe(); dispatch(); @@ -208,7 +205,7 @@ public void onError(Throwable e) { public void onCompleted() { if (!sourceDone) { sourceDone = true; - Object o = nl.completed(); + Object o = NotificationLite.completed(); add(o); connection.unsubscribe(); dispatch(); @@ -224,7 +221,7 @@ void dispatch() { } } } - + /** * Manages the subscription of child subscribers by setting up a replay producer and * performs auto-connection of the very first subscription. @@ -242,7 +239,7 @@ public void call(Subscriber t) { // we can connect first because we replay everything anyway ReplayProducer rp = new ReplayProducer(t, state); state.addProducer(rp); - + t.add(rp); t.setProducer(rp); @@ -250,11 +247,11 @@ public void call(Subscriber t) { if (!get() && compareAndSet(false, true)) { state.connect(); } - + // no need to call rp.replay() here because the very first request will trigger it anyway } } - + /** * Keeps track of the current request amount and the replay position for a child Subscriber. * @@ -267,14 +264,14 @@ static final class ReplayProducer extends AtomicLong implements Producer, Sub final Subscriber child; /** The cache state object. */ final CacheState state; - - /** + + /** * 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. + /** + * Contains the index into the currentBuffer where the next value is expected. * Accessed after reading state.size() and when emitting == true. */ int currentIndexInBuffer; @@ -287,7 +284,7 @@ static final class ReplayProducer extends AtomicLong implements Producer, Sub boolean emitting; /** Indicates there were some state changes/replay attempts; guarded by this. */ boolean missed; - + public ReplayProducer(Subscriber child, CacheState state) { this.child = child; this.state = state; @@ -317,7 +314,7 @@ public void request(long n) { public long produced(long n) { return addAndGet(-n); } - + @Override public boolean isUnsubscribed() { return get() < 0; @@ -332,7 +329,7 @@ public void unsubscribe() { } } } - + /** * Continue replaying available values if there are requests for them. */ @@ -347,24 +344,23 @@ public void replay() { } boolean skipFinal = false; try { - final NotificationLite nl = state.nl; final Subscriber child = this.child; - + for (;;) { - + long r = get(); - + if (r < 0L) { skipFinal = true; 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(); @@ -376,14 +372,14 @@ public void replay() { // eagerly emit any terminal event if (r == 0) { Object o = b[k]; - if (nl.isCompleted(o)) { + if (NotificationLite.isCompleted(o)) { child.onCompleted(); skipFinal = true; unsubscribe(); return; } else - if (nl.isError(o)) { - child.onError(nl.getError(o)); + if (NotificationLite.isError(o)) { + child.onError(NotificationLite.getError(o)); skipFinal = true; unsubscribe(); return; @@ -391,7 +387,7 @@ public void replay() { } else if (r > 0) { int valuesProduced = 0; - + while (j < s && r > 0) { if (child.isUnsubscribed()) { skipFinal = true; @@ -402,9 +398,9 @@ public void replay() { k = 0; } Object o = b[k]; - + try { - if (nl.accept(child, o)) { + if (NotificationLite.accept(child, o)) { skipFinal = true; unsubscribe(); return; @@ -413,30 +409,30 @@ public void replay() { Exceptions.throwIfFatal(err); skipFinal = true; unsubscribe(); - if (!nl.isError(o) && !nl.isCompleted(o)) { - child.onError(OnErrorThrowable.addValueAsLastCause(err, nl.getValue(o))); + if (!NotificationLite.isError(o) && !NotificationLite.isCompleted(o)) { + child.onError(OnErrorThrowable.addValueAsLastCause(err, NotificationLite.getValue(o))); } return; } - + k++; j++; r--; valuesProduced++; } - + if (child.isUnsubscribed()) { skipFinal = true; return; } - + index = j; currentIndexInBuffer = k; currentBuffer = b; produced(valuesProduced); } } - + synchronized (this) { if (!missed) { emitting = false; diff --git a/src/main/java/rx/internal/operators/CompletableFlatMapSingleToCompletable.java b/src/main/java/rx/internal/operators/CompletableFlatMapSingleToCompletable.java new file mode 100644 index 0000000000..a6289d4355 --- /dev/null +++ b/src/main/java/rx/internal/operators/CompletableFlatMapSingleToCompletable.java @@ -0,0 +1,92 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * 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 rx.internal.operators; + +import rx.Completable; +import rx.Completable.OnSubscribe; +import rx.CompletableSubscriber; +import rx.Single; +import rx.SingleSubscriber; +import rx.Subscription; +import rx.exceptions.Exceptions; +import rx.functions.Func1; + +public final class CompletableFlatMapSingleToCompletable implements OnSubscribe { + + final Single source; + + final Func1 mapper; + + public CompletableFlatMapSingleToCompletable(Single source, Func1 mapper) { + this.source = source; + this.mapper = mapper; + } + + @Override + public void call(CompletableSubscriber t) { + SourceSubscriber parent = new SourceSubscriber(t, mapper); + t.onSubscribe(parent); + source.subscribe(parent); + } + + static final class SourceSubscriber extends SingleSubscriber implements CompletableSubscriber { + final CompletableSubscriber actual; + + final Func1 mapper; + + public SourceSubscriber(CompletableSubscriber actual, Func1 mapper) { + this.actual = actual; + this.mapper = mapper; + } + + @Override + public void onSuccess(T value) { + Completable c; + + try { + c = mapper.call(value); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + onError(ex); + return; + } + + if (c == null) { + onError(new NullPointerException("The mapper returned a null Completable")); + return; + } + + c.subscribe(this); + } + + @Override + public void onError(Throwable error) { + actual.onError(error); + } + + @Override + public void onCompleted() { + actual.onCompleted(); + } + + @Override + public void onSubscribe(Subscription d) { + add(d); + } + } + +} diff --git a/src/main/java/rx/internal/operators/CompletableFromEmitter.java b/src/main/java/rx/internal/operators/CompletableFromEmitter.java new file mode 100644 index 0000000000..3be42f7aac --- /dev/null +++ b/src/main/java/rx/internal/operators/CompletableFromEmitter.java @@ -0,0 +1,114 @@ +/* + * Copyright 2016 Netflix, Inc. + * + * 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 rx.internal.operators; + +import java.util.concurrent.atomic.AtomicBoolean; + +import rx.*; +import rx.exceptions.Exceptions; +import rx.functions.*; +import rx.internal.subscriptions.*; +import rx.plugins.RxJavaHooks; + +/** + * Allows push-based emission of terminal events to a CompletableSubscriber. + */ +public final class CompletableFromEmitter implements Completable.OnSubscribe { + + final Action1 producer; + + public CompletableFromEmitter(Action1 producer) { + this.producer = producer; + } + + @Override + public void call(CompletableSubscriber t) { + FromEmitter emitter = new FromEmitter(t); + t.onSubscribe(emitter); + + try { + producer.call(emitter); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + emitter.onError(ex); + } + + } + + static final class FromEmitter + extends AtomicBoolean + implements CompletableEmitter, Subscription { + + /** */ + private static final long serialVersionUID = 5539301318568668881L; + + final CompletableSubscriber actual; + + final SequentialSubscription resource; + + public FromEmitter(CompletableSubscriber actual) { + this.actual = actual; + resource = new SequentialSubscription(); + } + + @Override + public void onCompleted() { + if (compareAndSet(false, true)) { + try { + actual.onCompleted(); + } finally { + resource.unsubscribe(); + } + } + } + + @Override + public void onError(Throwable t) { + if (compareAndSet(false, true)) { + try { + actual.onError(t); + } finally { + resource.unsubscribe(); + } + } else { + RxJavaHooks.onError(t); + } + } + + @Override + public void setSubscription(Subscription s) { + resource.update(s); + } + + @Override + public void setCancellation(Cancellable c) { + setSubscription(new CancellableSubscription(c)); + } + + @Override + public void unsubscribe() { + if (compareAndSet(false, true)) { + resource.unsubscribe(); + } + } + + @Override + public boolean isUnsubscribed() { + return get(); + } + + } +} diff --git a/src/main/java/rx/internal/operators/CompletableOnSubscribeConcat.java b/src/main/java/rx/internal/operators/CompletableOnSubscribeConcat.java index 96cccc23f7..10a7801beb 100644 --- a/src/main/java/rx/internal/operators/CompletableOnSubscribeConcat.java +++ b/src/main/java/rx/internal/operators/CompletableOnSubscribeConcat.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -19,68 +19,63 @@ import java.util.concurrent.atomic.*; import rx.*; -import rx.Completable.*; +import rx.Completable.OnSubscribe; import rx.exceptions.MissingBackpressureException; +import rx.internal.subscriptions.SequentialSubscription; import rx.internal.util.unsafe.SpscArrayQueue; import rx.plugins.RxJavaHooks; -import rx.subscriptions.SerialSubscription; -public final class CompletableOnSubscribeConcat implements CompletableOnSubscribe { +public final class CompletableOnSubscribeConcat implements OnSubscribe { final Observable sources; final int prefetch; - + @SuppressWarnings("unchecked") public CompletableOnSubscribeConcat(Observable sources, int prefetch) { this.sources = (Observable)sources; this.prefetch = prefetch; } - + @Override public void call(CompletableSubscriber s) { CompletableConcatSubscriber parent = new CompletableConcatSubscriber(s, prefetch); s.onSubscribe(parent); - sources.subscribe(parent); + sources.unsafeSubscribe(parent); } - + static final class CompletableConcatSubscriber extends Subscriber { final CompletableSubscriber actual; - final int prefetch; - final SerialSubscription sr; - + final SequentialSubscription sr; + final SpscArrayQueue queue; - - volatile boolean done; - final AtomicBoolean once; - final ConcatInnerSubscriber inner; - - final AtomicInteger wip; - + + final AtomicBoolean once; + + volatile boolean done; + + volatile boolean active; + public CompletableConcatSubscriber(CompletableSubscriber actual, int prefetch) { this.actual = actual; - this.prefetch = prefetch; this.queue = new SpscArrayQueue(prefetch); - this.sr = new SerialSubscription(); + this.sr = new SequentialSubscription(); this.inner = new ConcatInnerSubscriber(); - this.wip = new AtomicInteger(); this.once = new AtomicBoolean(); add(sr); request(prefetch); } - + @Override public void onNext(Completable t) { if (!queue.offer(t)) { onError(new MissingBackpressureException()); return; } - if (wip.getAndIncrement() == 0) { - next(); - } + drain(); } - + @Override public void onError(Throwable t) { if (once.compareAndSet(false, true)) { @@ -89,64 +84,75 @@ public void onError(Throwable t) { } RxJavaHooks.onError(t); } - + @Override public void onCompleted() { if (done) { return; } done = true; - if (wip.getAndIncrement() == 0) { - next(); - } + drain(); } - + void innerError(Throwable e) { unsubscribe(); onError(e); } - + void innerComplete() { - if (wip.decrementAndGet() != 0) { - next(); - } - if (!done) { - request(1); - } + active = false; + drain(); } - - void next() { - boolean d = done; - Completable c = queue.poll(); - if (c == null) { - if (d) { - if (once.compareAndSet(false, true)) { + + void drain() { + ConcatInnerSubscriber inner = this.inner; + if (inner.getAndIncrement() != 0) { + return; + } + + do { + if (isUnsubscribed()) { + return; + } + if (!active) { + boolean d = done; + Completable c = queue.poll(); + boolean empty = c == null; + + if (d && empty) { actual.onCompleted(); + return; + } + + if (!empty) { + active = true; + c.subscribe(inner); + + request(1); } - return; } - RxJavaHooks.onError(new IllegalStateException("Queue is empty?!")); - return; - } - - c.unsafeSubscribe(inner); + } while (inner.decrementAndGet() != 0); } - - final class ConcatInnerSubscriber implements CompletableSubscriber { + + final class ConcatInnerSubscriber + extends AtomicInteger + implements CompletableSubscriber { + private static final long serialVersionUID = 7233503139645205620L; + @Override public void onSubscribe(Subscription d) { sr.set(d); } - + @Override public void onError(Throwable e) { innerError(e); } - + @Override public void onCompleted() { innerComplete(); } } } -} \ No newline at end of file +} diff --git a/src/main/java/rx/internal/operators/CompletableOnSubscribeConcatArray.java b/src/main/java/rx/internal/operators/CompletableOnSubscribeConcatArray.java index 0b132acd43..cbac07ded5 100644 --- a/src/main/java/rx/internal/operators/CompletableOnSubscribeConcatArray.java +++ b/src/main/java/rx/internal/operators/CompletableOnSubscribeConcatArray.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -19,60 +19,60 @@ import java.util.concurrent.atomic.AtomicInteger; import rx.*; -import rx.Completable.*; -import rx.subscriptions.SerialSubscription; +import rx.Completable.OnSubscribe; +import rx.internal.subscriptions.SequentialSubscription; -public final class CompletableOnSubscribeConcatArray implements CompletableOnSubscribe { +public final class CompletableOnSubscribeConcatArray implements OnSubscribe { final Completable[] sources; - + public CompletableOnSubscribeConcatArray(Completable[] sources) { this.sources = sources; } - + @Override public void call(CompletableSubscriber s) { ConcatInnerSubscriber inner = new ConcatInnerSubscriber(s, sources); s.onSubscribe(inner.sd); inner.next(); } - + static final class ConcatInnerSubscriber extends AtomicInteger implements CompletableSubscriber { /** */ private static final long serialVersionUID = -7965400327305809232L; final CompletableSubscriber actual; final Completable[] sources; - + int index; - - final SerialSubscription sd; - + + final SequentialSubscription sd; + public ConcatInnerSubscriber(CompletableSubscriber actual, Completable[] sources) { this.actual = actual; this.sources = sources; - this.sd = new SerialSubscription(); + this.sd = new SequentialSubscription(); } - + @Override public void onSubscribe(Subscription d) { - sd.set(d); + sd.replace(d); } - + @Override public void onError(Throwable e) { actual.onError(e); } - + @Override public void onCompleted() { next(); } - + void next() { if (sd.isUnsubscribed()) { return; } - + if (getAndIncrement() != 0) { return; } @@ -82,13 +82,13 @@ void next() { if (sd.isUnsubscribed()) { return; } - + int idx = index++; if (idx == a.length) { actual.onCompleted(); return; } - + a[idx].unsafeSubscribe(this); } while (decrementAndGet() != 0); } diff --git a/src/main/java/rx/internal/operators/CompletableOnSubscribeConcatIterable.java b/src/main/java/rx/internal/operators/CompletableOnSubscribeConcatIterable.java index 0ce11fde78..7506286906 100644 --- a/src/main/java/rx/internal/operators/CompletableOnSubscribeConcatIterable.java +++ b/src/main/java/rx/internal/operators/CompletableOnSubscribeConcatIterable.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -20,21 +20,22 @@ import java.util.concurrent.atomic.AtomicInteger; import rx.*; -import rx.Completable.*; -import rx.subscriptions.*; +import rx.Completable.OnSubscribe; +import rx.internal.subscriptions.SequentialSubscription; +import rx.subscriptions.Subscriptions; -public final class CompletableOnSubscribeConcatIterable implements CompletableOnSubscribe { +public final class CompletableOnSubscribeConcatIterable implements OnSubscribe { final Iterable sources; - + public CompletableOnSubscribeConcatIterable(Iterable sources) { this.sources = sources; } - + @Override public void call(CompletableSubscriber s) { - + Iterator it; - + try { it = sources.iterator(); } catch (Throwable e) { @@ -42,55 +43,53 @@ public void call(CompletableSubscriber s) { s.onError(e); return; } - + if (it == null) { s.onSubscribe(Subscriptions.unsubscribed()); s.onError(new NullPointerException("The iterator returned is null")); return; } - + ConcatInnerSubscriber inner = new ConcatInnerSubscriber(s, it); s.onSubscribe(inner.sd); inner.next(); } - + static final class ConcatInnerSubscriber extends AtomicInteger implements CompletableSubscriber { /** */ private static final long serialVersionUID = -7965400327305809232L; final CompletableSubscriber actual; final Iterator sources; - - int index; - - final SerialSubscription sd; - + + final SequentialSubscription sd; + public ConcatInnerSubscriber(CompletableSubscriber actual, Iterator sources) { this.actual = actual; this.sources = sources; - this.sd = new SerialSubscription(); + this.sd = new SequentialSubscription(); } - + @Override public void onSubscribe(Subscription d) { - sd.set(d); + sd.replace(d); } - + @Override public void onError(Throwable e) { actual.onError(e); } - + @Override public void onCompleted() { next(); } - + void next() { if (sd.isUnsubscribed()) { return; } - + if (getAndIncrement() != 0) { return; } @@ -100,7 +99,7 @@ void next() { if (sd.isUnsubscribed()) { return; } - + boolean b; try { b = a.hasNext(); @@ -108,28 +107,28 @@ void next() { actual.onError(ex); return; } - + if (!b) { actual.onCompleted(); return; } - + Completable c; - + try { c = a.next(); } catch (Throwable ex) { actual.onError(ex); return; } - + if (c == null) { actual.onError(new NullPointerException("The completable returned is null")); return; } - + c.unsafeSubscribe(this); } while (decrementAndGet() != 0); } } -} \ No newline at end of file +} diff --git a/src/main/java/rx/internal/operators/CompletableOnSubscribeMerge.java b/src/main/java/rx/internal/operators/CompletableOnSubscribeMerge.java index 48dce53c9c..b60f6c56ed 100644 --- a/src/main/java/rx/internal/operators/CompletableOnSubscribeMerge.java +++ b/src/main/java/rx/internal/operators/CompletableOnSubscribeMerge.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -21,49 +21,47 @@ import java.util.concurrent.atomic.*; import rx.*; -import rx.Completable.*; +import rx.Completable.OnSubscribe; import rx.Observable; import rx.exceptions.CompositeException; import rx.plugins.RxJavaHooks; import rx.subscriptions.CompositeSubscription; -public final class CompletableOnSubscribeMerge implements CompletableOnSubscribe { +public final class CompletableOnSubscribeMerge implements OnSubscribe { final Observable source; final int maxConcurrency; final boolean delayErrors; - + @SuppressWarnings("unchecked") public CompletableOnSubscribeMerge(Observable source, int maxConcurrency, boolean delayErrors) { this.source = (Observable)source; this.maxConcurrency = maxConcurrency; this.delayErrors = delayErrors; } - + @Override public void call(CompletableSubscriber s) { CompletableMergeSubscriber parent = new CompletableMergeSubscriber(s, maxConcurrency, delayErrors); s.onSubscribe(parent); - source.subscribe(parent); + source.unsafeSubscribe(parent); } - + static final class CompletableMergeSubscriber extends Subscriber { final CompletableSubscriber actual; final CompositeSubscription set; - final int maxConcurrency; final boolean delayErrors; - + volatile boolean done; - + final AtomicReference> errors; - + final AtomicBoolean once; - + final AtomicInteger wip; - + public CompletableMergeSubscriber(CompletableSubscriber actual, int maxConcurrency, boolean delayErrors) { this.actual = actual; - this.maxConcurrency = maxConcurrency; this.delayErrors = delayErrors; this.set = new CompositeSubscription(); this.wip = new AtomicInteger(1); @@ -75,14 +73,14 @@ public CompletableMergeSubscriber(CompletableSubscriber actual, int maxConcurren request(maxConcurrency); } } - + Queue getOrCreateErrors() { Queue q = errors.get(); - + if (q != null) { return q; } - + q = new ConcurrentLinkedQueue(); if (errors.compareAndSet(null, q)) { return q; @@ -97,7 +95,7 @@ public void onNext(Completable t) { } wip.getAndIncrement(); - + t.unsafeSubscribe(new CompletableSubscriber() { Subscription d; boolean innerDone; @@ -106,7 +104,7 @@ public void onSubscribe(Subscription d) { this.d = d; set.add(d); } - + @Override public void onError(Throwable e) { if (innerDone) { @@ -115,16 +113,16 @@ public void onError(Throwable e) { } innerDone = true; set.remove(d); - + getOrCreateErrors().offer(e); - + terminate(); - + if (delayErrors && !done) { request(1); } } - + @Override public void onCompleted() { if (innerDone) { @@ -132,9 +130,9 @@ public void onCompleted() { } innerDone = true; set.remove(d); - + terminate(); - + if (!done) { request(1); } @@ -189,7 +187,7 @@ void terminate() { } } } - + /** * Collects the Throwables from the queue, adding subsequent Throwables as suppressed to * the first Throwable and returns it. @@ -198,7 +196,7 @@ void terminate() { */ public static Throwable collectErrors(Queue q) { List list = new ArrayList(); - + Throwable t; while ((t = q.poll()) != null) { list.add(t); @@ -211,4 +209,4 @@ public static Throwable collectErrors(Queue q) { } return new CompositeException(list); } -} \ No newline at end of file +} diff --git a/src/main/java/rx/internal/operators/CompletableOnSubscribeMergeArray.java b/src/main/java/rx/internal/operators/CompletableOnSubscribeMergeArray.java index d80f50abf6..7f5dbeed0e 100644 --- a/src/main/java/rx/internal/operators/CompletableOnSubscribeMergeArray.java +++ b/src/main/java/rx/internal/operators/CompletableOnSubscribeMergeArray.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -19,30 +19,30 @@ import java.util.concurrent.atomic.*; import rx.*; -import rx.Completable.*; +import rx.Completable.OnSubscribe; import rx.plugins.RxJavaHooks; import rx.subscriptions.CompositeSubscription; -public final class CompletableOnSubscribeMergeArray implements CompletableOnSubscribe { +public final class CompletableOnSubscribeMergeArray implements OnSubscribe { final Completable[] sources; - + public CompletableOnSubscribeMergeArray(Completable[] sources) { this.sources = sources; } - + @Override public void call(final CompletableSubscriber s) { final CompositeSubscription set = new CompositeSubscription(); final AtomicInteger wip = new AtomicInteger(sources.length + 1); final AtomicBoolean once = new AtomicBoolean(); - + s.onSubscribe(set); - + for (Completable c : sources) { if (set.isUnsubscribed()) { return; } - + if (c == null) { set.unsubscribe(); NullPointerException npe = new NullPointerException("A completable source is null"); @@ -53,7 +53,7 @@ public void call(final CompletableSubscriber s) { RxJavaHooks.onError(npe); } } - + c.unsafeSubscribe(new CompletableSubscriber() { @Override public void onSubscribe(Subscription d) { @@ -78,10 +78,10 @@ public void onCompleted() { } } } - + }); } - + if (wip.decrementAndGet() == 0) { if (once.compareAndSet(false, true)) { s.onCompleted(); diff --git a/src/main/java/rx/internal/operators/CompletableOnSubscribeMergeDelayErrorArray.java b/src/main/java/rx/internal/operators/CompletableOnSubscribeMergeDelayErrorArray.java index d98a6a039e..db93cf7cc7 100644 --- a/src/main/java/rx/internal/operators/CompletableOnSubscribeMergeDelayErrorArray.java +++ b/src/main/java/rx/internal/operators/CompletableOnSubscribeMergeDelayErrorArray.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -21,36 +21,36 @@ import java.util.concurrent.atomic.AtomicInteger; import rx.*; -import rx.Completable.*; +import rx.Completable.OnSubscribe; import rx.subscriptions.CompositeSubscription; -public final class CompletableOnSubscribeMergeDelayErrorArray implements CompletableOnSubscribe { +public final class CompletableOnSubscribeMergeDelayErrorArray implements OnSubscribe { final Completable[] sources; - + public CompletableOnSubscribeMergeDelayErrorArray(Completable[] sources) { this.sources = sources; } - + @Override public void call(final CompletableSubscriber s) { final CompositeSubscription set = new CompositeSubscription(); final AtomicInteger wip = new AtomicInteger(sources.length + 1); - + final Queue q = new ConcurrentLinkedQueue(); - + s.onSubscribe(set); - + for (Completable c : sources) { if (set.isUnsubscribed()) { return; } - + if (c == null) { q.offer(new NullPointerException("A completable source is null")); wip.decrementAndGet(); continue; } - + c.unsafeSubscribe(new CompletableSubscriber() { @Override public void onSubscribe(Subscription d) { @@ -67,7 +67,7 @@ public void onError(Throwable e) { public void onCompleted() { tryTerminate(); } - + void tryTerminate() { if (wip.decrementAndGet() == 0) { if (q.isEmpty()) { @@ -77,10 +77,10 @@ void tryTerminate() { } } } - + }); } - + if (wip.decrementAndGet() == 0) { if (q.isEmpty()) { s.onCompleted(); @@ -88,6 +88,6 @@ void tryTerminate() { s.onError(CompletableOnSubscribeMerge.collectErrors(q)); } } - + } } \ No newline at end of file diff --git a/src/main/java/rx/internal/operators/CompletableOnSubscribeMergeDelayErrorIterable.java b/src/main/java/rx/internal/operators/CompletableOnSubscribeMergeDelayErrorIterable.java index 9d2567135e..0c8f5bafee 100644 --- a/src/main/java/rx/internal/operators/CompletableOnSubscribeMergeDelayErrorIterable.java +++ b/src/main/java/rx/internal/operators/CompletableOnSubscribeMergeDelayErrorIterable.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -20,45 +20,53 @@ import java.util.concurrent.atomic.AtomicInteger; import rx.*; -import rx.Completable.*; -import rx.internal.util.unsafe.MpscLinkedQueue; +import rx.Completable.OnSubscribe; +import rx.internal.util.atomic.MpscLinkedAtomicQueue; +import rx.internal.util.unsafe.*; import rx.subscriptions.CompositeSubscription; -public final class CompletableOnSubscribeMergeDelayErrorIterable implements CompletableOnSubscribe { +public final class CompletableOnSubscribeMergeDelayErrorIterable implements OnSubscribe { final Iterable sources; - + public CompletableOnSubscribeMergeDelayErrorIterable(Iterable sources) { this.sources = sources; } - + @Override public void call(final CompletableSubscriber s) { final CompositeSubscription set = new CompositeSubscription(); - final AtomicInteger wip = new AtomicInteger(1); - - final Queue queue = new MpscLinkedQueue(); - + s.onSubscribe(set); - + Iterator iterator; - + try { iterator = sources.iterator(); } catch (Throwable e) { s.onError(e); return; } - + if (iterator == null) { s.onError(new NullPointerException("The source iterator returned is null")); return; } - + + final AtomicInteger wip = new AtomicInteger(1); + + final Queue queue; + + if (UnsafeAccess.isUnsafeAvailable()) { + queue = new MpscLinkedQueue(); + } else { + queue = new MpscLinkedAtomicQueue(); + } + for (;;) { if (set.isUnsubscribed()) { return; } - + boolean b; try { b = iterator.hasNext(); @@ -73,17 +81,17 @@ public void call(final CompletableSubscriber s) { } return; } - + if (!b) { break; } - + if (set.isUnsubscribed()) { return; } - + Completable c; - + try { c = iterator.next(); } catch (Throwable e) { @@ -97,11 +105,11 @@ public void call(final CompletableSubscriber s) { } return; } - + if (set.isUnsubscribed()) { return; } - + if (c == null) { NullPointerException e = new NullPointerException("A completable source is null"); queue.offer(e); @@ -114,9 +122,9 @@ public void call(final CompletableSubscriber s) { } return; } - + wip.getAndIncrement(); - + c.unsafeSubscribe(new CompletableSubscriber() { @Override public void onSubscribe(Subscription d) { @@ -133,7 +141,7 @@ public void onError(Throwable e) { public void onCompleted() { tryTerminate(); } - + void tryTerminate() { if (wip.decrementAndGet() == 0) { if (queue.isEmpty()) { @@ -145,7 +153,7 @@ void tryTerminate() { } }); } - + if (wip.decrementAndGet() == 0) { if (queue.isEmpty()) { s.onCompleted(); diff --git a/src/main/java/rx/internal/operators/CompletableOnSubscribeMergeIterable.java b/src/main/java/rx/internal/operators/CompletableOnSubscribeMergeIterable.java index fed111a6c7..fb737c77e7 100644 --- a/src/main/java/rx/internal/operators/CompletableOnSubscribeMergeIterable.java +++ b/src/main/java/rx/internal/operators/CompletableOnSubscribeMergeIterable.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -20,44 +20,45 @@ import java.util.concurrent.atomic.*; import rx.*; -import rx.Completable.*; +import rx.Completable.OnSubscribe; import rx.plugins.RxJavaHooks; import rx.subscriptions.CompositeSubscription; -public final class CompletableOnSubscribeMergeIterable implements CompletableOnSubscribe { +public final class CompletableOnSubscribeMergeIterable implements OnSubscribe { final Iterable sources; - + public CompletableOnSubscribeMergeIterable(Iterable sources) { this.sources = sources; } - + @Override public void call(final CompletableSubscriber s) { final CompositeSubscription set = new CompositeSubscription(); - final AtomicInteger wip = new AtomicInteger(1); - final AtomicBoolean once = new AtomicBoolean(); - + s.onSubscribe(set); - + Iterator iterator; - + try { iterator = sources.iterator(); } catch (Throwable e) { s.onError(e); return; } - + if (iterator == null) { s.onError(new NullPointerException("The source iterator returned is null")); return; } - + + final AtomicInteger wip = new AtomicInteger(1); + final AtomicBoolean once = new AtomicBoolean(); + for (;;) { if (set.isUnsubscribed()) { return; } - + boolean b; try { b = iterator.hasNext(); @@ -70,17 +71,17 @@ public void call(final CompletableSubscriber s) { } return; } - + if (!b) { break; } - + if (set.isUnsubscribed()) { return; } - + Completable c; - + try { c = iterator.next(); } catch (Throwable e) { @@ -92,11 +93,11 @@ public void call(final CompletableSubscriber s) { } return; } - + if (set.isUnsubscribed()) { return; } - + if (c == null) { set.unsubscribe(); NullPointerException npe = new NullPointerException("A completable source is null"); @@ -107,9 +108,9 @@ public void call(final CompletableSubscriber s) { } return; } - + wip.getAndIncrement(); - + c.unsafeSubscribe(new CompletableSubscriber() { @Override public void onSubscribe(Subscription d) { @@ -134,10 +135,10 @@ public void onCompleted() { } } } - + }); } - + if (wip.decrementAndGet() == 0) { if (once.compareAndSet(false, true)) { s.onCompleted(); diff --git a/src/main/java/rx/internal/operators/CompletableOnSubscribeTimeout.java b/src/main/java/rx/internal/operators/CompletableOnSubscribeTimeout.java index 672cfad61b..5ca8ce2d58 100644 --- a/src/main/java/rx/internal/operators/CompletableOnSubscribeTimeout.java +++ b/src/main/java/rx/internal/operators/CompletableOnSubscribeTimeout.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -20,20 +20,20 @@ import java.util.concurrent.atomic.AtomicBoolean; import rx.*; -import rx.Completable.*; +import rx.Completable.OnSubscribe; import rx.functions.Action0; import rx.plugins.RxJavaHooks; import rx.subscriptions.CompositeSubscription; -public final class CompletableOnSubscribeTimeout implements CompletableOnSubscribe { - +public final class CompletableOnSubscribeTimeout implements OnSubscribe { + final Completable source; final long timeout; final TimeUnit unit; final Scheduler scheduler; final Completable other; - public CompletableOnSubscribeTimeout(Completable source, long timeout, + public CompletableOnSubscribeTimeout(Completable source, long timeout, TimeUnit unit, Scheduler scheduler, Completable other) { this.source = source; this.timeout = timeout; @@ -46,11 +46,11 @@ public CompletableOnSubscribeTimeout(Completable source, long timeout, public void call(final CompletableSubscriber s) { final CompositeSubscription set = new CompositeSubscription(); s.onSubscribe(set); - + final AtomicBoolean once = new AtomicBoolean(); - + Scheduler.Worker w = scheduler.createWorker(); - + set.add(w); w.schedule(new Action0() { @Override @@ -61,30 +61,30 @@ public void call() { s.onError(new TimeoutException()); } else { other.unsafeSubscribe(new CompletableSubscriber() { - + @Override public void onSubscribe(Subscription d) { set.add(d); } - + @Override public void onError(Throwable e) { set.unsubscribe(); s.onError(e); } - + @Override public void onCompleted() { set.unsubscribe(); s.onCompleted(); } - + }); } } } }, timeout, unit); - + source.unsafeSubscribe(new CompletableSubscriber() { @Override @@ -109,7 +109,7 @@ public void onCompleted() { s.onCompleted(); } } - + }); } } \ No newline at end of file diff --git a/src/main/java/rx/internal/operators/DeferredScalarSubscriber.java b/src/main/java/rx/internal/operators/DeferredScalarSubscriber.java new file mode 100644 index 0000000000..bff33bfc72 --- /dev/null +++ b/src/main/java/rx/internal/operators/DeferredScalarSubscriber.java @@ -0,0 +1,177 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * 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 rx.internal.operators; + +import java.util.concurrent.atomic.AtomicInteger; + +import rx.*; + +/** + * Base class for Subscribers that consume the entire upstream and signal + * zero or one element (or an error) in a backpressure honoring fashion. + *

    + * Store any temporary value in {@link #value} and indicate there is + * a value available when completing by setting {@link #hasValue}. + * the source value type + * @param the result value type + */ +public abstract class DeferredScalarSubscriber extends Subscriber { + + /** The downstream subscriber. */ + protected final Subscriber actual; + + /** Indicates there is a value available in value. */ + protected boolean hasValue; + + /** The holder of the single value. */ + protected R value; + + /** The state, see the constants below. */ + final AtomicInteger state; + + /** Initial state. */ + static final int NO_REQUEST_NO_VALUE = 0; + /** Request came first. */ + static final int HAS_REQUEST_NO_VALUE = 1; + /** Value came first. */ + static final int NO_REQUEST_HAS_VALUE = 2; + /** Value will be emitted. */ + static final int HAS_REQUEST_HAS_VALUE = 3; + + public DeferredScalarSubscriber(Subscriber actual) { + this.actual = actual; + this.state = new AtomicInteger(); + } + + @Override + public void onError(Throwable ex) { + value = null; + actual.onError(ex); + } + + @Override + public void onCompleted() { + if (hasValue) { + complete(value); + } else { + complete(); + } + } + + /** + * Signals onCompleted() to the downstream subscriber. + */ + protected final void complete() { + actual.onCompleted(); + } + + /** + * Atomically switches to the terminal state and emits the value if + * there is a request for it or stores it for retrieval by {@code downstreamRequest(long)}. + * @param value the value to complete with + */ + protected final void complete(R value) { + Subscriber a = actual; + for (;;) { + int s = state.get(); + + if (s == NO_REQUEST_HAS_VALUE || s == HAS_REQUEST_HAS_VALUE || a.isUnsubscribed()) { + return; + } + if (s == HAS_REQUEST_NO_VALUE) { + a.onNext(value); + if (!a.isUnsubscribed()) { + a.onCompleted(); + } + state.lazySet(HAS_REQUEST_HAS_VALUE); + return; + } + this.value = value; + if (state.compareAndSet(NO_REQUEST_NO_VALUE, NO_REQUEST_HAS_VALUE)) { + return; + } + } + } + + final void downstreamRequest(long n) { + if (n < 0L) { + throw new IllegalArgumentException("n >= 0 required but it was " + n); + } + if (n != 0L) { + Subscriber a = actual; + for (;;) { + int s = state.get(); + if (s == HAS_REQUEST_NO_VALUE || s == HAS_REQUEST_HAS_VALUE || a.isUnsubscribed()) { + return; + } + if (s == NO_REQUEST_HAS_VALUE) { + if (state.compareAndSet(NO_REQUEST_HAS_VALUE, HAS_REQUEST_HAS_VALUE)) { + a.onNext(value); + if (!a.isUnsubscribed()) { + a.onCompleted(); + } + } + return; + } + if (state.compareAndSet(NO_REQUEST_NO_VALUE, HAS_REQUEST_NO_VALUE)) { + return; + } + } + } + } + + @Override + public final void setProducer(Producer p) { + p.request(Long.MAX_VALUE); + } + + /** + * Links up with the downstream Subscriber (cancellation, backpressure) and + * subscribes to the source Observable. + * @param source the source Observable + */ + public final void subscribeTo(Observable source) { + setupDownstream(); + source.unsafeSubscribe(this); + } + + /* test */ final void setupDownstream() { + Subscriber a = actual; + a.add(this); + a.setProducer(new InnerProducer(this)); + } + + /** + * Redirects the downstream request amount bach to the DeferredScalarSubscriber. + */ + static final class InnerProducer implements Producer { + final DeferredScalarSubscriber parent; + + public InnerProducer(DeferredScalarSubscriber parent) { + this.parent = parent; + } + + @Override + public void request(long n) { + parent.downstreamRequest(n); + } + } +} diff --git a/src/main/java/rx/internal/operators/DeferredScalarSubscriberSafe.java b/src/main/java/rx/internal/operators/DeferredScalarSubscriberSafe.java new file mode 100644 index 0000000000..2455819dd3 --- /dev/null +++ b/src/main/java/rx/internal/operators/DeferredScalarSubscriberSafe.java @@ -0,0 +1,57 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * 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 rx.internal.operators; + +import rx.Subscriber; +import rx.plugins.RxJavaHooks; + +/** + * Supplements {@code DeferredScalarSubscriber} with defensive behaviour that ensures no emissions + * occur after a terminal event. If {@code onError} is called more than once then errors after the first + * are reported to {@code RxJavaHooks.onError}. + * + * @param source value type + * @param result value type + */ +public abstract class DeferredScalarSubscriberSafe extends DeferredScalarSubscriber { + + protected boolean done; + + public DeferredScalarSubscriberSafe(Subscriber actual) { + super(actual); + } + + @Override + public void onError(Throwable ex) { + if (!done) { + done = true; + super.onError(ex); + } else { + RxJavaHooks.onError(ex); + } + } + + @Override + public void onCompleted() { + if (done) { + return; + } + done = true; + super.onCompleted(); + } + +} diff --git a/src/main/java/rx/internal/operators/EmptyObservableHolder.java b/src/main/java/rx/internal/operators/EmptyObservableHolder.java index 00f4e16f68..e386539c66 100644 --- a/src/main/java/rx/internal/operators/EmptyObservableHolder.java +++ b/src/main/java/rx/internal/operators/EmptyObservableHolder.java @@ -1,12 +1,12 @@ /** * Copyright 2016 Netflix, Inc. - * + * * 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. @@ -26,7 +26,11 @@ public enum EmptyObservableHolder implements OnSubscribe { INSTANCE ; - + + /** The singleton instance. */ + static final Observable EMPTY = Observable.unsafeCreate(INSTANCE); + + /** * Returns a type-corrected singleton instance of the empty Observable. * @param the value type @@ -36,10 +40,7 @@ public enum EmptyObservableHolder implements OnSubscribe { public static Observable instance() { return (Observable)EMPTY; } - - /** The singleton instance. */ - static final Observable EMPTY = Observable.create(INSTANCE); - + @Override public void call(Subscriber child) { child.onCompleted(); diff --git a/src/main/java/rx/internal/operators/NeverObservableHolder.java b/src/main/java/rx/internal/operators/NeverObservableHolder.java index 5114772872..056c08b034 100644 --- a/src/main/java/rx/internal/operators/NeverObservableHolder.java +++ b/src/main/java/rx/internal/operators/NeverObservableHolder.java @@ -1,12 +1,12 @@ /** * Copyright 2016 Netflix, Inc. - * + * * 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. @@ -26,7 +26,10 @@ public enum NeverObservableHolder implements OnSubscribe { INSTANCE ; - + + /** The singleton instance. */ + static final Observable NEVER = Observable.unsafeCreate(INSTANCE); + /** * Returns a type-corrected singleton instance of the never Observable. * @param the value type @@ -36,11 +39,9 @@ public enum NeverObservableHolder implements OnSubscribe { public static Observable instance() { return (Observable)NEVER; } - - /** The singleton instance. */ - static final Observable NEVER = Observable.create(INSTANCE); - + @Override public void call(Subscriber child) { + // deliberately no op } } diff --git a/src/main/java/rx/internal/operators/NotificationLite.java b/src/main/java/rx/internal/operators/NotificationLite.java index 223d5299cd..d5a988a6d8 100644 --- a/src/main/java/rx/internal/operators/NotificationLite.java +++ b/src/main/java/rx/internal/operators/NotificationLite.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -17,7 +17,6 @@ import java.io.Serializable; -import rx.Notification.Kind; import rx.Observer; /** @@ -28,33 +27,15 @@ *

    * An object is allocated inside {@link #error(Throwable)} to wrap the {@link Throwable} but this shouldn't * affect performance because exceptions should be exceptionally rare. - *

    - * It's implemented as a singleton to maintain some semblance of type safety that is completely non-existent. - * - * @param - * @warn type param undescribed */ -public final class NotificationLite { - private NotificationLite() { - } - - @SuppressWarnings("rawtypes") - private static final NotificationLite INSTANCE = new NotificationLite(); +public final class NotificationLite { - /** - * Gets the {@code NotificationLite} singleton. - * - * @param the value type - * @return the sole {@code NotificationLite} object - */ - @SuppressWarnings("unchecked") - public static NotificationLite instance() { - return INSTANCE; + private NotificationLite() { } private static final Object ON_COMPLETED_SENTINEL = new Serializable() { private static final long serialVersionUID = 1; - + @Override public String toString() { return "Notification=>Completed"; @@ -63,21 +44,21 @@ public String toString() { private static final Object ON_NEXT_NULL_SENTINEL = new Serializable() { private static final long serialVersionUID = 2; - + @Override public String toString() { return "Notification=>NULL"; } }; - private static class OnErrorSentinel implements Serializable { + static final class OnErrorSentinel implements Serializable { private static final long serialVersionUID = 3; final Throwable e; public OnErrorSentinel(Throwable e) { this.e = e; } - + @Override public String toString() { return "Notification=>Error:" + e; @@ -87,25 +68,27 @@ public String toString() { /** * Creates a lite {@code onNext} notification for the value passed in without doing any allocation. Can * be unwrapped and sent with the {@link #accept} method. - * + * + * @param the value type to convert * @param t * the item emitted to {@code onNext} * @return the item, or a null token representing the item if the item is {@code null} */ - public Object next(T t) { - if (t == null) + public static Object next(T t) { + if (t == null) { return ON_NEXT_NULL_SENTINEL; - else + } else { return t; + } } /** * Creates a lite {@code onCompleted} notification without doing any allocation. Can be unwrapped and * sent with the {@link #accept} method. - * + * * @return a completion token */ - public Object completed() { + public static Object completed() { return ON_COMPLETED_SENTINEL; } @@ -113,18 +96,19 @@ public Object completed() { * Create a lite {@code onError} notification. This call creates an object to wrap the {@link Throwable}, * but since there should only be one of these, the performance impact should be small. Can be unwrapped and * sent with the {@link #accept} method. - * + * * @param e * the {@code Throwable} in the {@code onError} notification * @return an object encapsulating the exception */ - public Object error(Throwable e) { + public static Object error(Throwable e) { return new OnErrorSentinel(e); } /** * Unwraps the lite notification and calls the appropriate method on the {@link Observer}. - * + * + * @param the value type to accept * @param o * the {@link Observer} to call {@code onNext}, {@code onCompleted}, or {@code onError}. * @param n @@ -136,7 +120,7 @@ public Object error(Throwable e) { * if the {@link Observer} is null. */ @SuppressWarnings("unchecked") - public boolean accept(Observer o, Object n) { + public static boolean accept(Observer o, Object n) { if (n == ON_COMPLETED_SENTINEL) { o.onCompleted(); return true; @@ -162,7 +146,7 @@ public boolean accept(Observer o, Object n) { * the lite notification * @return {@code true} if {@code n} represents an {@code onCompleted} event; {@code false} otherwise */ - public boolean isCompleted(Object n) { + public static boolean isCompleted(Object n) { return n == ON_COMPLETED_SENTINEL; } @@ -173,7 +157,7 @@ public boolean isCompleted(Object n) { * the lite notification * @return {@code true} if {@code n} represents an {@code onError} event; {@code false} otherwise */ - public boolean isError(Object n) { + public static boolean isError(Object n) { return n instanceof OnErrorSentinel; } @@ -182,7 +166,7 @@ public boolean isError(Object n) { * @param n the lite notification * @return {@code true} if {@code n} represents a wrapped {@code null} {@code onNext} event, {@code false} otherwise */ - public boolean isNull(Object n) { + public static boolean isNull(Object n) { return n == ON_NEXT_NULL_SENTINEL; } @@ -191,44 +175,22 @@ public boolean isNull(Object n) { * @param n the lite notification * @return {@code true} if {@code n} represents an {@code onNext} event, {@code false} otherwise */ - public boolean isNext(Object n) { + public static boolean isNext(Object n) { return n != null && !isError(n) && !isCompleted(n); } - /** - * Indicates which variety a particular lite notification is. If you need something more complex than - * simply calling the right method on an {@link Observer} then you can use this method to get the - * {@link rx.Notification.Kind}. - * - * @param n - * the lite notification - * @throws IllegalArgumentException - * if the notification is null. - * @return the {@link Kind} of lite notification {@code n} is: either {@code Kind.OnCompleted}, - * {@code Kind.OnError}, or {@code Kind.OnNext} - */ - public Kind kind(Object n) { - if (n == null) - throw new IllegalArgumentException("The lite notification can not be null"); - else if (n == ON_COMPLETED_SENTINEL) - return Kind.OnCompleted; - else if (n instanceof OnErrorSentinel) - return Kind.OnError; - else - // value or ON_NEXT_NULL_SENTINEL but either way it's an OnNext - return Kind.OnNext; - } /** * Returns the item corresponding to this {@code OnNext} lite notification. Bad things happen if you pass * this an {@code OnComplete} or {@code OnError} notification type. For performance reasons, this method * does not check for this, so you are expected to prevent such a mishap. - * + * + * @param the value type to convert * @param n * the lite notification (of type {@code Kind.OnNext}) * @return the unwrapped value, which can be null */ @SuppressWarnings("unchecked") - public T getValue(Object n) { + public static T getValue(Object n) { return n == ON_NEXT_NULL_SENTINEL ? null : (T) n; } @@ -236,12 +198,12 @@ public T getValue(Object n) { * Returns the {@link Throwable} corresponding to this {@code OnError} lite notification. Bad things happen * if you pass this an {@code OnComplete} or {@code OnNext} notification type. For performance reasons, this * method does not check for this, so you are expected to prevent such a mishap. - * + * * @param n * the lite notification (of type {@code Kind.OnError}) * @return the {@link Throwable} wrapped inside {@code n} */ - public Throwable getError(Object n) { + public static Throwable getError(Object n) { return ((OnErrorSentinel) n).e; } } diff --git a/src/main/java/rx/internal/operators/OnSubscribeAmb.java b/src/main/java/rx/internal/operators/OnSubscribeAmb.java index 3b39357b07..bd0b0fe2b5 100644 --- a/src/main/java/rx/internal/operators/OnSubscribeAmb.java +++ b/src/main/java/rx/internal/operators/OnSubscribeAmb.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -15,16 +15,13 @@ */ package rx.internal.operators; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; +import java.util.*; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.atomic.AtomicReference; +import rx.*; import rx.Observable; import rx.Observable.OnSubscribe; -import rx.Producer; -import rx.Subscriber; import rx.functions.Action0; import rx.subscriptions.Subscriptions; @@ -32,7 +29,10 @@ * Given multiple {@link Observable}s, propagates the one that first emits an item. * @param the value type */ -public final class OnSubscribeAmb implements OnSubscribe{ +public final class OnSubscribeAmb implements OnSubscribe { + //give default access instead of private as a micro-optimization + //for access from anonymous classes below + final Iterable> sources; /** * Given two {@link Observable}s, propagates the one that first emits an item. @@ -275,7 +275,7 @@ public static OnSubscribe amb(final Iterable(sources); } - private static final class AmbSubscriber extends Subscriber { + static final class AmbSubscriber extends Subscriber { private final Subscriber subscriber; private final Selection selection; @@ -294,38 +294,35 @@ private void requestMore(long n) { @Override public void onNext(T t) { - if (!isSelected()) { - return; + if (isSelected()) { + subscriber.onNext(t); } - subscriber.onNext(t); } @Override public void onCompleted() { - if (!isSelected()) { - return; + if (isSelected()) { + subscriber.onCompleted(); } - subscriber.onCompleted(); } @Override public void onError(Throwable e) { - if (!isSelected()) { - return; + if (isSelected()) { + subscriber.onError(e); } - subscriber.onError(e); } private boolean isSelected() { if (chosen) { return true; } - if (selection.choice.get() == this) { + if (selection.get() == this) { // fast-path chosen = true; return true; } else { - if (selection.choice.compareAndSet(null, this)) { + if (selection.compareAndSet(null, this)) { selection.unsubscribeOthers(this); chosen = true; return true; @@ -338,17 +335,17 @@ private boolean isSelected() { } } - private static class Selection { - final AtomicReference> choice = new AtomicReference>(); + @SuppressWarnings("serial") + static final class Selection extends AtomicReference> { final Collection> ambSubscribers = new ConcurrentLinkedQueue>(); public void unsubscribeLosers() { - AmbSubscriber winner = choice.get(); - if(winner != null) { + AmbSubscriber winner = get(); + if (winner != null) { unsubscribeOthers(winner); } } - + public void unsubscribeOthers(AmbSubscriber notThis) { for (AmbSubscriber other : ambSubscribers) { if (other != notThis) { @@ -359,11 +356,7 @@ public void unsubscribeOthers(AmbSubscriber notThis) { } } - - //give default access instead of private as a micro-optimization - //for access from anonymous classes below - final Iterable> sources; - + private OnSubscribeAmb(Iterable> sources) { this.sources = sources; } @@ -371,27 +364,26 @@ private OnSubscribeAmb(Iterable> sources) { @Override public void call(final Subscriber subscriber) { final Selection selection = new Selection(); - final AtomicReference> choice = selection.choice; - + //setup unsubscription of all the subscribers to the sources subscriber.add(Subscriptions.create(new Action0() { @Override public void call() { AmbSubscriber c; - if ((c = choice.get()) != null) { + if ((c = selection.get()) != null) { // there is a single winner so we unsubscribe it c.unsubscribe(); - } + } // if we are racing with others still existing, we'll also unsubscribe them - // if subscriptions are occurring as this is happening then this call may not + // if subscriptions are occurring as this is happening then this call may not // unsubscribe everything. We protect ourselves though by doing another unsubscribe check // after the subscription loop below unsubscribeAmbSubscribers(selection.ambSubscribers); } })); - + //need to subscribe to all the sources for (Observable source : sources) { if (subscriber.isUnsubscribed()) { @@ -400,10 +392,10 @@ public void call() { AmbSubscriber ambSubscriber = new AmbSubscriber(0, subscriber, selection); selection.ambSubscribers.add(ambSubscriber); // check again if choice has been made so can stop subscribing - // if all sources were backpressure aware then this check + // if all sources were backpressure aware then this check // would be pointless given that 0 was requested above from each ambSubscriber AmbSubscriber c; - if ((c = choice.get()) != null) { + if ((c = selection.get()) != null) { // Already chose one, the rest can be skipped and we can clean up selection.unsubscribeOthers(c); return; @@ -419,20 +411,20 @@ public void call() { @Override public void request(long n) { - final AmbSubscriber c; - if ((c = choice.get()) != null) { + AmbSubscriber c; + if ((c = selection.get()) != null) { // propagate the request to that single Subscriber that won c.requestMore(n); } else { //propagate the request to all the amb subscribers for (AmbSubscriber ambSubscriber: selection.ambSubscribers) { if (!ambSubscriber.isUnsubscribed()) { - // make a best endeavours check to not waste requests + // make a best endeavours check to not waste requests // if first emission has already occurred - if (choice.get() == ambSubscriber) { + if (selection.get() == ambSubscriber) { ambSubscriber.requestMore(n); // don't need to request from other subscribers because choice has been made - // and request has gone to choice + // and request has gone to choice return; } else { ambSubscriber.requestMore(n); @@ -445,7 +437,7 @@ public void request(long n) { } static void unsubscribeAmbSubscribers(Collection> ambSubscribers) { - if(!ambSubscribers.isEmpty()) { + if (!ambSubscribers.isEmpty()) { for (AmbSubscriber other : ambSubscribers) { other.unsubscribe(); } diff --git a/src/main/java/rx/internal/operators/OnSubscribeAutoConnect.java b/src/main/java/rx/internal/operators/OnSubscribeAutoConnect.java index 75ea9c82cf..cf6e7e7473 100644 --- a/src/main/java/rx/internal/operators/OnSubscribeAutoConnect.java +++ b/src/main/java/rx/internal/operators/OnSubscribeAutoConnect.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -17,9 +17,8 @@ import java.util.concurrent.atomic.AtomicInteger; +import rx.*; import rx.Observable.OnSubscribe; -import rx.Subscriber; -import rx.Subscription; import rx.functions.Action1; import rx.observables.ConnectableObservable; import rx.observers.Subscribers; @@ -30,12 +29,14 @@ * * @param the value type of the chain */ -public final class OnSubscribeAutoConnect implements OnSubscribe { +@SuppressWarnings("serial") +public final class OnSubscribeAutoConnect extends AtomicInteger implements OnSubscribe { + // AtomicInteger aspect of `this` represents the number of clients + final ConnectableObservable source; final int numberOfSubscribers; final Action1 connection; - final AtomicInteger clients; - + public OnSubscribeAutoConnect(ConnectableObservable source, int numberOfSubscribers, Action1 connection) { @@ -45,12 +46,12 @@ public OnSubscribeAutoConnect(ConnectableObservable source, this.source = source; this.numberOfSubscribers = numberOfSubscribers; this.connection = connection; - this.clients = new AtomicInteger(); } @Override public void call(Subscriber child) { source.unsafeSubscribe(Subscribers.wrap(child)); - if (clients.incrementAndGet() == numberOfSubscribers) { + //this.get() represents the number of clients + if (this.incrementAndGet() == numberOfSubscribers) { source.connect(connection); } } diff --git a/src/main/java/rx/internal/operators/OnSubscribeCollect.java b/src/main/java/rx/internal/operators/OnSubscribeCollect.java new file mode 100644 index 0000000000..7577349d88 --- /dev/null +++ b/src/main/java/rx/internal/operators/OnSubscribeCollect.java @@ -0,0 +1,79 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * 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 rx.internal.operators; + +import rx.*; +import rx.Observable.OnSubscribe; +import rx.exceptions.Exceptions; +import rx.functions.*; + +public final class OnSubscribeCollect implements OnSubscribe { + + final Observable source; + + final Func0 collectionFactory; + + final Action2 collector; + + public OnSubscribeCollect(Observable source, Func0 collectionFactory, Action2 collector) { + this.source = source; + this.collectionFactory = collectionFactory; + this.collector = collector; + } + + @Override + public void call(Subscriber t) { + R initialValue; + + try { + initialValue = collectionFactory.call(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + t.onError(ex); + return; + } + + new CollectSubscriber(t, initialValue, collector).subscribeTo(source); + } + + static final class CollectSubscriber extends DeferredScalarSubscriberSafe { + + final Action2 collector; + + public CollectSubscriber(Subscriber actual, R initialValue, Action2 collector) { + super(actual); + this.value = initialValue; + this.hasValue = true; + this.collector = collector; + } + + @Override + public void onNext(T t) { + if (done) { + return; + } + try { + collector.call(value, t); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + unsubscribe(); + onError(ex); + } + } + + } +} diff --git a/src/main/java/rx/internal/operators/OnSubscribeCombineLatest.java b/src/main/java/rx/internal/operators/OnSubscribeCombineLatest.java index e9dac2d026..d7f576bfae 100644 --- a/src/main/java/rx/internal/operators/OnSubscribeCombineLatest.java +++ b/src/main/java/rx/internal/operators/OnSubscribeCombineLatest.java @@ -1,11 +1,11 @@ /** * Copyright 2015 Netflix, Inc. - * + * * 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. @@ -31,12 +31,12 @@ public final class OnSubscribeCombineLatest implements OnSubscribe { final FuncN combiner; final int bufferSize; final boolean delayError; - + public OnSubscribeCombineLatest(Iterable> sourcesIterable, FuncN combiner) { this(null, sourcesIterable, combiner, RxRingBuffer.SIZE, false); } - + public OnSubscribeCombineLatest(Observable[] sources, Iterable> sourcesIterable, FuncN combiner, int bufferSize, @@ -48,7 +48,7 @@ public OnSubscribeCombineLatest(Observable[] sources, this.delayError = delayError; } - + @Override @SuppressWarnings({ "unchecked", "rawtypes" }) public void call(Subscriber s) { @@ -74,49 +74,47 @@ public void call(Subscriber s) { } else { count = sources.length; } - + if (count == 0) { s.onCompleted(); return; } - + LatestCoordinator lc = new LatestCoordinator(s, combiner, count, bufferSize, delayError); lc.subscribe(sources); } - + static final class LatestCoordinator extends AtomicInteger implements Producer, Subscription { /** */ private static final long serialVersionUID = 8567835998786448817L; final Subscriber actual; final FuncN combiner; - final int count; final CombinerSubscriber[] subscribers; final int bufferSize; final Object[] latest; final SpscLinkedArrayQueue queue; final boolean delayError; - + volatile boolean cancelled; - + volatile boolean done; - + final AtomicLong requested; final AtomicReference error; - + int active; int complete; - + /** Indicates the particular source hasn't emitted any value yet. */ static final Object MISSING = new Object(); - + @SuppressWarnings("unchecked") - public LatestCoordinator(Subscriber actual, - FuncN combiner, + public LatestCoordinator(Subscriber actual, + FuncN combiner, int count, int bufferSize, boolean delayError) { this.actual = actual; this.combiner = combiner; - this.count = count; this.bufferSize = bufferSize; this.delayError = delayError; this.latest = new Object[count]; @@ -126,7 +124,7 @@ public LatestCoordinator(Subscriber actual, this.requested = new AtomicLong(); this.error = new AtomicReference(); } - + @SuppressWarnings("unchecked") public void subscribe(Observable[] sources) { Subscriber[] as = subscribers; @@ -144,7 +142,7 @@ public void subscribe(Observable[] sources) { ((Observable)sources[i]).subscribe(as[i]); } } - + @Override public void request(long n) { if (n < 0) { @@ -155,30 +153,30 @@ public void request(long n) { drain(); } } - + @Override public void unsubscribe() { if (!cancelled) { cancelled = true; - + if (getAndIncrement() == 0) { cancel(queue); } } } - + @Override public boolean isUnsubscribed() { return cancelled; } - + void cancel(Queue q) { q.clear(); for (CombinerSubscriber s : subscribers) { s.unsubscribe(); } } - + /** * Combine the given notification value from the indexth source with the existing known * latest values. @@ -187,7 +185,7 @@ void cancel(Queue q) { */ void combine(Object value, int index) { CombinerSubscriber combinerSubscriber = subscribers[index]; - + int activeCount; int completedCount; int sourceCount; @@ -204,11 +202,11 @@ void combine(Object value, int index) { if (value == null) { complete = ++completedCount; } else { - latest[index] = combinerSubscriber.nl.getValue(value); + latest[index] = NotificationLite.getValue(value); } allSourcesFinished = activeCount == sourceCount; // see if either all sources completed - empty = completedCount == sourceCount + empty = completedCount == sourceCount || (value == null && o == MISSING); // or this source completed without any value if (!empty) { if (value != null && allSourcesFinished) { @@ -231,48 +229,47 @@ void drain() { if (getAndIncrement() != 0) { return; } - + final Queue q = queue; final Subscriber a = actual; final boolean delayError = this.delayError; final AtomicLong localRequested = this.requested; - + int missed = 1; for (;;) { - + if (checkTerminated(done, q.isEmpty(), a, q, delayError)) { return; } - + long requestAmount = localRequested.get(); - boolean unbounded = requestAmount == Long.MAX_VALUE; long emitted = 0L; - - while (requestAmount != 0L) { - + + while (emitted != requestAmount) { + boolean d = done; @SuppressWarnings("unchecked") CombinerSubscriber cs = (CombinerSubscriber)q.peek(); boolean empty = cs == null; - + if (checkTerminated(d, empty, a, q, delayError)) { return; } - + if (empty) { break; } q.poll(); Object[] array = (Object[])q.poll(); - + if (array == null) { cancelled = true; cancel(q); a.onError(new IllegalStateException("Broken queue?! Sender received but not the array.")); return; } - + R v; try { v = combiner.call(array); @@ -282,29 +279,26 @@ void drain() { a.onError(ex); return; } - + a.onNext(v); - + cs.requestMore(1); - - requestAmount--; - emitted--; + + emitted++; } - - if (emitted != 0L) { - if (!unbounded) { - localRequested.addAndGet(emitted); - } + + if (emitted != 0L && requestAmount != Long.MAX_VALUE) { + BackpressureUtils.produced(localRequested, emitted); } - + missed = addAndGet(-missed); if (missed == 0) { break; } } } - - + + boolean checkTerminated(boolean mainDone, boolean queueEmpty, Subscriber childSubscriber, Queue q, boolean delayError) { if (cancelled) { cancel(q); @@ -336,7 +330,7 @@ boolean checkTerminated(boolean mainDone, boolean queueEmpty, Subscriber chil } return false; } - + void onError(Throwable e) { AtomicReference localError = this.error; for (;;) { @@ -360,29 +354,27 @@ void onError(Throwable e) { } } } - + static final class CombinerSubscriber extends Subscriber { final LatestCoordinator parent; final int index; - final NotificationLite nl; - + boolean done; - + public CombinerSubscriber(LatestCoordinator parent, int index) { this.parent = parent; this.index = index; - this.nl = NotificationLite.instance(); request(parent.bufferSize); } - + @Override public void onNext(T t) { if (done) { return; } - parent.combine(nl.next(t), index); + parent.combine(NotificationLite.next(t), index); } - + @Override public void onError(Throwable t) { if (done) { @@ -393,7 +385,7 @@ public void onError(Throwable t) { done = true; parent.combine(null, index); } - + @Override public void onCompleted() { if (done) { @@ -402,7 +394,7 @@ public void onCompleted() { done = true; parent.combine(null, index); } - + public void requestMore(long n) { request(n); } diff --git a/src/main/java/rx/internal/operators/OnSubscribeConcatMap.java b/src/main/java/rx/internal/operators/OnSubscribeConcatMap.java index fba34e2f6b..befcb2761a 100644 --- a/src/main/java/rx/internal/operators/OnSubscribeConcatMap.java +++ b/src/main/java/rx/internal/operators/OnSubscribeConcatMap.java @@ -36,28 +36,28 @@ * to one at a time. * @param the source value type * @param the output value type - * + * * @since 1.1.2 */ public final class OnSubscribeConcatMap implements OnSubscribe { final Observable source; - + final Func1> mapper; - + final int prefetch; - - /** + + /** * How to handle errors from the main and inner Observables. * See the constants below. */ final int delayErrorMode; - + /** Whenever any Observable fires an error, terminate with that error immediately. */ public static final int IMMEDIATE = 0; - + /** Whenever the main fires an error, wait until the inner terminates. */ public static final int BOUNDARY = 1; - + /** Delay all errors to the very end. */ public static final int END = 2; @@ -68,19 +68,19 @@ public OnSubscribeConcatMap(Observable source, Func1 child) { Subscriber s; - + if (delayErrorMode == IMMEDIATE) { s = new SerializedSubscriber(child); } else { s = child; } - + final ConcatMapSubscriber parent = new ConcatMapSubscriber(s, mapper, prefetch, delayErrorMode); - + child.add(parent); child.add(parent.inner); child.setProducer(new Producer() { @@ -89,31 +89,31 @@ public void request(long n) { parent.requestMore(n); } }); - + if (!child.isUnsubscribed()) { source.unsafeSubscribe(parent); } } - + static final class ConcatMapSubscriber extends Subscriber { final Subscriber actual; - + final Func1> mapper; - + final int delayErrorMode; - + final ProducerArbiter arbiter; - + final Queue queue; - + final AtomicInteger wip; - + final AtomicReference error; - + final SerialSubscription inner; - + volatile boolean done; - + volatile boolean active; public ConcatMapSubscriber(Subscriber actual, @@ -137,14 +137,14 @@ public ConcatMapSubscriber(Subscriber actual, @Override public void onNext(T t) { - if (!queue.offer(NotificationLite.instance().next(t))) { + if (!queue.offer(NotificationLite.next(t))) { unsubscribe(); onError(new MissingBackpressureException()); } else { drain(); } } - + @Override public void onError(Throwable mainError) { if (ExceptionsUtils.addThrowable(error, mainError)) { @@ -162,13 +162,13 @@ public void onError(Throwable mainError) { pluginError(mainError); } } - + @Override public void onCompleted() { done = true; drain(); } - + void requestMore(long n) { if (n > 0) { arbiter.request(n); @@ -177,11 +177,11 @@ void requestMore(long n) { throw new IllegalArgumentException("n >= 0 required but it was " + n); } } - + void innerNext(R value) { actual.onNext(value); } - + void innerError(Throwable innerError, long produced) { if (!ExceptionsUtils.addThrowable(error, innerError)) { pluginError(innerError); @@ -200,7 +200,7 @@ void innerError(Throwable innerError, long produced) { drain(); } } - + void innerCompleted(long produced) { if (produced != 0L) { arbiter.produced(produced); @@ -208,23 +208,23 @@ void innerCompleted(long produced) { active = false; drain(); } - + void pluginError(Throwable e) { RxJavaHooks.onError(e); } - + void drain() { if (wip.getAndIncrement() != 0) { return; } final int delayErrorMode = this.delayErrorMode; - + for (;;) { if (actual.isUnsubscribed()) { return; } - + if (!active) { if (delayErrorMode == BOUNDARY) { if (error.get() != null) { @@ -235,11 +235,11 @@ void drain() { return; } } - + boolean mainDone = done; Object v = queue.poll(); boolean empty = v == null; - + if (mainDone && empty) { Throwable ex = ExceptionsUtils.terminate(error); if (ex == null) { @@ -250,39 +250,39 @@ void drain() { } return; } - + if (!empty) { - + Observable source; - + try { - source = mapper.call(NotificationLite.instance().getValue(v)); + source = mapper.call(NotificationLite.getValue(v)); } catch (Throwable mapperError) { Exceptions.throwIfFatal(mapperError); drainError(mapperError); return; } - + if (source == null) { drainError(new NullPointerException("The source returned by the mapper was null")); return; } - + if (source != Observable.empty()) { if (source instanceof ScalarSynchronousObservable) { ScalarSynchronousObservable scalarSource = (ScalarSynchronousObservable) source; - + active = true; - + arbiter.setProducer(new ConcatMapInnerScalarProducer(scalarSource.get(), this)); } else { ConcatMapInnerSubscriber innerSubscriber = new ConcatMapInnerSubscriber(this); inner.set(innerSubscriber); - + if (!innerSubscriber.isUnsubscribed()) { active = true; - + source.unsafeSubscribe(innerSubscriber); } else { return; @@ -300,10 +300,10 @@ void drain() { } } } - + void drainError(Throwable mapperError) { unsubscribe(); - + if (ExceptionsUtils.addThrowable(error, mapperError)) { Throwable ex = ExceptionsUtils.terminate(error); if (!ExceptionsUtils.isTerminated(ex)) { @@ -314,43 +314,43 @@ void drainError(Throwable mapperError) { } } } - + static final class ConcatMapInnerSubscriber extends Subscriber { final ConcatMapSubscriber parent; long produced; - + public ConcatMapInnerSubscriber(ConcatMapSubscriber parent) { this.parent = parent; } - + @Override public void setProducer(Producer p) { parent.arbiter.setProducer(p); } - + @Override public void onNext(R t) { produced++; parent.innerNext(t); } - + @Override public void onError(Throwable e) { parent.innerError(e, produced); } - + @Override public void onCompleted() { parent.innerCompleted(produced); } } - + static final class ConcatMapInnerScalarProducer implements Producer { final R value; - + final ConcatMapSubscriber parent; - + boolean once; public ConcatMapInnerScalarProducer(R value, ConcatMapSubscriber parent) { diff --git a/src/main/java/rx/internal/operators/OnSubscribeCreate.java b/src/main/java/rx/internal/operators/OnSubscribeCreate.java new file mode 100644 index 0000000000..497b240ec0 --- /dev/null +++ b/src/main/java/rx/internal/operators/OnSubscribeCreate.java @@ -0,0 +1,530 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * 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 rx.internal.operators; + +import java.util.Queue; +import java.util.concurrent.atomic.*; + +import rx.*; +import rx.Observable.OnSubscribe; +import rx.exceptions.MissingBackpressureException; +import rx.functions.*; +import rx.internal.subscriptions.CancellableSubscription; +import rx.internal.util.RxRingBuffer; +import rx.internal.util.atomic.SpscUnboundedAtomicArrayQueue; +import rx.internal.util.unsafe.*; +import rx.plugins.RxJavaHooks; +import rx.subscriptions.SerialSubscription; + +public final class OnSubscribeCreate implements OnSubscribe { + + final Action1> Emitter; + + final Emitter.BackpressureMode backpressure; + + public OnSubscribeCreate(Action1> Emitter, Emitter.BackpressureMode backpressure) { + this.Emitter = Emitter; + this.backpressure = backpressure; + } + + @Override + public void call(Subscriber t) { + BaseEmitter emitter; + + switch (backpressure) { + case NONE: { + emitter = new NoneEmitter(t); + break; + } + case ERROR: { + emitter = new ErrorEmitter(t); + break; + } + case DROP: { + emitter = new DropEmitter(t); + break; + } + case LATEST: { + emitter = new LatestEmitter(t); + break; + } + default: { + emitter = new BufferEmitter(t, RxRingBuffer.SIZE); + break; + } + } + + t.add(emitter); + t.setProducer(emitter); + Emitter.call(emitter); + + } + + static abstract class BaseEmitter + extends AtomicLong + implements Emitter, Producer, Subscription { + /** */ + private static final long serialVersionUID = 7326289992464377023L; + + final Subscriber actual; + + final SerialSubscription serial; + + public BaseEmitter(Subscriber actual) { + this.actual = actual; + this.serial = new SerialSubscription(); + } + + @Override + public void onCompleted() { + if (actual.isUnsubscribed()) { + return; + } + try { + actual.onCompleted(); + } finally { + serial.unsubscribe(); + } + } + + @Override + public void onError(Throwable e) { + if (actual.isUnsubscribed()) { + return; + } + try { + actual.onError(e); + } finally { + serial.unsubscribe(); + } + } + + @Override + public final void unsubscribe() { + serial.unsubscribe(); + onUnsubscribed(); + } + + void onUnsubscribed() { + // default is no-op + } + + @Override + public final boolean isUnsubscribed() { + return serial.isUnsubscribed(); + } + + @Override + public final void request(long n) { + if (BackpressureUtils.validate(n)) { + BackpressureUtils.getAndAddRequest(this, n); + onRequested(); + } + } + + void onRequested() { + // default is no-op + } + + @Override + public final void setSubscription(Subscription s) { + serial.set(s); + } + + @Override + public final void setCancellation(Cancellable c) { + setSubscription(new CancellableSubscription(c)); + } + + @Override + public final long requested() { + return get(); + } + } + + static final class NoneEmitter extends BaseEmitter { + + /** */ + private static final long serialVersionUID = 3776720187248809713L; + + public NoneEmitter(Subscriber actual) { + super(actual); + } + + @Override + public void onNext(T t) { + if (actual.isUnsubscribed()) { + return; + } + + actual.onNext(t); + + for (;;) { + long r = get(); + if (r == 0L || compareAndSet(r, r - 1)) { + return; + } + } + } + + } + + static abstract class NoOverflowBaseEmitter extends BaseEmitter { + + /** */ + private static final long serialVersionUID = 4127754106204442833L; + + public NoOverflowBaseEmitter(Subscriber actual) { + super(actual); + } + + @Override + public void onNext(T t) { + if (actual.isUnsubscribed()) { + return; + } + + if (get() != 0) { + actual.onNext(t); + BackpressureUtils.produced(this, 1); + } else { + onOverflow(); + } + } + + abstract void onOverflow(); + } + + static final class DropEmitter extends NoOverflowBaseEmitter { + + /** */ + private static final long serialVersionUID = 8360058422307496563L; + + public DropEmitter(Subscriber actual) { + super(actual); + } + + @Override + void onOverflow() { + // nothing to do + } + + } + + static final class ErrorEmitter extends NoOverflowBaseEmitter { + + /** */ + private static final long serialVersionUID = 338953216916120960L; + + private boolean done; + + public ErrorEmitter(Subscriber actual) { + super(actual); + } + + + @Override + public void onNext(T t) { + if (done) { + return; + } + super.onNext(t); + } + + + @Override + public void onCompleted() { + if (done) { + return; + } + done = true; + super.onCompleted(); + } + + + @Override + public void onError(Throwable e) { + if (done) { + RxJavaHooks.onError(e); + return; + } + done = true; + super.onError(e); + } + + + @Override + void onOverflow() { + onError(new MissingBackpressureException("create: could not emit value due to lack of requests")); + } + + } + + static final class BufferEmitter extends BaseEmitter { + + /** */ + private static final long serialVersionUID = 2427151001689639875L; + + final Queue queue; + + Throwable error; + volatile boolean done; + + final AtomicInteger wip; + + public BufferEmitter(Subscriber actual, int capacityHint) { + super(actual); + this.queue = UnsafeAccess.isUnsafeAvailable() + ? new SpscUnboundedArrayQueue(capacityHint) + : new SpscUnboundedAtomicArrayQueue(capacityHint); + this.wip = new AtomicInteger(); + } + + @Override + public void onNext(T t) { + queue.offer(NotificationLite.next(t)); + drain(); + } + + @Override + public void onError(Throwable e) { + error = e; + done = true; + drain(); + } + + @Override + public void onCompleted() { + done = true; + drain(); + } + + @Override + void onRequested() { + drain(); + } + + @Override + void onUnsubscribed() { + if (wip.getAndIncrement() == 0) { + queue.clear(); + } + } + + void drain() { + if (wip.getAndIncrement() != 0) { + return; + } + + int missed = 1; + final Subscriber a = actual; + final Queue q = queue; + + for (;;) { + long r = get(); + long e = 0L; + + while (e != r) { + if (a.isUnsubscribed()) { + q.clear(); + return; + } + + boolean d = done; + + Object o = q.poll(); + + boolean empty = o == null; + + if (d && empty) { + Throwable ex = error; + if (ex != null) { + super.onError(ex); + } else { + super.onCompleted(); + } + return; + } + + if (empty) { + break; + } + + a.onNext(NotificationLite.getValue(o)); + + e++; + } + + if (e == r) { + if (a.isUnsubscribed()) { + q.clear(); + return; + } + + boolean d = done; + + boolean empty = q.isEmpty(); + + if (d && empty) { + Throwable ex = error; + if (ex != null) { + super.onError(ex); + } else { + super.onCompleted(); + } + return; + } + } + + if (e != 0) { + BackpressureUtils.produced(this, e); + } + + missed = wip.addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + } + + static final class LatestEmitter extends BaseEmitter { + + /** */ + private static final long serialVersionUID = 4023437720691792495L; + + final AtomicReference queue; + + Throwable error; + volatile boolean done; + + final AtomicInteger wip; + + public LatestEmitter(Subscriber actual) { + super(actual); + this.queue = new AtomicReference(); + this.wip = new AtomicInteger(); + } + + @Override + public void onNext(T t) { + queue.set(NotificationLite.next(t)); + drain(); + } + + @Override + public void onError(Throwable e) { + error = e; + done = true; + drain(); + } + + @Override + public void onCompleted() { + done = true; + drain(); + } + + @Override + void onRequested() { + drain(); + } + + @Override + void onUnsubscribed() { + if (wip.getAndIncrement() == 0) { + queue.lazySet(null); + } + } + + void drain() { + if (wip.getAndIncrement() != 0) { + return; + } + + int missed = 1; + final Subscriber a = actual; + final AtomicReference q = queue; + + for (;;) { + long r = get(); + long e = 0L; + + while (e != r) { + if (a.isUnsubscribed()) { + q.lazySet(null); + return; + } + + boolean d = done; + + Object o = q.getAndSet(null); + + boolean empty = o == null; + + if (d && empty) { + Throwable ex = error; + if (ex != null) { + super.onError(ex); + } else { + super.onCompleted(); + } + return; + } + + if (empty) { + break; + } + + a.onNext(NotificationLite.getValue(o)); + + e++; + } + + if (e == r) { + if (a.isUnsubscribed()) { + q.lazySet(null); + return; + } + + boolean d = done; + + boolean empty = q.get() == null; + + if (d && empty) { + Throwable ex = error; + if (ex != null) { + super.onError(ex); + } else { + super.onCompleted(); + } + return; + } + } + + if (e != 0) { + BackpressureUtils.produced(this, e); + } + + missed = wip.addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + } + +} diff --git a/src/main/java/rx/internal/operators/OnSubscribeDefer.java b/src/main/java/rx/internal/operators/OnSubscribeDefer.java index c3dff6914b..985e149f98 100644 --- a/src/main/java/rx/internal/operators/OnSubscribeDefer.java +++ b/src/main/java/rx/internal/operators/OnSubscribeDefer.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -15,9 +15,8 @@ */ package rx.internal.operators; -import rx.Observable; +import rx.*; import rx.Observable.OnSubscribe; -import rx.Subscriber; import rx.exceptions.Exceptions; import rx.functions.Func0; import rx.observers.Subscribers; @@ -51,5 +50,5 @@ public void call(final Subscriber s) { } o.unsafeSubscribe(Subscribers.wrap(s)); } - + } diff --git a/src/main/java/rx/internal/operators/OnSubscribeDelaySubscription.java b/src/main/java/rx/internal/operators/OnSubscribeDelaySubscription.java index 1876db73a3..367b675413 100644 --- a/src/main/java/rx/internal/operators/OnSubscribeDelaySubscription.java +++ b/src/main/java/rx/internal/operators/OnSubscribeDelaySubscription.java @@ -25,7 +25,7 @@ /** * Delays the subscription to the source by the given amount, running on the given scheduler. - * + * * @param the value type */ public final class OnSubscribeDelaySubscription implements OnSubscribe { @@ -55,5 +55,5 @@ public void call() { } }, time, unit); } - + } diff --git a/src/main/java/rx/internal/operators/OnSubscribeDelaySubscriptionOther.java b/src/main/java/rx/internal/operators/OnSubscribeDelaySubscriptionOther.java index d25d940d61..04658d8a4d 100644 --- a/src/main/java/rx/internal/operators/OnSubscribeDelaySubscriptionOther.java +++ b/src/main/java/rx/internal/operators/OnSubscribeDelaySubscriptionOther.java @@ -19,7 +19,7 @@ import rx.*; import rx.Observable.OnSubscribe; import rx.observers.Subscribers; -import rx.plugins.*; +import rx.plugins.RxJavaHooks; import rx.subscriptions.*; /** @@ -31,28 +31,28 @@ public final class OnSubscribeDelaySubscriptionOther implements OnSubscribe { final Observable main; final Observable other; - + public OnSubscribeDelaySubscriptionOther(Observable main, Observable other) { this.main = main; this.other = other; } - + @Override public void call(Subscriber t) { final SerialSubscription serial = new SerialSubscription(); - + t.add(serial); - + final Subscriber child = Subscribers.wrap(t); - - + + Subscriber otherSubscriber = new Subscriber() { boolean done; @Override public void onNext(U t) { onCompleted(); } - + @Override public void onError(Throwable e) { if (done) { @@ -62,7 +62,7 @@ public void onError(Throwable e) { done = true; child.onError(e); } - + @Override public void onCompleted() { if (done) { @@ -70,13 +70,13 @@ public void onCompleted() { } done = true; serial.set(Subscriptions.unsubscribed()); - + main.unsafeSubscribe(child); } }; - + serial.set(otherSubscriber); - + other.unsafeSubscribe(otherSubscriber); } } diff --git a/src/main/java/rx/internal/operators/OnSubscribeDelaySubscriptionWithSelector.java b/src/main/java/rx/internal/operators/OnSubscribeDelaySubscriptionWithSelector.java index 6b3fb02d62..3de8140731 100644 --- a/src/main/java/rx/internal/operators/OnSubscribeDelaySubscriptionWithSelector.java +++ b/src/main/java/rx/internal/operators/OnSubscribeDelaySubscriptionWithSelector.java @@ -23,7 +23,7 @@ /** * Delays the subscription until the Observable emits an event. - * + * * @param * the value type * @param the value type of the Observable triggering the delayed subscription diff --git a/src/main/java/rx/internal/operators/OnSubscribeDetach.java b/src/main/java/rx/internal/operators/OnSubscribeDetach.java index e736583c85..612247aeeb 100644 --- a/src/main/java/rx/internal/operators/OnSubscribeDetach.java +++ b/src/main/java/rx/internal/operators/OnSubscribeDetach.java @@ -1,12 +1,12 @@ /** * Copyright 2016 Netflix, Inc. - * + * * 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. @@ -19,42 +19,42 @@ import rx.*; import rx.Observable.OnSubscribe; -import rx.internal.util.RxJavaPluginUtils; +import rx.plugins.RxJavaHooks; /** - * Nulls out references to upstream data structures when the source terminates or + * Nulls out references to upstream data structures when the source terminates or * the child unsubscribes. * @param the value type */ public final class OnSubscribeDetach implements OnSubscribe { - + final Observable source; - + public OnSubscribeDetach(Observable source) { this.source = source; } - + @Override public void call(Subscriber t) { DetachSubscriber parent = new DetachSubscriber(t); DetachProducer producer = new DetachProducer(parent); - + t.add(producer); t.setProducer(producer); - + source.unsafeSubscribe(parent); } - + /** * The parent subscriber that forwards events and cleans up on a terminal state. * @param the value type */ static final class DetachSubscriber extends Subscriber { - + final AtomicReference> actual; - + final AtomicReference producer; - + final AtomicLong requested; public DetachSubscriber(Subscriber actual) { @@ -62,11 +62,11 @@ public DetachSubscriber(Subscriber actual) { this.producer = new AtomicReference(); this.requested = new AtomicLong(); } - + @Override public void onNext(T t) { Subscriber a = actual.get(); - + if (a != null) { a.onNext(t); } @@ -76,25 +76,25 @@ public void onNext(T t) { public void onError(Throwable e) { producer.lazySet(TerminatedProducer.INSTANCE); Subscriber a = actual.getAndSet(null); - + if (a != null) { a.onError(e); } else { - RxJavaPluginUtils.handleException(e); + RxJavaHooks.onError(e); } } - + @Override public void onCompleted() { producer.lazySet(TerminatedProducer.INSTANCE); Subscriber a = actual.getAndSet(null); - + if (a != null) { a.onCompleted(); } } - + void innerRequest(long n) { if (n < 0L) { throw new IllegalArgumentException("n >= 0 required but it was " + n); @@ -111,7 +111,7 @@ void innerRequest(long n) { } } } - + @Override public void setProducer(Producer p) { if (producer.compareAndSet(null, p)) { @@ -123,7 +123,7 @@ public void setProducer(Producer p) { } } } - + void innerUnsubscribe() { producer.lazySet(TerminatedProducer.INSTANCE); actual.lazySet(null); @@ -131,7 +131,7 @@ void innerUnsubscribe() { unsubscribe(); } } - + /** * Callbacks from the child Subscriber. * @param the value type @@ -142,29 +142,29 @@ static final class DetachProducer implements Producer, Subscription { public DetachProducer(DetachSubscriber parent) { this.parent = parent; } - + @Override public void request(long n) { parent.innerRequest(n); } - + @Override public boolean isUnsubscribed() { return parent.isUnsubscribed(); } - + @Override public void unsubscribe() { parent.innerUnsubscribe(); } } - + /** * Singleton instance via enum. */ enum TerminatedProducer implements Producer { INSTANCE; - + @Override public void request(long n) { // ignored diff --git a/src/main/java/rx/internal/operators/OnSubscribeDoOnEach.java b/src/main/java/rx/internal/operators/OnSubscribeDoOnEach.java new file mode 100644 index 0000000000..8301d570c6 --- /dev/null +++ b/src/main/java/rx/internal/operators/OnSubscribeDoOnEach.java @@ -0,0 +1,104 @@ +/** + * Copyright 2014 Netflix, Inc. + * + * 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 rx.internal.operators; + +import java.util.Arrays; + +import rx.*; +import rx.Observable.OnSubscribe; +import rx.exceptions.*; +import rx.plugins.RxJavaHooks; + +/** + * Calls specified actions for each notification. + * + * @param the value type + */ +public class OnSubscribeDoOnEach implements OnSubscribe { + private final Observer doOnEachObserver; + private final Observable source; + + public OnSubscribeDoOnEach(Observable source, Observer doOnEachObserver) { + this.source = source; + this.doOnEachObserver = doOnEachObserver; + } + + @Override + public void call(final Subscriber subscriber) { + source.unsafeSubscribe(new DoOnEachSubscriber(subscriber, doOnEachObserver)); + } + + private static final class DoOnEachSubscriber extends Subscriber { + + private final Subscriber subscriber; + private final Observer doOnEachObserver; + + private boolean done; + + DoOnEachSubscriber(Subscriber subscriber, Observer doOnEachObserver) { + super(subscriber); + this.subscriber = subscriber; + this.doOnEachObserver = doOnEachObserver; + } + + @Override + public void onCompleted() { + if (done) { + return; + } + try { + doOnEachObserver.onCompleted(); + } catch (Throwable e) { + Exceptions.throwOrReport(e, this); + return; + } + // Set `done` here so that the error in `doOnEachObserver.onCompleted()` can be noticed by observer + done = true; + subscriber.onCompleted(); + } + + @Override + public void onError(Throwable e) { + if (done) { + RxJavaHooks.onError(e); + return; + } + done = true; + try { + doOnEachObserver.onError(e); + } catch (Throwable e2) { + Exceptions.throwIfFatal(e2); + subscriber.onError(new CompositeException(Arrays.asList(e, e2))); + return; + } + subscriber.onError(e); + } + + @Override + public void onNext(T value) { + if (done) { + return; + } + try { + doOnEachObserver.onNext(value); + } catch (Throwable e) { + Exceptions.throwOrReport(e, this, value); + return; + } + subscriber.onNext(value); + } + } +} \ No newline at end of file diff --git a/src/main/java/rx/internal/operators/OperatorFilter.java b/src/main/java/rx/internal/operators/OnSubscribeFilter.java similarity index 84% rename from src/main/java/rx/internal/operators/OperatorFilter.java rename to src/main/java/rx/internal/operators/OnSubscribeFilter.java index 6d489b0e67..d0edec3de3 100644 --- a/src/main/java/rx/internal/operators/OperatorFilter.java +++ b/src/main/java/rx/internal/operators/OnSubscribeFilter.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -16,10 +16,10 @@ package rx.internal.operators; import rx.*; -import rx.Observable.Operator; +import rx.Observable.OnSubscribe; import rx.exceptions.*; import rx.functions.Func1; -import rx.internal.util.RxJavaPluginUtils; +import rx.plugins.RxJavaHooks; /** * Filters an Observable by discarding any items it emits that do not meet some test. @@ -27,39 +27,42 @@ * * @param the value type */ -public final class OperatorFilter implements Operator { +public final class OnSubscribeFilter implements OnSubscribe { + + final Observable source; final Func1 predicate; - public OperatorFilter(Func1 predicate) { + public OnSubscribeFilter(Observable source, Func1 predicate) { + this.source = source; this.predicate = predicate; } @Override - public Subscriber call(final Subscriber child) { + public void call(final Subscriber child) { FilterSubscriber parent = new FilterSubscriber(child, predicate); child.add(parent); - return parent; + source.unsafeSubscribe(parent); } static final class FilterSubscriber extends Subscriber { - + final Subscriber actual; - + final Func1 predicate; boolean done; - + public FilterSubscriber(Subscriber actual, Func1 predicate) { this.actual = actual; this.predicate = predicate; request(0); } - + @Override public void onNext(T t) { boolean result; - + try { result = predicate.call(t); } catch (Throwable ex) { @@ -68,26 +71,26 @@ public void onNext(T t) { onError(OnErrorThrowable.addValueAsLastCause(ex, t)); return; } - + if (result) { actual.onNext(t); } else { request(1); } } - + @Override public void onError(Throwable e) { if (done) { - RxJavaPluginUtils.handleException(e); + RxJavaHooks.onError(e); return; } done = true; - + actual.onError(e); } - - + + @Override public void onCompleted() { if (done) { diff --git a/src/main/java/rx/internal/operators/OnSubscribeFlatMapCompletable.java b/src/main/java/rx/internal/operators/OnSubscribeFlatMapCompletable.java new file mode 100644 index 0000000000..8b22e740a0 --- /dev/null +++ b/src/main/java/rx/internal/operators/OnSubscribeFlatMapCompletable.java @@ -0,0 +1,215 @@ +/** + * Copyright 2017 Netflix, Inc. + * + * 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 rx.internal.operators; + +import java.util.concurrent.atomic.*; + +import rx.*; +import rx.exceptions.Exceptions; +import rx.functions.Func1; +import rx.internal.util.ExceptionsUtils; +import rx.plugins.RxJavaHooks; +import rx.subscriptions.CompositeSubscription; + +/** + * Maps upstream values to Completables and merges them, up to a given + * number of them concurrently, optionally delaying errors. + *

    History: 1.2.7 - experimental + * @param the upstream value type + * @since 1.3 + */ +public final class OnSubscribeFlatMapCompletable implements Observable.OnSubscribe { + + final Observable source; + + final Func1 mapper; + + final boolean delayErrors; + + final int maxConcurrency; + + public OnSubscribeFlatMapCompletable(Observable source, Func1 mapper, + boolean delayErrors, int maxConcurrency) { + if (mapper == null) { + throw new NullPointerException("mapper is null"); + } + if (maxConcurrency <= 0) { + throw new IllegalArgumentException("maxConcurrency > 0 required but it was " + maxConcurrency); + } + this.source = source; + this.mapper = mapper; + this.delayErrors = delayErrors; + this.maxConcurrency = maxConcurrency; + } + + @Override + public void call(Subscriber child) { + FlatMapCompletableSubscriber parent = new FlatMapCompletableSubscriber(child, mapper, delayErrors, maxConcurrency); + child.add(parent); + child.add(parent.set); + source.unsafeSubscribe(parent); + } + + static final class FlatMapCompletableSubscriber extends Subscriber { + + final Subscriber actual; + + final Func1 mapper; + + final boolean delayErrors; + + final int maxConcurrency; + + final AtomicInteger wip; + + final CompositeSubscription set; + + final AtomicReference errors; + + FlatMapCompletableSubscriber(Subscriber actual, Func1 mapper, + boolean delayErrors, int maxConcurrency) { + this.actual = actual; + this.mapper = mapper; + this.delayErrors = delayErrors; + this.maxConcurrency = maxConcurrency; + this.wip = new AtomicInteger(1); + this.errors = new AtomicReference(); + this.set = new CompositeSubscription(); + this.request(maxConcurrency != Integer.MAX_VALUE ? maxConcurrency : Long.MAX_VALUE); + } + + @Override + public void onNext(T t) { + Completable c; + + try { + c = mapper.call(t); + if (c == null) { + throw new NullPointerException("The mapper returned a null Completable"); + } + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + unsubscribe(); + onError(ex); + return; + } + + InnerSubscriber inner = new InnerSubscriber(); + set.add(inner); + wip.getAndIncrement(); + + c.unsafeSubscribe(inner); + } + + @Override + public void onError(Throwable e) { + if (delayErrors) { + ExceptionsUtils.addThrowable(errors, e); + onCompleted(); + } else { + set.unsubscribe(); + if (errors.compareAndSet(null, e)) { + actual.onError(ExceptionsUtils.terminate(errors)); + } else { + RxJavaHooks.onError(e); + } + } + } + + @Override + public void onCompleted() { + done(); + } + + boolean done() { + if (wip.decrementAndGet() == 0) { + Throwable ex = ExceptionsUtils.terminate(errors); + if (ex != null) { + actual.onError(ex); + } else { + actual.onCompleted(); + } + return true; + } + return false; + } + + public void innerError(InnerSubscriber inner, Throwable e) { + set.remove(inner); + if (delayErrors) { + ExceptionsUtils.addThrowable(errors, e); + if (!done() && maxConcurrency != Integer.MAX_VALUE) { + request(1); + } + } else { + set.unsubscribe(); + unsubscribe(); + if (errors.compareAndSet(null, e)) { + actual.onError(ExceptionsUtils.terminate(errors)); + } else { + RxJavaHooks.onError(e); + } + } + } + + public void innerComplete(InnerSubscriber inner) { + set.remove(inner); + if (!done() && maxConcurrency != Integer.MAX_VALUE) { + request(1); + } + } + + final class InnerSubscriber + extends AtomicReference + implements CompletableSubscriber, Subscription { + + private static final long serialVersionUID = -8588259593722659900L; + + @Override + public void unsubscribe() { + Subscription s = getAndSet(this); + if (s != null && s != this) { + s.unsubscribe(); + } + } + + @Override + public boolean isUnsubscribed() { + return get() == this; + } + + @Override + public void onCompleted() { + innerComplete(this); + } + + @Override + public void onError(Throwable e) { + innerError(this, e); + } + + @Override + public void onSubscribe(Subscription d) { + if (!compareAndSet(null, d)) { + d.unsubscribe(); + if (get() != this) { + RxJavaHooks.onError(new IllegalStateException("Subscription already set!")); + } + } + } + } + } +} diff --git a/src/main/java/rx/internal/operators/OnSubscribeFlatMapSingle.java b/src/main/java/rx/internal/operators/OnSubscribeFlatMapSingle.java new file mode 100644 index 0000000000..190d6acbc0 --- /dev/null +++ b/src/main/java/rx/internal/operators/OnSubscribeFlatMapSingle.java @@ -0,0 +1,334 @@ +/** + * Copyright 2017 Netflix, Inc. + * + * 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 rx.internal.operators; + +import java.util.Queue; +import java.util.concurrent.atomic.*; + +import rx.*; +import rx.exceptions.Exceptions; +import rx.functions.Func1; +import rx.internal.util.ExceptionsUtils; +import rx.internal.util.atomic.MpscLinkedAtomicQueue; +import rx.internal.util.unsafe.*; +import rx.plugins.RxJavaHooks; +import rx.subscriptions.CompositeSubscription; + +/** + * Maps upstream values to Singles and merges them, up to a given + * number of them concurrently, optionally delaying errors. + *

    History: 1.2.7 - experimental + * @param the upstream value type + * @param the inner Singles and result value type + * @since 1.3 + */ +public final class OnSubscribeFlatMapSingle implements Observable.OnSubscribe { + + final Observable source; + + final Func1> mapper; + + final boolean delayErrors; + + final int maxConcurrency; + + public OnSubscribeFlatMapSingle(Observable source, Func1> mapper, + boolean delayErrors, int maxConcurrency) { + if (mapper == null) { + throw new NullPointerException("mapper is null"); + } + if (maxConcurrency <= 0) { + throw new IllegalArgumentException("maxConcurrency > 0 required but it was " + maxConcurrency); + } + this.source = source; + this.mapper = mapper; + this.delayErrors = delayErrors; + this.maxConcurrency = maxConcurrency; + } + + @Override + public void call(Subscriber child) { + FlatMapSingleSubscriber parent = new FlatMapSingleSubscriber(child, mapper, delayErrors, maxConcurrency); + child.add(parent.set); + child.add(parent.requested); + child.setProducer(parent.requested); + source.unsafeSubscribe(parent); + } + + static final class FlatMapSingleSubscriber extends Subscriber { + + final Subscriber actual; + + final Func1> mapper; + + final boolean delayErrors; + + final int maxConcurrency; + + final AtomicInteger wip; + + final AtomicInteger active; + + final CompositeSubscription set; + + final AtomicReference errors; + + final Queue queue; + + final Requested requested; + + volatile boolean done; + + volatile boolean cancelled; + + FlatMapSingleSubscriber(Subscriber actual, + Func1> mapper, + boolean delayErrors, int maxConcurrency) { + this.actual = actual; + this.mapper = mapper; + this.delayErrors = delayErrors; + this.maxConcurrency = maxConcurrency; + this.wip = new AtomicInteger(); + this.errors = new AtomicReference(); + this.requested = new Requested(); + this.set = new CompositeSubscription(); + this.active = new AtomicInteger(); + if (UnsafeAccess.isUnsafeAvailable()) { + queue = new MpscLinkedQueue(); + } else { + queue = new MpscLinkedAtomicQueue(); + } + this.request(maxConcurrency != Integer.MAX_VALUE ? maxConcurrency : Long.MAX_VALUE); + } + + @Override + public void onNext(T t) { + Single c; + + try { + c = mapper.call(t); + if (c == null) { + throw new NullPointerException("The mapper returned a null Single"); + } + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + unsubscribe(); + onError(ex); + return; + } + + InnerSubscriber inner = new InnerSubscriber(); + set.add(inner); + active.incrementAndGet(); + + c.subscribe(inner); + } + + @Override + public void onError(Throwable e) { + if (delayErrors) { + ExceptionsUtils.addThrowable(errors, e); + } else { + set.unsubscribe(); + if (!errors.compareAndSet(null, e)) { + RxJavaHooks.onError(e); + return; + } + } + done = true; + drain(); + } + + @Override + public void onCompleted() { + done = true; + drain(); + } + + void innerSuccess(InnerSubscriber inner, R value) { + queue.offer(NotificationLite.next(value)); + set.remove(inner); + active.decrementAndGet(); + drain(); + } + + void innerError(InnerSubscriber inner, Throwable e) { + if (delayErrors) { + ExceptionsUtils.addThrowable(errors, e); + set.remove(inner); + if (!done && maxConcurrency != Integer.MAX_VALUE) { + request(1); + } + } else { + set.unsubscribe(); + unsubscribe(); + if (!errors.compareAndSet(null, e)) { + RxJavaHooks.onError(e); + return; + } + done = true; + } + active.decrementAndGet(); + drain(); + } + + void drain() { + if (wip.getAndIncrement() != 0) { + return; + } + + int missed = 1; + Subscriber a = actual; + Queue q = queue; + boolean delayError = this.delayErrors; + AtomicInteger act = active; + + for (;;) { + long r = requested.get(); + long e = 0L; + + while (e != r) { + if (cancelled) { + q.clear(); + return; + } + + boolean d = done; + + if (!delayError && d) { + Throwable ex = errors.get(); + if (ex != null) { + q.clear(); + a.onError(ExceptionsUtils.terminate(errors)); + return; + } + } + + Object o = q.poll(); + + boolean empty = o == null; + + if (d && act.get() == 0 && empty) { + Throwable ex = errors.get(); + if (ex != null) { + a.onError(ExceptionsUtils.terminate(errors)); + } else { + a.onCompleted(); + } + return; + } + + if (empty) { + break; + } + + a.onNext(NotificationLite.getValue(o)); + + e++; + } + + if (e == r) { + if (cancelled) { + q.clear(); + return; + } + + if (done) { + if (delayError) { + if (act.get() == 0 && q.isEmpty()) { + Throwable ex = errors.get(); + if (ex != null) { + a.onError(ExceptionsUtils.terminate(errors)); + } else { + a.onCompleted(); + } + return; + } + } else { + Throwable ex = errors.get(); + if (ex != null) { + q.clear(); + a.onError(ExceptionsUtils.terminate(errors)); + return; + } + else if (act.get() == 0 && q.isEmpty()) { + a.onCompleted(); + return; + } + } + } + } + + if (e != 0L) { + requested.produced(e); + if (!done && maxConcurrency != Integer.MAX_VALUE) { + request(e); + } + } + + missed = wip.addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + + final class Requested extends AtomicLong implements Producer, Subscription { + + private static final long serialVersionUID = -887187595446742742L; + + @Override + public void request(long n) { + if (n > 0L) { + BackpressureUtils.getAndAddRequest(this, n); + drain(); + } + } + + void produced(long e) { + BackpressureUtils.produced(this, e); + } + + @Override + public void unsubscribe() { + cancelled = true; + FlatMapSingleSubscriber.this.unsubscribe(); + if (wip.getAndIncrement() == 0) { + queue.clear(); + } + } + + @Override + public boolean isUnsubscribed() { + return cancelled; + } + } + + final class InnerSubscriber extends SingleSubscriber { + + @Override + public void onSuccess(R t) { + innerSuccess(this, t); + } + + @Override + public void onError(Throwable error) { + innerError(this, error); + } + } + } +} diff --git a/src/main/java/rx/internal/operators/OnSubscribeFlattenIterable.java b/src/main/java/rx/internal/operators/OnSubscribeFlattenIterable.java index fb3b9f97bd..fe85e55369 100644 --- a/src/main/java/rx/internal/operators/OnSubscribeFlattenIterable.java +++ b/src/main/java/rx/internal/operators/OnSubscribeFlattenIterable.java @@ -1,12 +1,12 @@ /** * Copyright 2016 Netflix, Inc. - * + * * 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. @@ -27,6 +27,7 @@ import rx.internal.util.*; import rx.internal.util.atomic.*; import rx.internal.util.unsafe.*; +import rx.plugins.RxJavaHooks; /** * Flattens a sequence if Iterable sources, generated via a function, into a single sequence. @@ -35,13 +36,13 @@ * @param the output value type */ public final class OnSubscribeFlattenIterable implements OnSubscribe { - + final Observable source; - + final Func1> mapper; - + final int prefetch; - + /** Protected: use createFrom to handle source-dependent optimizations. */ protected OnSubscribeFlattenIterable(Observable source, Func1> mapper, int prefetch) { @@ -49,11 +50,11 @@ protected OnSubscribeFlattenIterable(Observable source, this.mapper = mapper; this.prefetch = prefetch; } - + @Override public void call(Subscriber t) { final FlattenIterableSubscriber parent = new FlattenIterableSubscriber(t, mapper, prefetch); - + t.add(parent); t.setProducer(new Producer() { @Override @@ -61,42 +62,40 @@ public void request(long n) { parent.requestMore(n); } }); - + source.unsafeSubscribe(parent); } - + public static Observable createFrom(Observable source, Func1> mapper, int prefetch) { if (source instanceof ScalarSynchronousObservable) { T scalar = ((ScalarSynchronousObservable) source).get(); - return Observable.create(new OnSubscribeScalarFlattenIterable(scalar, mapper)); + return Observable.unsafeCreate(new OnSubscribeScalarFlattenIterable(scalar, mapper)); } - return Observable.create(new OnSubscribeFlattenIterable(source, mapper, prefetch)); + return Observable.unsafeCreate(new OnSubscribeFlattenIterable(source, mapper, prefetch)); } - + static final class FlattenIterableSubscriber extends Subscriber { final Subscriber actual; - + final Func1> mapper; - + final long limit; - + final Queue queue; final AtomicReference error; - + final AtomicLong requested; - + final AtomicInteger wip; - - final NotificationLite nl; - + volatile boolean done; - + long produced; - + Iterator active; - + public FlattenIterableSubscriber(Subscriber actual, Func1> mapper, int prefetch) { this.actual = actual; @@ -104,7 +103,6 @@ public FlattenIterableSubscriber(Subscriber actual, this.error = new AtomicReference(); this.wip = new AtomicInteger(); this.requested = new AtomicLong(); - this.nl = NotificationLite.instance(); if (prefetch == Integer.MAX_VALUE) { this.limit = Long.MAX_VALUE; this.queue = new SpscLinkedArrayQueue(RxRingBuffer.SIZE); @@ -119,33 +117,33 @@ public FlattenIterableSubscriber(Subscriber actual, } request(prefetch); } - + @Override public void onNext(T t) { - if (!queue.offer(nl.next(t))) { + if (!queue.offer(NotificationLite.next(t))) { unsubscribe(); onError(new MissingBackpressureException()); return; } drain(); } - + @Override public void onError(Throwable e) { if (ExceptionsUtils.addThrowable(error, e)) { done = true; drain(); } else { - RxJavaPluginUtils.handleException(e); + RxJavaHooks.onError(e); } } - + @Override public void onCompleted() { done = true; drain(); } - + void requestMore(long n) { if (n > 0) { BackpressureUtils.getAndAddRequest(requested, n); @@ -154,34 +152,34 @@ void requestMore(long n) { throw new IllegalStateException("n >= 0 required but it was " + n); } } - + void drain() { if (wip.getAndIncrement() != 0) { return; } - + final Subscriber actual = this.actual; final Queue queue = this.queue; - + int missed = 1; - + for (;;) { - + Iterator it = active; - + if (it == null) { boolean d = done; - + Object v = queue.poll(); - + boolean empty = v == null; if (checkTerminated(d, empty, actual, queue)) { return; } - + if (!empty) { - + long p = produced + 1; if (p == limit) { produced = 0L; @@ -189,43 +187,42 @@ void drain() { } else { produced = p; } - + boolean b; - + try { - Iterable iter = mapper.call(nl.getValue(v)); - - it = iter.iterator(); - + Iterable iterable = mapper.call(NotificationLite.getValue(v)); + + it = iterable.iterator(); + b = it.hasNext(); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); - - it = null; + onError(ex); - + continue; } - + if (!b) { continue; } - + active = it; } } - + if (it != null) { long r = requested.get(); long e = 0L; - + while (e != r) { if (checkTerminated(done, false, actual, queue)) { return; } - + R v; - + try { v = it.next(); } catch (Throwable ex) { @@ -235,7 +232,7 @@ void drain() { onError(ex); break; } - + actual.onNext(v); if (checkTerminated(done, false, actual, queue)) { @@ -245,7 +242,7 @@ void drain() { e++; boolean b; - + try { b = it.hasNext(); } catch (Throwable ex) { @@ -255,43 +252,43 @@ void drain() { onError(ex); break; } - + if (!b) { it = null; active = null; break; } } - + if (e == r) { if (checkTerminated(done, queue.isEmpty() && it == null, actual, queue)) { return; } } - + if (e != 0L) { BackpressureUtils.produced(requested, e); } - + if (it == null) { continue; } } - + missed = wip.addAndGet(-missed); if (missed == 0) { break; } } } - + boolean checkTerminated(boolean d, boolean empty, Subscriber a, Queue q) { if (a.isUnsubscribed()) { q.clear(); active = null; return true; } - + if (d) { Throwable ex = error.get(); if (ex != null) { @@ -299,23 +296,23 @@ boolean checkTerminated(boolean d, boolean empty, Subscriber a, Queue q) { unsubscribe(); q.clear(); active = null; - + a.onError(ex); return true; } else if (empty) { - + a.onCompleted(); return true; } } - + return false; } } - + /** - * A custom flattener that works from a scalar value and computes the iterable + * A custom flattening operator that works from a scalar value and computes the iterable * during subscription time. * * @param the scalar's value type @@ -323,7 +320,7 @@ boolean checkTerminated(boolean d, boolean empty, Subscriber a, Queue q) { */ static final class OnSubscribeScalarFlattenIterable implements OnSubscribe { final T value; - + final Func1> mapper; public OnSubscribeScalarFlattenIterable(T value, Func1> mapper) { @@ -333,25 +330,25 @@ public OnSubscribeScalarFlattenIterable(T value, Func1 t) { - Iterator itor; + Iterator iterator; boolean b; try { Iterable it = mapper.call(value); - - itor = it.iterator(); - - b = itor.hasNext(); + + iterator = it.iterator(); + + b = iterator.hasNext(); } catch (Throwable ex) { Exceptions.throwOrReport(ex, t, value); return; } - + if (!b) { t.onCompleted(); return; } - - t.setProducer(new OnSubscribeFromIterable.IterableProducer(t, itor)); + + t.setProducer(new OnSubscribeFromIterable.IterableProducer(t, iterator)); } } } diff --git a/src/main/java/rx/internal/operators/OnSubscribeFromArray.java b/src/main/java/rx/internal/operators/OnSubscribeFromArray.java index 623dcaa65f..3039fc0188 100644 --- a/src/main/java/rx/internal/operators/OnSubscribeFromArray.java +++ b/src/main/java/rx/internal/operators/OnSubscribeFromArray.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -26,28 +26,28 @@ public final class OnSubscribeFromArray implements OnSubscribe { public OnSubscribeFromArray(T[] array) { this.array = array; } - + @Override public void call(Subscriber child) { child.setProducer(new FromArrayProducer(child, array)); } - + static final class FromArrayProducer extends AtomicLong implements Producer { /** */ private static final long serialVersionUID = 3534218984725836979L; - + final Subscriber child; final T[] array; - + int index; - + public FromArrayProducer(Subscriber child, T[] array) { this.child = child; this.array = array; } - + @Override public void request(long n) { if (n < 0) { @@ -64,56 +64,56 @@ public void request(long n) { } } } - + void fastPath() { final Subscriber child = this.child; - + for (T t : array) { if (child.isUnsubscribed()) { return; } - + child.onNext(t); } - + if (child.isUnsubscribed()) { return; } child.onCompleted(); } - + void slowPath(long r) { final Subscriber child = this.child; final T[] array = this.array; final int n = array.length; - + long e = 0L; int i = index; for (;;) { - + while (r != 0L && i != n) { if (child.isUnsubscribed()) { return; } - + child.onNext(array[i]); - + i++; - + if (i == n) { if (!child.isUnsubscribed()) { child.onCompleted(); } return; } - + r--; e--; } - + r = get() + e; - + if (r == 0L) { index = i; r = addAndGet(e); diff --git a/src/main/java/rx/internal/operators/OnSubscribeFromCallable.java b/src/main/java/rx/internal/operators/OnSubscribeFromCallable.java index 2a81e9f1f0..98128e15ff 100644 --- a/src/main/java/rx/internal/operators/OnSubscribeFromCallable.java +++ b/src/main/java/rx/internal/operators/OnSubscribeFromCallable.java @@ -16,13 +16,12 @@ package rx.internal.operators; -import rx.Observable; -import rx.Subscriber; +import java.util.concurrent.Callable; + +import rx.*; import rx.exceptions.Exceptions; import rx.internal.producers.SingleDelayedProducer; -import java.util.concurrent.Callable; - /** * Do not invoke the function until an Observer subscribes; Invokes function on each * subscription. diff --git a/src/main/java/rx/internal/operators/OnSubscribeFromIterable.java b/src/main/java/rx/internal/operators/OnSubscribeFromIterable.java index 9ee3104f95..4dd0c6ba51 100644 --- a/src/main/java/rx/internal/operators/OnSubscribeFromIterable.java +++ b/src/main/java/rx/internal/operators/OnSubscribeFromIterable.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -44,22 +44,22 @@ public OnSubscribeFromIterable(Iterable iterable) { @Override public void call(final Subscriber o) { - final Iterator it; + Iterator it; boolean b; - + try { it = is.iterator(); - + b = it.hasNext(); } catch (Throwable ex) { Exceptions.throwOrReport(ex, o); return; } - + if (!o.isUnsubscribed()) { if (!b) { o.onCompleted(); - } else { + } else { o.setProducer(new IterableProducer(o, it)); } } @@ -83,37 +83,37 @@ public void request(long n) { return; } if (n == Long.MAX_VALUE && compareAndSet(0, Long.MAX_VALUE)) { - fastpath(); - } else + fastPath(); + } else if (n > 0 && BackpressureUtils.getAndAddRequest(this, n) == 0L) { - slowpath(n); + slowPath(n); } } - void slowpath(long n) { + void slowPath(long n) { // backpressure is requested final Subscriber o = this.o; final Iterator it = this.it; long r = n; long e = 0; - + for (;;) { while (e != r) { if (o.isUnsubscribed()) { return; } - + T value; - + try { value = it.next(); } catch (Throwable ex) { Exceptions.throwOrReport(ex, o); return; } - + o.onNext(value); if (o.isUnsubscribed()) { @@ -121,24 +121,24 @@ void slowpath(long n) { } boolean b; - + try { b = it.hasNext(); } catch (Throwable ex) { Exceptions.throwOrReport(ex, o); return; } - + if (!b) { if (!o.isUnsubscribed()) { o.onCompleted(); } return; } - + e++; } - + r = get(); if (e == r) { r = BackpressureUtils.produced(this, e); @@ -148,10 +148,10 @@ void slowpath(long n) { e = 0L; } } - + } - void fastpath() { + void fastPath() { // fast-path without backpressure final Subscriber o = this.o; final Iterator it = this.it; @@ -160,7 +160,7 @@ void fastpath() { if (o.isUnsubscribed()) { return; } - + T value; try { @@ -169,7 +169,7 @@ void fastpath() { Exceptions.throwOrReport(ex, o); return; } - + o.onNext(value); if (o.isUnsubscribed()) { diff --git a/src/main/java/rx/internal/operators/OnSubscribeGroupJoin.java b/src/main/java/rx/internal/operators/OnSubscribeGroupJoin.java index a8f13fb5e7..3c99b68262 100644 --- a/src/main/java/rx/internal/operators/OnSubscribeGroupJoin.java +++ b/src/main/java/rx/internal/operators/OnSubscribeGroupJoin.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -29,7 +29,7 @@ /** * Correlates two sequences when they overlap and groups the results. - * + * * @see MSDN: Observable.GroupJoin * @param the left value type * @param the right value type @@ -38,11 +38,11 @@ * @param the result value type */ public final class OnSubscribeGroupJoin implements OnSubscribe { - protected final Observable left; - protected final Observable right; - protected final Func1> leftDuration; - protected final Func1> rightDuration; - protected final Func2, ? extends R> resultSelector; + final Observable left; + final Observable right; + final Func1> leftDuration; + final Func1> rightDuration; + final Func2, ? extends R> resultSelector; public OnSubscribeGroupJoin( Observable left, @@ -65,25 +65,27 @@ public void call(Subscriber child) { } /** Manages sub-observers and subscriptions. */ - final class ResultManager implements Subscription { + final class ResultManager extends HashMap>implements Subscription { + // HashMap aspect of `this` refers to `leftMap` + + private static final long serialVersionUID = -3035156013812425335L; + final RefCountSubscription cancel; final Subscriber subscriber; final CompositeSubscription group; - final Object guard = new Object(); - /** Guarded by guard. */ + /** Guarded by this. */ int leftIds; - /** Guarded by guard. */ + /** Guarded by this. */ int rightIds; - /** Guarded by guard. */ - final Map> leftMap = new HashMap>(); - /** Guarded by guard. */ - final Map rightMap = new HashMap(); - /** Guarded by guard. */ + /** Guarded by this. */ + final Map rightMap = new HashMap(); // NOPMD + /** Guarded by this. */ boolean leftDone; - /** Guarded by guard. */ + /** Guarded by this. */ boolean rightDone; public ResultManager(Subscriber subscriber) { + super(); this.subscriber = subscriber; this.group = new CompositeSubscription(); this.cancel = new RefCountSubscription(group); @@ -93,7 +95,7 @@ public void init() { Subscriber s1 = new LeftObserver(); Subscriber s2 = new RightObserver(); - + group.add(s1); group.add(s2); @@ -105,20 +107,25 @@ public void init() { public void unsubscribe() { cancel.unsubscribe(); } - + @Override public boolean isUnsubscribed() { return cancel.isUnsubscribed(); } + + Map> leftMap() { + return this; + } + /** * Notify everyone and cleanup. * @param e the exception */ void errorAll(Throwable e) { List> list; - synchronized (guard) { - list = new ArrayList>(leftMap.values()); - leftMap.clear(); + synchronized (ResultManager.this) { + list = new ArrayList>(leftMap().values()); + leftMap().clear(); rightMap.clear(); } for (Observer o : list) { @@ -132,10 +139,10 @@ void errorAll(Throwable e) { * @param e the exception */ void errorMain(Throwable e) { - synchronized (guard) { - leftMap.clear(); + synchronized (ResultManager.this) { + leftMap().clear(); rightMap.clear(); - } + } subscriber.onError(e); cancel.unsubscribe(); } @@ -148,7 +155,7 @@ void complete(List> list) { cancel.unsubscribe(); } } - + /** Observe the left source. */ final class LeftObserver extends Subscriber { @Override @@ -157,13 +164,13 @@ public void onNext(T1 args) { int id; Subject subj = PublishSubject.create(); Observer subjSerial = new SerializedObserver(subj); - - synchronized (guard) { + + synchronized (ResultManager.this) { id = leftIds++; - leftMap.put(id, subjSerial); + leftMap().put(id, subjSerial); } - Observable window = Observable.create(new WindowObservableFunc(subj, cancel)); + Observable window = Observable.unsafeCreate(new WindowObservableFunc(subj, cancel)); Observable duration = leftDuration.call(args); @@ -174,16 +181,16 @@ public void onNext(T1 args) { R result = resultSelector.call(args, window); List rightMapValues; - synchronized (guard) { + synchronized (ResultManager.this) { rightMapValues = new ArrayList(rightMap.values()); } - + subscriber.onNext(result); for (T2 t2 : rightMapValues) { subjSerial.onNext(t2); } - - + + } catch (Throwable t) { Exceptions.throwOrReport(t, this); } @@ -192,11 +199,11 @@ public void onNext(T1 args) { @Override public void onCompleted() { List> list = null; - synchronized (guard) { + synchronized (ResultManager.this) { leftDone = true; if (rightDone) { - list = new ArrayList>(leftMap.values()); - leftMap.clear(); + list = new ArrayList>(leftMap().values()); + leftMap().clear(); rightMap.clear(); } } @@ -216,20 +223,20 @@ final class RightObserver extends Subscriber { public void onNext(T2 args) { try { int id; - synchronized (guard) { + synchronized (ResultManager.this) { id = rightIds++; rightMap.put(id, args); } Observable duration = rightDuration.call(args); Subscriber d2 = new RightDurationObserver(id); - + group.add(d2); duration.unsafeSubscribe(d2); List> list; - synchronized (guard) { - list = new ArrayList>(leftMap.values()); + synchronized (ResultManager.this) { + list = new ArrayList>(leftMap().values()); } for (Observer o : list) { o.onNext(args); @@ -242,11 +249,11 @@ public void onNext(T2 args) { @Override public void onCompleted() { List> list = null; - synchronized (guard) { + synchronized (ResultManager.this) { rightDone = true; if (leftDone) { - list = new ArrayList>(leftMap.values()); - leftMap.clear(); + list = new ArrayList>(leftMap().values()); + leftMap().clear(); rightMap.clear(); } } @@ -273,8 +280,8 @@ public void onCompleted() { if (once) { once = false; Observer gr; - synchronized (guard) { - gr = leftMap.remove(id); + synchronized (ResultManager.this) { + gr = leftMap().remove(id); } if (gr != null) { gr.onCompleted(); @@ -306,7 +313,7 @@ public RightDurationObserver(int id) { public void onCompleted() { if (once) { once = false; - synchronized (guard) { + synchronized (ResultManager.this) { rightMap.remove(id); } group.remove(this); @@ -345,7 +352,7 @@ public void call(Subscriber t1) { Subscription ref = refCount.get(); WindowSubscriber wo = new WindowSubscriber(t1, ref); wo.add(ref); - + underlying.unsafeSubscribe(wo); } diff --git a/src/main/java/rx/internal/operators/OnSubscribeJoin.java b/src/main/java/rx/internal/operators/OnSubscribeJoin.java index f93437c5d0..602263dc45 100644 --- a/src/main/java/rx/internal/operators/OnSubscribeJoin.java +++ b/src/main/java/rx/internal/operators/OnSubscribeJoin.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -27,7 +27,7 @@ /** * Correlates the elements of two sequences based on overlapping durations. - * + * * @param the left value type * @param the right value type * @param the left duration value type @@ -61,36 +61,42 @@ public void call(Subscriber t1) { } /** Manage the left and right sources. */ - final class ResultSink { + final class ResultSink extends HashMap { + //HashMap aspect of `this` refers to the `leftMap` + + private static final long serialVersionUID = 3491669543549085380L; + final CompositeSubscription group; final Subscriber subscriber; - final Object guard = new Object(); - /** Guarded by guard. */ + /** Guarded by this. */ boolean leftDone; - /** Guarded by guard. */ + /** Guarded by this. */ int leftId; - /** Guarded by guard. */ - final Map leftMap; - /** Guarded by guard. */ + /** Guarded by this. */ boolean rightDone; - /** Guarded by guard. */ + /** Guarded by this. */ int rightId; - /** Guarded by guard. */ + /** Guarded by this. */ final Map rightMap; public ResultSink(Subscriber subscriber) { + super(); this.subscriber = subscriber; this.group = new CompositeSubscription(); - this.leftMap = new HashMap(); + //`leftMap` is `this` this.rightMap = new HashMap(); } + HashMap leftMap() { + return this; + } + public void run() { subscriber.add(group); - + Subscriber s1 = new LeftSubscriber(); Subscriber s2 = new RightSubscriber(); - + group.add(s1); group.add(s2); @@ -103,8 +109,8 @@ final class LeftSubscriber extends Subscriber { protected void expire(int id, Subscription resource) { boolean complete = false; - synchronized (guard) { - if (leftMap.remove(id) != null && leftMap.isEmpty() && leftDone) { + synchronized (ResultSink.this) { + if (leftMap().remove(id) != null && leftMap().isEmpty() && leftDone) { complete = true; } } @@ -121,9 +127,9 @@ public void onNext(TLeft args) { int id; int highRightId; - synchronized (guard) { + synchronized (ResultSink.this) { id = leftId++; - leftMap.put(id, args); + leftMap().put(id, args); highRightId = rightId; } @@ -137,7 +143,7 @@ public void onNext(TLeft args) { duration.unsafeSubscribe(d1); List rightValues = new ArrayList(); - synchronized (guard) { + synchronized (ResultSink.this) { for (Map.Entry entry : rightMap.entrySet()) { if (entry.getKey() < highRightId) { rightValues.add(entry.getValue()); @@ -162,9 +168,9 @@ public void onError(Throwable e) { @Override public void onCompleted() { boolean complete = false; - synchronized (guard) { + synchronized (ResultSink.this) { leftDone = true; - if (rightDone || leftMap.isEmpty()) { + if (rightDone || leftMap().isEmpty()) { complete = true; } } @@ -211,7 +217,7 @@ final class RightSubscriber extends Subscriber { void expire(int id, Subscription resource) { boolean complete = false; - synchronized (guard) { + synchronized (ResultSink.this) { if (rightMap.remove(id) != null && rightMap.isEmpty() && rightDone) { complete = true; } @@ -226,9 +232,9 @@ void expire(int id, Subscription resource) { @Override public void onNext(TRight args) { - int id; + int id; int highLeftId; - synchronized (guard) { + synchronized (ResultSink.this) { id = rightId++; rightMap.put(id, args); highLeftId = leftId; @@ -242,24 +248,24 @@ public void onNext(TRight args) { Subscriber d2 = new RightDurationSubscriber(id); group.add(d2); - + duration.unsafeSubscribe(d2); - + List leftValues = new ArrayList(); - synchronized (guard) { - for (Map.Entry entry : leftMap.entrySet()) { + synchronized (ResultSink.this) { + for (Map.Entry entry : leftMap().entrySet()) { if (entry.getKey() < highLeftId) { leftValues.add(entry.getValue()); } } } - + for (TLeft lv : leftValues) { R result = resultSelector.call(lv, args); subscriber.onNext(result); } - + } catch (Throwable t) { Exceptions.throwOrReport(t, this); } @@ -274,7 +280,7 @@ public void onError(Throwable e) { @Override public void onCompleted() { boolean complete = false; - synchronized (guard) { + synchronized (ResultSink.this) { rightDone = true; if (leftDone || rightMap.isEmpty()) { complete = true; diff --git a/src/main/java/rx/internal/operators/OnSubscribeLift.java b/src/main/java/rx/internal/operators/OnSubscribeLift.java index 65f6f20d14..a2c67f084e 100644 --- a/src/main/java/rx/internal/operators/OnSubscribeLift.java +++ b/src/main/java/rx/internal/operators/OnSubscribeLift.java @@ -1,12 +1,12 @@ /** * Copyright 2016 Netflix, Inc. - * + * * 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. @@ -19,7 +19,7 @@ import rx.Observable.*; import rx.Subscriber; import rx.exceptions.Exceptions; -import rx.plugins.*; +import rx.plugins.RxJavaHooks; /** * Transforms the downstream Subscriber into a Subscriber via an operator @@ -28,7 +28,7 @@ * @param the result value type */ public final class OnSubscribeLift implements OnSubscribe { - + final OnSubscribe parent; final Operator operator; @@ -47,7 +47,7 @@ public void call(Subscriber o) { st.onStart(); parent.call(st); } catch (Throwable e) { - // localized capture of errors rather than it skipping all operators + // localized capture of errors rather than it skipping all operators // and ending up in the try/catch of the subscribe method which then // prevents onErrorResumeNext and other similar approaches to error handling Exceptions.throwIfFatal(e); diff --git a/src/main/java/rx/internal/operators/OperatorMap.java b/src/main/java/rx/internal/operators/OnSubscribeMap.java similarity index 83% rename from src/main/java/rx/internal/operators/OperatorMap.java rename to src/main/java/rx/internal/operators/OnSubscribeMap.java index a8a33178ca..009d736e45 100644 --- a/src/main/java/rx/internal/operators/OperatorMap.java +++ b/src/main/java/rx/internal/operators/OnSubscribeMap.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -16,52 +16,55 @@ package rx.internal.operators; import rx.*; -import rx.Observable.Operator; +import rx.Observable.OnSubscribe; import rx.exceptions.*; import rx.functions.Func1; -import rx.internal.util.RxJavaPluginUtils; +import rx.plugins.RxJavaHooks; /** * Applies a function of your choosing to every item emitted by an {@code Observable}, and emits the results of * this transformation as a new {@code Observable}. *

    * - * + * * @param the input value type * @param the return value type */ -public final class OperatorMap implements Operator { +public final class OnSubscribeMap implements OnSubscribe { + + final Observable source; final Func1 transformer; - public OperatorMap(Func1 transformer) { + public OnSubscribeMap(Observable source, Func1 transformer) { + this.source = source; this.transformer = transformer; } @Override - public Subscriber call(final Subscriber o) { + public void call(final Subscriber o) { MapSubscriber parent = new MapSubscriber(o, transformer); o.add(parent); - return parent; + source.unsafeSubscribe(parent); } - + static final class MapSubscriber extends Subscriber { - + final Subscriber actual; - + final Func1 mapper; boolean done; - + public MapSubscriber(Subscriber actual, Func1 mapper) { this.actual = actual; this.mapper = mapper; } - + @Override public void onNext(T t) { R result; - + try { result = mapper.call(t); } catch (Throwable ex) { @@ -70,22 +73,22 @@ public void onNext(T t) { onError(OnErrorThrowable.addValueAsLastCause(ex, t)); return; } - + actual.onNext(result); } - + @Override public void onError(Throwable e) { if (done) { - RxJavaPluginUtils.handleException(e); + RxJavaHooks.onError(e); return; } done = true; - + actual.onError(e); } - - + + @Override public void onCompleted() { if (done) { @@ -93,7 +96,7 @@ public void onCompleted() { } actual.onCompleted(); } - + @Override public void setProducer(Producer p) { actual.setProducer(p); diff --git a/src/main/java/rx/internal/operators/OnSubscribeOnAssembly.java b/src/main/java/rx/internal/operators/OnSubscribeOnAssembly.java index fd338c1ece..fab72b9833 100644 --- a/src/main/java/rx/internal/operators/OnSubscribeOnAssembly.java +++ b/src/main/java/rx/internal/operators/OnSubscribeOnAssembly.java @@ -1,12 +1,12 @@ /** * Copyright 2016 Netflix, Inc. - * + * * 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. @@ -15,8 +15,8 @@ */ package rx.internal.operators; -import rx.*; import rx.Observable.OnSubscribe; +import rx.Subscriber; import rx.exceptions.AssemblyStackTraceException; /** @@ -29,7 +29,7 @@ public final class OnSubscribeOnAssembly implements OnSubscribe { final OnSubscribe source; - + final String stacktrace; /** @@ -37,18 +37,18 @@ public final class OnSubscribeOnAssembly implements OnSubscribe { * stacktrace instead of the sanitized version. */ public static volatile boolean fullStackTrace; - + public OnSubscribeOnAssembly(OnSubscribe source) { this.source = source; this.stacktrace = createStacktrace(); } - + static String createStacktrace() { - StackTraceElement[] stes = Thread.currentThread().getStackTrace(); + StackTraceElement[] stacktraceElements = Thread.currentThread().getStackTrace(); StringBuilder sb = new StringBuilder("Assembly trace:"); - - for (StackTraceElement e : stes) { + + for (StackTraceElement e : stacktraceElements) { String row = e.toString(); if (!fullStackTrace) { if (e.getLineNumber() <= 1) { @@ -87,7 +87,7 @@ static String createStacktrace() { } sb.append("\n at ").append(row); } - + return sb.append("\nOriginal exception:").toString(); } @@ -95,13 +95,13 @@ static String createStacktrace() { public void call(Subscriber t) { source.call(new OnAssemblySubscriber(t, stacktrace)); } - + static final class OnAssemblySubscriber extends Subscriber { final Subscriber actual; - + final String stacktrace; - + public OnAssemblySubscriber(Subscriber actual, String stacktrace) { super(actual); this.actual = actual; @@ -115,7 +115,7 @@ public void onCompleted() { @Override public void onError(Throwable e) { - e = new AssemblyStackTraceException(stacktrace, e); + new AssemblyStackTraceException(stacktrace).attachTo(e); actual.onError(e); } @@ -123,6 +123,6 @@ public void onError(Throwable e) { public void onNext(T t) { actual.onNext(t); } - + } } diff --git a/src/main/java/rx/internal/operators/OnSubscribeOnAssemblyCompletable.java b/src/main/java/rx/internal/operators/OnSubscribeOnAssemblyCompletable.java index da5a144045..49e583f093 100644 --- a/src/main/java/rx/internal/operators/OnSubscribeOnAssemblyCompletable.java +++ b/src/main/java/rx/internal/operators/OnSubscribeOnAssemblyCompletable.java @@ -1,12 +1,12 @@ /** * Copyright 2016 Netflix, Inc. - * + * * 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. @@ -16,7 +16,6 @@ package rx.internal.operators; import rx.*; -import rx.Completable.CompletableSubscriber; import rx.exceptions.AssemblyStackTraceException; /** @@ -26,10 +25,10 @@ * * @param the value type */ -public final class OnSubscribeOnAssemblyCompletable implements Completable.CompletableOnSubscribe { +public final class OnSubscribeOnAssemblyCompletable implements Completable.OnSubscribe { + + final Completable.OnSubscribe source; - final Completable.CompletableOnSubscribe source; - final String stacktrace; /** @@ -37,23 +36,23 @@ public final class OnSubscribeOnAssemblyCompletable implements Completable.Co * stacktrace instead of the sanitized version. */ public static volatile boolean fullStackTrace; - - public OnSubscribeOnAssemblyCompletable(Completable.CompletableOnSubscribe source) { + + public OnSubscribeOnAssemblyCompletable(Completable.OnSubscribe source) { this.source = source; this.stacktrace = OnSubscribeOnAssembly.createStacktrace(); } - + @Override - public void call(Completable.CompletableSubscriber t) { + public void call(CompletableSubscriber t) { source.call(new OnAssemblyCompletableSubscriber(t, stacktrace)); } - + static final class OnAssemblyCompletableSubscriber implements CompletableSubscriber { final CompletableSubscriber actual; - + final String stacktrace; - + public OnAssemblyCompletableSubscriber(CompletableSubscriber actual, String stacktrace) { this.actual = actual; this.stacktrace = stacktrace; @@ -63,7 +62,7 @@ public OnAssemblyCompletableSubscriber(CompletableSubscriber actual, String stac public void onSubscribe(Subscription d) { actual.onSubscribe(d); } - + @Override public void onCompleted() { actual.onCompleted(); @@ -71,7 +70,7 @@ public void onCompleted() { @Override public void onError(Throwable e) { - e = new AssemblyStackTraceException(stacktrace, e); + new AssemblyStackTraceException(stacktrace).attachTo(e); actual.onError(e); } } diff --git a/src/main/java/rx/internal/operators/OnSubscribeOnAssemblySingle.java b/src/main/java/rx/internal/operators/OnSubscribeOnAssemblySingle.java index aeed623a7b..5c38b02ad4 100644 --- a/src/main/java/rx/internal/operators/OnSubscribeOnAssemblySingle.java +++ b/src/main/java/rx/internal/operators/OnSubscribeOnAssemblySingle.java @@ -1,12 +1,12 @@ /** * Copyright 2016 Netflix, Inc. - * + * * 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. @@ -28,7 +28,7 @@ public final class OnSubscribeOnAssemblySingle implements Single.OnSubscribe { final Single.OnSubscribe source; - + final String stacktrace; /** @@ -36,23 +36,23 @@ public final class OnSubscribeOnAssemblySingle implements Single.OnSubscribe< * stacktrace instead of the sanitized version. */ public static volatile boolean fullStackTrace; - + public OnSubscribeOnAssemblySingle(Single.OnSubscribe source) { this.source = source; this.stacktrace = OnSubscribeOnAssembly.createStacktrace(); } - + @Override public void call(SingleSubscriber t) { source.call(new OnAssemblySingleSubscriber(t, stacktrace)); } - + static final class OnAssemblySingleSubscriber extends SingleSubscriber { final SingleSubscriber actual; - + final String stacktrace; - + public OnAssemblySingleSubscriber(SingleSubscriber actual, String stacktrace) { this.actual = actual; this.stacktrace = stacktrace; @@ -61,7 +61,7 @@ public OnAssemblySingleSubscriber(SingleSubscriber actual, String sta @Override public void onError(Throwable e) { - e = new AssemblyStackTraceException(stacktrace, e); + new AssemblyStackTraceException(stacktrace).attachTo(e); actual.onError(e); } @@ -69,6 +69,6 @@ public void onError(Throwable e) { public void onSuccess(T t) { actual.onSuccess(t); } - + } } diff --git a/src/main/java/rx/internal/operators/OnSubscribePublishMulticast.java b/src/main/java/rx/internal/operators/OnSubscribePublishMulticast.java index c1d14b29fa..d48b857594 100644 --- a/src/main/java/rx/internal/operators/OnSubscribePublishMulticast.java +++ b/src/main/java/rx/internal/operators/OnSubscribePublishMulticast.java @@ -27,23 +27,23 @@ /** * Multicasts notifications coming through its input Subscriber view to its * client Subscribers via lockstep backpressure mode. - * + * *

    The difference between this class and OperatorPublish is that this * class doesn't consume the upstream if there are no child subscribers but * waits for them to show up. Plus if the upstream source terminates, late * subscribers will be immediately terminated with the same terminal event * unlike OperatorPublish which just waits for the next connection. - * + * *

    The class extends AtomicInteger which is the work-in-progress gate * for the drain-loop serializing subscriptions and child request changes. - * + * * @param the input and output type */ -public final class OnSubscribePublishMulticast extends AtomicInteger +public final class OnSubscribePublishMulticast extends AtomicInteger implements Observable.OnSubscribe, Observer, Subscription { /** */ private static final long serialVersionUID = -3741892510772238743L; - /** + /** * The prefetch queue holding onto a fixed amount of items until all * all child subscribers have requested something. */ @@ -52,7 +52,7 @@ public final class OnSubscribePublishMulticast extends AtomicInteger * The number of items to prefetch from the upstreams source. */ final int prefetch; - + /** * Delays the error delivery to happen only after all values have been consumed. */ @@ -63,10 +63,10 @@ public final class OnSubscribePublishMulticast extends AtomicInteger final ParentSubscriber parent; /** Indicates the upstream has completed. */ volatile boolean done; - /** + /** * Holds onto the upstream's exception if done is true and this field is non-null. *

    This field must be read after done or if subscribers == TERMINATED to - * establish a proper happens-before. + * establish a proper happens-before. */ Throwable error; @@ -78,10 +78,10 @@ public final class OnSubscribePublishMulticast extends AtomicInteger * A copy-on-write array of currently subscribed child subscribers' wrapper structure. */ volatile PublishProducer[] subscribers; - - /** - * Represents an empty array of subscriber wrapper, - * helps avoid allocating an empty array all the time. + + /** + * Represents an empty array of subscriber wrapper, + * helps avoid allocating an empty array all the time. */ static final PublishProducer[] EMPTY = new PublishProducer[0]; @@ -112,13 +112,13 @@ public OnSubscribePublishMulticast(int prefetch, boolean delayError) { this.subscribers = (PublishProducer[]) EMPTY; this.parent = new ParentSubscriber(this); } - + @Override public void call(Subscriber t) { PublishProducer pp = new PublishProducer(t, this); t.add(pp); t.setProducer(pp); - + if (add(pp)) { if (pp.isUnsubscribed()) { remove(pp); @@ -134,31 +134,31 @@ public void call(Subscriber t) { } } } - + @Override public void onNext(T t) { if (!queue.offer(t)) { parent.unsubscribe(); - + error = new MissingBackpressureException("Queue full?!"); done = true; } drain(); } - + @Override public void onError(Throwable e) { error = e; done = true; drain(); } - + @Override public void onCompleted() { done = true; drain(); } - + /** * Sets the main producer and issues the prefetch amount. * @param p the producer to set @@ -167,64 +167,64 @@ void setProducer(Producer p) { this.producer = p; p.request(prefetch); } - + /** * The serialization loop that determines the minimum request of * all subscribers and tries to emit as many items from the queue if * they are available. - * + * *

    The execution of the drain-loop is guaranteed to be thread-safe. */ void drain() { if (getAndIncrement() != 0) { return; } - + final Queue q = queue; - + int missed = 0; - + for (;;) { - + long r = Long.MAX_VALUE; PublishProducer[] a = subscribers; int n = a.length; - + for (PublishProducer inner : a) { r = Math.min(r, inner.get()); } - + if (n != 0) { long e = 0L; - + while (e != r) { boolean d = done; - + T v = q.poll(); - + boolean empty = v == null; - + if (checkTerminated(d, empty)) { return; } - + if (empty) { break; } - + for (PublishProducer inner : a) { inner.actual.onNext(v); } - + e++; } - + if (e == r) { if (checkTerminated(done, q.isEmpty())) { return; } } - + if (e != 0L) { Producer p = producer; if (p != null) { @@ -233,17 +233,17 @@ void drain() { for (PublishProducer inner : a) { BackpressureUtils.produced(inner, e); } - + } } - + missed = addAndGet(-missed); if (missed == 0) { break; } } } - + /** * Given the current source state, terminates all child subscribers. * @param d the source-done indicator @@ -306,7 +306,7 @@ PublishProducer[] terminate() { } return a; } - + /** * Atomically adds the given wrapper of a child Subscriber to the subscribers array. * @param inner the wrapper @@ -322,7 +322,7 @@ boolean add(PublishProducer inner) { if (a == TERMINATED) { return false; } - + int n = a.length; @SuppressWarnings("unchecked") PublishProducer[] b = new PublishProducer[n + 1]; @@ -348,21 +348,21 @@ void remove(PublishProducer inner) { if (a == TERMINATED || a == EMPTY) { return; } - + int j = -1; int n = a.length; - + for (int i = 0; i < n; i++) { if (a[i] == inner) { j = i; break; } } - + if (j < 0) { return; } - + PublishProducer[] b; if (n == 1) { b = (PublishProducer[])EMPTY; @@ -382,33 +382,33 @@ void remove(PublishProducer inner) { static final class ParentSubscriber extends Subscriber { /** The reference to the parent state where the events are forwarded to. */ final OnSubscribePublishMulticast state; - + public ParentSubscriber(OnSubscribePublishMulticast state) { super(); this.state = state; } - + @Override public void onNext(T t) { state.onNext(t); } - + @Override public void onError(Throwable e) { state.onError(e); } - + @Override public void onCompleted() { state.onCompleted(); } - + @Override public void setProducer(Producer p) { state.setProducer(p); } } - + /** * Returns the input subscriber of this class that must be subscribed * to the upstream source. @@ -417,27 +417,27 @@ public void setProducer(Producer p) { public Subscriber subscriber() { return parent; } - + @Override public void unsubscribe() { parent.unsubscribe(); } - + @Override public boolean isUnsubscribed() { return parent.isUnsubscribed(); } - + /** * A Producer and Subscription that wraps a child Subscriber and manages * its backpressure requests along with its unsubscription from the parent * class. - * + * *

    The class extends AtomicLong that holds onto the child's requested amount. - * + * * @param the output value type */ - static final class PublishProducer + static final class PublishProducer extends AtomicLong implements Producer, Subscription { /** */ @@ -445,10 +445,10 @@ static final class PublishProducer /** The actual subscriber to receive the events. */ final Subscriber actual; - + /** The parent object to request draining or removal. */ final OnSubscribePublishMulticast parent; - + /** Makes sure unsubscription happens only once. */ final AtomicBoolean once; @@ -457,7 +457,7 @@ public PublishProducer(Subscriber actual, OnSubscribePublishMulticast this.parent = parent; this.once = new AtomicBoolean(); } - + @Override public void request(long n) { if (n < 0) { @@ -468,12 +468,12 @@ public void request(long n) { parent.drain(); } } - + @Override public boolean isUnsubscribed() { return once.get(); } - + @Override public void unsubscribe() { if (once.compareAndSet(false, true)) { diff --git a/src/main/java/rx/internal/operators/OnSubscribeRange.java b/src/main/java/rx/internal/operators/OnSubscribeRange.java index 8f17303a2d..6a97a7032a 100644 --- a/src/main/java/rx/internal/operators/OnSubscribeRange.java +++ b/src/main/java/rx/internal/operators/OnSubscribeRange.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -21,7 +21,7 @@ import rx.Observable.OnSubscribe; /** - * Emit ints from start to end inclusive. + * Emit integers from start to end inclusive. */ public final class OnSubscribeRange implements OnSubscribe { @@ -38,10 +38,10 @@ public void call(final Subscriber childSubscriber) { childSubscriber.setProducer(new RangeProducer(childSubscriber, startIndex, endIndex)); } - private static final class RangeProducer extends AtomicLong implements Producer { + static final class RangeProducer extends AtomicLong implements Producer { /** */ private static final long serialVersionUID = 4114392207069098388L; - + private final Subscriber childSubscriber; private final int endOfRange; private long currentIndex; @@ -60,12 +60,12 @@ public void request(long requestedAmount) { } if (requestedAmount == Long.MAX_VALUE && compareAndSet(0L, Long.MAX_VALUE)) { // fast-path without backpressure - fastpath(); + fastPath(); } else if (requestedAmount > 0L) { long c = BackpressureUtils.getAndAddRequest(this, requestedAmount); if (c == 0L) { // backpressure is requested - slowpath(requestedAmount); + slowPath(requestedAmount); } } } @@ -73,37 +73,37 @@ public void request(long requestedAmount) { /** * Emits as many values as requested or remaining from the range, whichever is smaller. */ - void slowpath(long requestedAmount) { + void slowPath(long requestedAmount) { long emitted = 0L; long endIndex = endOfRange + 1L; long index = currentIndex; - + final Subscriber childSubscriber = this.childSubscriber; - + for (;;) { - + while (emitted != requestedAmount && index != endIndex) { if (childSubscriber.isUnsubscribed()) { return; } - + childSubscriber.onNext((int)index); - + index++; emitted++; } - + if (childSubscriber.isUnsubscribed()) { return; } - + if (index == endIndex) { childSubscriber.onCompleted(); return; } - + requestedAmount = get(); - + if (requestedAmount == emitted) { currentIndex = index; requestedAmount = addAndGet(-emitted); @@ -118,7 +118,7 @@ void slowpath(long requestedAmount) { /** * Emits all remaining values without decrementing the requested amount. */ - void fastpath() { + void fastPath() { final long endIndex = this.endOfRange + 1L; final Subscriber childSubscriber = this.childSubscriber; for (long index = currentIndex; index != endIndex; index++) { diff --git a/src/main/java/rx/internal/operators/OnSubscribeRedo.java b/src/main/java/rx/internal/operators/OnSubscribeRedo.java index d30ddc1343..de4f5e4d15 100644 --- a/src/main/java/rx/internal/operators/OnSubscribeRedo.java +++ b/src/main/java/rx/internal/operators/OnSubscribeRedo.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -17,13 +17,13 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -31,28 +31,25 @@ * limitations under the License. */ -import static rx.Observable.create; - -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicLong; - -import rx.Notification; -import rx.Observable; -import rx.Observable.OnSubscribe; -import rx.Observable.Operator; -import rx.Producer; -import rx.Scheduler; -import rx.Subscriber; -import rx.functions.Action0; -import rx.functions.Func1; -import rx.functions.Func2; +import static rx.Observable.unsafeCreate; // NOPMD + +import java.util.concurrent.atomic.*; + +import rx.*; +import rx.Observable.*; +import rx.functions.*; import rx.internal.producers.ProducerArbiter; import rx.observers.Subscribers; import rx.schedulers.Schedulers; -import rx.subjects.BehaviorSubject; +import rx.subjects.*; import rx.subscriptions.SerialSubscription; public final class OnSubscribeRedo implements OnSubscribe { + final Observable source; + private final Func1>, ? extends Observable> controlHandlerFunction; + final boolean stopOnComplete; + final boolean stopOnError; + private final Scheduler scheduler; static final Func1>, Observable> REDO_INFINITE = new Func1>, Observable>() { @Override @@ -77,22 +74,22 @@ public RedoFinite(long count) { public Observable call(Observable> ts) { return ts.map(new Func1, Notification>() { - int num=0; - + int num; + @Override public Notification call(Notification terminalNotification) { - if(count == 0) { + if (count == 0) { return terminalNotification; } - + num++; - if(num <= count) { + if (num <= count) { return Notification.createOnNext(num); } else { return terminalNotification; } } - + }).dematerialize(); } } @@ -111,10 +108,11 @@ public Observable> call(Observable call(Notification n, Notification term) { final int value = n.getValue(); - if (predicate.call(value, term.getThrowable())) + if (predicate.call(value, term.getThrowable())) { return Notification.createOnNext(value + 1); - else + } else { return (Notification) term; + } } }); } @@ -125,19 +123,21 @@ public static Observable retry(Observable source) { } public static Observable retry(Observable source, final long count) { - if (count < 0) + if (count < 0) { throw new IllegalArgumentException("count >= 0 expected"); - if (count == 0) + } + if (count == 0) { return source; + } return retry(source, new RedoFinite(count)); } public static Observable retry(Observable source, Func1>, ? extends Observable> notificationHandler) { - return create(new OnSubscribeRedo(source, notificationHandler, true, false, Schedulers.trampoline())); + return unsafeCreate(new OnSubscribeRedo(source, notificationHandler, true, false, Schedulers.trampoline())); } public static Observable retry(Observable source, Func1>, ? extends Observable> notificationHandler, Scheduler scheduler) { - return create(new OnSubscribeRedo(source, notificationHandler, true, false, scheduler)); + return unsafeCreate(new OnSubscribeRedo(source, notificationHandler, true, false, scheduler)); } public static Observable repeat(Observable source) { @@ -153,32 +153,27 @@ public static Observable repeat(Observable source, final long count) { } public static Observable repeat(Observable source, final long count, Scheduler scheduler) { - if(count == 0) { + if (count == 0) { return Observable.empty(); } - if (count < 0) + if (count < 0) { throw new IllegalArgumentException("count >= 0 expected"); + } return repeat(source, new RedoFinite(count - 1), scheduler); } public static Observable repeat(Observable source, Func1>, ? extends Observable> notificationHandler) { - return create(new OnSubscribeRedo(source, notificationHandler, false, true, Schedulers.trampoline())); + return unsafeCreate(new OnSubscribeRedo(source, notificationHandler, false, true, Schedulers.trampoline())); } public static Observable repeat(Observable source, Func1>, ? extends Observable> notificationHandler, Scheduler scheduler) { - return create(new OnSubscribeRedo(source, notificationHandler, false, true, scheduler)); + return unsafeCreate(new OnSubscribeRedo(source, notificationHandler, false, true, scheduler)); } public static Observable redo(Observable source, Func1>, ? extends Observable> notificationHandler, Scheduler scheduler) { - return create(new OnSubscribeRedo(source, notificationHandler, false, false, scheduler)); + return unsafeCreate(new OnSubscribeRedo(source, notificationHandler, false, false, scheduler)); } - final Observable source; - private final Func1>, ? extends Observable> controlHandlerFunction; - final boolean stopOnComplete; - final boolean stopOnError; - private final Scheduler scheduler; - private OnSubscribeRedo(Observable source, Func1>, ? extends Observable> f, boolean stopOnComplete, boolean stopOnError, Scheduler scheduler) { this.source = source; @@ -190,10 +185,10 @@ private OnSubscribeRedo(Observable source, Func1 child) { - + // when true is a marker to say we are ready to resubscribe to source final AtomicBoolean resumeBoundary = new AtomicBoolean(true); - + // incremented when requests are made, decremented when requests are fulfilled final AtomicLong consumerCapacity = new AtomicLong(); @@ -203,18 +198,18 @@ public void call(final Subscriber child) { final SerialSubscription sourceSubscriptions = new SerialSubscription(); child.add(sourceSubscriptions); - // use a subject to receive terminals (onCompleted and onError signals) from - // the source observable. We use a BehaviorSubject because subscribeToSource - // may emit a terminal before the restarts observable (transformed terminals) + // use a subject to receive terminals (onCompleted and onError signals) from + // the source observable. We use a BehaviorSubject because subscribeToSource + // may emit a terminal before the restarts observable (transformed terminals) // is subscribed - final BehaviorSubject> terminals = BehaviorSubject.create(); + final Subject, Notification> terminals = BehaviorSubject.>create().toSerialized(); final Subscriber> dummySubscriber = Subscribers.empty(); - // subscribe immediately so the last emission will be replayed to the next + // subscribe immediately so the last emission will be replayed to the next // subscriber (which is the one we care about) terminals.subscribe(dummySubscriber); final ProducerArbiter arbiter = new ProducerArbiter(); - + final Action0 subscribeToSource = new Action0() { @Override public void call() { @@ -279,8 +274,8 @@ public void setProducer(Producer producer) { } }; - // the observable received by the control handler function will receive notifications of onCompleted in the case of 'repeat' - // type operators or notifications of onError for 'retry' this is done by lifting in a custom operator to selectively divert + // the observable received by the control handler function will receive notifications of onCompleted in the case of 'repeat' + // type operators or notifications of onError for 'retry' this is done by lifting in a custom operator to selectively divert // the retry/repeat relevant values to the control handler final Observable restarts = controlHandlerFunction.call( terminals.lift(new Operator, Notification>() { @@ -334,8 +329,8 @@ public void onError(Throwable e) { @Override public void onNext(Object t) { if (!child.isUnsubscribed()) { - // perform a best endeavours check on consumerCapacity - // with the intent of only resubscribing immediately + // perform a best endeavours check on consumerCapacity + // with the intent of only resubscribing immediately // if there is outstanding capacity if (consumerCapacity.get() > 0) { worker.schedule(subscribeToSource); @@ -362,11 +357,12 @@ public void request(final long n) { if (n > 0) { BackpressureUtils.getAndAddRequest(consumerCapacity, n); arbiter.request(n); - if (resumeBoundary.compareAndSet(true, false)) + if (resumeBoundary.compareAndSet(true, false)) { worker.schedule(subscribeToSource); + } } } }); - + } } diff --git a/src/main/java/rx/internal/operators/OnSubscribeReduce.java b/src/main/java/rx/internal/operators/OnSubscribeReduce.java new file mode 100644 index 0000000000..d07daebcd4 --- /dev/null +++ b/src/main/java/rx/internal/operators/OnSubscribeReduce.java @@ -0,0 +1,126 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * 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 rx.internal.operators; + +import java.util.NoSuchElementException; + +import rx.*; +import rx.Observable.OnSubscribe; +import rx.exceptions.Exceptions; +import rx.functions.Func2; +import rx.plugins.RxJavaHooks; + +public final class OnSubscribeReduce implements OnSubscribe { + + final Observable source; + + final Func2 reducer; + + public OnSubscribeReduce(Observable source, Func2 reducer) { + this.source = source; + this.reducer = reducer; + } + + @Override + public void call(Subscriber t) { + final ReduceSubscriber parent = new ReduceSubscriber(t, reducer); + t.add(parent); + t.setProducer(new Producer() { + @Override + public void request(long n) { + parent.downstreamRequest(n); + } + }); + source.unsafeSubscribe(parent); + } + + static final class ReduceSubscriber extends Subscriber { + + final Subscriber actual; + + final Func2 reducer; + + T value; + + static final Object EMPTY = new Object(); + + boolean done; + + @SuppressWarnings("unchecked") + public ReduceSubscriber(Subscriber actual, Func2 reducer) { + this.actual = actual; + this.reducer = reducer; + this.value = (T)EMPTY; + this.request(0); + } + + @SuppressWarnings("unchecked") + @Override + public void onNext(T t) { + if (done) { + return; + } + Object o = value; + if (o == EMPTY) { + value = t; + } else { + try { + value = reducer.call((T)o, t); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + unsubscribe(); + onError(ex); + } + } + } + + @Override + public void onError(Throwable e) { + if (!done) { + done = true; + actual.onError(e); + } else { + RxJavaHooks.onError(e); + } + } + + @SuppressWarnings("unchecked") + @Override + public void onCompleted() { + if (done) { + return; + } + done = true; + Object o = value; + if (o != EMPTY) { + actual.onNext((T)o); + actual.onCompleted(); + } else { + actual.onError(new NoSuchElementException()); + } + } + + void downstreamRequest(long n) { + if (n < 0L) { + throw new IllegalArgumentException("n >= 0 required but it was " + n); + } + if (n != 0L) { + request(Long.MAX_VALUE); + } + } + } +} diff --git a/src/main/java/rx/internal/operators/OnSubscribeReduceSeed.java b/src/main/java/rx/internal/operators/OnSubscribeReduceSeed.java new file mode 100644 index 0000000000..90d978ffe4 --- /dev/null +++ b/src/main/java/rx/internal/operators/OnSubscribeReduceSeed.java @@ -0,0 +1,66 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * 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 rx.internal.operators; + +import rx.*; +import rx.Observable.OnSubscribe; +import rx.exceptions.Exceptions; +import rx.functions.Func2; + +public final class OnSubscribeReduceSeed implements OnSubscribe { + + final Observable source; + + final R initialValue; + + final Func2 reducer; + + public OnSubscribeReduceSeed(Observable source, R initialValue, Func2 reducer) { + this.source = source; + this.initialValue = initialValue; + this.reducer = reducer; + } + + @Override + public void call(Subscriber t) { + new ReduceSeedSubscriber(t, initialValue, reducer).subscribeTo(source); + } + + static final class ReduceSeedSubscriber extends DeferredScalarSubscriber { + + final Func2 reducer; + + public ReduceSeedSubscriber(Subscriber actual, R initialValue, Func2 reducer) { + super(actual); + this.value = initialValue; + this.hasValue = true; + this.reducer = reducer; + } + + @Override + public void onNext(T t) { + try { + value = reducer.call(value, t); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + unsubscribe(); + actual.onError(ex); + } + } + + } +} diff --git a/src/main/java/rx/internal/operators/OnSubscribeRefCount.java b/src/main/java/rx/internal/operators/OnSubscribeRefCount.java index 82ec0e6c38..4a34663fbb 100644 --- a/src/main/java/rx/internal/operators/OnSubscribeRefCount.java +++ b/src/main/java/rx/internal/operators/OnSubscribeRefCount.java @@ -15,23 +15,19 @@ */ package rx.internal.operators; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.*; import java.util.concurrent.locks.ReentrantLock; +import rx.*; import rx.Observable.OnSubscribe; -import rx.Subscriber; -import rx.Subscription; -import rx.functions.Action0; -import rx.functions.Action1; +import rx.functions.*; import rx.observables.ConnectableObservable; -import rx.subscriptions.CompositeSubscription; -import rx.subscriptions.Subscriptions; +import rx.subscriptions.*; /** * Returns an observable sequence that stays connected to the source as long as * there is at least one subscription to the observable sequence. - * + * * @param * the value type */ @@ -48,7 +44,7 @@ public final class OnSubscribeRefCount implements OnSubscribe { /** * Constructor. - * + * * @param source * observable to apply ref count to */ @@ -108,11 +104,11 @@ public void call(Subscription subscription) { } }; } - + void doSubscribe(final Subscriber subscriber, final CompositeSubscription currentBase) { // handle unsubscribing from the base subscription subscriber.add(disconnect(currentBase)); - + source.unsafeSubscribe(new Subscriber(subscriber) { @Override public void onError(Throwable e) { @@ -130,10 +126,16 @@ public void onCompleted() { } void cleanup() { // on error or completion we need to unsubscribe the base subscription - // and set the subscriptionCount to 0 + // and set the subscriptionCount to 0 lock.lock(); try { + if (baseSubscription == currentBase) { + // backdoor into the ConnectableObservable to cleanup and reset its state + if (source instanceof Subscription) { + ((Subscription)source).unsubscribe(); + } + baseSubscription.unsubscribe(); baseSubscription = new CompositeSubscription(); subscriptionCount.set(0); @@ -152,7 +154,13 @@ public void call() { lock.lock(); try { if (baseSubscription == current) { + if (subscriptionCount.decrementAndGet() == 0) { + // backdoor into the ConnectableObservable to cleanup and reset its state + if (source instanceof Subscription) { + ((Subscription)source).unsubscribe(); + } + baseSubscription.unsubscribe(); // need a new baseSubscription because once // unsubscribed stays that way diff --git a/src/main/java/rx/internal/operators/OnSubscribeSingle.java b/src/main/java/rx/internal/operators/OnSubscribeSingle.java index 7bfdcca511..7c2d8f2970 100644 --- a/src/main/java/rx/internal/operators/OnSubscribeSingle.java +++ b/src/main/java/rx/internal/operators/OnSubscribeSingle.java @@ -15,13 +15,10 @@ */ package rx.internal.operators; -import rx.Observable; -import rx.Single; -import rx.SingleSubscriber; -import rx.Subscriber; - import java.util.NoSuchElementException; +import rx.*; + /** * Allows conversion of an Observable to a Single ensuring that exactly one item is emitted - no more and no less. * Also forwards errors as appropriate. @@ -38,9 +35,9 @@ public OnSubscribeSingle(Observable observable) { @Override public void call(final SingleSubscriber child) { Subscriber parent = new Subscriber() { - private boolean emittedTooMany = false; - private boolean itemEmitted = false; - private T emission = null; + private boolean emittedTooMany; + private boolean itemEmitted; + private T emission; @Override public void onStart() { diff --git a/src/main/java/rx/internal/operators/OnSubscribeSkipTimed.java b/src/main/java/rx/internal/operators/OnSubscribeSkipTimed.java new file mode 100644 index 0000000000..d5dc901eed --- /dev/null +++ b/src/main/java/rx/internal/operators/OnSubscribeSkipTimed.java @@ -0,0 +1,94 @@ +/** + * Copyright 2014 Netflix, Inc. + * + * 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 rx.internal.operators; + +import java.util.concurrent.TimeUnit; + +import rx.Observable.OnSubscribe; +import rx.Observable; +import rx.Scheduler; +import rx.Scheduler.Worker; +import rx.Subscriber; +import rx.functions.Action0; + +/** + * Skips elements until a specified time elapses. + * @param the value type + */ +public final class OnSubscribeSkipTimed implements OnSubscribe { + final long time; + final TimeUnit unit; + final Scheduler scheduler; + final Observable source; + + public OnSubscribeSkipTimed(Observable source, long time, TimeUnit unit, Scheduler scheduler) { + this.source = source; + this.time = time; + this.unit = unit; + this.scheduler = scheduler; + } + + @Override + public void call(final Subscriber child) { + final Worker worker = scheduler.createWorker(); + SkipTimedSubscriber subscriber = new SkipTimedSubscriber(child); + subscriber.add(worker); + child.add(subscriber); + worker.schedule(subscriber, time, unit); + source.unsafeSubscribe(subscriber); + } + + final static class SkipTimedSubscriber extends Subscriber implements Action0 { + + final Subscriber child; + volatile boolean gate; + + SkipTimedSubscriber(Subscriber child) { + this.child = child; + } + + @Override + public void call() { + gate = true; + } + + @Override + public void onNext(T t) { + if (gate) { + child.onNext(t); + } + } + + @Override + public void onError(Throwable e) { + try { + child.onError(e); + } finally { + unsubscribe(); + } + } + + @Override + public void onCompleted() { + try { + child.onCompleted(); + } finally { + unsubscribe(); + } + } + + } +} diff --git a/src/main/java/rx/internal/operators/OperatorSwitchIfEmpty.java b/src/main/java/rx/internal/operators/OnSubscribeSwitchIfEmpty.java similarity index 60% rename from src/main/java/rx/internal/operators/OperatorSwitchIfEmpty.java rename to src/main/java/rx/internal/operators/OnSubscribeSwitchIfEmpty.java index 40b44289b7..db02dfbff9 100644 --- a/src/main/java/rx/internal/operators/OperatorSwitchIfEmpty.java +++ b/src/main/java/rx/internal/operators/OnSubscribeSwitchIfEmpty.java @@ -16,6 +16,8 @@ package rx.internal.operators; +import java.util.concurrent.atomic.AtomicInteger; + import rx.*; import rx.internal.producers.ProducerArbiter; import rx.subscriptions.SerialSubscription; @@ -26,37 +28,47 @@ * empty, the results of the given Observable will be emitted. * @param the value type */ -public final class OperatorSwitchIfEmpty implements Observable.Operator { - private final Observable alternate; +public final class OnSubscribeSwitchIfEmpty implements Observable.OnSubscribe { + + final Observable source; + + final Observable alternate; - public OperatorSwitchIfEmpty(Observable alternate) { + public OnSubscribeSwitchIfEmpty(Observable source, Observable alternate) { + this.source = source; this.alternate = alternate; } @Override - public Subscriber call(Subscriber child) { - final SerialSubscription ssub = new SerialSubscription(); + public void call(Subscriber child) { + final SerialSubscription serial = new SerialSubscription(); ProducerArbiter arbiter = new ProducerArbiter(); - final ParentSubscriber parent = new ParentSubscriber(child, ssub, arbiter, alternate); - ssub.set(parent); - child.add(ssub); + final ParentSubscriber parent = new ParentSubscriber(child, serial, arbiter, alternate); + + serial.set(parent); + child.add(serial); child.setProducer(arbiter); - return parent; + + parent.subscribe(source); } - private static final class ParentSubscriber extends Subscriber { + static final class ParentSubscriber extends Subscriber { private boolean empty = true; private final Subscriber child; - private final SerialSubscription ssub; + private final SerialSubscription serial; private final ProducerArbiter arbiter; private final Observable alternate; - ParentSubscriber(Subscriber child, final SerialSubscription ssub, ProducerArbiter arbiter, Observable alternate) { + final AtomicInteger wip; + volatile boolean active; + + ParentSubscriber(Subscriber child, final SerialSubscription serial, ProducerArbiter arbiter, Observable alternate) { this.child = child; - this.ssub = ssub; + this.serial = serial; this.arbiter = arbiter; this.alternate = alternate; + this.wip = new AtomicInteger(); } @Override @@ -69,14 +81,33 @@ public void onCompleted() { if (!empty) { child.onCompleted(); } else if (!child.isUnsubscribed()) { - subscribeToAlternate(); + active = false; + subscribe(null); } } - private void subscribeToAlternate() { - AlternateSubscriber as = new AlternateSubscriber(child, arbiter); - ssub.set(as); - alternate.unsafeSubscribe(as); + void subscribe(Observable source) { + if (wip.getAndIncrement() == 0) { + do { + if (child.isUnsubscribed()) { + break; + } + + if (!active) { + if (source == null) { + AlternateSubscriber as = new AlternateSubscriber(child, arbiter); + serial.set(as); + active = true; + alternate.unsafeSubscribe(as); + } else { + active = true; + source.unsafeSubscribe(this); + source = null; + } + } + + } while (wip.decrementAndGet() != 0); + } } @Override @@ -91,9 +122,9 @@ public void onNext(T t) { arbiter.produced(1); } } - - private static final class AlternateSubscriber extends Subscriber { - + + static final class AlternateSubscriber extends Subscriber { + private final ProducerArbiter arbiter; private final Subscriber child; @@ -101,7 +132,7 @@ private static final class AlternateSubscriber extends Subscriber { this.child = child; this.arbiter = arbiter; } - + @Override public void setProducer(final Producer producer) { arbiter.setProducer(producer); @@ -121,6 +152,6 @@ public void onError(Throwable e) { public void onNext(T t) { child.onNext(t); arbiter.produced(1); - } + } } } diff --git a/src/main/java/rx/internal/operators/OnSubscribeTakeLastOne.java b/src/main/java/rx/internal/operators/OnSubscribeTakeLastOne.java new file mode 100644 index 0000000000..7ad0959588 --- /dev/null +++ b/src/main/java/rx/internal/operators/OnSubscribeTakeLastOne.java @@ -0,0 +1,61 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * 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 rx.internal.operators; + +import rx.*; +import rx.Observable.OnSubscribe; + +public final class OnSubscribeTakeLastOne implements OnSubscribe { + + final Observable source; + + public OnSubscribeTakeLastOne(Observable source) { + this.source = source; + } + + @Override + public void call(Subscriber t) { + new TakeLastOneSubscriber(t).subscribeTo(source); + } + + static final class TakeLastOneSubscriber extends DeferredScalarSubscriber { + + static final Object EMPTY = new Object(); + + @SuppressWarnings("unchecked") + public TakeLastOneSubscriber(Subscriber actual) { + super(actual); + this.value = (T)EMPTY; + } + + @Override + public void onNext(T t) { + value = t; + } + + @SuppressWarnings("unchecked") + @Override + public void onCompleted() { + Object o = value; + if (o == EMPTY) { + complete(); + } else { + complete((T)o); + } + } + } +} diff --git a/src/main/java/rx/internal/operators/OnSubscribeThrow.java b/src/main/java/rx/internal/operators/OnSubscribeThrow.java index d39bba5939..381d5dd1cc 100644 --- a/src/main/java/rx/internal/operators/OnSubscribeThrow.java +++ b/src/main/java/rx/internal/operators/OnSubscribeThrow.java @@ -1,12 +1,12 @@ /** * Copyright 2016 Netflix, Inc. - * + * * 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. @@ -21,7 +21,7 @@ /** * An Observable that invokes {@link Observer#onError onError} when the {@link Observer} subscribes to it. - * + * * @param * the type of item (ostensibly) emitted by the Observable */ @@ -35,7 +35,7 @@ public OnSubscribeThrow(Throwable exception) { /** * Accepts an {@link Observer} and calls its {@link Observer#onError onError} method. - * + * * @param observer * an {@link Observer} of this Observable */ diff --git a/src/main/java/rx/internal/operators/OnSubscribeTimeoutSelectorWithFallback.java b/src/main/java/rx/internal/operators/OnSubscribeTimeoutSelectorWithFallback.java new file mode 100644 index 0000000000..75d75777c8 --- /dev/null +++ b/src/main/java/rx/internal/operators/OnSubscribeTimeoutSelectorWithFallback.java @@ -0,0 +1,247 @@ +/** + * Copyright 2014 Netflix, Inc. + * + * 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 rx.internal.operators; + +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicLong; + +import rx.*; +import rx.exceptions.Exceptions; +import rx.functions.Func1; +import rx.internal.operators.OnSubscribeTimeoutTimedWithFallback.FallbackSubscriber; +import rx.internal.producers.ProducerArbiter; +import rx.internal.subscriptions.SequentialSubscription; +import rx.plugins.RxJavaHooks; + +/** + * Switches to the fallback Observable if: the first upstream item doesn't arrive before + * the first timeout Observable signals an item or completes; or the Observable generated from + * the previous upstream item signals its item or completes before the upstream signals the next item + * of its own. + * + * @param the input and output value type + * @param the value type of the first timeout Observable + * @param the value type of the item-based timeout Observable + * + * @since 1.3.3 + */ +public final class OnSubscribeTimeoutSelectorWithFallback implements Observable.OnSubscribe { + + final Observable source; + + final Observable firstTimeoutIndicator; + + final Func1> itemTimeoutIndicator; + + final Observable fallback; + + public OnSubscribeTimeoutSelectorWithFallback(Observable source, + Observable firstTimeoutIndicator, + Func1> itemTimeoutIndicator, + Observable fallback) { + this.source = source; + this.firstTimeoutIndicator = firstTimeoutIndicator; + this.itemTimeoutIndicator = itemTimeoutIndicator; + this.fallback = fallback; + } + + @Override + public void call(Subscriber t) { + TimeoutMainSubscriber parent = new TimeoutMainSubscriber(t, itemTimeoutIndicator, fallback); + t.add(parent.upstream); + t.setProducer(parent.arbiter); + parent.startFirst(firstTimeoutIndicator); + source.subscribe(parent); + } + + static final class TimeoutMainSubscriber extends Subscriber { + + final Subscriber actual; + + final Func1> itemTimeoutIndicator; + + final Observable fallback; + + final ProducerArbiter arbiter; + + final AtomicLong index; + + final SequentialSubscription task; + + final SequentialSubscription upstream; + + long consumed; + + TimeoutMainSubscriber(Subscriber actual, + Func1> itemTimeoutIndicator, + Observable fallback) { + this.actual = actual; + this.itemTimeoutIndicator = itemTimeoutIndicator; + this.fallback = fallback; + this.arbiter = new ProducerArbiter(); + this.index = new AtomicLong(); + this.task = new SequentialSubscription(); + this.upstream = new SequentialSubscription(this); + this.add(task); + } + + + @Override + public void onNext(T t) { + long idx = index.get(); + if (idx == Long.MAX_VALUE || !index.compareAndSet(idx, idx + 1)) { + return; + } + + Subscription s = task.get(); + if (s != null) { + s.unsubscribe(); + } + + actual.onNext(t); + + consumed++; + + Observable timeoutObservable; + + try { + timeoutObservable = itemTimeoutIndicator.call(t); + if (timeoutObservable == null) { + throw new NullPointerException("The itemTimeoutIndicator returned a null Observable"); + } + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + unsubscribe(); + index.getAndSet(Long.MAX_VALUE); + actual.onError(ex); + return; + } + + TimeoutConsumer tc = new TimeoutConsumer(idx + 1); + if (task.replace(tc)) { + timeoutObservable.subscribe(tc); + } + + } + + void startFirst(Observable firstTimeoutIndicator) { + if (firstTimeoutIndicator != null) { + TimeoutConsumer tc = new TimeoutConsumer(0L); + if (task.replace(tc)) { + firstTimeoutIndicator.subscribe(tc); + } + } + } + + @Override + public void onError(Throwable e) { + if (index.getAndSet(Long.MAX_VALUE) != Long.MAX_VALUE) { + task.unsubscribe(); + + actual.onError(e); + } else { + RxJavaHooks.onError(e); + } + } + + @Override + public void onCompleted() { + if (index.getAndSet(Long.MAX_VALUE) != Long.MAX_VALUE) { + task.unsubscribe(); + + actual.onCompleted(); + } + } + + @Override + public void setProducer(Producer p) { + arbiter.setProducer(p); + } + + void onTimeout(long idx) { + if (!index.compareAndSet(idx, Long.MAX_VALUE)) { + return; + } + + unsubscribe(); + + if (fallback == null) { + actual.onError(new TimeoutException()); + } else { + long c = consumed; + if (c != 0L) { + arbiter.produced(c); + } + + FallbackSubscriber fallbackSubscriber = new FallbackSubscriber(actual, arbiter); + + if (upstream.replace(fallbackSubscriber)) { + fallback.subscribe(fallbackSubscriber); + } + } + } + + void onTimeoutError(long idx, Throwable ex) { + if (index.compareAndSet(idx, Long.MAX_VALUE)) { + unsubscribe(); + + actual.onError(ex); + } else { + RxJavaHooks.onError(ex); + } + + } + + final class TimeoutConsumer extends Subscriber { + + final long idx; + + boolean done; + + TimeoutConsumer(long idx) { + this.idx = idx; + } + + @Override + public void onNext(Object t) { + if (!done) { + done = true; + unsubscribe(); + onTimeout(idx); + } + } + + @Override + public void onError(Throwable e) { + if (!done) { + done = true; + onTimeoutError(idx, e); + } else { + RxJavaHooks.onError(e); + } + } + + @Override + public void onCompleted() { + if (!done) { + done = true; + onTimeout(idx); + } + } + } + } +} diff --git a/src/main/java/rx/internal/operators/OnSubscribeTimeoutTimedWithFallback.java b/src/main/java/rx/internal/operators/OnSubscribeTimeoutTimedWithFallback.java new file mode 100644 index 0000000000..e70c57d667 --- /dev/null +++ b/src/main/java/rx/internal/operators/OnSubscribeTimeoutTimedWithFallback.java @@ -0,0 +1,227 @@ +/** + * Copyright 2014 Netflix, Inc. + * + * 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 rx.internal.operators; + +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicLong; + +import rx.*; +import rx.Scheduler.Worker; +import rx.functions.Action0; +import rx.internal.producers.ProducerArbiter; +import rx.internal.subscriptions.SequentialSubscription; +import rx.plugins.RxJavaHooks; + +/** + * Switches to consuming a fallback Observable if the main source doesn't signal an onNext event + * within the given time frame after subscription or the previous onNext event. + * + * @param the value type + * @since 1.3.3 + */ +public final class OnSubscribeTimeoutTimedWithFallback implements Observable.OnSubscribe { + + final Observable source; + + final long timeout; + + final TimeUnit unit; + + final Scheduler scheduler; + + final Observable fallback; + + public OnSubscribeTimeoutTimedWithFallback(Observable source, long timeout, + TimeUnit unit, Scheduler scheduler, + Observable fallback) { + this.source = source; + this.timeout = timeout; + this.unit = unit; + this.scheduler = scheduler; + this.fallback = fallback; + } + + @Override + public void call(Subscriber t) { + TimeoutMainSubscriber parent = new TimeoutMainSubscriber(t, timeout, unit, scheduler.createWorker(), fallback); + t.add(parent.upstream); + t.setProducer(parent.arbiter); + parent.startTimeout(0L); + source.subscribe(parent); + } + + static final class TimeoutMainSubscriber extends Subscriber { + + final Subscriber actual; + + final long timeout; + + final TimeUnit unit; + + final Worker worker; + + final Observable fallback; + + final ProducerArbiter arbiter; + + final AtomicLong index; + + final SequentialSubscription task; + + final SequentialSubscription upstream; + + long consumed; + + TimeoutMainSubscriber(Subscriber actual, long timeout, + TimeUnit unit, Worker worker, + Observable fallback) { + this.actual = actual; + this.timeout = timeout; + this.unit = unit; + this.worker = worker; + this.fallback = fallback; + this.arbiter = new ProducerArbiter(); + this.index = new AtomicLong(); + this.task = new SequentialSubscription(); + this.upstream = new SequentialSubscription(this); + this.add(worker); + this.add(task); + } + + + @Override + public void onNext(T t) { + long idx = index.get(); + if (idx == Long.MAX_VALUE || !index.compareAndSet(idx, idx + 1)) { + return; + } + + Subscription s = task.get(); + if (s != null) { + s.unsubscribe(); + } + + consumed++; + + actual.onNext(t); + + startTimeout(idx + 1); + } + + void startTimeout(long nextIdx) { + task.replace(worker.schedule(new TimeoutTask(nextIdx), timeout, unit)); + } + + @Override + public void onError(Throwable e) { + if (index.getAndSet(Long.MAX_VALUE) != Long.MAX_VALUE) { + task.unsubscribe(); + + actual.onError(e); + + worker.unsubscribe(); + } else { + RxJavaHooks.onError(e); + } + } + + @Override + public void onCompleted() { + if (index.getAndSet(Long.MAX_VALUE) != Long.MAX_VALUE) { + task.unsubscribe(); + + actual.onCompleted(); + + worker.unsubscribe(); + } + } + + @Override + public void setProducer(Producer p) { + arbiter.setProducer(p); + } + + void onTimeout(long idx) { + if (!index.compareAndSet(idx, Long.MAX_VALUE)) { + return; + } + + unsubscribe(); + + if (fallback == null) { + actual.onError(new TimeoutException()); + } else { + long c = consumed; + if (c != 0L) { + arbiter.produced(c); + } + + FallbackSubscriber fallbackSubscriber = new FallbackSubscriber(actual, arbiter); + + if (upstream.replace(fallbackSubscriber)) { + fallback.subscribe(fallbackSubscriber); + } + } + } + + final class TimeoutTask implements Action0 { + + final long idx; + + TimeoutTask(long idx) { + this.idx = idx; + } + + @Override + public void call() { + onTimeout(idx); + } + } + } + + static final class FallbackSubscriber extends Subscriber { + + final Subscriber actual; + + final ProducerArbiter arbiter; + + FallbackSubscriber(Subscriber actual, ProducerArbiter arbiter) { + this.actual = actual; + this.arbiter = arbiter; + } + + @Override + public void onNext(T t) { + actual.onNext(t); + } + + @Override + public void onError(Throwable e) { + actual.onError(e); + } + + @Override + public void onCompleted() { + actual.onCompleted(); + } + + @Override + public void setProducer(Producer p) { + arbiter.setProducer(p); + } + } +} diff --git a/src/main/java/rx/internal/operators/OnSubscribeTimerOnce.java b/src/main/java/rx/internal/operators/OnSubscribeTimerOnce.java index 4d2a7389b2..b6acfa1b6f 100644 --- a/src/main/java/rx/internal/operators/OnSubscribeTimerOnce.java +++ b/src/main/java/rx/internal/operators/OnSubscribeTimerOnce.java @@ -16,11 +16,11 @@ package rx.internal.operators; import java.util.concurrent.TimeUnit; + +import rx.*; import rx.Observable.OnSubscribe; -import rx.Scheduler; import rx.Scheduler.Worker; import rx.exceptions.Exceptions; -import rx.Subscriber; import rx.functions.Action0; /** @@ -55,5 +55,5 @@ public void call() { } }, time, unit); } - + } diff --git a/src/main/java/rx/internal/operators/OnSubscribeTimerPeriodically.java b/src/main/java/rx/internal/operators/OnSubscribeTimerPeriodically.java index dedf978f96..d718a6906b 100644 --- a/src/main/java/rx/internal/operators/OnSubscribeTimerPeriodically.java +++ b/src/main/java/rx/internal/operators/OnSubscribeTimerPeriodically.java @@ -16,11 +16,11 @@ package rx.internal.operators; import java.util.concurrent.TimeUnit; + +import rx.*; import rx.Observable.OnSubscribe; -import rx.Scheduler; import rx.Scheduler.Worker; import rx.exceptions.Exceptions; -import rx.Subscriber; import rx.functions.Action0; /** @@ -58,7 +58,7 @@ public void call() { } } } - + }, initialDelay, period, unit); } } diff --git a/src/main/java/rx/internal/operators/OperatorToMap.java b/src/main/java/rx/internal/operators/OnSubscribeToMap.java similarity index 53% rename from src/main/java/rx/internal/operators/OperatorToMap.java rename to src/main/java/rx/internal/operators/OnSubscribeToMap.java index aa82c49cc4..6d72925011 100644 --- a/src/main/java/rx/internal/operators/OperatorToMap.java +++ b/src/main/java/rx/internal/operators/OnSubscribeToMap.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -19,126 +19,118 @@ import java.util.HashMap; import java.util.Map; -import rx.Observable.Operator; +import rx.Observable; +import rx.Observable.OnSubscribe; import rx.Subscriber; import rx.exceptions.Exceptions; import rx.functions.Func0; import rx.functions.Func1; -import rx.observers.Subscribers; /** * Maps the elements of the source observable into a java.util.Map instance and * emits that once the source observable completes. - * + * * @see Issue #96 * @param the value type of the input * @param the map-key type * @param the map-value type */ -public final class OperatorToMap implements Operator, T> { - - /** - * The default map factory. - * @param the key type - * @param the value type - */ - public static final class DefaultToMapFactory implements Func0> { - @Override - public Map call() { - return new HashMap(); - } - } +public final class OnSubscribeToMap implements OnSubscribe>, Func0> { + final Observable source; final Func1 keySelector; final Func1 valueSelector; - private final Func0> mapFactory; - + final Func0> mapFactory; /** * ToMap with key selector, value selector and default HashMap factory. + * @param source the source Observable instance * @param keySelector the function extracting the map-key from the main value * @param valueSelector the function extracting the map-value from the main value */ - public OperatorToMap( + public OnSubscribeToMap(Observable source, Func1 keySelector, Func1 valueSelector) { - this(keySelector, valueSelector, new DefaultToMapFactory()); + this(source, keySelector, valueSelector, null); } /** * ToMap with key selector, value selector and custom Map factory. + * @param source the source Observable instance * @param keySelector the function extracting the map-key from the main value * @param valueSelector the function extracting the map-value from the main value * @param mapFactory function that returns a Map instance to store keys and values into */ - public OperatorToMap( + public OnSubscribeToMap(Observable source, Func1 keySelector, Func1 valueSelector, Func0> mapFactory) { + this.source = source; this.keySelector = keySelector; this.valueSelector = valueSelector; - this.mapFactory = mapFactory; + if (mapFactory == null) { + this.mapFactory = this; + } else { + this.mapFactory = mapFactory; + } + } + @Override + public Map call() { + return new HashMap(); } @Override - public Subscriber call(final Subscriber> subscriber) { - - Map localMap; - + public void call(final Subscriber> subscriber) { + Map map; try { - localMap = mapFactory.call(); + map = mapFactory.call(); } catch (Throwable ex) { Exceptions.throwOrReport(ex, subscriber); - Subscriber parent = Subscribers.empty(); - parent.unsubscribe(); - return parent; + return; } - - final Map fLocalMap = localMap; - - return new Subscriber(subscriber) { + new ToMapSubscriber(subscriber, map, keySelector, valueSelector) + .subscribeTo(source); + } - private Map map = fLocalMap; + static final class ToMapSubscriber extends DeferredScalarSubscriberSafe> { - @Override - public void onStart() { - request(Long.MAX_VALUE); - } - - @Override - public void onNext(T v) { - K key; - V value; - - try { - key = keySelector.call(v); - value = valueSelector.call(v); - } catch (Throwable ex) { - Exceptions.throwOrReport(ex, subscriber); - return; - } - - map.put(key, value); - } + final Func1 keySelector; + final Func1 valueSelector; - @Override - public void onError(Throwable e) { - map = null; - subscriber.onError(e); - } + ToMapSubscriber(Subscriber> actual, Map map, Func1 keySelector, + Func1 valueSelector) { + super(actual); + this.value = map; + this.hasValue = true; + this.keySelector = keySelector; + this.valueSelector = valueSelector; + } - @Override - public void onCompleted() { - Map map0 = map; - map = null; - subscriber.onNext(map0); - subscriber.onCompleted(); + @Override + public void onStart() { + request(Long.MAX_VALUE); + } + + @Override + public void onNext(T t) { + if (done) { + return; + } + try { + K key = keySelector.call(t); + V val = valueSelector.call(t); + value.put(key, val); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + unsubscribe(); + onError(ex); } - }; + } } + } diff --git a/src/main/java/rx/internal/operators/OnSubscribeToMultimap.java b/src/main/java/rx/internal/operators/OnSubscribeToMultimap.java new file mode 100644 index 0000000000..c412ea5af5 --- /dev/null +++ b/src/main/java/rx/internal/operators/OnSubscribeToMultimap.java @@ -0,0 +1,205 @@ +/** + * Copyright one 2014 Netflix, Inc. + * + * 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 rx.internal.operators; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import rx.Observable; +import rx.Observable.OnSubscribe; +import rx.Subscriber; +import rx.exceptions.Exceptions; +import rx.functions.Func0; +import rx.functions.Func1; + +/** + * Maps the elements of the source observable into a multimap + * (Map<K, Collection<V>>) where each + * key entry has a collection of the source's values. + * + * @see Issue #97 + * @param the value type of the input + * @param the multimap-key type + * @param the multimap-value type + */ +public final class OnSubscribeToMultimap implements OnSubscribe>>, Func0>> { + + private final Func1 keySelector; + private final Func1 valueSelector; + private final Func0>> mapFactory; + private final Func1> collectionFactory; + private final Observable source; + + /** + * ToMultimap with key selector, custom value selector, + * default HashMap factory and default ArrayList collection factory. + * @param source the source Observable instance + * @param keySelector the function extracting the map-key from the main value + * @param valueSelector the function extracting the map-value from the main value + */ + public OnSubscribeToMultimap( + Observable source, + Func1 keySelector, + Func1 valueSelector) { + this(source, keySelector, valueSelector, + null, + DefaultMultimapCollectionFactory.instance()); + } + + /** + * ToMultimap with key selector, custom value selector, + * custom Map factory and default ArrayList collection factory. + * @param source the source Observable instance + * @param keySelector the function extracting the map-key from the main value + * @param valueSelector the function extracting the map-value from the main value + * @param mapFactory function that returns a Map instance to store keys and values into + */ + public OnSubscribeToMultimap( + Observable source, + Func1 keySelector, + Func1 valueSelector, + Func0>> mapFactory) { + this(source, keySelector, valueSelector, + mapFactory, + DefaultMultimapCollectionFactory.instance()); + } + + /** + * ToMultimap with key selector, custom value selector, + * custom Map factory and custom collection factory. + * @param source the observable source + * @param keySelector the function extracting the map-key from the main value + * @param valueSelector the function extracting the map-value from the main value + * @param mapFactory function that returns a Map instance to store keys and values into + * @param collectionFactory function that returns a Collection for a particular key to store values into + */ + public OnSubscribeToMultimap( + Observable source, + Func1 keySelector, + Func1 valueSelector, + Func0>> mapFactory, + Func1> collectionFactory) { + this.source = source; + this.keySelector = keySelector; + this.valueSelector = valueSelector; + if (mapFactory == null) { + this.mapFactory = this; + } else { + this.mapFactory = mapFactory; + } + this.collectionFactory = collectionFactory; + } + + // default map factory + @Override + public Map> call() { + return new HashMap>(); + } + + @Override + public void call(final Subscriber>> subscriber) { + + Map> map; + try { + map = mapFactory.call(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + subscriber.onError(ex); + return; + } + new ToMultimapSubscriber( + subscriber, map, keySelector, valueSelector, collectionFactory) + .subscribeTo(source); + } + + private static final class ToMultimapSubscriber + extends DeferredScalarSubscriberSafe>> { + + private final Func1 keySelector; + private final Func1 valueSelector; + private final Func1> collectionFactory; + + ToMultimapSubscriber( + Subscriber>> subscriber, + Map> map, + Func1 keySelector, Func1 valueSelector, + Func1> collectionFactory) { + super(subscriber); + this.value = map; + this.hasValue = true; + this.keySelector = keySelector; + this.valueSelector = valueSelector; + this.collectionFactory = collectionFactory; + } + + @Override + public void onStart() { + request(Long.MAX_VALUE); + } + + @Override + public void onNext(T t) { + if (done) { + return; + } + try { + // any interaction with keySelector, valueSelector, collectionFactory, collection or value + // may fail unexpectedly because their behaviour is customisable by the user. For this + // reason we wrap their calls in try-catch block. + + K key = keySelector.call(t); + V v = valueSelector.call(t); + Collection collection = value.get(key); + if (collection == null) { + collection = collectionFactory.call(key); + value.put(key, collection); + } + collection.add(v); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + unsubscribe(); + onError(ex); + } + + } + } + + /** + * The default collection factory for a key in the multimap returning + * an ArrayList independent of the key. + * @param the key type + * @param the value type + */ + private static final class DefaultMultimapCollectionFactory + implements Func1> { + + private static final DefaultMultimapCollectionFactory INSTANCE = new DefaultMultimapCollectionFactory(); + + @SuppressWarnings("unchecked") + static DefaultMultimapCollectionFactory instance() { + return (DefaultMultimapCollectionFactory) INSTANCE; + } + + @Override + public Collection call(K t1) { + return new ArrayList(); + } + } + +} \ No newline at end of file diff --git a/src/main/java/rx/internal/operators/OnSubscribeToObservableFuture.java b/src/main/java/rx/internal/operators/OnSubscribeToObservableFuture.java index 573c4065cd..b86b331753 100644 --- a/src/main/java/rx/internal/operators/OnSubscribeToObservableFuture.java +++ b/src/main/java/rx/internal/operators/OnSubscribeToObservableFuture.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -15,12 +15,11 @@ */ package rx.internal.operators; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; +import java.util.concurrent.*; import rx.Observable.OnSubscribe; -import rx.exceptions.Exceptions; import rx.Subscriber; +import rx.exceptions.Exceptions; import rx.functions.Action0; import rx.internal.producers.SingleProducer; import rx.subscriptions.Subscriptions; @@ -75,7 +74,7 @@ public void call() { T value = (unit == null) ? (T) that.get() : (T) that.get(time, unit); subscriber.setProducer(new SingleProducer(subscriber, value)); } catch (Throwable e) { - // If this Observable is unsubscribed, we will receive an CancellationException. + // If this Observable is unsubscribed, we will receive a CancellationException. // However, CancellationException will not be passed to the final Subscriber // since it's already subscribed. // If the Future is canceled in other place, CancellationException will be still diff --git a/src/main/java/rx/internal/operators/OnSubscribeUsing.java b/src/main/java/rx/internal/operators/OnSubscribeUsing.java index 352c699056..110a8393e7 100644 --- a/src/main/java/rx/internal/operators/OnSubscribeUsing.java +++ b/src/main/java/rx/internal/operators/OnSubscribeUsing.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -25,7 +25,7 @@ /** * Constructs an observable sequence that depends on a resource object. - * + * * @param the output value type * @param the resource type */ @@ -58,7 +58,7 @@ public void call(final Subscriber subscriber) { // dispose on unsubscription subscriber.add(disposeOnceOnly); // create the observable - final Observable source; + Observable source; try { source = observableFactory @@ -77,7 +77,7 @@ public void call(final Subscriber subscriber) { return; } - final Observable observable; + Observable observable; // supplement with on termination disposal if requested if (disposeEagerly) { observable = source @@ -88,7 +88,7 @@ public void call(final Subscriber subscriber) { // dispose after the terminal signals were sent out .doAfterTerminate(disposeOnceOnly); } - + try { // start observable.unsafeSubscribe(Subscribers.wrap(subscriber)); @@ -96,11 +96,12 @@ public void call(final Subscriber subscriber) { Throwable disposeError = dispose(disposeOnceOnly); Exceptions.throwIfFatal(e); Exceptions.throwIfFatal(disposeError); - if (disposeError != null) + if (disposeError != null) { subscriber.onError(new CompositeException(e, disposeError)); - else + } else { // propagate error subscriber.onError(e); + } } } catch (Throwable e) { // then propagate error @@ -117,7 +118,7 @@ private Throwable dispose(final Action0 disposeOnceOnly) { } } - private static final class DisposeAction extends AtomicBoolean implements Action0, + static final class DisposeAction extends AtomicBoolean implements Action0, Subscription { private static final long serialVersionUID = 4262875056400218316L; diff --git a/src/main/java/rx/internal/operators/OperatorAll.java b/src/main/java/rx/internal/operators/OperatorAll.java index 48053ed710..73c7a487e1 100644 --- a/src/main/java/rx/internal/operators/OperatorAll.java +++ b/src/main/java/rx/internal/operators/OperatorAll.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -20,6 +20,7 @@ import rx.exceptions.Exceptions; import rx.functions.Func1; import rx.internal.producers.SingleDelayedProducer; +import rx.plugins.RxJavaHooks; /** * Returns an Observable that emits a Boolean that indicates whether all items emitted by an @@ -43,6 +44,9 @@ public Subscriber call(final Subscriber child) { @Override public void onNext(T t) { + if (done) { + return; + } Boolean result; try { result = predicate.call(t); @@ -50,18 +54,23 @@ public void onNext(T t) { Exceptions.throwOrReport(e, this, t); return; } - if (!result && !done) { + if (!result) { done = true; producer.setValue(false); unsubscribe(); - } - // note that don't need to request more of upstream because this subscriber + } + // note that don't need to request more of upstream because this subscriber // defaults to requesting Long.MAX_VALUE } @Override public void onError(Throwable e) { - child.onError(e); + if (!done) { + done = true; + child.onError(e); + } else { + RxJavaHooks.onError(e); + } } @Override diff --git a/src/main/java/rx/internal/operators/OperatorAny.java b/src/main/java/rx/internal/operators/OperatorAny.java index 9369b62b39..5e2b73632b 100644 --- a/src/main/java/rx/internal/operators/OperatorAny.java +++ b/src/main/java/rx/internal/operators/OperatorAny.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -21,6 +21,7 @@ import rx.exceptions.Exceptions; import rx.functions.Func1; import rx.internal.producers.SingleDelayedProducer; +import rx.plugins.RxJavaHooks; /** * Returns an {@link Observable} that emits true if any element of @@ -45,6 +46,9 @@ public Subscriber call(final Subscriber child) { @Override public void onNext(T t) { + if (done) { + return; + } hasElements = true; boolean result; try { @@ -53,18 +57,23 @@ public void onNext(T t) { Exceptions.throwOrReport(e, this, t); return; } - if (result && !done) { + if (result) { done = true; producer.setValue(!returnOnEmpty); unsubscribe(); - } - // note that don't need to request more of upstream because this subscriber + } + // note that don't need to request more of upstream because this subscriber // defaults to requesting Long.MAX_VALUE } @Override public void onError(Throwable e) { - child.onError(e); + if (!done) { + done = true; + child.onError(e); + } else { + RxJavaHooks.onError(e); + } } @Override diff --git a/src/main/java/rx/internal/operators/OperatorAsObservable.java b/src/main/java/rx/internal/operators/OperatorAsObservable.java index 9cb9d8ac56..26037450b7 100644 --- a/src/main/java/rx/internal/operators/OperatorAsObservable.java +++ b/src/main/java/rx/internal/operators/OperatorAsObservable.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -20,13 +20,13 @@ /** * Hides the identity of another observable. - * + * * @param * the return value type of the wrapped observable. */ public final class OperatorAsObservable implements Operator { /** Lazy initialization via inner-class holder. */ - private static final class Holder { + static final class Holder { /** A singleton instance. */ static final OperatorAsObservable INSTANCE = new OperatorAsObservable(); } @@ -38,10 +38,13 @@ private static final class Holder { public static OperatorAsObservable instance() { return (OperatorAsObservable)Holder.INSTANCE; } - OperatorAsObservable() { } + OperatorAsObservable() { + // singleton + } + @Override public Subscriber call(Subscriber s) { return s; } - + } diff --git a/src/main/java/rx/internal/operators/OperatorBufferWithSingleObservable.java b/src/main/java/rx/internal/operators/OperatorBufferWithSingleObservable.java index b5f9a68493..c495513b14 100644 --- a/src/main/java/rx/internal/operators/OperatorBufferWithSingleObservable.java +++ b/src/main/java/rx/internal/operators/OperatorBufferWithSingleObservable.java @@ -15,17 +15,15 @@ */ package rx.internal.operators; -import java.util.ArrayList; -import java.util.List; +import java.util.*; import rx.Observable; import rx.Observable.Operator; -import rx.exceptions.Exceptions; import rx.Observer; import rx.Subscriber; +import rx.exceptions.Exceptions; import rx.functions.Func0; -import rx.observers.SerializedSubscriber; -import rx.observers.Subscribers; +import rx.observers.*; /** * This operation takes @@ -39,7 +37,7 @@ * Note that this operation only produces non-overlapping chunks. At all times there is * exactly one buffer actively storing values. *

    - * + * * @param the buffered value type * @param the value type of the Observable signaling the end of each buffer */ @@ -84,34 +82,34 @@ public Subscriber call(final Subscriber> child) { Exceptions.throwOrReport(t, child); return Subscribers.empty(); } - final BufferingSubscriber bsub = new BufferingSubscriber(new SerializedSubscriber>(child)); + final BufferingSubscriber s = new BufferingSubscriber(new SerializedSubscriber>(child)); Subscriber closingSubscriber = new Subscriber() { @Override public void onNext(TClosing t) { - bsub.emit(); + s.emit(); } @Override public void onError(Throwable e) { - bsub.onError(e); + s.onError(e); } @Override public void onCompleted() { - bsub.onCompleted(); + s.onCompleted(); } }; child.add(closingSubscriber); - child.add(bsub); - + child.add(s); + closing.unsafeSubscribe(closingSubscriber); - - return bsub; + + return s; } - + final class BufferingSubscriber extends Subscriber { final Subscriber> child; /** Guarded by this. */ @@ -165,7 +163,7 @@ public void onCompleted() { child.onCompleted(); unsubscribe(); } - + void emit() { List toEmit; synchronized (this) { @@ -189,5 +187,5 @@ void emit() { } } } - + } diff --git a/src/main/java/rx/internal/operators/OperatorBufferWithSize.java b/src/main/java/rx/internal/operators/OperatorBufferWithSize.java index 6475547563..5b9877e842 100644 --- a/src/main/java/rx/internal/operators/OperatorBufferWithSize.java +++ b/src/main/java/rx/internal/operators/OperatorBufferWithSize.java @@ -44,7 +44,7 @@ public final class OperatorBufferWithSize implements Operator, T> { * @param count * the number of elements a buffer should have before being emitted * @param skip - * the interval with which chunks have to be created. Note that when {@code skip == count} + * the interval with which chunks have to be created. Note that when {@code skip == count} * the operator will produce non-overlapping chunks. If * {@code skip < count}, this buffer operation will produce overlapping chunks and if * {@code skip > count} non-overlapping chunks will be created and some values will not be pushed @@ -65,40 +65,40 @@ public OperatorBufferWithSize(int count, int skip) { public Subscriber call(final Subscriber> child) { if (skip == count) { BufferExact parent = new BufferExact(child, count); - + child.add(parent); child.setProducer(parent.createProducer()); - + return parent; } if (skip > count) { BufferSkip parent = new BufferSkip(child, count, skip); - + child.add(parent); child.setProducer(parent.createProducer()); - + return parent; } BufferOverlap parent = new BufferOverlap(child, count, skip); - + child.add(parent); child.setProducer(parent.createProducer()); - + return parent; } - + static final class BufferExact extends Subscriber { final Subscriber> actual; final int count; List buffer; - + public BufferExact(Subscriber> actual, int count) { this.actual = actual; this.count = count; this.request(0L); } - + @Override public void onNext(T t) { List b = buffer; @@ -106,21 +106,21 @@ public void onNext(T t) { b = new ArrayList(count); buffer = b; } - + b.add(t); - + if (b.size() == count) { buffer = null; actual.onNext(b); } } - + @Override public void onError(Throwable e) { buffer = null; actual.onError(e); } - + @Override public void onCompleted() { List b = buffer; @@ -129,7 +129,7 @@ public void onCompleted() { } actual.onCompleted(); } - + Producer createProducer() { return new Producer() { @Override @@ -145,14 +145,14 @@ public void request(long n) { }; } } - + static final class BufferSkip extends Subscriber { final Subscriber> actual; final int count; final int skip; - + long index; - + List buffer; public BufferSkip(Subscriber> actual, int count, int skip) { @@ -161,7 +161,7 @@ public BufferSkip(Subscriber> actual, int count, int skip) { this.skip = skip; this.request(0L); } - + @Override public void onNext(T t) { long i = index; @@ -176,23 +176,23 @@ public void onNext(T t) { } else { index = i; } - + if (b != null) { b.add(t); - + if (b.size() == count) { buffer = null; actual.onNext(b); } } } - + @Override public void onError(Throwable e) { buffer = null; actual.onError(e); } - + @Override public void onCompleted() { List b = buffer; @@ -202,11 +202,11 @@ public void onCompleted() { } actual.onCompleted(); } - + Producer createProducer() { return new BufferSkipProducer(); } - + final class BufferSkipProducer extends AtomicBoolean implements Producer { @@ -233,18 +233,18 @@ public void request(long n) { } } } - + static final class BufferOverlap extends Subscriber { final Subscriber> actual; final int count; final int skip; - + long index; - + final ArrayDeque> queue; - + final AtomicLong requested; - + long produced; public BufferOverlap(Subscriber> actual, int count, int skip) { @@ -269,11 +269,11 @@ public void onNext(T t) { } else { index = i; } - + for (List list : queue) { list.add(t); } - + List b = queue.peek(); if (b != null && b.size() == count) { queue.poll(); @@ -281,18 +281,18 @@ public void onNext(T t) { actual.onNext(b); } } - + @Override public void onError(Throwable e) { queue.clear(); - + actual.onError(e); } - + @Override public void onCompleted() { long p = produced; - + if (p != 0L) { if (p > requested.get()) { actual.onError(new MissingBackpressureException("More produced than requested? " + p)); @@ -300,14 +300,14 @@ public void onCompleted() { } requested.addAndGet(-p); } - + BackpressureUtils.postCompleteDone(requested, queue, actual); } - + Producer createProducer() { return new BufferOverlapProducer(); } - + final class BufferOverlapProducer extends AtomicBoolean implements Producer { /** */ @@ -321,7 +321,7 @@ public void request(long n) { if (!get() && compareAndSet(false, true)) { long u = BackpressureUtils.multiplyCap(parent.skip, n - 1); long v = BackpressureUtils.addCap(u, parent.count); - + parent.request(v); } else { long u = BackpressureUtils.multiplyCap(parent.skip, n); @@ -330,7 +330,7 @@ public void request(long n) { } } } - + } } } diff --git a/src/main/java/rx/internal/operators/OperatorBufferWithStartEndObservable.java b/src/main/java/rx/internal/operators/OperatorBufferWithStartEndObservable.java index 9938bc4534..cbcb2b584f 100644 --- a/src/main/java/rx/internal/operators/OperatorBufferWithStartEndObservable.java +++ b/src/main/java/rx/internal/operators/OperatorBufferWithStartEndObservable.java @@ -15,15 +15,13 @@ */ package rx.internal.operators; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; +import java.util.*; + import rx.Observable; import rx.Observable.Operator; -import rx.exceptions.Exceptions; import rx.Observer; import rx.Subscriber; +import rx.exceptions.Exceptions; import rx.functions.Func1; import rx.observers.SerializedSubscriber; import rx.subscriptions.CompositeSubscription; @@ -43,7 +41,7 @@ * Note that when using this operation multiple overlapping chunks could be active at any * one point. *

    - * + * * @param the buffered value type * @param the value type of the Observable opening buffers * @param the value type of the Observable closing buffers @@ -68,33 +66,33 @@ public OperatorBufferWithStartEndObservable(Observable buffe @Override public Subscriber call(final Subscriber> child) { - - final BufferingSubscriber bsub = new BufferingSubscriber(new SerializedSubscriber>(child)); - + + final BufferingSubscriber s = new BufferingSubscriber(new SerializedSubscriber>(child)); + Subscriber openSubscriber = new Subscriber() { @Override public void onNext(TOpening t) { - bsub.startBuffer(t); + s.startBuffer(t); } @Override public void onError(Throwable e) { - bsub.onError(e); + s.onError(e); } @Override public void onCompleted() { - bsub.onCompleted(); + s.onCompleted(); } - + }; child.add(openSubscriber); - child.add(bsub); - + child.add(s); + bufferOpening.unsafeSubscribe(openSubscriber); - - return bsub; + + return s; } final class BufferingSubscriber extends Subscriber { final Subscriber> child; @@ -187,10 +185,10 @@ public void onCompleted() { closingSubscriptions.remove(this); endBuffer(chunk); } - + }; closingSubscriptions.add(closeSubscriber); - + cobs.unsafeSubscribe(closeSubscriber); } void endBuffer(List toEnd) { @@ -213,6 +211,6 @@ void endBuffer(List toEnd) { child.onNext(toEnd); } } - + } } diff --git a/src/main/java/rx/internal/operators/OperatorBufferWithTime.java b/src/main/java/rx/internal/operators/OperatorBufferWithTime.java index b13fe2fa9e..a701e14799 100644 --- a/src/main/java/rx/internal/operators/OperatorBufferWithTime.java +++ b/src/main/java/rx/internal/operators/OperatorBufferWithTime.java @@ -15,16 +15,13 @@ */ package rx.internal.operators; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; +import java.util.*; import java.util.concurrent.TimeUnit; + +import rx.*; import rx.Observable; import rx.Observable.Operator; -import rx.Scheduler; import rx.Scheduler.Worker; -import rx.Subscriber; import rx.exceptions.Exceptions; import rx.functions.Action0; import rx.observers.SerializedSubscriber; @@ -40,7 +37,7 @@ * Note that this operation can produce non-connected, or overlapping chunks depending * on the input parameters. *

    - * + * * @param the buffered value type */ public final class OperatorBufferWithTime implements Operator, T> { @@ -74,21 +71,21 @@ public OperatorBufferWithTime(long timespan, long timeshift, TimeUnit unit, int public Subscriber call(final Subscriber> child) { final Worker inner = scheduler.createWorker(); SerializedSubscriber> serialized = new SerializedSubscriber>(child); - + if (timespan == timeshift) { - ExactSubscriber bsub = new ExactSubscriber(serialized, inner); - bsub.add(inner); - child.add(bsub); - bsub.scheduleExact(); - return bsub; + ExactSubscriber parent = new ExactSubscriber(serialized, inner); + parent.add(inner); + child.add(parent); + parent.scheduleExact(); + return parent; } - - InexactSubscriber bsub = new InexactSubscriber(serialized, inner); - bsub.add(inner); - child.add(bsub); - bsub.startNewChunk(); - bsub.scheduleChunk(); - return bsub; + + InexactSubscriber parent = new InexactSubscriber(serialized, inner); + parent.add(inner); + child.add(parent); + parent.startNewChunk(); + parent.scheduleChunk(); + return parent; } /** Subscriber when the buffer chunking time and length differ. */ final class InexactSubscriber extends Subscriber { diff --git a/src/main/java/rx/internal/operators/OperatorCast.java b/src/main/java/rx/internal/operators/OperatorCast.java index be14ef91e8..ed2d517036 100644 --- a/src/main/java/rx/internal/operators/OperatorCast.java +++ b/src/main/java/rx/internal/operators/OperatorCast.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -18,7 +18,7 @@ import rx.*; import rx.Observable.Operator; import rx.exceptions.*; -import rx.internal.util.RxJavaPluginUtils; +import rx.plugins.RxJavaHooks; /** * Converts the elements of an observable sequence to the specified type. @@ -39,24 +39,24 @@ public Subscriber call(final Subscriber o) { o.add(parent); return parent; } - + static final class CastSubscriber extends Subscriber { - + final Subscriber actual; - + final Class castClass; boolean done; - + public CastSubscriber(Subscriber actual, Class castClass) { this.actual = actual; this.castClass = castClass; } - + @Override public void onNext(T t) { R result; - + try { result = castClass.cast(t); } catch (Throwable ex) { @@ -65,22 +65,22 @@ public void onNext(T t) { onError(OnErrorThrowable.addValueAsLastCause(ex, t)); return; } - + actual.onNext(result); } - + @Override public void onError(Throwable e) { if (done) { - RxJavaPluginUtils.handleException(e); + RxJavaHooks.onError(e); return; } done = true; - + actual.onError(e); } - - + + @Override public void onCompleted() { if (done) { @@ -88,7 +88,7 @@ public void onCompleted() { } actual.onCompleted(); } - + @Override public void setProducer(Producer p) { actual.setProducer(p); diff --git a/src/main/java/rx/internal/operators/OperatorDebounceWithSelector.java b/src/main/java/rx/internal/operators/OperatorDebounceWithSelector.java index 6be5ff2210..dee6acb4a1 100644 --- a/src/main/java/rx/internal/operators/OperatorDebounceWithSelector.java +++ b/src/main/java/rx/internal/operators/OperatorDebounceWithSelector.java @@ -15,10 +15,9 @@ */ package rx.internal.operators; -import rx.Observable; +import rx.*; import rx.Observable.Operator; import rx.exceptions.Exceptions; -import rx.Subscriber; import rx.functions.Func1; import rx.internal.operators.OperatorDebounceWithTime.DebounceState; import rx.observers.SerializedSubscriber; @@ -26,47 +25,47 @@ /** * Delay the emission via another observable if no new source appears in the meantime. - * + * * @param the value type of the main sequence * @param the value type of the boundary sequence */ public final class OperatorDebounceWithSelector implements Operator { final Func1> selector; - + public OperatorDebounceWithSelector(Func1> selector) { this.selector = selector; } - + @Override public Subscriber call(final Subscriber child) { final SerializedSubscriber s = new SerializedSubscriber(child); - final SerialSubscription ssub = new SerialSubscription(); - child.add(ssub); - + final SerialSubscription serial = new SerialSubscription(); + child.add(serial); + return new Subscriber(child) { final DebounceState state = new DebounceState(); final Subscriber self = this; - + @Override public void onStart() { // debounce wants to receive everything as a firehose without backpressure request(Long.MAX_VALUE); } - + @Override public void onNext(T t) { Observable debouncer; - + try { debouncer = selector.call(t); } catch (Throwable e) { Exceptions.throwOrReport(e, this); return; } - - + + final int index = state.next(t); - + Subscriber debounceSubscriber = new Subscriber() { @Override @@ -85,10 +84,10 @@ public void onCompleted() { unsubscribe(); } }; - ssub.set(debounceSubscriber); - + serial.set(debounceSubscriber); + debouncer.unsafeSubscribe(debounceSubscriber); - + } @Override @@ -104,5 +103,5 @@ public void onCompleted() { } }; } - + } \ No newline at end of file diff --git a/src/main/java/rx/internal/operators/OperatorDebounceWithTime.java b/src/main/java/rx/internal/operators/OperatorDebounceWithTime.java index f98639aff3..1fe101525f 100644 --- a/src/main/java/rx/internal/operators/OperatorDebounceWithTime.java +++ b/src/main/java/rx/internal/operators/OperatorDebounceWithTime.java @@ -16,11 +16,11 @@ package rx.internal.operators; import java.util.concurrent.TimeUnit; + +import rx.*; import rx.Observable.Operator; -import rx.Scheduler; import rx.Scheduler.Worker; import rx.exceptions.Exceptions; -import rx.Subscriber; import rx.functions.Action0; import rx.observers.SerializedSubscriber; import rx.subscriptions.SerialSubscription; @@ -50,16 +50,16 @@ public OperatorDebounceWithTime(long timeout, TimeUnit unit, Scheduler scheduler this.unit = unit; this.scheduler = scheduler; } - + @Override public Subscriber call(final Subscriber child) { final Worker worker = scheduler.createWorker(); final SerializedSubscriber s = new SerializedSubscriber(child); - final SerialSubscription ssub = new SerialSubscription(); - + final SerialSubscription serial = new SerialSubscription(); + s.add(worker); - s.add(ssub); - + s.add(serial); + return new Subscriber(child) { final DebounceState state = new DebounceState(); final Subscriber self = this; @@ -71,23 +71,23 @@ public void onStart() { @Override public void onNext(final T t) { - + final int index = state.next(t); - ssub.set(worker.schedule(new Action0() { + serial.set(worker.schedule(new Action0() { @Override public void call() { state.emit(index, s, self); } }, timeout, unit)); } - + @Override public void onError(Throwable e) { s.onError(e); unsubscribe(); state.clear(); } - + @Override public void onCompleted() { state.emitAndComplete(s, this); @@ -109,8 +109,8 @@ static final class DebounceState { boolean terminate; /** Guarded by this. */ boolean emitting; - - public synchronized int next(T value) { + + public synchronized int next(T value) { // NOPMD this.value = value; this.hasValue = true; return ++index; @@ -122,7 +122,7 @@ public void emit(int index, Subscriber onNextAndComplete, Subscriber onErr return; } localValue = value; - + value = null; hasValue = false; emitting = true; @@ -142,13 +142,13 @@ public void emit(int index, Subscriber onNextAndComplete, Subscriber onErr return; } } - + onNextAndComplete.onCompleted(); } public void emitAndComplete(Subscriber onNextAndComplete, Subscriber onError) { T localValue; boolean localHasValue; - + synchronized (this) { if (emitting) { terminate = true; @@ -156,7 +156,7 @@ public void emitAndComplete(Subscriber onNextAndComplete, Subscriber onErr } localValue = value; localHasValue = hasValue; - + value = null; hasValue = false; @@ -173,7 +173,7 @@ public void emitAndComplete(Subscriber onNextAndComplete, Subscriber onErr } onNextAndComplete.onCompleted(); } - public synchronized void clear() { + public synchronized void clear() { // NOPMD ++index; value = null; hasValue = false; diff --git a/src/main/java/rx/internal/operators/OperatorDelay.java b/src/main/java/rx/internal/operators/OperatorDelay.java index 7edf5199b3..ef271ae37e 100644 --- a/src/main/java/rx/internal/operators/OperatorDelay.java +++ b/src/main/java/rx/internal/operators/OperatorDelay.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -17,15 +17,14 @@ import java.util.concurrent.TimeUnit; +import rx.*; import rx.Observable.Operator; -import rx.Scheduler; import rx.Scheduler.Worker; -import rx.Subscriber; import rx.functions.Action0; /** * Delays the emission of onNext events by a given amount of time. - * + * * @param * the value type */ diff --git a/src/main/java/rx/internal/operators/OperatorDelayWithSelector.java b/src/main/java/rx/internal/operators/OperatorDelayWithSelector.java index 1c4447c1d2..8b34096dcc 100644 --- a/src/main/java/rx/internal/operators/OperatorDelayWithSelector.java +++ b/src/main/java/rx/internal/operators/OperatorDelayWithSelector.java @@ -15,18 +15,16 @@ */ package rx.internal.operators; -import rx.Observable; +import rx.*; import rx.Observable.Operator; import rx.exceptions.Exceptions; -import rx.Subscriber; import rx.functions.Func1; -import rx.observers.SerializedSubscriber; -import rx.observers.Subscribers; +import rx.observers.*; import rx.subjects.PublishSubject; /** * Delay the subscription and emission of the source items by a per-item observable that fires its first element. - * + * * @param * the item type * @param diff --git a/src/main/java/rx/internal/operators/OperatorDematerialize.java b/src/main/java/rx/internal/operators/OperatorDematerialize.java index 5fd8d7fdfa..3065a6dd76 100644 --- a/src/main/java/rx/internal/operators/OperatorDematerialize.java +++ b/src/main/java/rx/internal/operators/OperatorDematerialize.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -15,9 +15,8 @@ */ package rx.internal.operators; -import rx.Notification; +import rx.*; import rx.Observable.Operator; -import rx.Subscriber; /** * Reverses the effect of {@link OperatorMaterialize} by transforming the Notification objects @@ -26,12 +25,12 @@ * *

    * See here for the Microsoft Rx equivalent. - * + * * @param the wrapped value type */ public final class OperatorDematerialize implements Operator> { /** Lazy initialization via inner-class holder. */ - private static final class Holder { + static final class Holder { /** A singleton instance. */ static final OperatorDematerialize INSTANCE = new OperatorDematerialize(); } @@ -42,7 +41,10 @@ private static final class Holder { public static OperatorDematerialize instance() { return Holder.INSTANCE; // using raw types because the type inference is not good enough } - OperatorDematerialize() { } + OperatorDematerialize() { + // singleton + } + @Override public Subscriber> call(final Subscriber child) { return new Subscriber>(child) { @@ -62,6 +64,9 @@ public void onNext(Notification t) { case OnCompleted: onCompleted(); break; + default: + onError(new IllegalArgumentException("Unsupported notification type: " + t)); + break; } } @@ -80,8 +85,8 @@ public void onCompleted() { child.onCompleted(); } } - + }; } - + } diff --git a/src/main/java/rx/internal/operators/OperatorDistinct.java b/src/main/java/rx/internal/operators/OperatorDistinct.java index a581b1b1e0..04f41e1133 100644 --- a/src/main/java/rx/internal/operators/OperatorDistinct.java +++ b/src/main/java/rx/internal/operators/OperatorDistinct.java @@ -15,8 +15,7 @@ */ package rx.internal.operators; -import java.util.HashSet; -import java.util.Set; +import java.util.*; import rx.Observable.Operator; import rx.Subscriber; @@ -25,23 +24,23 @@ /** * Returns an Observable that emits all distinct items emitted by the source. - * + * * @param the value type * @param the key type */ public final class OperatorDistinct implements Operator { final Func1 keySelector; - - private static class Holder { + + static final class Holder { static final OperatorDistinct INSTANCE = new OperatorDistinct(UtilityFunctions.identity()); } - + /** - * Returns a singleton instance of OperatorDistinct that was built using + * Returns a singleton instance of OperatorDistinct that was built using * the identity function for comparison (new OperatorDistinct(UtilityFunctions.identity())). - * + * * @param the value type - * @return Operator that emits distinct values only (regardless of order) using the identity function for comparison + * @return Operator that emits distinct values only (regardless of order) using the identity function for comparison */ @SuppressWarnings("unchecked") public static OperatorDistinct instance() { @@ -78,7 +77,7 @@ public void onCompleted() { keyMemory = null; child.onCompleted(); } - + }; } } diff --git a/src/main/java/rx/internal/operators/OperatorDistinctUntilChanged.java b/src/main/java/rx/internal/operators/OperatorDistinctUntilChanged.java index 43529df76f..9b41ced8fd 100644 --- a/src/main/java/rx/internal/operators/OperatorDistinctUntilChanged.java +++ b/src/main/java/rx/internal/operators/OperatorDistinctUntilChanged.java @@ -28,20 +28,20 @@ */ public final class OperatorDistinctUntilChanged implements Operator, Func2 { final Func1 keySelector; - + final Func2 comparator; - - private static class Holder { + + static final class Holder { static final OperatorDistinctUntilChanged INSTANCE = new OperatorDistinctUntilChanged(UtilityFunctions.identity()); } - + /** - * Returns a singleton instance of OperatorDistinctUntilChanged that was built using + * Returns a singleton instance of OperatorDistinctUntilChanged that was built using * the identity function for comparison (new OperatorDistinctUntilChanged(UtilityFunctions.identity())). - * + * * @param the value type - * @return Operator that emits sequentially distinct values only using the identity function for comparison + * @return Operator that emits sequentially distinct values only using the identity function for comparison */ @SuppressWarnings("unchecked") public static OperatorDistinctUntilChanged instance() { @@ -51,7 +51,7 @@ public static OperatorDistinctUntilChanged instance() { public OperatorDistinctUntilChanged(Func1 keySelector) { this.keySelector = keySelector; this.comparator = this; - + } @SuppressWarnings({ "unchecked", "rawtypes" }) @@ -59,7 +59,7 @@ public OperatorDistinctUntilChanged(Func2 compara this.keySelector = (Func1)UtilityFunctions.identity(); this.comparator = comparator; } - + @Override public Boolean call(U t1, U t2) { return (t1 == t2 || (t1 != null && t1.equals(t2))); @@ -72,26 +72,26 @@ public Subscriber call(final Subscriber child) { boolean hasPrevious; @Override public void onNext(T t) { - U currentKey = previousKey; - final U key; + U key; try { key = keySelector.call(t); } catch (Throwable e) { Exceptions.throwOrReport(e, child, t); return; } + U currentKey = previousKey; previousKey = key; - + if (hasPrevious) { boolean comparison; - + try { comparison = comparator.call(currentKey, key); } catch (Throwable e) { Exceptions.throwOrReport(e, child, key); return; } - + if (!comparison) { child.onNext(t); } else { @@ -112,8 +112,8 @@ public void onError(Throwable e) { public void onCompleted() { child.onCompleted(); } - + }; } - + } diff --git a/src/main/java/rx/internal/operators/OperatorDoAfterTerminate.java b/src/main/java/rx/internal/operators/OperatorDoAfterTerminate.java index a3efa8f845..c53bb51756 100644 --- a/src/main/java/rx/internal/operators/OperatorDoAfterTerminate.java +++ b/src/main/java/rx/internal/operators/OperatorDoAfterTerminate.java @@ -28,7 +28,7 @@ *

    * See also the MSDN Observable.Finally * method - * + * * @param the value type */ public final class OperatorDoAfterTerminate implements Operator { @@ -67,7 +67,7 @@ public void onCompleted() { callAction(); } } - + void callAction() { try { action.call(); @@ -78,5 +78,5 @@ void callAction() { } }; } - + } diff --git a/src/main/java/rx/internal/operators/OperatorDoOnEach.java b/src/main/java/rx/internal/operators/OperatorDoOnEach.java deleted file mode 100644 index e0e71a2188..0000000000 --- a/src/main/java/rx/internal/operators/OperatorDoOnEach.java +++ /dev/null @@ -1,90 +0,0 @@ -/** - * Copyright 2014 Netflix, Inc. - * - * 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 rx.internal.operators; - -import java.util.Arrays; - -import rx.*; -import rx.Observable.Operator; -import rx.exceptions.*; - -/** - * Converts the elements of an observable sequence to the specified type. - * @param the value type - */ -public class OperatorDoOnEach implements Operator { - final Observer doOnEachObserver; - - public OperatorDoOnEach(Observer doOnEachObserver) { - this.doOnEachObserver = doOnEachObserver; - } - - @Override - public Subscriber call(final Subscriber observer) { - return new Subscriber(observer) { - - private boolean done = false; - - @Override - public void onCompleted() { - if (done) { - return; - } - try { - doOnEachObserver.onCompleted(); - } catch (Throwable e) { - Exceptions.throwOrReport(e, this); - return; - } - // Set `done` here so that the error in `doOnEachObserver.onCompleted()` can be noticed by observer - done = true; - observer.onCompleted(); - } - - @Override - public void onError(Throwable e) { - // need to throwIfFatal since we swallow errors after terminated - Exceptions.throwIfFatal(e); - if (done) { - return; - } - done = true; - try { - doOnEachObserver.onError(e); - } catch (Throwable e2) { - Exceptions.throwIfFatal(e2); - observer.onError(new CompositeException(Arrays.asList(e, e2))); - return; - } - observer.onError(e); - } - - @Override - public void onNext(T value) { - if (done) { - return; - } - try { - doOnEachObserver.onNext(value); - } catch (Throwable e) { - Exceptions.throwOrReport(e, this, value); - return; - } - observer.onNext(value); - } - }; - } -} \ No newline at end of file diff --git a/src/main/java/rx/internal/operators/OperatorDoOnRequest.java b/src/main/java/rx/internal/operators/OperatorDoOnRequest.java index d45e895b40..3c26a73846 100644 --- a/src/main/java/rx/internal/operators/OperatorDoOnRequest.java +++ b/src/main/java/rx/internal/operators/OperatorDoOnRequest.java @@ -15,23 +15,22 @@ */ package rx.internal.operators; +import rx.*; import rx.Observable.Operator; -import rx.Producer; -import rx.Subscriber; import rx.functions.Action1; /** - * This operator modifies an {@link rx.Observable} so a given action is invoked when the + * This operator modifies an {@link rx.Observable} so a given action is invoked when the * {@link rx.Producer} receives a request. - * + * * @param * The type of the elements in the {@link rx.Observable} that this operator modifies */ public class OperatorDoOnRequest implements Operator { - final Action1 request; + final Action1 request; - public OperatorDoOnRequest(Action1 request) { + public OperatorDoOnRequest(Action1 request) { this.request = request; } @@ -53,7 +52,7 @@ public void request(long n) { return parent; } - private static final class ParentSubscriber extends Subscriber { + static final class ParentSubscriber extends Subscriber { private final Subscriber child; ParentSubscriber(Subscriber child) { diff --git a/src/main/java/rx/internal/operators/OperatorDoOnUnsubscribe.java b/src/main/java/rx/internal/operators/OperatorDoOnUnsubscribe.java index 217c46977f..ab7b0ea4cb 100644 --- a/src/main/java/rx/internal/operators/OperatorDoOnUnsubscribe.java +++ b/src/main/java/rx/internal/operators/OperatorDoOnUnsubscribe.java @@ -16,7 +16,7 @@ package rx.internal.operators; import rx.Observable.Operator; -import rx.*; +import rx.Subscriber; import rx.functions.Action0; import rx.observers.Subscribers; import rx.subscriptions.Subscriptions; diff --git a/src/main/java/rx/internal/operators/OperatorEagerConcatMap.java b/src/main/java/rx/internal/operators/OperatorEagerConcatMap.java index cfa46837b5..c6d4c29c25 100644 --- a/src/main/java/rx/internal/operators/OperatorEagerConcatMap.java +++ b/src/main/java/rx/internal/operators/OperatorEagerConcatMap.java @@ -37,20 +37,20 @@ public OperatorEagerConcatMap(Func1 this.bufferSize = bufferSize; this.maxConcurrent = maxConcurrent; } - + @Override public Subscriber call(Subscriber t) { EagerOuterSubscriber outer = new EagerOuterSubscriber(mapper, bufferSize, maxConcurrent, t); outer.init(); return outer; } - + static final class EagerOuterProducer extends AtomicLong implements Producer { /** */ private static final long serialVersionUID = -657299606803478389L; - + final EagerOuterSubscriber parent; - + public EagerOuterProducer(EagerOuterSubscriber parent) { this.parent = parent; } @@ -60,29 +60,29 @@ public void request(long n) { if (n < 0) { throw new IllegalStateException("n >= 0 required but it was " + n); } - + if (n > 0) { BackpressureUtils.getAndAddRequest(this, n); parent.drain(); } } } - + static final class EagerOuterSubscriber extends Subscriber { final Func1> mapper; final int bufferSize; final Subscriber actual; - - final LinkedList> subscribers; - + + final Queue> subscribers; + volatile boolean done; Throwable error; - + volatile boolean cancelled; - + final AtomicInteger wip; private EagerOuterProducer sharedProducer; - + public EagerOuterSubscriber(Func1> mapper, int bufferSize, int maxConcurrent, Subscriber actual) { this.mapper = mapper; @@ -92,7 +92,7 @@ public EagerOuterSubscriber(Func1> this.wip = new AtomicInteger(); request(maxConcurrent == Integer.MAX_VALUE ? Long.MAX_VALUE : maxConcurrent); } - + void init() { sharedProducer = new EagerOuterProducer(this); add(Subscriptions.create(new Action0() { @@ -107,34 +107,36 @@ public void call() { actual.add(this); actual.setProducer(sharedProducer); } - + void cleanup() { List list; synchronized (subscribers) { list = new ArrayList(subscribers); subscribers.clear(); } - + for (Subscription s : list) { s.unsubscribe(); } } - + @Override public void onNext(T t) { Observable observable; - + try { observable = mapper.call(t); } catch (Throwable e) { Exceptions.throwOrReport(e, actual, t); return; } - - EagerInnerSubscriber inner = new EagerInnerSubscriber(this, bufferSize); + if (cancelled) { return; } + + EagerInnerSubscriber inner = new EagerInnerSubscriber(this, bufferSize); + synchronized (subscribers) { if (cancelled) { return; @@ -147,45 +149,44 @@ public void onNext(T t) { observable.unsafeSubscribe(inner); drain(); } - + @Override public void onError(Throwable e) { error = e; done = true; drain(); } - + @Override public void onCompleted() { done = true; drain(); } - + void drain() { if (wip.getAndIncrement() != 0) { return; } int missed = 1; - + final AtomicLong requested = sharedProducer; final Subscriber actualSubscriber = this.actual; - final NotificationLite nl = NotificationLite.instance(); - + for (;;) { - + if (cancelled) { cleanup(); return; } - + EagerInnerSubscriber innerSubscriber; - + boolean outerDone = done; synchronized (subscribers) { innerSubscriber = subscribers.peek(); } boolean empty = innerSubscriber == null; - + if (outerDone) { Throwable error = this.error; if (error != null) { @@ -202,17 +203,16 @@ void drain() { if (!empty) { long requestedAmount = requested.get(); long emittedAmount = 0L; - boolean unbounded = requestedAmount == Long.MAX_VALUE; - + Queue innerQueue = innerSubscriber.queue; boolean innerDone = false; - - + + for (;;) { outerDone = innerSubscriber.done; Object v = innerQueue.peek(); empty = v == null; - + if (outerDone) { Throwable innerError = innerSubscriber.error; if (innerError != null) { @@ -230,42 +230,41 @@ void drain() { break; } } - + if (empty) { break; } - - if (requestedAmount == 0L) { + + if (requestedAmount == emittedAmount) { break; } - + innerQueue.poll(); - + try { - actualSubscriber.onNext(nl.getValue(v)); + actualSubscriber.onNext(NotificationLite.getValue(v)); } catch (Throwable ex) { Exceptions.throwOrReport(ex, actualSubscriber, v); return; } - - requestedAmount--; - emittedAmount--; + + emittedAmount++; } - + if (emittedAmount != 0L) { - if (!unbounded) { - requested.addAndGet(emittedAmount); + if (requestedAmount != Long.MAX_VALUE) { + BackpressureUtils.produced(requested, emittedAmount); } if (!innerDone) { - innerSubscriber.requestMore(-emittedAmount); + innerSubscriber.requestMore(emittedAmount); } } - + if (innerDone) { continue; } } - + missed = wip.addAndGet(-missed); if (missed == 0) { return; @@ -273,15 +272,14 @@ void drain() { } } } - + static final class EagerInnerSubscriber extends Subscriber { final EagerOuterSubscriber parent; final Queue queue; - final NotificationLite nl; - + volatile boolean done; Throwable error; - + public EagerInnerSubscriber(EagerOuterSubscriber parent, int bufferSize) { super(); this.parent = parent; @@ -292,29 +290,28 @@ public EagerInnerSubscriber(EagerOuterSubscriber parent, int bufferSize) { q = new SpscAtomicArrayQueue(bufferSize); } this.queue = q; - this.nl = NotificationLite.instance(); request(bufferSize); } - + @Override public void onNext(T t) { - queue.offer(nl.next(t)); + queue.offer(NotificationLite.next(t)); parent.drain(); } - + @Override public void onError(Throwable e) { error = e; done = true; parent.drain(); } - + @Override public void onCompleted() { done = true; parent.drain(); } - + void requestMore(long n) { request(n); } diff --git a/src/main/java/rx/internal/operators/OperatorElementAt.java b/src/main/java/rx/internal/operators/OperatorElementAt.java index 6f79d6a285..b8e8be3821 100644 --- a/src/main/java/rx/internal/operators/OperatorElementAt.java +++ b/src/main/java/rx/internal/operators/OperatorElementAt.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -17,9 +17,8 @@ import java.util.concurrent.atomic.AtomicBoolean; +import rx.*; import rx.Observable.Operator; -import rx.Producer; -import rx.Subscriber; /** * Returns the element at a specified index in a sequence. @@ -52,7 +51,7 @@ private OperatorElementAt(int index, T defaultValue, boolean hasDefault) { public Subscriber call(final Subscriber child) { Subscriber parent = new Subscriber() { - private int currentIndex = 0; + private int currentIndex; @Override public void onNext(T value) { @@ -80,14 +79,14 @@ public void onCompleted() { } } } - + @Override public void setProducer(Producer p) { child.setProducer(new InnerProducer(p)); } }; child.add(parent); - + return parent; } /** @@ -97,9 +96,9 @@ public void setProducer(Producer p) { static class InnerProducer extends AtomicBoolean implements Producer { /** */ private static final long serialVersionUID = 1L; - + final Producer actual; - + public InnerProducer(Producer actual) { this.actual = actual; } diff --git a/src/main/java/rx/internal/operators/OperatorGroupBy.java b/src/main/java/rx/internal/operators/OperatorGroupBy.java index 4ed1a504f3..5d6ec2f556 100644 --- a/src/main/java/rx/internal/operators/OperatorGroupBy.java +++ b/src/main/java/rx/internal/operators/OperatorGroupBy.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -21,10 +21,12 @@ import rx.*; import rx.Observable.*; +import rx.exceptions.Exceptions; import rx.functions.*; import rx.internal.producers.ProducerArbiter; import rx.internal.util.*; import rx.observables.GroupedObservable; +import rx.observers.Subscribers; import rx.plugins.RxJavaHooks; import rx.subscriptions.Subscriptions; @@ -40,48 +42,66 @@ * the source and group value type * @param * the value type of the groups + * @deprecated + * since 1.3.7, use {@link OperatorGroupByEvicting} instead */ -public final class OperatorGroupBy implements Operator, T>{ +@Deprecated +public final class OperatorGroupBy implements Operator, T> { final Func1 keySelector; final Func1 valueSelector; final int bufferSize; final boolean delayError; - + final Func1, Map> mapFactory; //nullable + @SuppressWarnings({ "unchecked", "rawtypes" }) public OperatorGroupBy(Func1 keySelector) { - this(keySelector, (Func1)UtilityFunctions.identity(), RxRingBuffer.SIZE, false); + this(keySelector, (Func1)UtilityFunctions.identity(), RxRingBuffer.SIZE, false, null); } public OperatorGroupBy(Func1 keySelector, Func1 valueSelector) { - this(keySelector, valueSelector, RxRingBuffer.SIZE, false); + this(keySelector, valueSelector, RxRingBuffer.SIZE, false, null); } - public OperatorGroupBy(Func1 keySelector, Func1 valueSelector, int bufferSize, boolean delayError) { + public OperatorGroupBy(Func1 keySelector, Func1 valueSelector, Func1, Map> mapFactory) { + this(keySelector, valueSelector, RxRingBuffer.SIZE, false, mapFactory); + } + + public OperatorGroupBy(Func1 keySelector, Func1 valueSelector, int bufferSize, boolean delayError, Func1, Map> mapFactory) { this.keySelector = keySelector; this.valueSelector = valueSelector; this.bufferSize = bufferSize; this.delayError = delayError; + this.mapFactory = mapFactory; } - + @Override - public Subscriber call(Subscriber> t) { - final GroupBySubscriber parent = new GroupBySubscriber(t, keySelector, valueSelector, bufferSize, delayError); + public Subscriber call(Subscriber> child) { + final GroupBySubscriber parent; // NOPMD + try { + parent = new GroupBySubscriber(child, keySelector, valueSelector, bufferSize, delayError, mapFactory); + } catch (Throwable ex) { + //Can reach here because mapFactory.call() may throw in constructor of GroupBySubscriber + Exceptions.throwOrReport(ex, child); + Subscriber parent2 = Subscribers.empty(); + parent2.unsubscribe(); + return parent2; + } - t.add(Subscriptions.create(new Action0() { + child.add(Subscriptions.create(new Action0() { @Override public void call() { parent.cancel(); } })); - t.setProducer(parent.producer); - + child.setProducer(parent.producer); + return parent; } public static final class GroupByProducer implements Producer { final GroupBySubscriber parent; - + public GroupByProducer(GroupBySubscriber parent) { this.parent = parent; } @@ -90,8 +110,8 @@ public void request(long n) { parent.requestMore(n); } } - - public static final class GroupBySubscriber + + public static final class GroupBySubscriber extends Subscriber { final Subscriber> actual; final Func1 keySelector; @@ -99,31 +119,37 @@ public static final class GroupBySubscriber final int bufferSize; final boolean delayError; final Map> groups; + + // double store the groups to workaround the bug in the + // signature of groupBy with evicting map factory + final Map> groupsCopy; final Queue> queue; final GroupByProducer producer; - + final Queue evictedKeys; + static final Object NULL_KEY = new Object(); - + final ProducerArbiter s; - + final AtomicBoolean cancelled; final AtomicLong requested; final AtomicInteger groupCount; - + Throwable error; volatile boolean done; final AtomicInteger wip; - public GroupBySubscriber(Subscriber> actual, Func1 keySelector, Func1 valueSelector, int bufferSize, boolean delayError) { + public GroupBySubscriber(Subscriber> actual, Func1 keySelector, + Func1 valueSelector, int bufferSize, boolean delayError, + Func1, Map> mapFactory) { this.actual = actual; this.keySelector = keySelector; this.valueSelector = valueSelector; this.bufferSize = bufferSize; this.delayError = delayError; - this.groups = new ConcurrentHashMap>(); this.queue = new ConcurrentLinkedQueue>(); this.s = new ProducerArbiter(); this.s.request(bufferSize); @@ -132,13 +158,40 @@ public GroupBySubscriber(Subscriber> actual, Fun this.requested = new AtomicLong(); this.groupCount = new AtomicInteger(1); this.wip = new AtomicInteger(); + if (mapFactory == null) { + this.groups = new ConcurrentHashMap>(); + this.evictedKeys = null; + } else { + this.evictedKeys = new ConcurrentLinkedQueue(); + this.groups = createMap(mapFactory, new EvictionAction(evictedKeys)); + } + this.groupsCopy = new ConcurrentHashMap>(); } - + + static class EvictionAction implements Action1 { + + final Queue evictedKeys; + + EvictionAction(Queue evictedKeys) { + this.evictedKeys = evictedKeys; + } + + @Override + public void call(K key) { + evictedKeys.offer(key); + } + } + + @SuppressWarnings("unchecked") + private Map> createMap(Func1, Map> mapFactory, Action1 evictionAction) { + return (Map>)(Map) mapFactory.call(evictionAction); + } + @Override public void setProducer(Producer s) { this.s.setProducer(s); } - + @Override public void onNext(T t) { if (done) { @@ -156,8 +209,8 @@ public void onNext(T t) { errorAll(a, q, ex); return; } - - boolean notNew = true; + + boolean newGroup = false; Object mapKey = key != null ? key : NULL_KEY; GroupedUnicast group = groups.get(mapKey); if (group == null) { @@ -166,17 +219,18 @@ public void onNext(T t) { if (!cancelled.get()) { group = GroupedUnicast.createWith(key, bufferSize, this, delayError); groups.put(mapKey, group); - + if (evictedKeys != null) { + groupsCopy.put(mapKey, group); + } + groupCount.getAndIncrement(); - - notNew = false; - q.offer(group); - drain(); + + newGroup = true; } else { return; } } - + V v; try { v = valueSelector.call(t); @@ -188,11 +242,24 @@ public void onNext(T t) { group.onNext(v); - if (notNew) { - s.request(1); + if (evictedKeys != null) { + K evictedKey; + while ((evictedKey = evictedKeys.poll()) != null) { + GroupedUnicast g = groupsCopy.remove(evictedKey); + // do a null check on g because cancel(K) could have cleared + // the map + if (g != null) { + g.onComplete(); + } + } + } + + if (newGroup) { + q.offer(group); + drain(); } } - + @Override public void onError(Throwable t) { if (done) { @@ -204,7 +271,7 @@ public void onError(Throwable t) { groupCount.decrementAndGet(); drain(); } - + @Override public void onCompleted() { if (done) { @@ -215,6 +282,10 @@ public void onCompleted() { e.onComplete(); } groups.clear(); + if (evictedKeys != null) { + groupsCopy.clear(); + evictedKeys.clear(); + } done = true; groupCount.decrementAndGet(); @@ -225,11 +296,11 @@ public void requestMore(long n) { if (n < 0) { throw new IllegalArgumentException("n >= 0 required but it was " + n); } - + BackpressureUtils.getAndAddRequest(requested, n); drain(); } - + public void cancel() { // cancelling the main source means we don't want any more groups // but running groups still require new values @@ -239,7 +310,7 @@ public void cancel() { } } } - + public void cancel(K key) { Object mapKey = key != null ? key : NULL_KEY; if (groups.remove(mapKey) != null) { @@ -247,76 +318,81 @@ public void cancel(K key) { unsubscribe(); } } + if (evictedKeys != null) { + groupsCopy.remove(mapKey); + } } - + void drain() { if (wip.getAndIncrement() != 0) { return; } - + int missed = 1; - + final Queue> q = this.queue; final Subscriber> a = this.actual; - + for (;;) { - + if (checkTerminated(done, q.isEmpty(), a, q)) { return; } - + long r = requested.get(); - boolean unbounded = r == Long.MAX_VALUE; long e = 0L; - - while (r != 0) { + + while (e != r) { boolean d = done; - + GroupedObservable t = q.poll(); - + boolean empty = t == null; - + if (checkTerminated(d, empty, a, q)) { return; } - + if (empty) { break; } a.onNext(t); - - r--; - e--; + + e++; } - + if (e != 0L) { - if (!unbounded) { - requested.addAndGet(e); + if (r != Long.MAX_VALUE) { + BackpressureUtils.produced(requested, e); } - s.request(-e); + s.request(e); } - + missed = wip.addAndGet(-missed); if (missed == 0) { break; } } } - + void errorAll(Subscriber> a, Queue q, Throwable ex) { q.clear(); List> list = new ArrayList>(groups.values()); groups.clear(); - + if (evictedKeys != null) { + groupsCopy.clear(); + evictedKeys.clear(); + } + for (GroupedUnicast e : list) { e.onError(ex); } - + a.onError(ex); } - - boolean checkTerminated(boolean d, boolean empty, + + boolean checkTerminated(boolean d, boolean empty, Subscriber> a, Queue q) { if (d) { Throwable err = error; @@ -332,34 +408,34 @@ boolean checkTerminated(boolean d, boolean empty, return false; } } - + static final class GroupedUnicast extends GroupedObservable { - + final State state; + + public static GroupedUnicast createWith(K key, int bufferSize, GroupBySubscriber parent, boolean delayError) { State state = new State(bufferSize, parent, key, delayError); return new GroupedUnicast(key, state); } - - final State state; - + protected GroupedUnicast(K key, State state) { super(key, state); this.state = state; } - + public void onNext(T t) { state.onNext(t); } - + public void onError(Throwable e) { state.onError(e); } - + public void onComplete() { state.onComplete(); } } - + static final class State extends AtomicInteger implements Producer, Subscription, OnSubscribe { /** */ private static final long serialVersionUID = -3852313036005250360L; @@ -368,20 +444,20 @@ static final class State extends AtomicInteger implements Producer, Subscr final Queue queue; final GroupBySubscriber parent; final boolean delayError; - + final AtomicLong requested; - + volatile boolean done; Throwable error; - + final AtomicBoolean cancelled; final AtomicReference> actual; final AtomicBoolean once; - - public State(int bufferSize, GroupBySubscriber parent, K key, boolean delayError) { + + public State(int bufferSize, GroupBySubscriber parent, K key, boolean delayError) { // NOPMD this.queue = new ConcurrentLinkedQueue(); this.parent = parent; this.key = key; @@ -391,7 +467,7 @@ public State(int bufferSize, GroupBySubscriber parent, K key, boolean d this.once = new AtomicBoolean(); this.requested = new AtomicLong(); } - + @Override public void request(long n) { if (n < 0) { @@ -402,12 +478,12 @@ public void request(long n) { drain(); } } - + @Override public boolean isUnsubscribed() { return cancelled.get(); } - + @Override public void unsubscribe() { if (cancelled.compareAndSet(false, true)) { @@ -416,7 +492,7 @@ public void unsubscribe() { } } } - + @Override public void call(Subscriber s) { if (once.compareAndSet(false, true)) { @@ -434,17 +510,17 @@ public void onNext(T t) { error = new NullPointerException(); done = true; } else { - queue.offer(NotificationLite.instance().next(t)); + queue.offer(NotificationLite.next(t)); } drain(); } - + public void onError(Throwable e) { error = e; done = true; drain(); } - + public void onComplete() { done = true; drain(); @@ -455,48 +531,45 @@ void drain() { return; } int missed = 1; - + final Queue q = queue; final boolean delayError = this.delayError; Subscriber a = actual.get(); - NotificationLite nl = NotificationLite.instance(); for (;;) { if (a != null) { if (checkTerminated(done, q.isEmpty(), a, delayError)) { return; } - + long r = requested.get(); - boolean unbounded = r == Long.MAX_VALUE; long e = 0; - - while (r != 0L) { + + while (e != r) { boolean d = done; Object v = q.poll(); boolean empty = v == null; - + if (checkTerminated(d, empty, a, delayError)) { return; } - + if (empty) { break; } - - a.onNext(nl.getValue(v)); - - r--; - e--; + + a.onNext(NotificationLite.getValue(v)); + + e++; } - + if (e != 0L) { - if (!unbounded) { - requested.addAndGet(e); + if (r != Long.MAX_VALUE) { + BackpressureUtils.produced(requested, e); } - parent.s.request(-e); + parent.s.request(e); } } - + missed = addAndGet(-missed); if (missed == 0) { break; @@ -506,14 +579,14 @@ void drain() { } } } - + boolean checkTerminated(boolean d, boolean empty, Subscriber a, boolean delayError) { if (cancelled.get()) { queue.clear(); parent.cancel(key); return true; } - + if (d) { if (delayError) { if (empty) { @@ -538,7 +611,7 @@ boolean checkTerminated(boolean d, boolean empty, Subscriber a, boole } } } - + return false; } } diff --git a/src/main/java/rx/internal/operators/OperatorGroupByEvicting.java b/src/main/java/rx/internal/operators/OperatorGroupByEvicting.java new file mode 100644 index 0000000000..d02ecf5d39 --- /dev/null +++ b/src/main/java/rx/internal/operators/OperatorGroupByEvicting.java @@ -0,0 +1,605 @@ +/** + * Copyright 2018 Netflix, Inc. + * + * 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 rx.internal.operators; + +import java.util.*; +import java.util.concurrent.*; +import java.util.concurrent.atomic.*; + +import rx.*; +import rx.Observable.*; +import rx.exceptions.Exceptions; +import rx.functions.*; +import rx.internal.producers.ProducerArbiter; +import rx.internal.util.*; +import rx.observables.GroupedObservable; +import rx.plugins.RxJavaHooks; +import rx.observers.Subscribers; +import rx.subscriptions.Subscriptions; + +/** + * Groups the items emitted by an Observable according to a specified criterion, and emits these + * grouped items as Observables, one Observable per group. + *

    + * + * + * @param + * the key type + * @param + * the source and group value type + * @param + * the value type of the groups + */ +public final class OperatorGroupByEvicting implements Operator, T>{ + + final Func1 keySelector; + final Func1 valueSelector; + final int bufferSize; + final boolean delayError; + final Func1, Map> mapFactory; //nullable + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public OperatorGroupByEvicting(Func1 keySelector) { + this(keySelector, (Func1)UtilityFunctions.identity(), RxRingBuffer.SIZE, false, null); + } + + public OperatorGroupByEvicting(Func1 keySelector, Func1 valueSelector) { + this(keySelector, valueSelector, RxRingBuffer.SIZE, false, null); + } + + public OperatorGroupByEvicting(Func1 keySelector, Func1 valueSelector, int bufferSize, boolean delayError, Func1, Map> mapFactory) { + this.keySelector = keySelector; + this.valueSelector = valueSelector; + this.bufferSize = bufferSize; + this.delayError = delayError; + this.mapFactory = mapFactory; + } + + @SuppressWarnings("unchecked") + @Override + public Subscriber call(Subscriber> child) { + Map> groups; + Queue> evictedGroups; + + if (mapFactory == null) { + evictedGroups = null; + groups = new ConcurrentHashMap>(); + } else { + evictedGroups = new ConcurrentLinkedQueue>(); + Action1 evictionAction = (Action1)(Action1) + new EvictionAction(evictedGroups); + try { + groups = (Map>)(Map) + mapFactory.call((Action1)(Action1) evictionAction); + } catch (Throwable ex) { + //Can reach here because mapFactory.call() may throw + Exceptions.throwOrReport(ex, child); + Subscriber parent2 = Subscribers.empty(); + parent2.unsubscribe(); + return parent2; + } + } + final GroupBySubscriber parent = new GroupBySubscriber( + child, keySelector, valueSelector, bufferSize, delayError, groups, evictedGroups); + + child.add(Subscriptions.create(new Action0() { + @Override + public void call() { + parent.cancel(); + } + })); + + child.setProducer(parent.producer); + + return parent; + } + + public static final class GroupByProducer implements Producer { + final GroupBySubscriber parent; + + public GroupByProducer(GroupBySubscriber parent) { + this.parent = parent; + } + @Override + public void request(long n) { + parent.requestMore(n); + } + } + + public static final class GroupBySubscriber + extends Subscriber { + final Subscriber> actual; + final Func1 keySelector; + final Func1 valueSelector; + final int bufferSize; + final boolean delayError; + final Map> groups; + final Queue> queue; + final GroupByProducer producer; + final Queue> evictedGroups; + + static final Object NULL_KEY = new Object(); + + final ProducerArbiter s; + + final AtomicBoolean cancelled; + + final AtomicLong requested; + + final AtomicInteger groupCount; + + Throwable error; + volatile boolean done; + + final AtomicInteger wip; + + public GroupBySubscriber(Subscriber> actual, Func1 keySelector, + Func1 valueSelector, int bufferSize, boolean delayError, Map> groups, + Queue> evictedGroups) { + this.actual = actual; + this.keySelector = keySelector; + this.valueSelector = valueSelector; + this.bufferSize = bufferSize; + this.delayError = delayError; + this.queue = new ConcurrentLinkedQueue>(); + this.s = new ProducerArbiter(); + this.s.request(bufferSize); + this.producer = new GroupByProducer(this); + this.cancelled = new AtomicBoolean(); + this.requested = new AtomicLong(); + this.groupCount = new AtomicInteger(1); + this.wip = new AtomicInteger(); + this.groups = groups; + this.evictedGroups = evictedGroups; + } + + @Override + public void setProducer(Producer s) { + this.s.setProducer(s); + } + + @Override + public void onNext(T t) { + if (done) { + return; + } + + final Queue> q = this.queue; + final Subscriber> a = this.actual; + + K key; + try { + key = keySelector.call(t); + } catch (Throwable ex) { + unsubscribe(); + errorAll(a, q, ex); + return; + } + + boolean newGroup = false; + @SuppressWarnings("unchecked") + K mapKey = key != null ? key : (K) NULL_KEY; + GroupedUnicast group = groups.get(mapKey); + if (group == null) { + // if the main has been cancelled, stop creating groups + // and skip this value + if (!cancelled.get()) { + group = GroupedUnicast.createWith(key, bufferSize, this, delayError); + groups.put(mapKey, group); + + groupCount.getAndIncrement(); + + newGroup = false; + q.offer(group); + drain(); + } else { + return; + } + } + + V v; + try { + v = valueSelector.call(t); + } catch (Throwable ex) { + unsubscribe(); + errorAll(a, q, ex); + return; + } + + group.onNext(v); + + if (evictedGroups != null) { + GroupedUnicast evictedGroup; + while ((evictedGroup = evictedGroups.poll()) != null) { + evictedGroup.onComplete(); + } + } + + if (newGroup) { + q.offer(group); + drain(); + } + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaHooks.onError(t); + return; + } + error = t; + done = true; + groupCount.decrementAndGet(); + drain(); + } + + @Override + public void onCompleted() { + if (done) { + return; + } + + for (GroupedUnicast e : groups.values()) { + e.onComplete(); + } + groups.clear(); + if (evictedGroups != null) { + evictedGroups.clear(); + } + + done = true; + groupCount.decrementAndGet(); + drain(); + } + + public void requestMore(long n) { + if (n < 0) { + throw new IllegalArgumentException("n >= 0 required but it was " + n); + } + + BackpressureUtils.getAndAddRequest(requested, n); + drain(); + } + + 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)) { + if (groupCount.decrementAndGet() == 0) { + unsubscribe(); + } + } + } + + public void cancel(K key) { + Object mapKey = key != null ? key : NULL_KEY; + if (groups.remove(mapKey) != null) { + if (groupCount.decrementAndGet() == 0) { + unsubscribe(); + } + } + } + + void drain() { + if (wip.getAndIncrement() != 0) { + return; + } + + int missed = 1; + + final Queue> q = this.queue; + final Subscriber> a = this.actual; + + for (;;) { + + if (checkTerminated(done, q.isEmpty(), a, q)) { + return; + } + + long r = requested.get(); + boolean unbounded = r == Long.MAX_VALUE; + long e = 0L; + + while (r != 0) { + boolean d = done; + + GroupedObservable t = q.poll(); + + boolean empty = t == null; + + if (checkTerminated(d, empty, a, q)) { + return; + } + + if (empty) { + break; + } + + a.onNext(t); + + r--; + e--; + } + + if (e != 0L) { + if (!unbounded) { + requested.addAndGet(e); + } + s.request(-e); + } + + missed = wip.addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + + void errorAll(Subscriber> a, Queue q, Throwable ex) { + q.clear(); + List> list = new ArrayList>(groups.values()); + groups.clear(); + if (evictedGroups != null) { + evictedGroups.clear(); + } + + for (GroupedUnicast e : list) { + e.onError(ex); + } + + a.onError(ex); + } + + boolean checkTerminated(boolean d, boolean empty, + Subscriber> a, Queue q) { + if (d) { + Throwable err = error; + if (err != null) { + errorAll(a, q, err); + return true; + } else + if (empty) { + actual.onCompleted(); + return true; + } + } + return false; + } + } + + static class EvictionAction implements Action1> { + + final Queue> evictedGroups; + + EvictionAction(Queue> evictedGroups) { + this.evictedGroups = evictedGroups; + } + + @Override + public void call(GroupedUnicast group) { + evictedGroups.offer(group); + } + } + + static final class GroupedUnicast extends GroupedObservable { + + public static GroupedUnicast createWith(K key, int bufferSize, GroupBySubscriber parent, boolean delayError) { + State state = new State(bufferSize, parent, key, delayError); + return new GroupedUnicast(key, state); + } + + final State state; + + protected GroupedUnicast(K key, State state) { + super(key, state); + this.state = state; + } + + public void onNext(T t) { + state.onNext(t); + } + + public void onError(Throwable e) { + state.onError(e); + } + + public void onComplete() { + state.onComplete(); + } + } + + static final class State extends AtomicInteger implements Producer, Subscription, OnSubscribe { + /** */ + private static final long serialVersionUID = -3852313036005250360L; + + final K key; + final Queue queue; + final GroupBySubscriber parent; + final boolean delayError; + + final AtomicLong requested; + + volatile boolean done; + Throwable error; + + final AtomicBoolean cancelled; + + final AtomicReference> actual; + + final AtomicBoolean once; + + + public State(int bufferSize, GroupBySubscriber parent, K key, boolean delayError) { + this.queue = new ConcurrentLinkedQueue(); + this.parent = parent; + this.key = key; + this.delayError = delayError; + this.cancelled = new AtomicBoolean(); + this.actual = new AtomicReference>(); + this.once = new AtomicBoolean(); + this.requested = new AtomicLong(); + } + + @Override + public void request(long n) { + if (n < 0) { + throw new IllegalArgumentException("n >= required but it was " + n); + } + if (n != 0L) { + BackpressureUtils.getAndAddRequest(requested, n); + drain(); + } + } + + @Override + public boolean isUnsubscribed() { + return cancelled.get(); + } + + @Override + public void unsubscribe() { + if (cancelled.compareAndSet(false, true)) { + if (getAndIncrement() == 0) { + parent.cancel(key); + } + } + } + + @Override + public void call(Subscriber s) { + if (once.compareAndSet(false, true)) { + s.add(this); + s.setProducer(this); + actual.lazySet(s); + drain(); + } else { + s.onError(new IllegalStateException("Only one Subscriber allowed!")); + } + } + + public void onNext(T t) { + if (t == null) { + error = new NullPointerException(); + done = true; + } else { + queue.offer(NotificationLite.next(t)); + } + drain(); + } + + public void onError(Throwable e) { + error = e; + done = true; + drain(); + } + + public void onComplete() { + done = true; + drain(); + } + + void drain() { + if (getAndIncrement() != 0) { + return; + } + int missed = 1; + + final Queue q = queue; + final boolean delayError = this.delayError; + Subscriber a = actual.get(); + for (;;) { + if (a != null) { + if (checkTerminated(done, q.isEmpty(), a, delayError)) { + return; + } + + long r = requested.get(); + boolean unbounded = r == Long.MAX_VALUE; + long e = 0; + + while (r != 0L) { + boolean d = done; + Object v = q.poll(); + boolean empty = v == null; + + if (checkTerminated(d, empty, a, delayError)) { + return; + } + + if (empty) { + break; + } + + a.onNext(NotificationLite.getValue(v)); + + r--; + e--; + } + + if (e != 0L) { + if (!unbounded) { + requested.addAndGet(e); + } + parent.s.request(-e); + } + } + + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + if (a == null) { + a = actual.get(); + } + } + } + + boolean checkTerminated(boolean d, boolean empty, Subscriber a, boolean delayError) { + if (cancelled.get()) { + queue.clear(); + parent.cancel(key); + return true; + } + + if (d) { + if (delayError) { + if (empty) { + Throwable e = error; + if (e != null) { + a.onError(e); + } else { + a.onCompleted(); + } + return true; + } + } else { + Throwable e = error; + if (e != null) { + queue.clear(); + a.onError(e); + return true; + } else + if (empty) { + a.onCompleted(); + return true; + } + } + } + + return false; + } + } +} diff --git a/src/main/java/rx/internal/operators/OperatorIgnoreElements.java b/src/main/java/rx/internal/operators/OperatorIgnoreElements.java index 00098f85a2..c5f441f393 100644 --- a/src/main/java/rx/internal/operators/OperatorIgnoreElements.java +++ b/src/main/java/rx/internal/operators/OperatorIgnoreElements.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -20,17 +20,17 @@ public class OperatorIgnoreElements implements Operator { - private static class Holder { + static final class Holder { static final OperatorIgnoreElements INSTANCE = new OperatorIgnoreElements(); } - + @SuppressWarnings("unchecked") public static OperatorIgnoreElements instance() { return (OperatorIgnoreElements) Holder.INSTANCE; } OperatorIgnoreElements() { - + // singleton } @Override diff --git a/src/main/java/rx/internal/operators/OperatorMapNotification.java b/src/main/java/rx/internal/operators/OperatorMapNotification.java index 77f85d04ad..3316bb4911 100644 --- a/src/main/java/rx/internal/operators/OperatorMapNotification.java +++ b/src/main/java/rx/internal/operators/OperatorMapNotification.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -54,17 +54,17 @@ public void request(long n) { }); return parent; } - + static final class MapNotificationSubscriber extends Subscriber { - + final Subscriber actual; - + final Func1 onNext; - + final Func1 onError; - + final Func0 onCompleted; - + final AtomicLong requested; final AtomicLong missedRequested; @@ -72,12 +72,12 @@ static final class MapNotificationSubscriber extends Subscriber { final AtomicReference producer; long produced; - + R value; - + static final long COMPLETED_FLAG = Long.MIN_VALUE; static final long REQUESTED_MASK = Long.MAX_VALUE; - + public MapNotificationSubscriber(Subscriber actual, Func1 onNext, Func1 onError, Func0 onCompleted) { this.actual = actual; @@ -98,7 +98,7 @@ public void onNext(T t) { Exceptions.throwOrReport(ex, actual, t); } } - + @Override public void onError(Throwable e) { accountProduced(); @@ -109,7 +109,7 @@ public void onError(Throwable e) { } tryEmit(); } - + @Override public void onCompleted() { accountProduced(); @@ -120,14 +120,14 @@ public void onCompleted() { } tryEmit(); } - + void accountProduced() { long p = produced; if (p != 0L && producer.get() != null) { BackpressureUtils.produced(requested, p); } } - + @Override public void setProducer(Producer p) { if (producer.compareAndSet(null, p)) { @@ -139,7 +139,7 @@ public void setProducer(Producer p) { throw new IllegalStateException("Producer already set!"); } } - + void tryEmit() { for (;;) { long r = requested.get(); @@ -159,7 +159,7 @@ void tryEmit() { } } } - + void requestInner(long n) { if (n < 0L) { throw new IllegalArgumentException("n >= 0 required but it was " + n); @@ -169,7 +169,7 @@ void requestInner(long n) { } for (;;) { long r = requested.get(); - + if ((r & COMPLETED_FLAG) != 0L) { long v = r & REQUESTED_MASK; long u = BackpressureUtils.addCap(v, n) | COMPLETED_FLAG; @@ -191,7 +191,7 @@ void requestInner(long n) { } } } - + AtomicReference localProducer = producer; Producer actualProducer = localProducer.get(); if (actualProducer != null) { diff --git a/src/main/java/rx/internal/operators/OperatorMapPair.java b/src/main/java/rx/internal/operators/OperatorMapPair.java index 5db9596b6b..94389d6799 100644 --- a/src/main/java/rx/internal/operators/OperatorMapPair.java +++ b/src/main/java/rx/internal/operators/OperatorMapPair.java @@ -19,7 +19,7 @@ import rx.Observable.Operator; import rx.exceptions.*; import rx.functions.*; -import rx.internal.util.RxJavaPluginUtils; +import rx.plugins.RxJavaHooks; /** * An {@link Operator} that pairs up items emitted by a source {@link Observable} with the sequence of items @@ -34,10 +34,12 @@ * the type of items to be emitted by this {@code Operator} */ public final class OperatorMapPair implements Operator, T> { + final Func1> collectionSelector; + final Func2 resultSelector; /** * Creates the function that generates a {@code Observable} based on an item emitted by another {@code Observable}. - * + * * @param the input value type * @param the value type of the generated Observable * @param selector @@ -54,9 +56,6 @@ public Observable call(T t1) { }; } - final Func1> collectionSelector; - final Func2 resultSelector; - public OperatorMapPair(final Func1> collectionSelector, final Func2 resultSelector) { this.collectionSelector = collectionSelector; this.resultSelector = resultSelector; @@ -68,29 +67,29 @@ public Subscriber call(final Subscriber extends Subscriber { - + final Subscriber> actual; - + final Func1> collectionSelector; final Func2 resultSelector; boolean done; - - public MapPairSubscriber(Subscriber> actual, + + public MapPairSubscriber(Subscriber> actual, Func1> collectionSelector, Func2 resultSelector) { this.actual = actual; this.collectionSelector = collectionSelector; this.resultSelector = resultSelector; } - + @Override public void onNext(T outer) { - + Observable intermediate; - + try { intermediate = collectionSelector.call(outer); } catch (Throwable ex) { @@ -99,22 +98,22 @@ public void onNext(T outer) { onError(OnErrorThrowable.addValueAsLastCause(ex, outer)); return; } - + actual.onNext(intermediate.map(new OuterInnerMapper(outer, resultSelector))); } - + @Override public void onError(Throwable e) { if (done) { - RxJavaPluginUtils.handleException(e); + RxJavaHooks.onError(e); return; } done = true; - + actual.onError(e); } - - + + @Override public void onCompleted() { if (done) { @@ -122,7 +121,7 @@ public void onCompleted() { } actual.onCompleted(); } - + @Override public void setProducer(Producer p) { actual.setProducer(p); @@ -137,11 +136,11 @@ public OuterInnerMapper(T outer, Func2 result this.outer = outer; this.resultSelector = resultSelector; } - + @Override public R call(U inner) { return resultSelector.call(outer, inner); } - + } } \ No newline at end of file diff --git a/src/main/java/rx/internal/operators/OperatorMaterialize.java b/src/main/java/rx/internal/operators/OperatorMaterialize.java index 4d01dadf39..ce9e1be604 100644 --- a/src/main/java/rx/internal/operators/OperatorMaterialize.java +++ b/src/main/java/rx/internal/operators/OperatorMaterialize.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -33,7 +33,7 @@ public final class OperatorMaterialize implements Operator, T> { /** Lazy initialization via inner-class holder. */ - private static final class Holder { + static final class Holder { /** A singleton instance. */ static final OperatorMaterialize INSTANCE = new OperatorMaterialize(); } @@ -48,6 +48,7 @@ public static OperatorMaterialize instance() { } OperatorMaterialize() { + // singleton instances } @Override @@ -65,16 +66,16 @@ public void request(long n) { return parent; } - private static class ParentSubscriber extends Subscriber { + static class ParentSubscriber extends Subscriber { private final Subscriber> child; private volatile Notification terminalNotification; - + // guarded by this - private boolean busy = false; + private boolean busy; // guarded by this - private boolean missed = false; + private boolean missed; private final AtomicLong requested = new AtomicLong(); @@ -132,7 +133,8 @@ private void drain() { // set flag to force extra loop if drain loop running missed = true; return; - } + } + busy = true; } // drain loop final AtomicLong localRequested = this.requested; diff --git a/src/main/java/rx/internal/operators/OperatorMerge.java b/src/main/java/rx/internal/operators/OperatorMerge.java index 3391d18cd8..a52eee07e9 100644 --- a/src/main/java/rx/internal/operators/OperatorMerge.java +++ b/src/main/java/rx/internal/operators/OperatorMerge.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -53,13 +53,16 @@ * the type of the items emitted by both the source and merged {@code Observable}s */ public final class OperatorMerge implements Operator> { + final boolean delayErrors; + final int maxConcurrent; + /** Lazy initialization via inner-class holder. */ - private static final class HolderNoDelay { + static final class HolderNoDelay { /** A singleton instance. */ static final OperatorMerge INSTANCE = new OperatorMerge(false, Integer.MAX_VALUE); } /** Lazy initialization via inner-class holder. */ - private static final class HolderDelayErrors { + static final class HolderDelayErrors { /** A singleton instance. */ static final OperatorMerge INSTANCE = new OperatorMerge(true, Integer.MAX_VALUE); } @@ -78,7 +81,8 @@ public static OperatorMerge instance(boolean delayErrors) { /** * Creates a new instance of the operator with the given delayError and maxConcurrency settings. * @param the value type - * @param delayErrors + * @param delayErrors if true, errors are delayed till all sources terminate, if false the first error will + * be emitted and all sequences unsubscribed * @param maxConcurrent the maximum number of concurrent subscriptions or Integer.MAX_VALUE for unlimited * @return the Operator instance with the given settings */ @@ -92,9 +96,6 @@ public static OperatorMerge instance(boolean delayErrors, int maxConcurre return new OperatorMerge(delayErrors, maxConcurrent); } - final boolean delayErrors; - final int maxConcurrent; - OperatorMerge(boolean delayErrors, int maxConcurrent) { this.delayErrors = delayErrors; this.maxConcurrent = maxConcurrent; @@ -105,10 +106,10 @@ public Subscriber> call(final Subscriber chil MergeSubscriber subscriber = new MergeSubscriber(child, delayErrors, maxConcurrent); MergeProducer producer = new MergeProducer(subscriber); subscriber.producer = producer; - + child.add(subscriber); child.setProducer(producer); - + return subscriber; } @@ -117,11 +118,11 @@ static final class MergeProducer extends AtomicLong implements Producer { private static final long serialVersionUID = -1214379189873595503L; final MergeSubscriber subscriber; - + public MergeProducer(MergeSubscriber subscriber) { this.subscriber = subscriber; } - + @Override public void request(long n) { if (n > 0) { @@ -130,7 +131,7 @@ public void request(long n) { } BackpressureUtils.getAndAddRequest(this, n); subscriber.emit(); - } else + } else if (n < 0) { throw new IllegalArgumentException("n >= 0 required"); } @@ -139,58 +140,55 @@ public long produced(int n) { return addAndGet(-n); } } - + /** - * The subscriber that observes Observables. + * The subscriber that observes Observables. * @param the value type */ static final class MergeSubscriber extends Subscriber> { final Subscriber child; final boolean delayErrors; final int maxConcurrent; - + MergeProducer producer; - + volatile Queue queue; - + /** Tracks the active subscriptions to sources. */ volatile CompositeSubscription subscriptions; /** Due to the emission loop, we need to store errors somewhere if !delayErrors. */ volatile ConcurrentLinkedQueue errors; - - final NotificationLite nl; - + volatile boolean done; - + /** Guarded by this. */ boolean emitting; /** Guarded by this. */ boolean missed; - + final Object innerGuard; /** Copy-on-write array, guarded by innerGuard. */ volatile InnerSubscriber[] innerSubscribers; - + /** Used to generate unique InnerSubscriber IDs. Modified from onNext only. */ long uniqueId; - + /** Which was the last InnerSubscriber that emitted? Accessed if emitting == true. */ long lastId; /** What was its index in the innerSubscribers array? Accessed if emitting == true. */ int lastIndex; - - /** An empty array to avoid creating new empty arrays in removeInner. */ + + /** An empty array to avoid creating new empty arrays in removeInner. */ static final InnerSubscriber[] EMPTY = new InnerSubscriber[0]; final int scalarEmissionLimit; - + int scalarEmissionCount; - + public MergeSubscriber(Subscriber child, boolean delayErrors, int maxConcurrent) { this.child = child; this.delayErrors = delayErrors; this.maxConcurrent = maxConcurrent; - this.nl = NotificationLite.instance(); this.innerGuard = new Object(); this.innerSubscribers = EMPTY; if (maxConcurrent == Integer.MAX_VALUE) { @@ -201,7 +199,7 @@ public MergeSubscriber(Subscriber child, boolean delayErrors, int max request(maxConcurrent); } } - + Queue getOrCreateErrorQueue() { ConcurrentLinkedQueue q = errors; if (q == null) { @@ -233,7 +231,7 @@ CompositeSubscription getOrCreateComposite() { } return c; } - + @Override public void onNext(Observable t) { if (t == null) { @@ -251,7 +249,7 @@ public void onNext(Observable t) { emit(); } } - + void emitEmpty() { int produced = scalarEmissionCount + 1; if (produced == scalarEmissionLimit) { @@ -261,7 +259,7 @@ void emitEmpty() { scalarEmissionCount = produced; } } - + private void reportError() { List list = new ArrayList(errors); if (list.size() == 1) { @@ -270,7 +268,7 @@ private void reportError() { child.onError(new CompositeException(list)); } } - + @Override public void onError(Throwable e) { getOrCreateErrorQueue().offer(e); @@ -282,7 +280,7 @@ public void onCompleted() { done = true; emit(); } - + void addInner(InnerSubscriber inner) { getOrCreateComposite().add(inner); synchronized (innerGuard) { @@ -326,7 +324,7 @@ void removeInner(InnerSubscriber inner) { innerSubscribers = b; } } - + /** * Tries to emit the value directly to the child if * no concurrent emission is happening at the moment. @@ -334,9 +332,9 @@ void removeInner(InnerSubscriber inner) { * Since the scalar-value queue optimization applies * to both the main source and the inner subscribers, * we handle things in a shared manner. - * - * @param subscriber - * @param value + * + * @param subscriber the subscriber to one of the inner Observables running + * @param value the value that inner Observable produced */ void tryEmit(InnerSubscriber subscriber, T value) { boolean success = false; @@ -352,9 +350,16 @@ void tryEmit(InnerSubscriber subscriber, T value) { } } if (success) { - emitScalar(subscriber, value, r); + RxRingBuffer subscriberQueue = subscriber.queue; + if (subscriberQueue == null || subscriberQueue.isEmpty()) { + emitScalar(subscriber, value, r); + } else { + queueScalar(subscriber, value); + emitLoop(); + } } else { queueScalar(subscriber, value); + emit(); } } @@ -371,19 +376,16 @@ protected void queueScalar(InnerSubscriber subscriber, T value) { subscriber.queue = q; } try { - q.onNext(nl.next(value)); + q.onNext(NotificationLite.next(value)); } catch (MissingBackpressureException ex) { subscriber.unsubscribe(); subscriber.onError(ex); - return; } catch (IllegalStateException ex) { if (!subscriber.isUnsubscribed()) { subscriber.unsubscribe(); subscriber.onError(ex); } - return; } - emit(); } protected void emitScalar(InnerSubscriber subscriber, T value, long r) { @@ -425,7 +427,7 @@ protected void emitScalar(InnerSubscriber subscriber, T value, long r) { * In the synchronized block below request(1) we check * if there was a concurrent emission attempt and if there was, * we stay in emission mode and enter the emission loop - * which will take care all the queued up state and + * which will take care all the queued up state and * emission possibilities. */ emitLoop(); @@ -434,7 +436,7 @@ protected void emitScalar(InnerSubscriber subscriber, T value, long r) { public void requestMore(long n) { request(n); } - + /** * Tries to emit the value directly to the child if * no concurrent emission is happening at the moment. @@ -442,9 +444,8 @@ public void requestMore(long n) { * Since the scalar-value queue optimization applies * to both the main source and the inner subscribers, * we handle things in a shared manner. - * - * @param subscriber - * @param value + * + * @param value the scalar value the main Observable emitted through {@code just()} */ void tryEmit(T value) { boolean success = false; @@ -460,9 +461,16 @@ void tryEmit(T value) { } } if (success) { - emitScalar(value, r); + Queue mainQueue = queue; + if (mainQueue == null || mainQueue.isEmpty()) { + emitScalar(value, r); + } else { + queueScalar(value); + emitLoop(); + } } else { queueScalar(value); + emit(); } } @@ -490,12 +498,10 @@ protected void queueScalar(T value) { } this.queue = q; } - if (!q.offer(nl.next(value))) { + if (!q.offer(NotificationLite.next(value))) { unsubscribe(); onError(OnErrorThrowable.addValueAsLastCause(new MissingBackpressureException(), value)); - return; } - emit(); } protected void emitScalar(T value, long r) { @@ -516,7 +522,7 @@ protected void emitScalar(T value, long r) { if (r != Long.MAX_VALUE) { producer.produced(1); } - + int produced = scalarEmissionCount + 1; if (produced == scalarEmissionLimit) { scalarEmissionCount = 0; @@ -524,7 +530,7 @@ protected void emitScalar(T value, long r) { } else { scalarEmissionCount = produced; } - + // check if some state changed while emitting synchronized (this) { skipFinal = true; @@ -545,12 +551,12 @@ protected void emitScalar(T value, long r) { * In the synchronized block below request(1) we check * if there was a concurrent emission attempt and if there was, * we stay in emission mode and enter the emission loop - * which will take care all the queued up state and + * which will take care all the queued up state and * emission possibilities. */ emitLoop(); } - + void emit() { synchronized (this) { if (emitting) { @@ -575,10 +581,10 @@ void emitLoop() { return; } Queue svq = queue; - + long r = producer.get(); boolean unbounded = r == Long.MAX_VALUE; - + // count the number of 'completed' sources to replenish them in batches int replenishMain = 0; @@ -597,7 +603,7 @@ void emitLoop() { if (o == null) { break; } - T v = nl.getValue(o); + T v = NotificationLite.getValue(o); // if child throws, report bounce it back immediately try { child.onNext(v); @@ -641,8 +647,8 @@ void emitLoop() { // read the current set of inner subscribers InnerSubscriber[] inner = innerSubscribers; int n = inner.length; - - // check if upstream is done, there are no scalar values + + // check if upstream is done, there are no scalar values // and no active inner subscriptions if (d && (svq == null || svq.isEmpty()) && n == 0) { Queue e = errors; @@ -654,13 +660,13 @@ void emitLoop() { skipFinal = true; return; } - + boolean innerCompleted = false; if (n > 0) { // let's continue the round-robin emission from last location long startId = lastId; int index = lastIndex; - + // in case there were changes in the array or the index // no longer points to the inner with the cached id if (n <= index || inner[index].id != startId) { @@ -685,7 +691,7 @@ void emitLoop() { lastIndex = j; lastId = inner[j].id; } - + int j = index; // loop through all sources once to avoid delaying any new sources too much for (int i = 0; i < n; i++) { @@ -696,7 +702,7 @@ void emitLoop() { } @SuppressWarnings("unchecked") InnerSubscriber is = (InnerSubscriber)inner[j]; - + Object o = null; for (;;) { int produced = 0; @@ -714,7 +720,7 @@ void emitLoop() { if (o == null) { break; } - T v = nl.getValue(o); + T v = NotificationLite.getValue(o); // if child throws, report bounce it back immediately try { child.onNext(v); @@ -759,7 +765,7 @@ void emitLoop() { if (r == 0) { break; } - + // wrap around in round-robin fashion j++; if (j == n) { @@ -770,7 +776,7 @@ void emitLoop() { lastIndex = j; lastId = inner[j].id; } - + if (replenishMain > 0) { request(replenishMain); } @@ -796,7 +802,7 @@ void emitLoop() { } } } - + /** * Check if the operator reached some terminal state: child unsubscribed, * an error was reported and we don't delay errors. @@ -824,8 +830,8 @@ static final class InnerSubscriber extends Subscriber { volatile boolean done; volatile RxRingBuffer queue; int outstanding; - static final int limit = RxRingBuffer.SIZE / 4; - + static final int LIMIT = RxRingBuffer.SIZE / 4; + public InnerSubscriber(MergeSubscriber parent, long id) { this.parent = parent; this.id = id; @@ -841,8 +847,11 @@ public void onNext(T t) { } @Override public void onError(Throwable e) { - done = true; + // Need to queue the error first before setting done, so that after emitLoop() removes the subscriber, + // it is guaranteed to notice the error. Otherwise it would be possible that inner subscribers count was 0, + // and at the same time the error queue was empty. parent.getOrCreateErrorQueue().offer(e); + done = true; parent.emit(); } @Override @@ -852,7 +861,7 @@ public void onCompleted() { } public void requestMore(long n) { int r = outstanding - (int)n; - if (r > limit) { + if (r > LIMIT) { outstanding = r; return; } diff --git a/src/main/java/rx/internal/operators/OperatorMulticast.java b/src/main/java/rx/internal/operators/OperatorMulticast.java index ee5551fcef..3f0777ca91 100644 --- a/src/main/java/rx/internal/operators/OperatorMulticast.java +++ b/src/main/java/rx/internal/operators/OperatorMulticast.java @@ -15,16 +15,12 @@ */ package rx.internal.operators; -import java.util.ArrayList; -import java.util.List; +import java.util.*; import java.util.concurrent.atomic.AtomicReference; +import rx.*; import rx.Observable; -import rx.Subscriber; -import rx.Subscription; -import rx.functions.Action0; -import rx.functions.Action1; -import rx.functions.Func0; +import rx.functions.*; import rx.observables.ConnectableObservable; import rx.observers.Subscribers; import rx.subjects.Subject; @@ -32,7 +28,7 @@ /** * Shares a single subscription to a source through a Subject. - * + * * @param * the source value type * @param @@ -90,7 +86,7 @@ public void connect(Action1 connection) { } else { // we aren't connected, so let's create a new Subject and connect final Subject subject = subjectFactory.call(); - // create new Subscriber that will pass-thru to the subject we just created + // create new Subscriber that will pass-through to the subject we just created // we do this since it is also a Subscription whereas the Subject is not subscription = Subscribers.from(subject); final AtomicReference gs = new AtomicReference(); @@ -104,8 +100,9 @@ public void call() { subscription = null; guardedSubscription = null; connectedSubject.set(null); - } else + } else { return; + } } if (s != null) { s.unsubscribe(); @@ -113,9 +110,9 @@ public void call() { } })); guardedSubscription = gs.get(); - + // register any subscribers that are waiting with this new subject - for(final Subscriber s : waitingForConnect) { + for (final Subscriber s : waitingForConnect) { subject.unsafeSubscribe(new Subscriber(s) { @Override public void onNext(R t) { @@ -136,7 +133,7 @@ public void onCompleted() { // record the Subject so OnSubscribe can see it connectedSubject.set(subject); } - + } // in the lock above we determined we should subscribe, do it now outside the lock @@ -145,11 +142,12 @@ public void onCompleted() { // now that everything is hooked up let's subscribe // as long as the subscription is not null (which can happen if already unsubscribed) - Subscriber sub; + Subscriber sub; synchronized (guard) { sub = subscription; } - if (sub != null) + if (sub != null) { ((Observable)source).subscribe(sub); + } } } \ No newline at end of file diff --git a/src/main/java/rx/internal/operators/OperatorObserveOn.java b/src/main/java/rx/internal/operators/OperatorObserveOn.java index 42fc5518ae..351cb35aca 100644 --- a/src/main/java/rx/internal/operators/OperatorObserveOn.java +++ b/src/main/java/rx/internal/operators/OperatorObserveOn.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -31,9 +31,9 @@ /** * Delivers events on the specified {@code Scheduler} asynchronously via an unbounded buffer. - * + * * - * + * * @param * the transmitted value type */ @@ -76,7 +76,7 @@ public Subscriber call(Subscriber child) { return parent; } } - + public static Operator rebatch(final int n) { return new Operator() { @Override @@ -89,28 +89,27 @@ public Subscriber call(Subscriber child) { } /** Observe through individual queue per observer. */ - private static final class ObserveOnSubscriber extends Subscriber implements Action0 { + static final class ObserveOnSubscriber extends Subscriber implements Action0 { final Subscriber child; final Scheduler.Worker recursiveScheduler; - final NotificationLite on; final boolean delayError; final Queue queue; /** The emission threshold that should trigger a replenishing request. */ final int limit; - + // the status of the current stream volatile boolean finished; final AtomicLong requested = new AtomicLong(); - + final AtomicLong counter = new AtomicLong(); - - /** + + /** * The single exception if not null, should be written before setting finished (release) and read after * reading finished (acquire). */ Throwable error; - + /** Remembers how many elements have been emitted before the requests run out. */ long emitted; @@ -120,7 +119,6 @@ public ObserveOnSubscriber(Scheduler scheduler, Subscriber child, boo this.child = child; this.recursiveScheduler = scheduler.createWorker(); this.delayError = delayError; - this.on = NotificationLite.instance(); int calculatedSize = (bufferSize > 0) ? bufferSize : RxRingBuffer.SIZE; // this formula calculates the 75% of the bufferSize, rounded up to the next integer this.limit = calculatedSize - (calculatedSize >> 2); @@ -132,12 +130,12 @@ public ObserveOnSubscriber(Scheduler scheduler, Subscriber child, boo // signal that this is an async operator capable of receiving this many request(calculatedSize); } - + void init() { - // don't want this code in the constructor because `this` can escape through the + // don't want this code in the constructor because `this` can escape through the // setProducer call Subscriber localChild = child; - + localChild.setProducer(new Producer() { @Override @@ -158,7 +156,7 @@ public void onNext(final T t) { if (isUnsubscribed() || finished) { return; } - if (!queue.offer(on.next(t))) { + if (!queue.offer(NotificationLite.next(t))) { onError(new MissingBackpressureException()); return; } @@ -202,29 +200,28 @@ public void call() { // of the constant fields final Queue q = this.queue; final Subscriber localChild = this.child; - final NotificationLite localOn = this.on; - + // requested and counter are not included to avoid JIT issues with register spilling // and their access is is amortized because they are part of the outer loop which runs // less frequently (usually after each bufferSize elements) - + for (;;) { long requestAmount = requested.get(); - + while (requestAmount != currentEmission) { boolean done = finished; Object v = q.poll(); boolean empty = v == null; - + if (checkTerminated(done, empty, localChild, q)) { return; } - + if (empty) { break; } - - localChild.onNext(localOn.getValue(v)); + + localChild.onNext(NotificationLite.getValue(v)); currentEmission++; if (currentEmission == limit) { @@ -233,7 +230,7 @@ public void call() { currentEmission = 0L; } } - + if (requestAmount == currentEmission) { if (checkTerminated(finished, q.isEmpty(), localChild, q)) { return; @@ -247,13 +244,13 @@ public void call() { } } } - + boolean checkTerminated(boolean done, boolean isEmpty, Subscriber a, Queue q) { if (a.isUnsubscribed()) { q.clear(); return true; } - + if (done) { if (delayError) { if (isEmpty) { @@ -288,9 +285,9 @@ boolean checkTerminated(boolean done, boolean isEmpty, Subscriber a, return true; } } - + } - + return false; } } diff --git a/src/main/java/rx/internal/operators/OperatorOnBackpressureBuffer.java b/src/main/java/rx/internal/operators/OperatorOnBackpressureBuffer.java index 04e6e81be9..f47c39b125 100644 --- a/src/main/java/rx/internal/operators/OperatorOnBackpressureBuffer.java +++ b/src/main/java/rx/internal/operators/OperatorOnBackpressureBuffer.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -15,28 +15,25 @@ */ package rx.internal.operators; +import static rx.BackpressureOverflow.ON_OVERFLOW_DEFAULT; + import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.*; -import rx.BackpressureOverflow; +import rx.*; +import rx.BackpressureOverflow.Strategy; import rx.Observable.Operator; -import rx.Producer; -import rx.Subscriber; -import rx.exceptions.Exceptions; -import rx.exceptions.MissingBackpressureException; +import rx.exceptions.*; import rx.functions.Action0; import rx.internal.util.BackpressureDrainManager; -import static rx.BackpressureOverflow.*; - public class OperatorOnBackpressureBuffer implements Operator { private final Long capacity; private final Action0 onOverflow; - private final BackpressureOverflow.Strategy overflowStrategy; + private final Strategy overflowStrategy; - private static class Holder { + static final class Holder { static final OperatorOnBackpressureBuffer INSTANCE = new OperatorOnBackpressureBuffer(); } @@ -80,7 +77,7 @@ public OperatorOnBackpressureBuffer(long capacity, Action0 onOverflow) { * @param overflowStrategy the {@code BackpressureOverflow.Strategy} to handle overflows, it must not be null. */ public OperatorOnBackpressureBuffer(long capacity, Action0 onOverflow, - BackpressureOverflow.Strategy overflowStrategy) { + Strategy overflowStrategy) { if (capacity <= 0) { throw new IllegalArgumentException("Buffer capacity must be > 0"); } @@ -107,19 +104,18 @@ public Subscriber call(final Subscriber child) { return parent; } - private static final class BufferSubscriber extends Subscriber implements BackpressureDrainManager.BackpressureQueueCallback { + static final class BufferSubscriber extends Subscriber implements BackpressureDrainManager.BackpressureQueueCallback { // TODO get a different queue implementation private final ConcurrentLinkedQueue queue = new ConcurrentLinkedQueue(); private final AtomicLong capacity; private final Subscriber child; private final AtomicBoolean saturated = new AtomicBoolean(false); private final BackpressureDrainManager manager; - private final NotificationLite on = NotificationLite.instance(); private final Action0 onOverflow; - private final BackpressureOverflow.Strategy overflowStrategy; - + private final Strategy overflowStrategy; + public BufferSubscriber(final Subscriber child, Long capacity, Action0 onOverflow, - BackpressureOverflow.Strategy overflowStrategy) { + Strategy overflowStrategy) { this.child = child; this.capacity = capacity != null ? new AtomicLong(capacity) : null; this.onOverflow = onOverflow; @@ -151,13 +147,13 @@ public void onNext(T t) { if (!assertCapacity()) { return; } - queue.offer(on.next(t)); + queue.offer(NotificationLite.next(t)); manager.drain(); } @Override public boolean accept(Object value) { - return on.accept(child, value); + return NotificationLite.accept(child, value); } @Override public void complete(Throwable exception) { diff --git a/src/main/java/rx/internal/operators/OperatorOnBackpressureDrop.java b/src/main/java/rx/internal/operators/OperatorOnBackpressureDrop.java index 98f4f9479d..5ed221746d 100644 --- a/src/main/java/rx/internal/operators/OperatorOnBackpressureDrop.java +++ b/src/main/java/rx/internal/operators/OperatorOnBackpressureDrop.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -17,16 +17,18 @@ import java.util.concurrent.atomic.AtomicLong; +import rx.*; import rx.Observable.Operator; -import rx.Producer; -import rx.Subscriber; import rx.exceptions.Exceptions; import rx.functions.Action1; +import rx.plugins.RxJavaHooks; public class OperatorOnBackpressureDrop implements Operator { + final Action1 onDrop; + /** Lazy initialization via inner-class holder. */ - private static final class Holder { + static final class Holder { /** A singleton instance. */ static final OperatorOnBackpressureDrop INSTANCE = new OperatorOnBackpressureDrop(); } @@ -40,8 +42,6 @@ public static OperatorOnBackpressureDrop instance() { return (OperatorOnBackpressureDrop)Holder.INSTANCE; } - final Action1 onDrop; - OperatorOnBackpressureDrop() { this(null); } @@ -63,6 +63,9 @@ public void request(long n) { }); return new Subscriber(child) { + + boolean done; + @Override public void onStart() { request(Long.MAX_VALUE); @@ -70,27 +73,37 @@ public void onStart() { @Override public void onCompleted() { - child.onCompleted(); + if (!done) { + done = true; + child.onCompleted(); + } } @Override public void onError(Throwable e) { - child.onError(e); + if (!done) { + done = true; + child.onError(e); + } else { + RxJavaHooks.onError(e); + } } @Override public void onNext(T t) { + if (done) { + return; + } if (requested.get() > 0) { child.onNext(t); requested.decrementAndGet(); } else { // item dropped - if(onDrop != null) { + if (onDrop != null) { try { onDrop.call(t); } catch (Throwable e) { - Exceptions.throwOrReport(e, child, t); - return; + Exceptions.throwOrReport(e, this, t); } } } diff --git a/src/main/java/rx/internal/operators/OperatorOnBackpressureLatest.java b/src/main/java/rx/internal/operators/OperatorOnBackpressureLatest.java index 02225d3afc..cffb7de326 100644 --- a/src/main/java/rx/internal/operators/OperatorOnBackpressureLatest.java +++ b/src/main/java/rx/internal/operators/OperatorOnBackpressureLatest.java @@ -17,8 +17,8 @@ import java.util.concurrent.atomic.*; -import rx.Observable.Operator; import rx.*; +import rx.Observable.Operator; /** * An operator which drops all but the last received value in case the downstream @@ -30,7 +30,7 @@ public final class OperatorOnBackpressureLatest implements Operator { static final class Holder { static final OperatorOnBackpressureLatest INSTANCE = new OperatorOnBackpressureLatest(); } - + /** * Returns a singleton instance of the OnBackpressureLatest operator since it is stateless. * @param the value type @@ -40,7 +40,7 @@ static final class Holder { public static OperatorOnBackpressureLatest instance() { return (OperatorOnBackpressureLatest)Holder.INSTANCE; } - + @Override public Subscriber call(Subscriber child) { final LatestEmitter producer = new LatestEmitter(child); @@ -73,7 +73,7 @@ static final class LatestEmitter extends AtomicLong implements Producer, Subs public LatestEmitter(Subscriber child) { this.child = child; this.value = new AtomicReference(EMPTY); - this.lazySet(NOT_REQUESTED); // not + this.lazySet(NOT_REQUESTED); // not } @Override public void request(long n) { @@ -124,7 +124,7 @@ public void unsubscribe() { getAndSet(Long.MIN_VALUE); } } - + @Override public void onNext(T t) { value.lazySet(t); // emit's synchronized block does a full release @@ -203,7 +203,7 @@ static final class LatestSubscriber extends Subscriber { @Override public void onStart() { // don't run until the child actually requested to avoid synchronous problems - request(0); + request(0); } @Override diff --git a/src/main/java/rx/internal/operators/OperatorOnErrorResumeNextViaFunction.java b/src/main/java/rx/internal/operators/OperatorOnErrorResumeNextViaFunction.java index d78b75ce57..9fd7d3228b 100644 --- a/src/main/java/rx/internal/operators/OperatorOnErrorResumeNextViaFunction.java +++ b/src/main/java/rx/internal/operators/OperatorOnErrorResumeNextViaFunction.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -44,9 +44,9 @@ */ public final class OperatorOnErrorResumeNextViaFunction implements Operator { - final Func1> resumeFunction; + final Func1> resumeFunction; - public static OperatorOnErrorResumeNextViaFunction withSingle(final Func1 resumeFunction) { + public static OperatorOnErrorResumeNextViaFunction withSingle(final Func1 resumeFunction) { return new OperatorOnErrorResumeNextViaFunction(new Func1>() { @Override public Observable call(Throwable t) { @@ -76,22 +76,22 @@ public Observable call(Throwable t) { }); } - public OperatorOnErrorResumeNextViaFunction(Func1> f) { + public OperatorOnErrorResumeNextViaFunction(Func1> f) { this.resumeFunction = f; } @Override public Subscriber call(final Subscriber child) { final ProducerArbiter pa = new ProducerArbiter(); - - final SerialSubscription ssub = new SerialSubscription(); - + + final SerialSubscription serial = new SerialSubscription(); + Subscriber parent = new Subscriber() { private boolean done; - + long produced; - + @Override public void onCompleted() { if (done) { @@ -130,15 +130,15 @@ public void setProducer(Producer producer) { pa.setProducer(producer); } }; - ssub.set(next); - + serial.set(next); + long p = produced; if (p != 0L) { pa.produced(p); } - + Observable resume = resumeFunction.call(e); - + resume.unsafeSubscribe(next); } catch (Throwable e2) { Exceptions.throwOrReport(e2, child); @@ -153,18 +153,18 @@ public void onNext(T t) { produced++; child.onNext(t); } - + @Override public void setProducer(final Producer producer) { pa.setProducer(producer); } }; - ssub.set(parent); + serial.set(parent); - child.add(ssub); + child.add(serial); child.setProducer(pa); - + return parent; } diff --git a/src/main/java/rx/internal/operators/OperatorPublish.java b/src/main/java/rx/internal/operators/OperatorPublish.java index 1e5002e116..c07d084abe 100644 --- a/src/main/java/rx/internal/operators/OperatorPublish.java +++ b/src/main/java/rx/internal/operators/OperatorPublish.java @@ -21,14 +21,15 @@ import rx.*; import rx.exceptions.*; import rx.functions.*; -import rx.internal.util.*; +import rx.internal.util.RxRingBuffer; +import rx.internal.util.atomic.SpscAtomicArrayQueue; import rx.internal.util.unsafe.*; import rx.observables.ConnectableObservable; import rx.subscriptions.Subscriptions; /** * A connectable observable which shares an underlying source and dispatches source values to subscribers in a backpressure-aware - * manner. + * manner. * @param the value type */ public final class OperatorPublish extends ConnectableObservable { @@ -49,7 +50,7 @@ public static ConnectableObservable create(Observable source OnSubscribe onSubscribe = new OnSubscribe() { @Override public void call(Subscriber child) { - // concurrent connection/disconnection may change the state, + // concurrent connection/disconnection may change the state, // we loop to be atomic while the child subscribes for (;;) { // get the current subscriber-to-source @@ -62,90 +63,89 @@ public void call(Subscriber child) { u.init(); // let's try setting it as the current subscriber-to-source if (!curr.compareAndSet(r, u)) { - // didn't work, maybe someone else did it or the current subscriber + // didn't work, maybe someone else did it or the current subscriber // to source has just finished continue; } // we won, let's use it going onwards r = u; } - + // create the backpressure-managing producer for this child InnerProducer inner = new InnerProducer(r, child); /* - * Try adding it to the current subscriber-to-source, add is atomic in respect + * Try adding it to the current subscriber-to-source, add is atomic in respect * to other adds and the termination of the subscriber-to-source. */ - if (!r.add(inner)) { - /* - * The current PublishSubscriber has been terminated, try with a newer one. - */ - continue; - /* - * Note: although technically correct, concurrent disconnects can cause - * unexpected behavior such as child subscribers never receiving anything - * (unless connected again). An alternative approach, similar to - * PublishSubject would be to immediately terminate such child - * subscribers as well: - * - * Object term = r.terminalEvent; - * if (r.nl.isCompleted(term)) { - * child.onCompleted(); - * } else { - * child.onError(r.nl.getError(term)); - * } - * return; - * - * The original concurrent behavior was non-deterministic in this regard as well. - * Allowing this behavior, however, may introduce another unexpected behavior: - * after disconnecting a previous connection, one might not be able to prepare - * a new connection right after a previous termination by subscribing new child - * subscribers asynchronously before a connect call. - */ + if (r.add(inner)) { + // the producer has been registered with the current subscriber-to-source so + // at least it will receive the next terminal event + child.add(inner); + // setting the producer will trigger the first request to be considered by + // the subscriber-to-source. + child.setProducer(inner); + break; // NOPMD } - // the producer has been registered with the current subscriber-to-source so - // at least it will receive the next terminal event - child.add(inner); - // setting the producer will trigger the first request to be considered by - // the subscriber-to-source. - child.setProducer(inner); - break; + /* + * The current PublishSubscriber has been terminated, try with a newer one. + */ + /* + * Note: although technically correct, concurrent disconnects can cause + * unexpected behavior such as child subscribers never receiving anything + * (unless connected again). An alternative approach, similar to + * PublishSubject would be to immediately terminate such child + * subscribers as well: + * + * Object term = r.terminalEvent; + * if (NotificationLite.isCompleted(term)) { + * child.onCompleted(); + * } else { + * child.onError(NotificationLite.getError(term)); + * } + * return; + * + * The original concurrent behavior was non-deterministic in this regard as well. + * Allowing this behavior, however, may introduce another unexpected behavior: + * after disconnecting a previous connection, one might not be able to prepare + * a new connection right after a previous termination by subscribing new child + * subscribers asynchronously before a connect call. + */ } } }; return new OperatorPublish(onSubscribe, source, curr); } - public static Observable create(final Observable source, + public static Observable create(final Observable source, final Func1, ? extends Observable> selector) { return create(source, selector, false); } - - public static Observable create(final Observable source, + + public static Observable create(final Observable source, final Func1, ? extends Observable> selector, final boolean delayError) { - return create(new OnSubscribe() { + return unsafeCreate(new OnSubscribe() { @Override public void call(final Subscriber child) { final OnSubscribePublishMulticast op = new OnSubscribePublishMulticast(RxRingBuffer.SIZE, delayError); - + Subscriber subscriber = new Subscriber() { @Override public void onNext(R t) { child.onNext(t); } - + @Override public void onError(Throwable e) { op.unsubscribe(); child.onError(e); } - + @Override public void onCompleted() { op.unsubscribe(); child.onCompleted(); } - + @Override public void setProducer(Producer p) { child.setProducer(p); @@ -154,15 +154,15 @@ public void setProducer(Producer p) { child.add(op); child.add(subscriber); - - selector.call(Observable.create(op)).unsafeSubscribe(subscriber); - + + selector.call(Observable.unsafeCreate(op)).unsafeSubscribe(subscriber); + source.unsafeSubscribe(op.subscriber()); } }); } - private OperatorPublish(OnSubscribe onSubscribe, Observable source, + private OperatorPublish(OnSubscribe onSubscribe, Observable source, final AtomicReference> current) { super(onSubscribe); this.source = source; @@ -171,7 +171,7 @@ private OperatorPublish(OnSubscribe onSubscribe, Observable sour @Override public void connect(Action1 connection) { - boolean doConnect = false; + boolean doConnect; PublishSubscriber ps; // we loop because concurrent connect/disconnect and termination may change the state for (;;) { @@ -185,28 +185,28 @@ public void connect(Action1 connection) { u.init(); // try setting it as the current subscriber-to-source if (!current.compareAndSet(ps, u)) { - // did not work, perhaps a new subscriber arrived + // did not work, perhaps a new subscriber arrived // and created a new subscriber-to-source as well, retry continue; } ps = u; } - // if connect() was called concurrently, only one of them should actually + // if connect() was called concurrently, only one of them should actually // connect to the source doConnect = !ps.shouldConnect.get() && ps.shouldConnect.compareAndSet(false, true); - break; + break; // NOPMD } - /* + /* * Notify the callback that we have a (new) connection which it can unsubscribe * but since ps is unique to a connection, multiple calls to connect() will return the * same Subscription and even if there was a connect-disconnect-connect pair, the older * references won't disconnect the newer connection. * Synchronous source consumers have the opportunity to disconnect via unsubscribe on the * Subscription as unsafeSubscribe may never return in its own. - * - * Note however, that asynchronously disconnecting a running source might leave - * child-subscribers without any terminal event; PublishSubject does not have this - * issue because the unsubscription was always triggered by the child-subscribers + * + * Note however, that asynchronously disconnecting a running source might leave + * child-subscribers without any terminal event; PublishSubject does not have this + * issue because the unsubscription was always triggered by the child-subscribers * themselves. */ connection.call(ps); @@ -214,47 +214,44 @@ public void connect(Action1 connection) { source.unsafeSubscribe(ps); } } - + @SuppressWarnings("rawtypes") static final class PublishSubscriber extends Subscriber implements Subscription { /** Holds notifications from upstream. */ final Queue queue; - /** The notification-lite factory. */ - final NotificationLite nl; /** Holds onto the current connected PublishSubscriber. */ final AtomicReference> current; /** Contains either an onCompleted or an onError token from upstream. */ volatile Object terminalEvent; - + /** Indicates an empty array of inner producers. */ static final InnerProducer[] EMPTY = new InnerProducer[0]; /** Indicates a terminated PublishSubscriber. */ static final InnerProducer[] TERMINATED = new InnerProducer[0]; - + /** Tracks the subscribed producers. */ final AtomicReference producers; - /** - * Atomically changed from false to true by connect to make sure the - * connection is only performed by one thread. + /** + * Atomically changed from false to true by connect to make sure the + * connection is only performed by one thread. */ final AtomicBoolean shouldConnect; - + /** Guarded by this. */ boolean emitting; /** Guarded by this. */ boolean missed; - + public PublishSubscriber(AtomicReference> current) { - this.queue = UnsafeAccess.isUnsafeAvailable() - ? new SpscArrayQueue(RxRingBuffer.SIZE) - : new SynchronizedQueue(RxRingBuffer.SIZE); - - this.nl = NotificationLite.instance(); + this.queue = UnsafeAccess.isUnsafeAvailable() + ? new SpscArrayQueue(RxRingBuffer.SIZE) + : new SpscAtomicArrayQueue(RxRingBuffer.SIZE); + this.producers = new AtomicReference(EMPTY); this.current = current; this.shouldConnect = new AtomicBoolean(); } - + /** Should be called after the constructor finished to setup nulling-out the current reference. */ void init() { add(Subscriptions.create(new Action0() { @@ -262,15 +259,15 @@ void init() { public void call() { PublishSubscriber.this.producers.getAndSet(TERMINATED); current.compareAndSet(PublishSubscriber.this, null); - // we don't care if it fails because it means the current has + // we don't care if it fails because it means the current has // been replaced in the meantime } })); } - + @Override public void onStart() { - // since subscribers may have different amount of requests, we try to + // since subscribers may have different amount of requests, we try to // optimize by buffering values up-front and replaying it on individual demand request(RxRingBuffer.SIZE); } @@ -278,10 +275,10 @@ public void onStart() { public void onNext(T t) { // we expect upstream to honor backpressure requests // nl is required because JCTools queue doesn't accept nulls. - if (!queue.offer(nl.next(t))) { + if (!queue.offer(NotificationLite.next(t))) { onError(new MissingBackpressureException()); } else { - // since many things can happen concurrently, we have a common dispatch + // since many things can happen concurrently, we have a common dispatch // loop to act on the current state serially dispatch(); } @@ -291,8 +288,8 @@ public void onError(Throwable e) { // The observer front is accessed serially as required by spec so // no need to CAS in the terminal value if (terminalEvent == null) { - terminalEvent = nl.error(e); - // since many things can happen concurrently, we have a common dispatch + terminalEvent = NotificationLite.error(e); + // since many things can happen concurrently, we have a common dispatch // loop to act on the current state serially dispatch(); } @@ -302,13 +299,13 @@ public void onCompleted() { // The observer front is accessed serially as required by spec so // no need to CAS in the terminal value if (terminalEvent == null) { - terminalEvent = nl.completed(); - // since many things can happen concurrently, we have a common dispatch loop + terminalEvent = NotificationLite.completed(); + // since many things can happen concurrently, we have a common dispatch loop // to act on the current state serially dispatch(); } } - + /** * Atomically try adding a new InnerProducer to this Subscriber or return false if this * Subscriber was terminated. @@ -323,7 +320,7 @@ boolean add(InnerProducer producer) { for (;;) { // get the current producer array InnerProducer[] c = producers.get(); - // if this subscriber-to-source reached a terminal state by receiving + // if this subscriber-to-source reached a terminal state by receiving // an onError or onCompleted, just refuse to add the new producer if (c == TERMINATED) { return false; @@ -341,7 +338,7 @@ boolean add(InnerProducer producer) { // so retry } } - + /** * Atomically removes the given producer from the producers array. * @param producer the producer to remove @@ -391,7 +388,7 @@ void remove(InnerProducer producer) { // (a concurrent add/remove or termination), we need to retry } } - + /** * Perform termination actions in case the source has terminated in some way and * the queue has also become empty. @@ -403,10 +400,10 @@ boolean checkTerminated(Object term, boolean empty) { // first of all, check if there is actually a terminal event if (term != null) { // is it a completion event (impl. note, this is much cheaper than checking for isError) - if (nl.isCompleted(term)) { + if (NotificationLite.isCompleted(term)) { // but we also need to have an empty queue if (empty) { - // this will prevent OnSubscribe spinning on a terminated but + // this will prevent OnSubscribe spinning on a terminated but // not yet unsubscribed PublishSubscriber current.compareAndSet(this, null); try { @@ -414,7 +411,7 @@ boolean checkTerminated(Object term, boolean empty) { * This will swap in a terminated array so add() in OnSubscribe will reject * child subscribers to associate themselves with a terminated and thus * never again emitting chain. - * + * * Since we atomically change the contents of 'producers' only one * operation wins at a time. If an add() wins before this getAndSet, * its value will be part of the returned array by getAndSet and thus @@ -434,8 +431,8 @@ boolean checkTerminated(Object term, boolean empty) { return true; } } else { - Throwable t = nl.getError(term); - // this will prevent OnSubscribe spinning on a terminated + Throwable t = NotificationLite.getError(term); + // this will prevent OnSubscribe spinning on a terminated // but not yet unsubscribed PublishSubscriber current.compareAndSet(this, null); try { @@ -457,7 +454,7 @@ boolean checkTerminated(Object term, boolean empty) { // there is still work to be done return false; } - + /** * The common serialization point of events arriving from upstream and child-subscribers * requesting more. @@ -509,7 +506,7 @@ void dispatch() { skipFinal = true; return; } - + // We have elements queued. Note that due to the serialization nature of 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. @@ -518,13 +515,13 @@ void dispatch() { // Concurrent subscribers may miss this iteration, but it is to be expected @SuppressWarnings("unchecked") InnerProducer[] ps = producers.get(); - + int len = ps.length; // Let's assume everyone requested the maximum value. long maxRequested = Long.MAX_VALUE; // count how many have triggered unsubscription int unsubscribed = 0; - + // Now find the minimum amount each child-subscriber requested // since we can only emit that much to all of them without violating // backpressure constraints @@ -541,7 +538,7 @@ void dispatch() { } // we ignore those with NOT_REQUESTED as if they aren't even there } - + // it may happen everyone has unsubscribed between here and producers.get() // or we have no subscribers at all to begin with if (len == unsubscribed) { @@ -576,7 +573,7 @@ void dispatch() { break; } // we need to unwrap potential nulls - T value = nl.getValue(v); + T value = NotificationLite.getValue(v); // let's emit this value to all child subscribers for (InnerProducer ip : ps) { // if ip.get() is negative, the child has either unsubscribed in the @@ -599,19 +596,19 @@ void dispatch() { // indicate we emitted one element d++; } - + // if we did emit at least one element, request more to replenish the queue if (d > 0) { request(d); } // if we have requests but not an empty queue after emission - // let's try again to see if more requests/child-subscribers are + // let's try again to see if more requests/child-subscribers are // ready to receive more if (maxRequested != 0L && !empty) { continue; } } - + // we did what we could: either the queue is empty or child subscribers // haven't requested more (or both), let's try to finish dispatching synchronized (this) { @@ -650,14 +647,14 @@ void dispatch() { static final class InnerProducer extends AtomicLong implements Producer, Subscription { /** */ private static final long serialVersionUID = -4453897557930727610L; - /** + /** * The parent subscriber-to-source used to allow removing the child in case of * child unsubscription. */ final PublishSubscriber parent; /** The actual child subscriber. */ final Subscriber child; - /** + /** * Indicates this child has been unsubscribed: the state is swapped in atomically and * will prevent the dispatch() to emit (too many) values to a terminated child subscriber. */ @@ -670,13 +667,13 @@ static final class InnerProducer extends AtomicLong implements Producer, Subs * should not cause any problems. */ static final long NOT_REQUESTED = Long.MIN_VALUE / 2; - + public InnerProducer(PublishSubscriber parent, Subscriber child) { this.parent = parent; this.child = child; this.lazySet(NOT_REQUESTED); } - + @Override public void request(long n) { // ignore negative requests @@ -718,11 +715,11 @@ public void request(long n) { parent.dispatch(); return; } - // otherwise, someone else changed the state (perhaps a concurrent + // otherwise, someone else changed the state (perhaps a concurrent // request or unsubscription so retry } } - + /** * Indicate that values have been emitted to this child subscriber by the dispatch() method. * @param n the number of items emitted @@ -759,7 +756,7 @@ public long produced(long n) { // otherwise, some concurrent activity happened and we need to retry } } - + @Override public boolean isUnsubscribed() { return get() == UNSUBSCRIBED; diff --git a/src/main/java/rx/internal/operators/OperatorReplay.java b/src/main/java/rx/internal/operators/OperatorReplay.java index 84338b96f9..6447b2c737 100644 --- a/src/main/java/rx/internal/operators/OperatorReplay.java +++ b/src/main/java/rx/internal/operators/OperatorReplay.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -28,7 +28,7 @@ import rx.schedulers.Timestamped; import rx.subscriptions.Subscriptions; -public final class OperatorReplay extends ConnectableObservable { +public final class OperatorReplay extends ConnectableObservable implements Subscription { /** The source observable. */ final Observable source; /** Holds the current subscriber that is, will be or just was subscribed to the source observable. */ @@ -43,7 +43,7 @@ public Object call() { return new UnboundedReplayBuffer(16); } }; - + /** * Given a connectable observable factory, it multicasts over the generated * ConnectableObservable via a selector function. @@ -53,12 +53,12 @@ public Object call() { * @param connectableFactory the factory that returns a ConnectableObservable instance * @param selector the function applied on the ConnectableObservable and returns the Observable * the downstream will subscribe to. - * @return the Observable multicasting over a transformation of a ConnectableObserable + * @return the Observable multicasting over a transformation of a ConnectableObservable */ public static Observable multicastSelector( final Func0> connectableFactory, final Func1, ? extends Observable> selector) { - return Observable.create(new OnSubscribe() { + return Observable.unsafeCreate(new OnSubscribe() { @Override public void call(final Subscriber child) { ConnectableObservable co; @@ -70,9 +70,9 @@ public void call(final Subscriber child) { Exceptions.throwOrReport(e, child); return; } - + observable.subscribe(child); - + co.connect(new Action1() { @Override public void call(Subscription t) { @@ -82,7 +82,7 @@ public void call(Subscription t) { } }); } - + /** * Child Subscribers will observe the events of the ConnectableObservable on the * specified scheduler. @@ -120,7 +120,7 @@ public void connect(Action1 connection) { } }; } - + /** * Creates a replaying ConnectableObservable with an unbounded buffer. * @param the value type @@ -131,7 +131,7 @@ public void connect(Action1 connection) { public static ConnectableObservable create(Observable source) { return create(source, DEFAULT_UNBOUNDED_FACTORY); } - + /** * Creates a replaying ConnectableObservable with a size bound buffer. * @param the value type @@ -140,7 +140,7 @@ public static ConnectableObservable create(Observable source * @return the replaying ConnectableObservable */ @SuppressWarnings("cast") - public static ConnectableObservable create(Observable source, + public static ConnectableObservable create(Observable source, final int bufferSize) { if (bufferSize == Integer.MAX_VALUE) { return (ConnectableObservable)create(source); @@ -163,7 +163,7 @@ public ReplayBuffer call() { * @return the replaying ConnectableObservable */ @SuppressWarnings("cast") - public static ConnectableObservable create(Observable source, + public static ConnectableObservable create(Observable source, long maxAge, TimeUnit unit, Scheduler scheduler) { return (ConnectableObservable)create(source, maxAge, unit, scheduler, Integer.MAX_VALUE); } @@ -178,7 +178,7 @@ public static ConnectableObservable create(Observable source * @param bufferSize the maximum number of elements buffered * @return the replaying ConnectableObservable */ - public static ConnectableObservable create(Observable source, + public static ConnectableObservable create(Observable source, long maxAge, TimeUnit unit, final Scheduler scheduler, final int bufferSize) { final long maxAgeInMillis = unit.toMillis(maxAge); return create(source, new Func0>() { @@ -195,14 +195,14 @@ public ReplayBuffer call() { * @param bufferFactory the factory to instantiate the appropriate buffer when the observable becomes active * @return the connectable observable */ - static ConnectableObservable create(Observable source, + static ConnectableObservable create(Observable source, final Func0> bufferFactory) { // the current connection to source needs to be shared between the operator and its onSubscribe call final AtomicReference> curr = new AtomicReference>(); OnSubscribe onSubscribe = new OnSubscribe() { @Override public void call(Subscriber child) { - // concurrent connection/disconnection may change the state, + // concurrent connection/disconnection may change the state, // we loop to be atomic while the child subscribes for (;;) { // get the current subscriber-to-source @@ -210,42 +210,42 @@ public void call(Subscriber child) { // if there isn't one if (r == null) { // create a new subscriber to source - ReplaySubscriber u = new ReplaySubscriber(curr, bufferFactory.call()); + ReplaySubscriber u = new ReplaySubscriber(bufferFactory.call()); // perform extra initialization to avoid 'this' to escape during construction u.init(); // let's try setting it as the current subscriber-to-source if (!curr.compareAndSet(r, u)) { - // didn't work, maybe someone else did it or the current subscriber + // didn't work, maybe someone else did it or the current subscriber // to source has just finished continue; } // we won, let's use it going onwards r = u; } - + // create the backpressure-managing producer for this child InnerProducer inner = new InnerProducer(r, child); // we try to add it to the array of producers // if it fails, no worries because we will still have its buffer // so it is going to replay it for us r.add(inner); - // the producer has been registered with the current subscriber-to-source so + // the producer has been registered with the current subscriber-to-source so // at least it will receive the next terminal event child.add(inner); - + // pin the head of the buffer here, shouldn't affect anything else r.buffer.replay(inner); - - // setting the producer will trigger the first request to be considered by + + // setting the producer will trigger the first request to be considered by // the subscriber-to-source. child.setProducer(inner); - break; + break; // NOPMD } } }; return new OperatorReplay(onSubscribe, source, curr, bufferFactory); } - private OperatorReplay(OnSubscribe onSubscribe, Observable source, + private OperatorReplay(OnSubscribe onSubscribe, Observable source, final AtomicReference> current, final Func0> bufferFactory) { super(onSubscribe); @@ -254,9 +254,20 @@ private OperatorReplay(OnSubscribe onSubscribe, Observable sourc this.bufferFactory = bufferFactory; } + @Override + public void unsubscribe() { + current.lazySet(null); + } + + @Override + public boolean isUnsubscribed() { + ReplaySubscriber ps = current.get(); + return ps == null || ps.isUnsubscribed(); + } + @Override public void connect(Action1 connection) { - boolean doConnect = false; + boolean doConnect; ReplaySubscriber ps; // we loop because concurrent connect/disconnect and termination may change the state for (;;) { @@ -265,33 +276,33 @@ public void connect(Action1 connection) { // if there is none yet or the current has unsubscribed if (ps == null || ps.isUnsubscribed()) { // create a new subscriber-to-source - ReplaySubscriber u = new ReplaySubscriber(current, bufferFactory.call()); + ReplaySubscriber u = new ReplaySubscriber(bufferFactory.call()); // initialize out the constructor to avoid 'this' to escape u.init(); // try setting it as the current subscriber-to-source if (!current.compareAndSet(ps, u)) { - // did not work, perhaps a new subscriber arrived + // did not work, perhaps a new subscriber arrived // and created a new subscriber-to-source as well, retry continue; } ps = u; } - // if connect() was called concurrently, only one of them should actually + // if connect() was called concurrently, only one of them should actually // connect to the source doConnect = !ps.shouldConnect.get() && ps.shouldConnect.compareAndSet(false, true); - break; + break; // NOPMD } - /* + /* * Notify the callback that we have a (new) connection which it can unsubscribe * but since ps is unique to a connection, multiple calls to connect() will return the * same Subscription and even if there was a connect-disconnect-connect pair, the older * references won't disconnect the newer connection. * Synchronous source consumers have the opportunity to disconnect via unsubscribe on the * Subscription as unsafeSubscribe may never return in its own. - * - * Note however, that asynchronously disconnecting a running source might leave - * child-subscribers without any terminal event; ReplaySubject does not have this - * issue because the unsubscription was always triggered by the child-subscribers + * + * Note however, that asynchronously disconnecting a running source might leave + * child-subscribers without any terminal event; ReplaySubject does not have this + * issue because the unsubscription was always triggered by the child-subscribers * themselves. */ connection.call(ps); @@ -299,21 +310,19 @@ public void connect(Action1 connection) { source.unsafeSubscribe(ps); } } - + @SuppressWarnings("rawtypes") static final class ReplaySubscriber extends Subscriber implements Subscription { /** Holds notifications from upstream. */ final ReplayBuffer buffer; - /** The notification-lite factory. */ - final NotificationLite nl; /** Contains either an onCompleted or an onError token from upstream. */ boolean done; - + /** Indicates an empty array of inner producers. */ static final InnerProducer[] EMPTY = new InnerProducer[0]; /** Indicates a terminated ReplaySubscriber. */ static final InnerProducer[] TERMINATED = new InnerProducer[0]; - + /** Indicates no further InnerProducers are accepted. */ volatile boolean terminated; /** Tracks the subscribed producers. Guarded by itself. */ @@ -324,36 +333,33 @@ static final class ReplaySubscriber extends Subscriber implements Subscrip volatile long producersVersion; /** Contains the number of modifications that the producersCache holds. */ long producersCacheVersion; - /** - * Atomically changed from false to true by connect to make sure the - * connection is only performed by one thread. + /** + * Atomically changed from false to true by connect to make sure the + * connection is only performed by one thread. */ final AtomicBoolean shouldConnect; - + /** Guarded by this. */ boolean emitting; /** Guarded by this. */ boolean missed; - - + + /** Contains the maximum element index the child Subscribers requested so far. Accessed while emitting is true. */ long maxChildRequested; /** Counts the outstanding upstream requests until the producer arrives. */ long maxUpstreamRequested; /** The upstream producer. */ volatile Producer producer; - + /** The queue that holds producers with request changes that need to be coordinated. */ List> coordinationQueue; /** Indicate that all request amounts should be considered. */ boolean coordinateAll; - + @SuppressWarnings("unchecked") - public ReplaySubscriber(AtomicReference> current, - ReplayBuffer buffer) { + public ReplaySubscriber(ReplayBuffer buffer) { this.buffer = buffer; - - this.nl = NotificationLite.instance(); this.producers = new OpenHashSet>(); this.producersCache = EMPTY; this.shouldConnect = new AtomicBoolean(); @@ -378,7 +384,7 @@ public void call() { // unlike OperatorPublish, we can't null out the terminated so // late subscribers can still get replay // current.compareAndSet(ReplaySubscriber.this, null); - // we don't care if it fails because it means the current has + // we don't care if it fails because it means the current has // been replaced in the meantime } })); @@ -400,17 +406,18 @@ boolean add(InnerProducer producer) { if (terminated) { return false; } - + producers.add(producer); producersVersion++; } return true; } - + /** * Atomically removes the given producer from the producers array. * @param producer the producer to remove */ + @SuppressWarnings("unchecked") void remove(InnerProducer producer) { if (terminated) { return; @@ -420,10 +427,13 @@ void remove(InnerProducer producer) { return; } producers.remove(producer); + if (producers.isEmpty()) { + producersCache = EMPTY; + } producersVersion++; } } - + @Override public void setProducer(Producer p) { Producer p0 = producer; @@ -434,7 +444,7 @@ public void setProducer(Producer p) { manageRequests(null); replay(); } - + @Override public void onNext(T t) { if (!done) { @@ -470,7 +480,7 @@ public void onCompleted() { } } } - + /** * Coordinates the request amounts of various child Subscribers. */ @@ -496,10 +506,10 @@ void manageRequests(InnerProducer inner) { } emitting = true; } - + long ri = maxChildRequested; long maxTotalRequested; - + if (inner != null) { maxTotalRequested = Math.max(ri, inner.totalRequested.get()); } else { @@ -511,16 +521,16 @@ void manageRequests(InnerProducer inner) { maxTotalRequested = Math.max(maxTotalRequested, rp.totalRequested.get()); } } - + } makeRequest(maxTotalRequested, ri); - + for (;;) { // if the upstream has completed, no more requesting is possible if (isUnsubscribed()) { return; } - + List> q; boolean all; synchronized (this) { @@ -534,7 +544,7 @@ void manageRequests(InnerProducer inner) { all = coordinateAll; coordinateAll = false; } - + ri = maxChildRequested; maxTotalRequested = ri; @@ -542,8 +552,8 @@ void manageRequests(InnerProducer inner) { for (InnerProducer rp : q) { maxTotalRequested = Math.max(maxTotalRequested, rp.totalRequested.get()); } - } - + } + if (all) { InnerProducer[] a = copyProducers(); for (InnerProducer rp : a) { @@ -552,11 +562,11 @@ void manageRequests(InnerProducer inner) { } } } - + makeRequest(maxTotalRequested, ri); } } - + InnerProducer[] copyProducers() { synchronized (producers) { Object[] a = producers.values(); @@ -567,7 +577,7 @@ InnerProducer[] copyProducers() { return result; } } - + void makeRequest(long maxTotalRequests, long previousTotalRequests) { long ur = maxUpstreamRequested; Producer p = producer; @@ -598,7 +608,7 @@ void makeRequest(long maxTotalRequests, long previousTotalRequests) { p.request(ur); } } - + /** * Tries to replay the buffer contents to all known subscribers. */ @@ -638,16 +648,16 @@ void replay() { static final class InnerProducer extends AtomicLong implements Producer, Subscription { /** */ private static final long serialVersionUID = -4453897557930727610L; - /** + /** * The parent subscriber-to-source used to allow removing the child in case of * child unsubscription. */ final ReplaySubscriber parent; /** The actual child subscriber. */ - final Subscriber child; - /** + Subscriber child; + /** * Holds an object that represents the current location in the buffer. - * Guarded by the emitter loop. + * Guarded by the emitter loop. */ Object index; /** @@ -658,18 +668,18 @@ static final class InnerProducer extends AtomicLong implements Producer, Subs boolean emitting; /** Indicates a missed update. Guarded by this. */ boolean missed; - /** + /** * Indicates this child has been unsubscribed: the state is swapped in atomically and * will prevent the dispatch() to emit (too many) values to a terminated child subscriber. */ static final long UNSUBSCRIBED = Long.MIN_VALUE; - + public InnerProducer(ReplaySubscriber parent, Subscriber child) { this.parent = parent; this.child = child; this.totalRequested = new AtomicLong(); } - + @Override public void request(long n) { // ignore negative requests @@ -704,15 +714,15 @@ public void request(long n) { // if successful, notify the parent dispatcher this child can receive more // elements parent.manageRequests(this); - + parent.buffer.replay(this); return; } - // otherwise, someone else changed the state (perhaps a concurrent + // otherwise, someone else changed the state (perhaps a concurrent // request or unsubscription so retry } } - + /** * Increments the total requested amount. * @param n the additional request amount @@ -729,7 +739,7 @@ void addTotalRequested(long n) { } } } - + /** * Indicate that values have been emitted to this child subscriber by the dispatch() method. * @param n the number of items emitted @@ -761,7 +771,7 @@ public long produced(long n) { // otherwise, some concurrent activity happened and we need to retry } } - + @Override public boolean isUnsubscribed() { return get() == UNSUBSCRIBED; @@ -785,12 +795,14 @@ public void unsubscribe() { // the others had non-zero. By removing this 'blocking' child, the others // are now free to receive events parent.manageRequests(this); + // break the reference + child = null; } } } /** * Convenience method to auto-cast the index object. - * @return + * @return the associated index object or null */ @SuppressWarnings("unchecked") U index() { @@ -805,12 +817,12 @@ U index() { interface ReplayBuffer { /** * Adds a regular value to the buffer. - * @param value + * @param value the value to buffer */ void next(T value); /** * Adds a terminal exception to the buffer - * @param e + * @param e the Throwable to buffer */ void error(Throwable e); /** @@ -822,11 +834,11 @@ interface ReplayBuffer { * subscriber inside the output if there * is new value and requests available at the * same time. - * @param output + * @param output the producer of the downstream consumer */ void replay(InnerProducer output); } - + /** * Holds an unbounded list of events. * @@ -835,29 +847,27 @@ interface ReplayBuffer { static final class UnboundedReplayBuffer extends ArrayList implements ReplayBuffer { /** */ private static final long serialVersionUID = 7063189396499112664L; - final NotificationLite nl; /** The total number of events in the buffer. */ volatile int size; - + public UnboundedReplayBuffer(int capacityHint) { super(capacityHint); - nl = NotificationLite.instance(); } @Override public void next(T value) { - add(nl.next(value)); + add(NotificationLite.next(value)); size++; } @Override public void error(Throwable e) { - add(nl.error(e)); + add(NotificationLite.error(e)); size++; } @Override public void complete() { - add(nl.completed()); + add(NotificationLite.completed()); size++; } @@ -875,42 +885,45 @@ public void replay(InnerProducer output) { return; } int sourceIndex = size; - - Integer destIndexObject = output.index(); - int destIndex = destIndexObject != null ? destIndexObject : 0; - + + Integer destinationIndexObject = output.index(); + int destinationIndex = destinationIndexObject != null ? destinationIndexObject : 0; + + Subscriber child = output.child; + if (child == null) { + return; + } + long r = output.get(); - long r0 = r; long e = 0L; - - while (r != 0L && destIndex < sourceIndex) { - Object o = get(destIndex); + + while (e != r && destinationIndex < sourceIndex) { + Object o = get(destinationIndex); try { - if (nl.accept(output.child, o)) { + if (NotificationLite.accept(child, o)) { return; } } catch (Throwable err) { Exceptions.throwIfFatal(err); output.unsubscribe(); - if (!nl.isError(o) && !nl.isCompleted(o)) { - output.child.onError(OnErrorThrowable.addValueAsLastCause(err, nl.getValue(o))); + if (!NotificationLite.isError(o) && !NotificationLite.isCompleted(o)) { + child.onError(OnErrorThrowable.addValueAsLastCause(err, NotificationLite.getValue(o))); } return; } if (output.isUnsubscribed()) { return; } - destIndex++; - r--; + destinationIndex++; e++; } if (e != 0L) { - output.index = destIndex; - if (r0 != Long.MAX_VALUE) { + output.index = destinationIndex; + if (r != Long.MAX_VALUE) { output.produced(e); } } - + synchronized (output) { if (!output.missed) { output.emitting = false; @@ -921,25 +934,25 @@ public void replay(InnerProducer output) { } } } - + /** * Represents a node in a bounded replay buffer's linked list. */ static final class Node extends AtomicReference { /** */ private static final long serialVersionUID = 245354315435971818L; - + /** The contained value. */ final Object value; /** The absolute index of the value. */ final long index; - + public Node(Object value, long index) { this.value = value; this.index = index; } } - + /** * Base class for bounded buffering with options to specify an * enter and leave transforms and custom truncation behavior. @@ -949,24 +962,22 @@ public Node(Object value, long index) { static class BoundedReplayBuffer extends AtomicReference implements ReplayBuffer { /** */ private static final long serialVersionUID = 2346567790059478686L; - final NotificationLite nl; - + Node tail; int size; - + /** The total number of received values so far. */ long index; - + public BoundedReplayBuffer() { - nl = NotificationLite.instance(); Node n = new Node(null, 0); tail = n; set(n); } - + /** * Add a new node to the linked list. - * @param n + * @param n the node to add as last */ final void addLast(Node n) { tail.set(n); @@ -994,17 +1005,17 @@ final void removeFirst() { n--; size--; } - + setFirst(head); } /** * Arranges the given node is the new head from now on. - * @param n + * @param n the node to set as first */ final void setFirst(Node n) { set(n); } - + /** * Returns the current head for initializing the replay location * for a new subscriber. @@ -1014,10 +1025,10 @@ final void setFirst(Node n) { Node getInitialHead() { return get(); } - + @Override public final void next(T value) { - Object o = enterTransform(nl.next(value)); + Object o = enterTransform(NotificationLite.next(value)); Node n = new Node(o, ++index); addLast(n); truncate(); @@ -1025,7 +1036,7 @@ public final void next(T value) { @Override public final void error(Throwable e) { - Object o = enterTransform(nl.error(e)); + Object o = enterTransform(NotificationLite.error(e)); Node n = new Node(o, ++index); addLast(n); truncateFinal(); @@ -1033,7 +1044,7 @@ public final void error(Throwable e) { @Override public final void complete() { - Object o = enterTransform(nl.completed()); + Object o = enterTransform(NotificationLite.completed()); Node n = new Node(o, ++index); addLast(n); truncateFinal(); @@ -1053,17 +1064,13 @@ public final void replay(InnerProducer output) { return; } - long r = output.get(); - boolean unbounded = r == Long.MAX_VALUE; - long e = 0L; - Node node = output.index(); if (node == null) { node = getInitialHead(); output.index = node; - + /* - * Since this is a latecommer, fix its total requested amount + * Since this is a latecomer, fix its total requested amount * as if it got all the values up to the node.index */ output.addTotalRequested(node.index); @@ -1073,12 +1080,20 @@ public final void replay(InnerProducer output) { return; } - while (r != 0) { + Subscriber child = output.child; + if (child == null) { + return; + } + + long r = output.get(); + long e = 0L; + + while (e != r) { Node v = node.get(); if (v != null) { Object o = leaveTransform(v.value); try { - if (nl.accept(output.child, o)) { + if (NotificationLite.accept(child, o)) { output.index = null; return; } @@ -1086,13 +1101,12 @@ public final void replay(InnerProducer output) { output.index = null; Exceptions.throwIfFatal(err); output.unsubscribe(); - if (!nl.isError(o) && !nl.isCompleted(o)) { - output.child.onError(OnErrorThrowable.addValueAsLastCause(err, nl.getValue(o))); + if (!NotificationLite.isError(o) && !NotificationLite.isCompleted(o)) { + child.onError(OnErrorThrowable.addValueAsLastCause(err, NotificationLite.getValue(o))); } return; } e++; - r--; node = v; } else { break; @@ -1104,11 +1118,11 @@ public final void replay(InnerProducer output) { if (e != 0L) { output.index = node; - if (!unbounded) { + if (r != Long.MAX_VALUE) { output.produced(e); } } - + synchronized (output) { if (!output.missed) { output.emitting = false; @@ -1117,14 +1131,14 @@ public final void replay(InnerProducer output) { output.missed = false; } } - + } - + /** * Override this to wrap the NotificationLite object into a * container to be used later by truncate. - * @param value - * @return + * @param value the value to transform into the internal representation + * @return the internal representation of the value */ Object enterTransform(Object value) { return value; @@ -1132,8 +1146,9 @@ Object enterTransform(Object value) { /** * Override this to unwrap the transformed value into a * NotificationLite object. - * @param value - * @return + * @param value the value to transform back to external representation from + * the internal representation + * @return the external representation of the value */ Object leaveTransform(Object value) { return value; @@ -1143,14 +1158,14 @@ Object leaveTransform(Object value) { * based on its current properties. */ void truncate() { - + // no op by default } /** * Override this method to truncate a terminated buffer * based on its properties (i.e., truncate but the very last node). */ void truncateFinal() { - + // no op by default } /* test */ final void collect(Collection output) { Node n = getInitialHead(); @@ -1159,10 +1174,10 @@ void truncateFinal() { if (next != null) { Object o = next.value; Object v = leaveTransform(o); - if (nl.isCompleted(v) || nl.isError(v)) { + if (NotificationLite.isCompleted(v) || NotificationLite.isError(v)) { break; } - output.add(nl.getValue(v)); + output.add(NotificationLite.getValue(v)); n = next; } else { break; @@ -1170,13 +1185,13 @@ void truncateFinal() { } } /* test */ boolean hasError() { - return tail.value != null && nl.isError(leaveTransform(tail.value)); + return tail.value != null && NotificationLite.isError(leaveTransform(tail.value)); } /* test */ boolean hasCompleted() { - return tail.value != null && nl.isCompleted(leaveTransform(tail.value)); + return tail.value != null && NotificationLite.isCompleted(leaveTransform(tail.value)); } } - + /** * A bounded replay buffer implementation with size limit only. * @@ -1185,12 +1200,12 @@ void truncateFinal() { static final class SizeBoundReplayBuffer extends BoundedReplayBuffer { /** */ private static final long serialVersionUID = -5898283885385201806L; - + final int limit; public SizeBoundReplayBuffer(int limit) { this.limit = limit; } - + @Override void truncate() { // overflow can be at most one element @@ -1198,13 +1213,13 @@ void truncate() { removeFirst(); } } - + // no need for final truncation because values are truncated one by one } - + /** * Size and time bound replay buffer. - * + * * @param the buffered value type */ static final class SizeAndTimeBoundReplayBuffer extends BoundedReplayBuffer { @@ -1218,38 +1233,47 @@ public SizeAndTimeBoundReplayBuffer(int limit, long maxAgeInMillis, Scheduler sc this.limit = limit; this.maxAgeInMillis = maxAgeInMillis; } - + @Override Object enterTransform(Object value) { return new Timestamped(scheduler.now(), value); } - + @Override Object leaveTransform(Object value) { return ((Timestamped)value).getValue(); } - + @Override Node getInitialHead() { long timeLimit = scheduler.now() - maxAgeInMillis; Node prev = get(); - + Node next = prev.get(); - while (next != null && ((Timestamped)next.value).getTimestampMillis() <= timeLimit) { - prev = next; - next = next.get(); + while (next != null) { + Object o = next.value; + Object v = leaveTransform(o); + if (NotificationLite.isCompleted(v) || NotificationLite.isError(v)) { + break; + } + if (((Timestamped)o).getTimestampMillis() <= timeLimit) { + prev = next; + next = next.get(); + } else { + break; + } } - + return prev; } - + @Override void truncate() { long timeLimit = scheduler.now() - maxAgeInMillis; - + Node prev = get(); Node next = prev.get(); - + int e = 0; for (;;) { if (next != null) { @@ -1280,10 +1304,10 @@ void truncate() { @Override void truncateFinal() { long timeLimit = scheduler.now() - maxAgeInMillis; - + Node prev = get(); Node next = prev.get(); - + int e = 0; for (;;) { if (next != null && size > 1) { diff --git a/src/main/java/rx/internal/operators/OperatorRetryWithPredicate.java b/src/main/java/rx/internal/operators/OperatorRetryWithPredicate.java index 0e5111b6c4..649f4774d9 100644 --- a/src/main/java/rx/internal/operators/OperatorRetryWithPredicate.java +++ b/src/main/java/rx/internal/operators/OperatorRetryWithPredicate.java @@ -17,12 +17,8 @@ import java.util.concurrent.atomic.AtomicInteger; -import rx.Observable; -import rx.Producer; -import rx.Scheduler; -import rx.Subscriber; -import rx.functions.Action0; -import rx.functions.Func2; +import rx.*; +import rx.functions.*; import rx.internal.producers.ProducerArbiter; import rx.schedulers.Schedulers; import rx.subscriptions.SerialSubscription; @@ -37,7 +33,7 @@ public OperatorRetryWithPredicate(Func2 predicate) public Subscriber> call(final Subscriber child) { final Scheduler.Worker inner = Schedulers.trampoline().createWorker(); child.add(inner); - + final SerialSubscription serialSubscription = new SerialSubscription(); // add serialSubscription so it gets unsubscribed if child is unsubscribed child.add(serialSubscription); @@ -45,19 +41,19 @@ public Subscriber> call(final Subscriber child) child.setProducer(pa); return new SourceSubscriber(child, predicate, inner, serialSubscription, pa); } - + static final class SourceSubscriber extends Subscriber> { final Subscriber child; final Func2 predicate; final Scheduler.Worker inner; final SerialSubscription serialSubscription; final ProducerArbiter pa; - + final AtomicInteger attempts = new AtomicInteger(); - public SourceSubscriber(Subscriber child, - final Func2 predicate, - Scheduler.Worker inner, + public SourceSubscriber(Subscriber child, + final Func2 predicate, + Scheduler.Worker inner, SerialSubscription serialSubscription, ProducerArbiter pa) { this.child = child; @@ -66,8 +62,8 @@ public SourceSubscriber(Subscriber child, this.serialSubscription = serialSubscription; this.pa = pa; } - - + + @Override public void onCompleted() { // ignore as we expect a single nested Observable @@ -126,7 +122,7 @@ public void setProducer(Producer p) { pa.setProducer(p); } }; - // register this Subscription (and unsubscribe previous if exists) + // register this Subscription (and unsubscribe previous if exists) serialSubscription.set(subscriber); o.unsafeSubscribe(subscriber); } diff --git a/src/main/java/rx/internal/operators/OperatorSampleWithObservable.java b/src/main/java/rx/internal/operators/OperatorSampleWithObservable.java index 605ea96120..0186e0600f 100644 --- a/src/main/java/rx/internal/operators/OperatorSampleWithObservable.java +++ b/src/main/java/rx/internal/operators/OperatorSampleWithObservable.java @@ -23,9 +23,9 @@ /** * Sample with the help of another observable. - * + * * @see MSDN: Observable.Sample - * + * * @param the source and result value type * @param the element type of the sampler Observable */ @@ -41,11 +41,11 @@ public OperatorSampleWithObservable(Observable sampler) { @Override public Subscriber call(Subscriber child) { final SerializedSubscriber s = new SerializedSubscriber(child); - + final AtomicReference value = new AtomicReference(EMPTY_TOKEN); - + final AtomicReference main = new AtomicReference(); - + final Subscriber samplerSub = new Subscriber() { @Override public void onNext(U t) { @@ -72,7 +72,7 @@ public void onCompleted() { main.get().unsubscribe(); } }; - + Subscriber result = new Subscriber() { @Override public void onNext(T t) { @@ -82,7 +82,7 @@ public void onNext(T t) { @Override public void onError(Throwable e) { s.onError(e); - + samplerSub.unsubscribe(); } @@ -94,14 +94,14 @@ public void onCompleted() { samplerSub.unsubscribe(); } }; - + main.lazySet(result); - + child.add(result); child.add(samplerSub); - + sampler.unsafeSubscribe(samplerSub); - + return result; } } diff --git a/src/main/java/rx/internal/operators/OperatorSampleWithTime.java b/src/main/java/rx/internal/operators/OperatorSampleWithTime.java index e4c4a82c53..dc7f212886 100644 --- a/src/main/java/rx/internal/operators/OperatorSampleWithTime.java +++ b/src/main/java/rx/internal/operators/OperatorSampleWithTime.java @@ -18,11 +18,10 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; +import rx.*; import rx.Observable.Operator; -import rx.Scheduler; import rx.Scheduler.Worker; import rx.exceptions.Exceptions; -import rx.Subscriber; import rx.functions.Action0; import rx.observers.SerializedSubscriber; @@ -31,7 +30,7 @@ * Observable at a specified time interval. *

    * - * + * * @param the value type */ public final class OperatorSampleWithTime implements Operator { @@ -50,7 +49,7 @@ public Subscriber call(Subscriber child) { final SerializedSubscriber s = new SerializedSubscriber(child); final Worker worker = scheduler.createWorker(); child.add(worker); - + SamplerSubscriber sampler = new SamplerSubscriber(s); child.add(sampler); worker.schedulePeriodically(sampler, time, time, unit); @@ -70,12 +69,12 @@ static final class SamplerSubscriber extends Subscriber implements Action0 public SamplerSubscriber(Subscriber subscriber) { this.subscriber = subscriber; } - + @Override public void onStart() { request(Long.MAX_VALUE); } - + @Override public void onNext(T t) { value.set(t); diff --git a/src/main/java/rx/internal/operators/OperatorScan.java b/src/main/java/rx/internal/operators/OperatorScan.java index 547edf5c1b..4f0e2d1e12 100644 --- a/src/main/java/rx/internal/operators/OperatorScan.java +++ b/src/main/java/rx/internal/operators/OperatorScan.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -20,7 +20,7 @@ import rx.*; import rx.Observable.Operator; -import rx.exceptions.*; +import rx.exceptions.Exceptions; import rx.functions.*; import rx.internal.util.atomic.SpscLinkedAtomicQueue; import rx.internal.util.unsafe.*; @@ -37,7 +37,7 @@ *

    * Note that when you pass a seed to {@code scan} the resulting Observable will emit that seed as its * first emitted item. - * + * * @param the aggregate and output type * @param the input value type */ @@ -51,7 +51,7 @@ public final class OperatorScan implements Operator { /** * Applies an accumulator function over an observable sequence and returns each intermediate result with the * specified source and accumulator. - * + * * @param initialValue * the initial (seed) accumulator value * @param accumulator @@ -66,10 +66,10 @@ public OperatorScan(final R initialValue, Func2 accumulator) { public R call() { return initialValue; } - + }, accumulator); } - + public OperatorScan(Func0 initialValueFactory, Func2 accumulator) { this.initialValueFactory = initialValueFactory; this.accumulator = accumulator; @@ -78,7 +78,7 @@ public OperatorScan(Func0 initialValueFactory, Func2 accumul /** * Applies an accumulator function over an observable sequence and returns each intermediate result with the * specified source and accumulator. - * + * * @param accumulator * an accumulator function to be invoked on each element from the sequence * @see Observable.Scan(TSource) Method (IObservable(TSource), Func(TSource, TSource, TSource)) @@ -91,7 +91,7 @@ public OperatorScan(final Func2 accumulator) { @Override public Subscriber call(final Subscriber child) { final R initialValue = initialValueFactory.call(); - + if (initialValue == NO_INITIAL_VALUE) { return new Subscriber(child) { boolean once; @@ -125,9 +125,9 @@ public void onCompleted() { } }; } - + final InitialProducer ip = new InitialProducer(initialValue, child); - + Subscriber parent = new Subscriber() { private R value = initialValue; @@ -153,22 +153,22 @@ public void onError(Throwable e) { public void onCompleted() { ip.onCompleted(); } - + @Override public void setProducer(final Producer producer) { ip.setProducer(producer); } }; - + child.add(parent); child.setProducer(ip); return parent; } - + static final class InitialProducer implements Producer, Observer { final Subscriber child; final Queue queue; - + boolean emitting; /** Missed a terminal event. */ boolean missed; @@ -178,10 +178,10 @@ static final class InitialProducer implements Producer, Observer { final AtomicLong requested; /** The current producer. */ volatile Producer producer; - + volatile boolean done; Throwable error; - + public InitialProducer(R initialValue, Subscriber child) { this.child = child; Queue q; @@ -192,16 +192,16 @@ public InitialProducer(R initialValue, Subscriber child) { q = new SpscLinkedAtomicQueue(); // new SpscUnboundedAtomicArrayQueue(8); } this.queue = q; - q.offer(NotificationLite.instance().next(initialValue)); + q.offer(NotificationLite.next(initialValue)); this.requested = new AtomicLong(); } - + @Override public void onNext(R t) { - queue.offer(NotificationLite.instance().next(t)); + queue.offer(NotificationLite.next(t)); emit(); } - + boolean checkTerminated(boolean d, boolean empty, Subscriber child) { if (child.isUnsubscribed()) { return true; @@ -219,20 +219,20 @@ boolean checkTerminated(boolean d, boolean empty, Subscriber child) { } return false; } - + @Override public void onError(Throwable e) { error = e; done = true; emit(); } - + @Override public void onCompleted() { done = true; emit(); } - + @Override public void request(long n) { if (n < 0L) { @@ -257,7 +257,7 @@ public void request(long n) { emit(); } } - + public void setProducer(Producer p) { if (p == null) { throw new NullPointerException(); @@ -277,13 +277,13 @@ public void setProducer(Producer p) { missedRequested = 0L; producer = p; } - + if (mr > 0L) { p.request(mr); } emit(); } - + void emit() { synchronized (this) { if (emitting) { @@ -294,23 +294,21 @@ void emit() { } emitLoop(); } - + void emitLoop() { final Subscriber child = this.child; final Queue queue = this.queue; - final NotificationLite nl = NotificationLite.instance(); AtomicLong requested = this.requested; - + long r = requested.get(); for (;;) { - boolean max = r == Long.MAX_VALUE; boolean d = done; boolean empty = queue.isEmpty(); if (checkTerminated(d, empty, child)) { return; } long e = 0L; - while (r != 0L) { + while (e != r) { d = done; Object o = queue.poll(); empty = o == null; @@ -320,21 +318,20 @@ void emitLoop() { if (empty) { break; } - R v = nl.getValue(o); + R v = NotificationLite.getValue(o); try { child.onNext(v); } catch (Throwable ex) { Exceptions.throwOrReport(ex, child, v); return; } - r--; - e--; + e++; } - - if (e != 0 && !max) { - r = requested.addAndGet(e); + + if (e != 0 && r != Long.MAX_VALUE) { + r = BackpressureUtils.produced(requested, e); } - + synchronized (this) { if (!missed) { emitting = false; diff --git a/src/main/java/rx/internal/operators/OperatorSequenceEqual.java b/src/main/java/rx/internal/operators/OperatorSequenceEqual.java index 53deb16edf..cef1ab9aa7 100644 --- a/src/main/java/rx/internal/operators/OperatorSequenceEqual.java +++ b/src/main/java/rx/internal/operators/OperatorSequenceEqual.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -15,12 +15,10 @@ */ package rx.internal.operators; -import static rx.Observable.concat; -import static rx.Observable.just; -import static rx.Observable.zip; +import static rx.Observable.*; + import rx.Observable; -import rx.functions.Func1; -import rx.functions.Func2; +import rx.functions.*; import rx.internal.util.UtilityFunctions; /** @@ -28,22 +26,16 @@ * {@code Observable}s emit sequences of items that are equivalent to each other. */ public final class OperatorSequenceEqual { + + /** NotificationLite doesn't work as zip uses it. */ + static final Object LOCAL_ON_COMPLETED = new Object(); + private OperatorSequenceEqual() { throw new IllegalStateException("No instances!"); } - /** NotificationLite doesn't work as zip uses it. */ - static final Object LOCAL_ONCOMPLETED = new Object(); static Observable materializeLite(Observable source) { - return concat( - source.map(new Func1() { - - @Override - public Object call(T t1) { - return t1; - } - - }), just(LOCAL_ONCOMPLETED)); + return concat(source, just(LOCAL_ON_COMPLETED)); } /** @@ -73,8 +65,8 @@ public static Observable sequenceEqual( @Override @SuppressWarnings("unchecked") public Boolean call(Object t1, Object t2) { - boolean c1 = t1 == LOCAL_ONCOMPLETED; - boolean c2 = t2 == LOCAL_ONCOMPLETED; + boolean c1 = t1 == LOCAL_ON_COMPLETED; + boolean c2 = t2 == LOCAL_ON_COMPLETED; if (c1 && c2) { return true; } diff --git a/src/main/java/rx/internal/operators/OperatorSerialize.java b/src/main/java/rx/internal/operators/OperatorSerialize.java index 9c313f450e..98dc2455b4 100644 --- a/src/main/java/rx/internal/operators/OperatorSerialize.java +++ b/src/main/java/rx/internal/operators/OperatorSerialize.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -21,7 +21,7 @@ public final class OperatorSerialize implements Operator { /** Lazy initialization via inner-class holder. */ - private static final class Holder { + static final class Holder { /** A singleton instance. */ static final OperatorSerialize INSTANCE = new OperatorSerialize(); } @@ -33,7 +33,11 @@ private static final class Holder { public static OperatorSerialize instance() { return (OperatorSerialize)Holder.INSTANCE; } - OperatorSerialize() { } + + OperatorSerialize() { + // singleton + } + @Override public Subscriber call(final Subscriber s) { return new SerializedSubscriber(new Subscriber(s) { diff --git a/src/main/java/rx/internal/operators/OperatorSingle.java b/src/main/java/rx/internal/operators/OperatorSingle.java index 2ecdbfecb0..81aba32564 100644 --- a/src/main/java/rx/internal/operators/OperatorSingle.java +++ b/src/main/java/rx/internal/operators/OperatorSingle.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -20,7 +20,7 @@ import rx.Observable.Operator; import rx.Subscriber; import rx.internal.producers.SingleProducer; -import rx.internal.util.RxJavaPluginUtils; +import rx.plugins.RxJavaHooks; /** * If the Observable completes after emitting a single item that matches a @@ -33,14 +33,14 @@ public final class OperatorSingle implements Operator { private final boolean hasDefaultValue; private final T defaultValue; - private static class Holder { + static final class Holder { final static OperatorSingle INSTANCE = new OperatorSingle(); } - + /** - * Returns a singleton instance of OperatorSingle (if the stream is empty or has + * Returns a singleton instance of OperatorSingle (if the stream is empty or has * more than one element an error will be emitted) that is cast to the generic type. - * + * * @param the value type * @return a singleton instance of an Operator that will emit a single value only unless the stream has zero or more than one element in which case it will emit an error. */ @@ -71,16 +71,16 @@ public Subscriber call(final Subscriber child) { return parent; } - private static final class ParentSubscriber extends Subscriber { + static final class ParentSubscriber extends Subscriber { private final Subscriber child; private final boolean hasDefaultValue; private final T defaultValue; - + private T value; private boolean isNonEmpty; private boolean hasTooManyElements; - + ParentSubscriber(Subscriber child, boolean hasDefaultValue, T defaultValue) { this.child = child; @@ -91,24 +91,21 @@ private static final class ParentSubscriber extends Subscriber { @Override public void onNext(T value) { - if (hasTooManyElements) { - return; - } else - if (isNonEmpty) { - hasTooManyElements = true; - child.onError(new IllegalArgumentException("Sequence contains too many elements")); - unsubscribe(); - } else { - this.value = value; - isNonEmpty = true; + if (!hasTooManyElements) { + if (isNonEmpty) { + hasTooManyElements = true; + child.onError(new IllegalArgumentException("Sequence contains too many elements")); + unsubscribe(); + } else { + this.value = value; + isNonEmpty = true; + } } } @Override public void onCompleted() { - if (hasTooManyElements) { - // We have already sent an onError message - } else { + if (!hasTooManyElements) { if (isNonEmpty) { child.setProducer(new SingleProducer(child, value)); } else { @@ -119,15 +116,16 @@ public void onCompleted() { } } } + // Otherwise we have already sent an onError message } @Override public void onError(Throwable e) { if (hasTooManyElements) { - RxJavaPluginUtils.handleException(e); + RxJavaHooks.onError(e); return; } - + child.onError(e); } diff --git a/src/main/java/rx/internal/operators/OperatorSkip.java b/src/main/java/rx/internal/operators/OperatorSkip.java index 6b06fe47b4..5e7ecebd91 100644 --- a/src/main/java/rx/internal/operators/OperatorSkip.java +++ b/src/main/java/rx/internal/operators/OperatorSkip.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -42,7 +42,7 @@ public OperatorSkip(int n) { public Subscriber call(final Subscriber child) { return new Subscriber(child) { - int skipped = 0; + int skipped; @Override public void onCompleted() { diff --git a/src/main/java/rx/internal/operators/OperatorSkipLast.java b/src/main/java/rx/internal/operators/OperatorSkipLast.java index 87276382bb..de5acf1349 100644 --- a/src/main/java/rx/internal/operators/OperatorSkipLast.java +++ b/src/main/java/rx/internal/operators/OperatorSkipLast.java @@ -15,8 +15,7 @@ */ package rx.internal.operators; -import java.util.ArrayDeque; -import java.util.Deque; +import java.util.*; import rx.Observable.Operator; import rx.Subscriber; @@ -40,8 +39,6 @@ public OperatorSkipLast(int count) { public Subscriber call(final Subscriber subscriber) { return new Subscriber(subscriber) { - private final NotificationLite on = NotificationLite.instance(); - /** * Store the last count elements until now. */ @@ -67,11 +64,11 @@ public void onNext(T value) { return; } if (deque.size() == count) { - subscriber.onNext(on.getValue(deque.removeFirst())); + subscriber.onNext(NotificationLite.getValue(deque.removeFirst())); } else { request(1); } - deque.offerLast(on.next(value)); + deque.offerLast(NotificationLite.next(value)); } }; diff --git a/src/main/java/rx/internal/operators/OperatorSkipLastTimed.java b/src/main/java/rx/internal/operators/OperatorSkipLastTimed.java index b3fe766308..1662dffbc2 100644 --- a/src/main/java/rx/internal/operators/OperatorSkipLastTimed.java +++ b/src/main/java/rx/internal/operators/OperatorSkipLastTimed.java @@ -15,13 +15,11 @@ */ package rx.internal.operators; -import java.util.ArrayDeque; -import java.util.Deque; +import java.util.*; import java.util.concurrent.TimeUnit; +import rx.*; import rx.Observable.Operator; -import rx.Scheduler; -import rx.Subscriber; import rx.schedulers.Timestamped; /** diff --git a/src/main/java/rx/internal/operators/OperatorSkipTimed.java b/src/main/java/rx/internal/operators/OperatorSkipTimed.java deleted file mode 100644 index 77e1fe1322..0000000000 --- a/src/main/java/rx/internal/operators/OperatorSkipTimed.java +++ /dev/null @@ -1,80 +0,0 @@ -/** - * Copyright 2014 Netflix, Inc. - * - * 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 rx.internal.operators; - -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; -import rx.Observable.Operator; -import rx.Scheduler; -import rx.Scheduler.Worker; -import rx.Subscriber; -import rx.functions.Action0; - -/** - * Skips elements until a specified time elapses. - * @param the value type - */ -public final class OperatorSkipTimed implements Operator { - final long time; - final TimeUnit unit; - final Scheduler scheduler; - - public OperatorSkipTimed(long time, TimeUnit unit, Scheduler scheduler) { - this.time = time; - this.unit = unit; - this.scheduler = scheduler; - } - - @Override - public Subscriber call(final Subscriber child) { - final Worker worker = scheduler.createWorker(); - child.add(worker); - final AtomicBoolean gate = new AtomicBoolean(); - worker.schedule(new Action0() { - @Override - public void call() { - gate.set(true); - } - }, time, unit); - return new Subscriber(child) { - - @Override - public void onNext(T t) { - if (gate.get()) { - child.onNext(t); - } - } - - @Override - public void onError(Throwable e) { - try { - child.onError(e); - } finally { - unsubscribe(); - } - } - - @Override - public void onCompleted() { - try { - child.onCompleted(); - } finally { - unsubscribe(); - } - } - }; - } -} diff --git a/src/main/java/rx/internal/operators/OperatorSkipUntil.java b/src/main/java/rx/internal/operators/OperatorSkipUntil.java index e4270b2010..cde5238a40 100644 --- a/src/main/java/rx/internal/operators/OperatorSkipUntil.java +++ b/src/main/java/rx/internal/operators/OperatorSkipUntil.java @@ -16,19 +16,19 @@ package rx.internal.operators; import java.util.concurrent.atomic.AtomicBoolean; -import rx.Observable; + +import rx.*; import rx.Observable.Operator; -import rx.Subscriber; import rx.observers.SerializedSubscriber; /** * Skip elements from the source Observable until the secondary * observable fires an element. - * + * * If the secondary Observable fires no elements, the primary won't fire any elements. - * + * * @see MSDN: Observable.SkipUntil - * + * * @param the source and result value type * @param element type of the signalling observable */ @@ -65,7 +65,7 @@ public void onCompleted() { }; child.add(u); other.unsafeSubscribe(u); - + return new Subscriber(child) { @Override public void onNext(T t) { diff --git a/src/main/java/rx/internal/operators/OperatorSkipWhile.java b/src/main/java/rx/internal/operators/OperatorSkipWhile.java index b62e7f24a5..70c7ed75cd 100644 --- a/src/main/java/rx/internal/operators/OperatorSkipWhile.java +++ b/src/main/java/rx/internal/operators/OperatorSkipWhile.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -18,8 +18,7 @@ import rx.Observable.Operator; import rx.Subscriber; import rx.exceptions.Exceptions; -import rx.functions.Func1; -import rx.functions.Func2; +import rx.functions.*; /** * Skips any emitted source items as long as the specified condition holds true. Emits all further source items @@ -42,7 +41,7 @@ public void onNext(T t) { if (!skipping) { child.onNext(t); } else { - final boolean skip; + boolean skip; try { skip = predicate.call(t, index++); } catch (Throwable e) { @@ -69,11 +68,11 @@ public void onCompleted() { } }; } - /** + /** * Convert to Func2 type predicate. * @param the input value type * @param predicate the single argument predicate function - * @return The two argument function which ignores its second parameter + * @return The two argument function which ignores its second parameter */ public static Func2 toPredicate2(final Func1 predicate) { return new Func2() { diff --git a/src/main/java/rx/internal/operators/OperatorSubscribeOn.java b/src/main/java/rx/internal/operators/OperatorSubscribeOn.java index 70bc2fa592..0be1b01829 100644 --- a/src/main/java/rx/internal/operators/OperatorSubscribeOn.java +++ b/src/main/java/rx/internal/operators/OperatorSubscribeOn.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -24,75 +24,99 @@ * Subscribes Observers on the specified {@code Scheduler}. *

    * - * + * * @param the value type of the actual source */ public final class OperatorSubscribeOn implements OnSubscribe { final Scheduler scheduler; final Observable source; + final boolean requestOn; - public OperatorSubscribeOn(Observable source, Scheduler scheduler) { + public OperatorSubscribeOn(Observable source, Scheduler scheduler, boolean requestOn) { this.scheduler = scheduler; this.source = source; + this.requestOn = requestOn; } @Override public void call(final Subscriber subscriber) { final Worker inner = scheduler.createWorker(); + + SubscribeOnSubscriber parent = new SubscribeOnSubscriber(subscriber, requestOn, inner, source); + subscriber.add(parent); subscriber.add(inner); - - inner.schedule(new Action0() { - @Override - public void call() { - final Thread t = Thread.currentThread(); - - Subscriber s = new Subscriber(subscriber) { - @Override - public void onNext(T t) { - subscriber.onNext(t); - } - - @Override - public void onError(Throwable e) { - try { - subscriber.onError(e); - } finally { - inner.unsubscribe(); - } - } - - @Override - public void onCompleted() { - try { - subscriber.onCompleted(); - } finally { - inner.unsubscribe(); - } - } - - @Override - public void setProducer(final Producer p) { - subscriber.setProducer(new Producer() { + + inner.schedule(parent); + } + + static final class SubscribeOnSubscriber extends Subscriber implements Action0 { + + final Subscriber actual; + + final boolean requestOn; + + final Worker worker; + + Observable source; + + Thread t; + + SubscribeOnSubscriber(Subscriber actual, boolean requestOn, Worker worker, Observable source) { + this.actual = actual; + this.requestOn = requestOn; + this.worker = worker; + this.source = source; + } + + @Override + public void onNext(T t) { + actual.onNext(t); + } + + @Override + public void onError(Throwable e) { + try { + actual.onError(e); + } finally { + worker.unsubscribe(); + } + } + + @Override + public void onCompleted() { + try { + actual.onCompleted(); + } finally { + worker.unsubscribe(); + } + } + + @Override + public void call() { + Observable src = source; + source = null; + t = Thread.currentThread(); + src.unsafeSubscribe(this); + } + + @Override + public void setProducer(final Producer p) { + actual.setProducer(new Producer() { + @Override + public void request(final long n) { + if (t == Thread.currentThread() || !requestOn) { + p.request(n); + } else { + worker.schedule(new Action0() { @Override - public void request(final long n) { - if (t == Thread.currentThread()) { - p.request(n); - } else { - inner.schedule(new Action0() { - @Override - public void call() { - p.request(n); - } - }); - } + public void call() { + p.request(n); } }); } - }; - - source.unsafeSubscribe(s); - } - }); + } + }); + } } } \ No newline at end of file diff --git a/src/main/java/rx/internal/operators/OperatorSwitch.java b/src/main/java/rx/internal/operators/OperatorSwitch.java index c76e3008c9..e50e7cafc9 100644 --- a/src/main/java/rx/internal/operators/OperatorSwitch.java +++ b/src/main/java/rx/internal/operators/OperatorSwitch.java @@ -33,17 +33,18 @@ * emits the items emitted by the most recently published of those Observables. *

    * - * + * * @param the value type */ public final class OperatorSwitch implements Operator> { + final boolean delayError; /** Lazy initialization via inner-class holder. */ - private static final class Holder { + static final class Holder { /** A singleton instance. */ static final OperatorSwitch INSTANCE = new OperatorSwitch(false); } /** Lazy initialization via inner-class holder. */ - private static final class HolderDelayError { + static final class HolderDelayError { /** A singleton instance. */ static final OperatorSwitch INSTANCE = new OperatorSwitch(true); } @@ -61,9 +62,7 @@ public static OperatorSwitch instance(boolean delayError) { return (OperatorSwitch)Holder.INSTANCE; } - final boolean delayError; - - OperatorSwitch(boolean delayError) { + OperatorSwitch(boolean delayError) { this.delayError = delayError; } @@ -75,48 +74,46 @@ public Subscriber> call(final Subscriber extends Subscriber> { + static final class SwitchSubscriber extends Subscriber> { final Subscriber child; - final SerialSubscription ssub; + final SerialSubscription serial; final boolean delayError; final AtomicLong index; final SpscLinkedArrayQueue queue; - final NotificationLite nl; boolean emitting; - + boolean missed; - + long requested; - + Producer producer; - + volatile boolean mainDone; Throwable error; - + boolean innerActive; - + static final Throwable TERMINAL_ERROR = new Throwable("Terminal error"); SwitchSubscriber(Subscriber child, boolean delayError) { this.child = child; - this.ssub = new SerialSubscription(); + this.serial = new SerialSubscription(); this.delayError = delayError; this.index = new AtomicLong(); this.queue = new SpscLinkedArrayQueue(RxRingBuffer.SIZE); - this.nl = NotificationLite.instance(); } - + void init() { - child.add(ssub); + child.add(serial); child.add(Subscriptions.create(new Action0() { @Override public void call() { clearProducer(); } })); - child.setProducer(new Producer(){ + child.setProducer(new Producer() { @Override public void request(long n) { @@ -129,39 +126,39 @@ public void request(long n) { } }); } - + void clearProducer() { synchronized (this) { producer = null; } } - + @Override public void onNext(Observable t) { long id = index.incrementAndGet(); - - Subscription s = ssub.get(); + + Subscription s = serial.get(); if (s != null) { s.unsubscribe(); } - + InnerSubscriber inner; - + synchronized (this) { inner = new InnerSubscriber(id, this); innerActive = true; producer = null; } - ssub.set(inner); - + serial.set(inner); + t.unsafeSubscribe(inner); } @Override public void onError(Throwable e) { boolean success; - + synchronized (this) { success = updateError(e); } @@ -190,20 +187,20 @@ boolean updateError(Throwable next) { } return true; } - + @Override public void onCompleted() { mainDone = true; drain(); } - + void emit(T value, InnerSubscriber inner) { synchronized (this) { if (index.get() != inner.id) { return; } - - queue.offer(inner, nl.next(value)); + + queue.offer(inner, NotificationLite.next(value)); } drain(); } @@ -225,7 +222,7 @@ void error(Throwable e, long id) { pluginError(e); } } - + void complete(long id) { synchronized (this) { if (index.get() != id) { @@ -240,7 +237,7 @@ void complete(long id) { void pluginError(Throwable e) { RxJavaHooks.onError(e); } - + void innerProducer(Producer p, long id) { long n; synchronized (this) { @@ -250,10 +247,10 @@ void innerProducer(Producer p, long id) { n = requested; producer = p; } - + p.request(n); } - + void childRequested(long n) { Producer p; synchronized (this) { @@ -265,9 +262,8 @@ void childRequested(long n) { } drain(); } - + void drain() { - boolean localMainDone = mainDone; boolean localInnerActive; long localRequested; Throwable localError; @@ -288,6 +284,7 @@ void drain() { final SpscLinkedArrayQueue localQueue = queue; final AtomicLong localIndex = index; final Subscriber localChild = child; + boolean localMainDone = mainDone; for (;;) { @@ -299,52 +296,52 @@ void drain() { } boolean empty = localQueue.isEmpty(); - - if (checkTerminated(localMainDone, localInnerActive, localError, + + if (checkTerminated(localMainDone, localInnerActive, localError, localQueue, localChild, empty)) { return; } - + if (empty) { break; } - + @SuppressWarnings("unchecked") InnerSubscriber inner = (InnerSubscriber)localQueue.poll(); - T value = nl.getValue(localQueue.poll()); - + T value = NotificationLite.getValue(localQueue.poll()); + if (localIndex.get() == inner.id) { localChild.onNext(value); localEmission++; } } - + if (localEmission == localRequested) { if (localChild.isUnsubscribed()) { return; } - - if (checkTerminated(mainDone, localInnerActive, localError, localQueue, + + if (checkTerminated(mainDone, localInnerActive, localError, localQueue, localChild, localQueue.isEmpty())) { return; } } - - + + synchronized (this) { - + localRequested = requested; if (localRequested != Long.MAX_VALUE) { localRequested -= localEmission; requested = localRequested; } - + if (!missed) { emitting = false; return; } missed = false; - + localMainDone = mainDone; localInnerActive = innerActive; localError = error; @@ -380,7 +377,7 @@ protected boolean checkTerminated(boolean localMainDone, boolean localInnerActiv return false; } } - + static final class InnerSubscriber extends Subscriber { private final long id; @@ -391,7 +388,7 @@ static final class InnerSubscriber extends Subscriber { this.id = id; this.parent = parent; } - + @Override public void setProducer(Producer p) { parent.innerProducer(p, id); diff --git a/src/main/java/rx/internal/operators/OperatorTake.java b/src/main/java/rx/internal/operators/OperatorTake.java index d08c5a3b92..d49f155d9d 100644 --- a/src/main/java/rx/internal/operators/OperatorTake.java +++ b/src/main/java/rx/internal/operators/OperatorTake.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -17,9 +17,9 @@ import java.util.concurrent.atomic.AtomicLong; +import rx.*; import rx.Observable.Operator; -import rx.Producer; -import rx.Subscriber; +import rx.plugins.RxJavaHooks; /** * An {@code Observable} that emits the first {@code num} items emitted by the source {@code Observable}. @@ -67,6 +67,8 @@ public void onError(Throwable e) { } finally { unsubscribe(); } + } else { + RxJavaHooks.onError(e); } } @@ -92,21 +94,21 @@ public void onNext(T i) { @Override public void setProducer(final Producer producer) { child.setProducer(new Producer() { - + // keeps track of requests up to maximum of `limit` final AtomicLong requested = new AtomicLong(0); - + @Override public void request(long n) { - if (n >0 && !completed) { - // because requests may happen concurrently use a CAS loop to + if (n > 0 && !completed) { + // because requests may happen concurrently use a CAS loop to // ensure we only request as much as needed, no more no less while (true) { long r = requested.get(); long c = Math.min(n, limit - r); - if (c == 0) + if (c == 0) { break; - else if (requested.compareAndSet(r, r + c)) { + } else if (requested.compareAndSet(r, r + c)) { producer.request(c); break; } @@ -126,9 +128,9 @@ else if (requested.compareAndSet(r, r + c)) { /* * We decouple the parent and child subscription so there can be multiple take() in a chain such as for * the groupBy Observer use case where you may take(1) on groups and take(20) on the children. - * + * * Thus, we only unsubscribe UPWARDS to the parent and an onComplete DOWNSTREAM. - * + * * However, if we receive an unsubscribe from the child we still want to propagate it upwards so we * register 'parent' with 'child' */ diff --git a/src/main/java/rx/internal/operators/OperatorTakeLast.java b/src/main/java/rx/internal/operators/OperatorTakeLast.java index 77f8c93993..d25d06be3d 100644 --- a/src/main/java/rx/internal/operators/OperatorTakeLast.java +++ b/src/main/java/rx/internal/operators/OperatorTakeLast.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -26,7 +26,7 @@ * Returns an Observable that emits the at most the last count items emitted by the source Observable. *

    * - * + * * @param the value type */ public final class OperatorTakeLast implements Operator { @@ -43,7 +43,7 @@ public OperatorTakeLast(int count) { @Override public Subscriber call(final Subscriber subscriber) { final TakeLastSubscriber parent = new TakeLastSubscriber(subscriber, count); - + subscriber.add(parent); subscriber.setProducer(new Producer() { @Override @@ -51,49 +51,47 @@ public void request(long n) { parent.requestMore(n); } }); - + return parent; } - + static final class TakeLastSubscriber extends Subscriber implements Func1 { final Subscriber actual; final AtomicLong requested; final ArrayDeque queue; final int count; - final NotificationLite nl; - + public TakeLastSubscriber(Subscriber actual, int count) { this.actual = actual; this.count = count; this.requested = new AtomicLong(); this.queue = new ArrayDeque(); - this.nl = NotificationLite.instance(); } - + @Override public void onNext(T t) { if (queue.size() == count) { queue.poll(); } - queue.offer(nl.next(t)); + queue.offer(NotificationLite.next(t)); } - + @Override public void onError(Throwable e) { queue.clear(); actual.onError(e); } - + @Override public void onCompleted() { BackpressureUtils.postCompleteDone(requested, queue, actual, this); } - + @Override public T call(Object t) { - return nl.getValue(t); + return NotificationLite.getValue(t); } - + void requestMore(long n) { if (n > 0L) { BackpressureUtils.postCompleteRequest(requested, n, queue, actual, this); diff --git a/src/main/java/rx/internal/operators/OperatorTakeLastOne.java b/src/main/java/rx/internal/operators/OperatorTakeLastOne.java deleted file mode 100644 index 2c20f2a465..0000000000 --- a/src/main/java/rx/internal/operators/OperatorTakeLastOne.java +++ /dev/null @@ -1,190 +0,0 @@ -/** - * Copyright 2016 Netflix, Inc. - * - * 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 rx.internal.operators; - -import java.util.concurrent.atomic.AtomicInteger; - -import rx.Observable.Operator; -import rx.exceptions.Exceptions; -import rx.Producer; -import rx.Subscriber; - -public class OperatorTakeLastOne implements Operator { - - private static class Holder { - static final OperatorTakeLastOne INSTANCE = new OperatorTakeLastOne(); - } - - @SuppressWarnings("unchecked") - public static OperatorTakeLastOne instance() { - return (OperatorTakeLastOne) Holder.INSTANCE; - } - - OperatorTakeLastOne() { - - } - - @Override - public Subscriber call(Subscriber child) { - final ParentSubscriber parent = new ParentSubscriber(child); - child.setProducer(new Producer() { - - @Override - public void request(long n) { - parent.requestMore(n); - } - }); - child.add(parent); - return parent; - } - - private static class ParentSubscriber extends Subscriber { - - private final static int NOT_REQUESTED_NOT_COMPLETED = 0; - private final static int NOT_REQUESTED_COMPLETED = 1; - private final static int REQUESTED_NOT_COMPLETED = 2; - private final static int REQUESTED_COMPLETED = 3; - - /* - * These are the expected state transitions: - * - * NOT_REQUESTED_NOT_COMPLETED --> REQUESTED_NOT_COMPLETED - * | | - * V V - * NOT_REQUESTED_COMPLETED --> REQUESTED_COMPLETED - * - * Once at REQUESTED_COMPLETED we emit the last value if one exists - */ - - // Used as the initial value of last - private static final Object ABSENT = new Object(); - - // the downstream subscriber - private final Subscriber child; - - @SuppressWarnings("unchecked") - // we can get away with this cast at runtime because of type erasure - private T last = (T) ABSENT; - - // holds the current state of the stream so that we can make atomic - // updates to it - private final AtomicInteger state = new AtomicInteger(NOT_REQUESTED_NOT_COMPLETED); - - ParentSubscriber(Subscriber child) { - this.child = child; - } - - void requestMore(long n) { - if (n > 0) { - // CAS loop to atomically change state given that onCompleted() - // or another requestMore() may be acting concurrently - while (true) { - // read the value of state and then try state transitions - // only if the value of state does not change in the - // meantime (in another requestMore() or onCompleted()). If - // the value has changed and we expect to do a transition - // still then we loop and try again. - final int s = state.get(); - if (s == NOT_REQUESTED_NOT_COMPLETED) { - if (state.compareAndSet(NOT_REQUESTED_NOT_COMPLETED, - REQUESTED_NOT_COMPLETED)) { - return; - } - } else if (s == NOT_REQUESTED_COMPLETED) { - if (state.compareAndSet(NOT_REQUESTED_COMPLETED, REQUESTED_COMPLETED)) { - emit(); - return; - } - } else - // already requested so we exit - return; - } - } - } - - @Override - public void onCompleted() { - //shortcut if an empty stream - if (last == ABSENT) { - child.onCompleted(); - return; - } - // CAS loop to atomically change state given that requestMore() - // may be acting concurrently - while (true) { - // read the value of state and then try state transitions - // only if the value of state does not change in the meantime - // (in another requestMore()). If the value has changed and - // we expect to do a transition still then we loop and try - // again. - final int s = state.get(); - if (s == NOT_REQUESTED_NOT_COMPLETED) { - if (state.compareAndSet(NOT_REQUESTED_NOT_COMPLETED, NOT_REQUESTED_COMPLETED)) { - return; - } - } else if (s == REQUESTED_NOT_COMPLETED) { - if (state.compareAndSet(REQUESTED_NOT_COMPLETED, REQUESTED_COMPLETED)) { - emit(); - return; - } - } else - // already completed so we exit - return; - } - } - - /** - * If not unsubscribed then emits last value and completed to the child - * subscriber. - */ - private void emit() { - if (isUnsubscribed()) { - // release for gc - last = null; - return; - } - // Note that last is safely published despite not being volatile - // because a CAS update must have happened in the current thread just before - // emit() was called - T t = last; - // release for gc - last = null; - if (t != ABSENT) { - try { - child.onNext(t); - } catch (Throwable e) { - Exceptions.throwOrReport(e, child); - return; - } - } - if (!isUnsubscribed()) - child.onCompleted(); - } - - @Override - public void onError(Throwable e) { - child.onError(e); - } - - @Override - public void onNext(T t) { - last = t; - } - - } - -} diff --git a/src/main/java/rx/internal/operators/OperatorTakeLastTimed.java b/src/main/java/rx/internal/operators/OperatorTakeLastTimed.java index 383f41715c..b05e6e77aa 100644 --- a/src/main/java/rx/internal/operators/OperatorTakeLastTimed.java +++ b/src/main/java/rx/internal/operators/OperatorTakeLastTimed.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -27,7 +27,7 @@ * Returns an Observable that emits the last count items emitted by the source Observable. *

    * - * + * * @param the value type */ public final class OperatorTakeLastTimed implements Operator { @@ -54,7 +54,7 @@ public OperatorTakeLastTimed(int count, long time, TimeUnit unit, Scheduler sche @Override public Subscriber call(final Subscriber subscriber) { final TakeLastTimedSubscriber parent = new TakeLastTimedSubscriber(subscriber, count, ageMillis, scheduler); - + subscriber.add(parent); subscriber.setProducer(new Producer() { @Override @@ -62,10 +62,10 @@ public void request(long n) { parent.requestMore(n); } }); - + return parent; } - + static final class TakeLastTimedSubscriber extends Subscriber implements Func1 { final Subscriber actual; final long ageMillis; @@ -74,7 +74,6 @@ static final class TakeLastTimedSubscriber extends Subscriber implements F final AtomicLong requested; final ArrayDeque queue; final ArrayDeque queueTimes; - final NotificationLite nl; public TakeLastTimedSubscriber(Subscriber actual, int count, long ageMillis, Scheduler scheduler) { this.actual = actual; @@ -84,22 +83,21 @@ public TakeLastTimedSubscriber(Subscriber actual, int count, long age this.requested = new AtomicLong(); this.queue = new ArrayDeque(); this.queueTimes = new ArrayDeque(); - this.nl = NotificationLite.instance(); } - + @Override public void onNext(T t) { if (count != 0) { long now = scheduler.now(); - + if (queue.size() == count) { queue.poll(); queueTimes.poll(); } - + evictOld(now); - - queue.offer(nl.next(t)); + + queue.offer(NotificationLite.next(t)); queueTimes.offer(now); } } @@ -115,28 +113,28 @@ protected void evictOld(long now) { queueTimes.poll(); } } - + @Override public void onError(Throwable e) { queue.clear(); queueTimes.clear(); actual.onError(e); } - + @Override public void onCompleted() { evictOld(scheduler.now()); - + queueTimes.clear(); - + BackpressureUtils.postCompleteDone(requested, queue, actual, this); } - + @Override public T call(Object t) { - return nl.getValue(t); + return NotificationLite.getValue(t); } - + void requestMore(long n) { BackpressureUtils.postCompleteRequest(requested, n, queue, actual, this); } diff --git a/src/main/java/rx/internal/operators/OperatorTakeTimed.java b/src/main/java/rx/internal/operators/OperatorTakeTimed.java index faa95b4b79..1b1aa0726c 100644 --- a/src/main/java/rx/internal/operators/OperatorTakeTimed.java +++ b/src/main/java/rx/internal/operators/OperatorTakeTimed.java @@ -16,16 +16,16 @@ package rx.internal.operators; import java.util.concurrent.TimeUnit; + +import rx.*; import rx.Observable.Operator; -import rx.Scheduler; import rx.Scheduler.Worker; -import rx.Subscriber; import rx.functions.Action0; import rx.observers.SerializedSubscriber; /** * Takes values from the source until the specific time elapses. - * + * * @param * the result value type */ @@ -44,7 +44,7 @@ public OperatorTakeTimed(long time, TimeUnit unit, Scheduler scheduler) { public Subscriber call(Subscriber child) { Worker worker = scheduler.createWorker(); child.add(worker); - + TakeSubscriber ts = new TakeSubscriber(new SerializedSubscriber(child)); worker.schedule(ts, time, unit); return ts; @@ -78,7 +78,7 @@ public void onCompleted() { public void call() { onCompleted(); } - - + + } } diff --git a/src/main/java/rx/internal/operators/OperatorTakeUntil.java b/src/main/java/rx/internal/operators/OperatorTakeUntil.java index dad80632b4..3981855730 100644 --- a/src/main/java/rx/internal/operators/OperatorTakeUntil.java +++ b/src/main/java/rx/internal/operators/OperatorTakeUntil.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -15,9 +15,8 @@ */ package rx.internal.operators; -import rx.Observable; +import rx.*; import rx.Observable.Operator; -import rx.Subscriber; import rx.observers.SerializedSubscriber; /** @@ -39,7 +38,7 @@ public OperatorTakeUntil(final Observable other) { @Override public Subscriber call(final Subscriber child) { final Subscriber serial = new SerializedSubscriber(child, false); - + final Subscriber main = new Subscriber(serial, false) { @Override public void onNext(T t) { @@ -62,13 +61,13 @@ public void onCompleted() { } } }; - + final Subscriber so = new Subscriber() { @Override public void onStart() { request(Long.MAX_VALUE); } - + @Override public void onCompleted() { main.onCompleted(); @@ -88,9 +87,9 @@ public void onNext(E t) { serial.add(main); serial.add(so); - + child.add(serial); - + other.unsafeSubscribe(so); return main; diff --git a/src/main/java/rx/internal/operators/OperatorTakeUntilPredicate.java b/src/main/java/rx/internal/operators/OperatorTakeUntilPredicate.java index 8ed22f9108..339c69c51e 100644 --- a/src/main/java/rx/internal/operators/OperatorTakeUntilPredicate.java +++ b/src/main/java/rx/internal/operators/OperatorTakeUntilPredicate.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -27,10 +27,30 @@ * @param the value type */ public final class OperatorTakeUntilPredicate implements Operator { + final Func1 stopPredicate; + + public OperatorTakeUntilPredicate(final Func1 stopPredicate) { + this.stopPredicate = stopPredicate; + } + + @Override + public Subscriber call(final Subscriber child) { + final ParentSubscriber parent = new ParentSubscriber(child); + child.add(parent); // don't unsubscribe downstream + child.setProducer(new Producer() { + @Override + public void request(long n) { + parent.downstreamRequest(n); + } + }); + + return parent; + } + /** Subscriber returned to the upstream. */ - private final class ParentSubscriber extends Subscriber { + final class ParentSubscriber extends Subscriber { private final Subscriber child; - private boolean done = false; + private boolean done; ParentSubscriber(Subscriber child) { this.child = child; @@ -39,8 +59,8 @@ private final class ParentSubscriber extends Subscriber { @Override public void onNext(T t) { child.onNext(t); - - boolean stop = false; + + boolean stop; try { stop = stopPredicate.call(t); } catch (Throwable e) { @@ -73,25 +93,4 @@ void downstreamRequest(long n) { request(n); } } - - final Func1 stopPredicate; - - public OperatorTakeUntilPredicate(final Func1 stopPredicate) { - this.stopPredicate = stopPredicate; - } - - @Override - public Subscriber call(final Subscriber child) { - final ParentSubscriber parent = new ParentSubscriber(child); - child.add(parent); // don't unsubscribe downstream - child.setProducer(new Producer() { - @Override - public void request(long n) { - parent.downstreamRequest(n); - } - }); - - return parent; - } - } diff --git a/src/main/java/rx/internal/operators/OperatorTakeWhile.java b/src/main/java/rx/internal/operators/OperatorTakeWhile.java index 2a5fdc5bc0..9ee2147f2d 100644 --- a/src/main/java/rx/internal/operators/OperatorTakeWhile.java +++ b/src/main/java/rx/internal/operators/OperatorTakeWhile.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -48,9 +48,9 @@ public OperatorTakeWhile(Func2 predicate) { public Subscriber call(final Subscriber subscriber) { Subscriber s = new Subscriber(subscriber, false) { - private int counter = 0; + private int counter; - private boolean done = false; + private boolean done; @Override public void onNext(T t) { diff --git a/src/main/java/rx/internal/operators/OperatorThrottleFirst.java b/src/main/java/rx/internal/operators/OperatorThrottleFirst.java index 906b207856..f9093efa39 100644 --- a/src/main/java/rx/internal/operators/OperatorThrottleFirst.java +++ b/src/main/java/rx/internal/operators/OperatorThrottleFirst.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -38,20 +38,20 @@ public OperatorThrottleFirst(long windowDuration, TimeUnit unit, Scheduler sched public Subscriber call(final Subscriber subscriber) { return new Subscriber(subscriber) { - private long lastOnNext = 0; + private long lastOnNext = -1; @Override public void onStart() { request(Long.MAX_VALUE); } - + @Override public void onNext(T v) { long now = scheduler.now(); - if (lastOnNext == 0 || now - lastOnNext >= timeInMilliseconds) { + if (lastOnNext == -1 || now < lastOnNext || now - lastOnNext >= timeInMilliseconds) { lastOnNext = now; subscriber.onNext(v); - } + } } @Override diff --git a/src/main/java/rx/internal/operators/OperatorTimeInterval.java b/src/main/java/rx/internal/operators/OperatorTimeInterval.java index 606f4d529f..3900bc27aa 100644 --- a/src/main/java/rx/internal/operators/OperatorTimeInterval.java +++ b/src/main/java/rx/internal/operators/OperatorTimeInterval.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -15,9 +15,8 @@ */ package rx.internal.operators; +import rx.*; import rx.Observable.Operator; -import rx.Scheduler; -import rx.Subscriber; import rx.schedulers.TimeInterval; /** diff --git a/src/main/java/rx/internal/operators/OperatorTimeout.java b/src/main/java/rx/internal/operators/OperatorTimeout.java deleted file mode 100644 index 8234669508..0000000000 --- a/src/main/java/rx/internal/operators/OperatorTimeout.java +++ /dev/null @@ -1,60 +0,0 @@ -/** - * Copyright 2014 Netflix, Inc. - * - * 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 rx.internal.operators; - -import java.util.concurrent.TimeUnit; - -import rx.Observable; -import rx.Scheduler; -import rx.Subscription; -import rx.functions.Action0; - -/** - * Applies a timeout policy for each element in the observable sequence, using - * the specified scheduler to run timeout timers. If the next element isn't - * received within the specified timeout duration starting from its predecessor, - * the other observable sequence is used to produce future messages from that - * point on. - * @param the value type - */ -public final class OperatorTimeout extends OperatorTimeoutBase { - - public OperatorTimeout(final long timeout, final TimeUnit timeUnit, Observable other, Scheduler scheduler) { - super(new FirstTimeoutStub() { - - @Override - public Subscription call(final TimeoutSubscriber timeoutSubscriber, final Long seqId, Scheduler.Worker inner) { - return inner.schedule(new Action0() { - @Override - public void call() { - timeoutSubscriber.onTimeout(seqId); - } - }, timeout, timeUnit); - } - }, new TimeoutStub() { - - @Override - public Subscription call(final TimeoutSubscriber timeoutSubscriber, final Long seqId, T value, Scheduler.Worker inner) { - return inner.schedule(new Action0() { - @Override - public void call() { - timeoutSubscriber.onTimeout(seqId); - } - }, timeout, timeUnit); - } - }, other, scheduler); - } -} diff --git a/src/main/java/rx/internal/operators/OperatorTimeoutBase.java b/src/main/java/rx/internal/operators/OperatorTimeoutBase.java deleted file mode 100644 index 823831bc3a..0000000000 --- a/src/main/java/rx/internal/operators/OperatorTimeoutBase.java +++ /dev/null @@ -1,207 +0,0 @@ -/** - * Copyright 2014 Netflix, Inc. - * - * 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 rx.internal.operators; - -import java.util.concurrent.TimeoutException; - -import rx.*; -import rx.Observable.Operator; -import rx.functions.*; -import rx.internal.producers.ProducerArbiter; -import rx.observers.SerializedSubscriber; -import rx.subscriptions.SerialSubscription; - -class OperatorTimeoutBase implements Operator { - - /** - * Set up the timeout action on the first value. - * - * @param - */ - /* package-private */interface FirstTimeoutStub extends - Func3, Long, Scheduler.Worker, Subscription> { - } - - /** - * Set up the timeout action based on every value - * - * @param - */ - /* package-private */interface TimeoutStub extends - Func4, Long, T, Scheduler.Worker, Subscription> { - } - - final FirstTimeoutStub firstTimeoutStub; - final TimeoutStub timeoutStub; - final Observable other; - final Scheduler scheduler; - - /* package-private */OperatorTimeoutBase(FirstTimeoutStub firstTimeoutStub, TimeoutStub timeoutStub, Observable other, Scheduler scheduler) { - this.firstTimeoutStub = firstTimeoutStub; - this.timeoutStub = timeoutStub; - this.other = other; - this.scheduler = scheduler; - } - - @Override - public Subscriber call(Subscriber subscriber) { - Scheduler.Worker inner = scheduler.createWorker(); - subscriber.add(inner); - // Use SynchronizedSubscriber for safe memory access - // as the subscriber will be accessed in the current thread or the - // scheduler or other Observables. - final SerializedSubscriber synchronizedSubscriber = new SerializedSubscriber(subscriber); - - final SerialSubscription serial = new SerialSubscription(); - synchronizedSubscriber.add(serial); - - TimeoutSubscriber timeoutSubscriber = new TimeoutSubscriber(synchronizedSubscriber, timeoutStub, serial, other, inner); - - synchronizedSubscriber.add(timeoutSubscriber); - synchronizedSubscriber.setProducer(timeoutSubscriber.arbiter); - - serial.set(firstTimeoutStub.call(timeoutSubscriber, 0L, inner)); - - return timeoutSubscriber; - } - - /* package-private */static final class TimeoutSubscriber extends - Subscriber { - - final SerialSubscription serial; - - final SerializedSubscriber serializedSubscriber; - - final TimeoutStub timeoutStub; - - final Observable other; - - final Scheduler.Worker inner; - - final ProducerArbiter arbiter; - - /** Guarded by this. */ - boolean terminated; - /** Guarded by this. */ - long actual; - - TimeoutSubscriber( - SerializedSubscriber serializedSubscriber, - TimeoutStub timeoutStub, SerialSubscription serial, - Observable other, - Scheduler.Worker inner) { - this.serializedSubscriber = serializedSubscriber; - this.timeoutStub = timeoutStub; - this.serial = serial; - this.other = other; - this.inner = inner; - this.arbiter = new ProducerArbiter(); - } - - @Override - public void setProducer(Producer p) { - arbiter.setProducer(p); - } - - @Override - public void onNext(T value) { - boolean onNextWins = false; - long a; - synchronized (this) { - if (!terminated) { - a = ++actual; - onNextWins = true; - } else { - a = actual; - } - } - if (onNextWins) { - serializedSubscriber.onNext(value); - serial.set(timeoutStub.call(this, a, value, inner)); - } - } - - @Override - public void onError(Throwable error) { - boolean onErrorWins = false; - synchronized (this) { - if (!terminated) { - terminated = true; - onErrorWins = true; - } - } - if (onErrorWins) { - serial.unsubscribe(); - serializedSubscriber.onError(error); - } - } - - @Override - public void onCompleted() { - boolean onCompletedWins = false; - synchronized (this) { - if (!terminated) { - terminated = true; - onCompletedWins = true; - } - } - if (onCompletedWins) { - serial.unsubscribe(); - serializedSubscriber.onCompleted(); - } - } - - public void onTimeout(long seqId) { - long expected = seqId; - boolean timeoutWins = false; - synchronized (this) { - if (expected == actual && !terminated) { - terminated = true; - timeoutWins = true; - } - } - if (timeoutWins) { - if (other == null) { - serializedSubscriber.onError(new TimeoutException()); - } else { - Subscriber second = new Subscriber() { - @Override - public void onNext(T t) { - serializedSubscriber.onNext(t); - } - - @Override - public void onError(Throwable e) { - serializedSubscriber.onError(e); - } - - @Override - public void onCompleted() { - serializedSubscriber.onCompleted(); - } - - @Override - public void setProducer(Producer p) { - arbiter.setProducer(p); - } - }; - other.unsafeSubscribe(second); - serial.set(second); - } - } - } - } -} \ No newline at end of file diff --git a/src/main/java/rx/internal/operators/OperatorTimeoutWithSelector.java b/src/main/java/rx/internal/operators/OperatorTimeoutWithSelector.java deleted file mode 100644 index 1e6c20e8a2..0000000000 --- a/src/main/java/rx/internal/operators/OperatorTimeoutWithSelector.java +++ /dev/null @@ -1,115 +0,0 @@ -/** - * Copyright 2014 Netflix, Inc. - * - * 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 rx.internal.operators; - -import rx.Observable; -import rx.Scheduler; -import rx.Subscriber; -import rx.Subscription; -import rx.exceptions.Exceptions; -import rx.functions.Func0; -import rx.functions.Func1; -import rx.schedulers.Schedulers; -import rx.subscriptions.Subscriptions; - -/** - * Returns an Observable that mirrors the source Observable. If either the first - * item emitted by the source Observable or any subsequent item don't arrive - * within time windows defined by provided Observables, switch to the - * other Observable if provided, or emit a TimeoutException . - * @param the value type of the main Observable - * @param the value type of the first timeout Observable - * @param the value type of the subsequent timeout Observable - */ -public class OperatorTimeoutWithSelector extends - OperatorTimeoutBase { - - public OperatorTimeoutWithSelector( - final Func0> firstTimeoutSelector, - final Func1> timeoutSelector, - Observable other) { - super(new FirstTimeoutStub() { - - @Override - public Subscription call( - final TimeoutSubscriber timeoutSubscriber, - final Long seqId, Scheduler.Worker inner) { - if (firstTimeoutSelector != null) { - Observable o = null; - try { - o = firstTimeoutSelector.call(); - } catch (Throwable t) { - Exceptions.throwOrReport(t, timeoutSubscriber); - return Subscriptions.unsubscribed(); - } - return o.unsafeSubscribe(new Subscriber() { - - @Override - public void onCompleted() { - timeoutSubscriber.onTimeout(seqId); - } - - @Override - public void onError(Throwable e) { - timeoutSubscriber.onError(e); - } - - @Override - public void onNext(U t) { - timeoutSubscriber.onTimeout(seqId); - } - - }); - } else { - return Subscriptions.unsubscribed(); - } - } - }, new TimeoutStub() { - - @Override - public Subscription call( - final TimeoutSubscriber timeoutSubscriber, - final Long seqId, T value, Scheduler.Worker inner) { - Observable o = null; - try { - o = timeoutSelector.call(value); - } catch (Throwable t) { - Exceptions.throwOrReport(t, timeoutSubscriber); - return Subscriptions.unsubscribed(); - } - return o.unsafeSubscribe(new Subscriber() { - - @Override - public void onCompleted() { - timeoutSubscriber.onTimeout(seqId); - } - - @Override - public void onError(Throwable e) { - timeoutSubscriber.onError(e); - } - - @Override - public void onNext(V t) { - timeoutSubscriber.onTimeout(seqId); - } - - }); - } - }, other, Schedulers.immediate()); - } - -} diff --git a/src/main/java/rx/internal/operators/OperatorTimestamp.java b/src/main/java/rx/internal/operators/OperatorTimestamp.java index 5d0706afa4..6e6c693a06 100644 --- a/src/main/java/rx/internal/operators/OperatorTimestamp.java +++ b/src/main/java/rx/internal/operators/OperatorTimestamp.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -15,9 +15,8 @@ */ package rx.internal.operators; +import rx.*; import rx.Observable.Operator; -import rx.Scheduler; -import rx.Subscriber; import rx.schedulers.Timestamped; /** diff --git a/src/main/java/rx/internal/operators/OperatorToMultimap.java b/src/main/java/rx/internal/operators/OperatorToMultimap.java deleted file mode 100644 index fe753fee95..0000000000 --- a/src/main/java/rx/internal/operators/OperatorToMultimap.java +++ /dev/null @@ -1,190 +0,0 @@ -/** - * Copyright 2014 Netflix, Inc. - * - * 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 rx.internal.operators; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; - -import rx.Observable.Operator; -import rx.exceptions.Exceptions; -import rx.Subscriber; -import rx.functions.Func0; -import rx.functions.Func1; -import rx.observers.Subscribers; - -/** - * Maps the elements of the source observable into a multimap - * (Map<K, Collection<V>>) where each - * key entry has a collection of the source's values. - * - * @see Issue #97 - * @param the value type of the input - * @param the multimap-key type - * @param the multimap-value type - */ -public final class OperatorToMultimap implements Operator>, T> { - /** - * The default multimap factory returning a HashMap. - * @param the key type - * @param the value type - */ - public static final class DefaultToMultimapFactory implements Func0>> { - @Override - public Map> call() { - return new HashMap>(); - } - } - - /** - * The default collection factory for a key in the multimap returning - * an ArrayList independent of the key. - * @param the key type - * @param the value type - */ - public static final class DefaultMultimapCollectionFactory - implements Func1> { - @Override - public Collection call(K t1) { - return new ArrayList(); - } - } - - final Func1 keySelector; - final Func1 valueSelector; - private final Func0>> mapFactory; - final Func1> collectionFactory; - - /** - * ToMultimap with key selector, custom value selector, - * default HashMap factory and default ArrayList collection factory. - * @param keySelector the function extracting the map-key from the main value - * @param valueSelector the function extracting the map-value from the main value - */ - public OperatorToMultimap( - Func1 keySelector, - Func1 valueSelector) { - this(keySelector, valueSelector, - new DefaultToMultimapFactory(), - new DefaultMultimapCollectionFactory()); - } - - /** - * ToMultimap with key selector, custom value selector, - * custom Map factory and default ArrayList collection factory. - * @param keySelector the function extracting the map-key from the main value - * @param valueSelector the function extracting the map-value from the main value - * @param mapFactory function that returns a Map instance to store keys and values into - */ - public OperatorToMultimap( - Func1 keySelector, - Func1 valueSelector, - Func0>> mapFactory) { - this(keySelector, valueSelector, - mapFactory, - new DefaultMultimapCollectionFactory()); - } - - /** - * ToMultimap with key selector, custom value selector, - * custom Map factory and custom collection factory. - * @param keySelector the function extracting the map-key from the main value - * @param valueSelector the function extracting the map-value from the main value - * @param mapFactory function that returns a Map instance to store keys and values into - * @param collectionFactory function that returns a Collection for a particular key to store values into - */ - public OperatorToMultimap( - Func1 keySelector, - Func1 valueSelector, - Func0>> mapFactory, - Func1> collectionFactory) { - this.keySelector = keySelector; - this.valueSelector = valueSelector; - this.mapFactory = mapFactory; - this.collectionFactory = collectionFactory; - } - - @Override - public Subscriber call(final Subscriber>> subscriber) { - - Map> localMap; - - try { - localMap = mapFactory.call(); - } catch (Throwable ex) { - Exceptions.throwIfFatal(ex); - subscriber.onError(ex); - - Subscriber parent = Subscribers.empty(); - parent.unsubscribe(); - return parent; - } - - final Map> fLocalMap = localMap; - - return new Subscriber(subscriber) { - private Map> map = fLocalMap; - - @Override - public void onStart() { - request(Long.MAX_VALUE); - } - - @Override - public void onNext(T v) { - K key; - V value; - - try { - key = keySelector.call(v); - value = valueSelector.call(v); - } catch (Throwable ex) { - Exceptions.throwOrReport(ex, subscriber); - return; - } - - Collection collection = map.get(key); - if (collection == null) { - try { - collection = collectionFactory.call(key); - } catch (Throwable ex) { - Exceptions.throwOrReport(ex, subscriber); - return; - } - map.put(key, collection); - } - collection.add(value); - } - - @Override - public void onError(Throwable e) { - map = null; - subscriber.onError(e); - } - - @Override - public void onCompleted() { - Map> map0 = map; - map = null; - subscriber.onNext(map0); - subscriber.onCompleted(); - } - - }; - } -} \ No newline at end of file diff --git a/src/main/java/rx/internal/operators/OperatorToObservableList.java b/src/main/java/rx/internal/operators/OperatorToObservableList.java index 518a9a37c1..2d2ffaa99c 100644 --- a/src/main/java/rx/internal/operators/OperatorToObservableList.java +++ b/src/main/java/rx/internal/operators/OperatorToObservableList.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -18,8 +18,8 @@ import java.util.*; import rx.Observable.Operator; +import rx.Subscriber; import rx.exceptions.Exceptions; -import rx.*; import rx.internal.producers.SingleDelayedProducer; /** @@ -39,7 +39,7 @@ */ public final class OperatorToObservableList implements Operator, T> { /** Lazy initialization via inner-class holder. */ - private static final class Holder { + static final class Holder { /** A singleton instance. */ static final OperatorToObservableList INSTANCE = new OperatorToObservableList(); } @@ -51,13 +51,16 @@ private static final class Holder { public static OperatorToObservableList instance() { return (OperatorToObservableList)Holder.INSTANCE; } - OperatorToObservableList() { } + OperatorToObservableList() { + // singleton + } + @Override public Subscriber call(final Subscriber> o) { final SingleDelayedProducer> producer = new SingleDelayedProducer>(o); Subscriber result = new Subscriber() { - boolean completed = false; + boolean completed; List list = new LinkedList(); @Override @@ -72,11 +75,11 @@ public void onCompleted() { List result; try { /* - * Ideally this should just return Collections.unmodifiableList(list) and not copy it, - * but, it ends up being a breaking change if we make that modification. - * + * Ideally this should just return Collections.unmodifiableList(list) and not copy it, + * but, it ends up being a breaking change if we make that modification. + * * Here is an example of is being done with these lists that breaks if we make it immutable: - * + * * Caused by: java.lang.UnsupportedOperationException * at java.util.Collections$UnmodifiableList$1.set(Collections.java:1244) * at java.util.Collections.sort(Collections.java:221) diff --git a/src/main/java/rx/internal/operators/OperatorToObservableSortedList.java b/src/main/java/rx/internal/operators/OperatorToObservableSortedList.java index ab74e44f17..ab14cf02a4 100644 --- a/src/main/java/rx/internal/operators/OperatorToObservableSortedList.java +++ b/src/main/java/rx/internal/operators/OperatorToObservableSortedList.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -18,8 +18,8 @@ import java.util.*; import rx.Observable.Operator; +import rx.Subscriber; import rx.exceptions.Exceptions; -import rx.*; import rx.functions.Func2; import rx.internal.producers.SingleDelayedProducer; @@ -29,13 +29,16 @@ * items in the sequence, or you must pass in a sort function). *

    * - * + * * @param * the type of the items emitted by the source and the resulting {@code Observable}s */ public final class OperatorToObservableSortedList implements Operator, T> { final Comparator sortFunction; final int initialCapacity; + // raw because we want to support Object for this default + @SuppressWarnings("rawtypes") + private static final Comparator DEFAULT_SORT_FUNCTION = new DefaultComparableFunction(); @SuppressWarnings("unchecked") public OperatorToObservableSortedList(int initialCapacity) { @@ -60,7 +63,7 @@ public Subscriber call(final Subscriber> child) { List list = new ArrayList(initialCapacity); boolean completed; - + @Override public void onStart() { request(Long.MAX_VALUE); @@ -100,13 +103,8 @@ public void onNext(T value) { child.setProducer(producer); return result; } - // raw because we want to support Object for this default - @SuppressWarnings("rawtypes") - private static Comparator DEFAULT_SORT_FUNCTION = new DefaultComparableFunction(); - private static class DefaultComparableFunction implements Comparator { - DefaultComparableFunction() { - } + static final class DefaultComparableFunction implements Comparator { // unchecked because we want to support Object for this default @SuppressWarnings("unchecked") diff --git a/src/main/java/rx/internal/operators/OperatorUnsubscribeOn.java b/src/main/java/rx/internal/operators/OperatorUnsubscribeOn.java index f0b0151063..a581009884 100644 --- a/src/main/java/rx/internal/operators/OperatorUnsubscribeOn.java +++ b/src/main/java/rx/internal/operators/OperatorUnsubscribeOn.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -15,9 +15,8 @@ */ package rx.internal.operators; +import rx.*; import rx.Observable.Operator; -import rx.Scheduler; -import rx.Subscriber; import rx.functions.Action0; import rx.subscriptions.Subscriptions; @@ -53,8 +52,12 @@ public void onNext(T t) { subscriber.onNext(t); } + @Override + public void setProducer(Producer p) { + subscriber.setProducer(p); + } }; - + subscriber.add(Subscriptions.create(new Action0() { @Override @@ -71,7 +74,7 @@ public void call() { } })); - + return parent; diff --git a/src/main/java/rx/internal/operators/OperatorWindowWithObservable.java b/src/main/java/rx/internal/operators/OperatorWindowWithObservable.java index 6d7324bf9f..47b0e3be59 100644 --- a/src/main/java/rx/internal/operators/OperatorWindowWithObservable.java +++ b/src/main/java/rx/internal/operators/OperatorWindowWithObservable.java @@ -17,46 +17,44 @@ import java.util.*; -import rx.*; -import rx.Observable.Operator; import rx.Observable; +import rx.Observable.Operator; import rx.Observer; +import rx.Subscriber; import rx.observers.SerializedSubscriber; import rx.subjects.UnicastSubject; /** * Creates non-overlapping windows of items where each window is terminated by * an event from a secondary observable and a new window is started immediately. - * + * * @param the value type * @param the boundary value type */ public final class OperatorWindowWithObservable implements Operator, T> { final Observable other; + /** Indicate the current subject should complete and a new subject be emitted. */ + static final Object NEXT_SUBJECT = new Object(); public OperatorWindowWithObservable(final Observable other) { this.other = other; } - + @Override public Subscriber call(Subscriber> child) { - + SourceSubscriber sub = new SourceSubscriber(child); - BoundarySubscriber bs = new BoundarySubscriber(child, sub); - + BoundarySubscriber bs = new BoundarySubscriber(sub); + child.add(sub); child.add(bs); - + sub.replaceWindow(); - + other.unsafeSubscribe(bs); - + return sub; } - /** Indicate the current subject should complete and a new subject be emitted. */ - static final Object NEXT_SUBJECT = new Object(); - /** For error and completion indication. */ - static final NotificationLite nl = NotificationLite.instance(); /** Observes the source. */ static final class SourceSubscriber extends Subscriber { final Subscriber> child; @@ -69,17 +67,17 @@ static final class SourceSubscriber extends Subscriber { boolean emitting; /** Guarded by guard. */ List queue; - + public SourceSubscriber(Subscriber> child) { this.child = new SerializedSubscriber>(child); this.guard = new Object(); } - + @Override public void onStart() { request(Long.MAX_VALUE); } - + @Override public void onNext(T t) { List localQueue; @@ -104,7 +102,7 @@ public void onNext(T t) { once = false; emitValue(t); } - + synchronized (guard) { localQueue = queue; queue = null; @@ -132,11 +130,11 @@ void drain(List queue) { if (o == NEXT_SUBJECT) { replaceSubject(); } else - if (nl.isError(o)) { - error(nl.getError(o)); + if (NotificationLite.isError(o)) { + error(NotificationLite.getError(o)); break; } else - if (nl.isCompleted(o)) { + if (NotificationLite.isCompleted(o)) { complete(); break; } else { @@ -165,12 +163,12 @@ void emitValue(T t) { s.onNext(t); } } - + @Override public void onError(Throwable e) { synchronized (guard) { if (emitting) { - queue = Collections.singletonList(nl.error(e)); + queue = Collections.singletonList(NotificationLite.error(e)); return; } queue = null; @@ -187,7 +185,7 @@ public void onCompleted() { if (queue == null) { queue = new ArrayList(); } - queue.add(nl.completed()); + queue.add(NotificationLite.completed()); return; } localQueue = queue; @@ -247,7 +245,7 @@ void complete() { Observer s = consumer; consumer = null; producer = null; - + if (s != null) { s.onCompleted(); } @@ -258,7 +256,7 @@ void error(Throwable e) { Observer s = consumer; consumer = null; producer = null; - + if (s != null) { s.onError(e); } @@ -269,15 +267,15 @@ void error(Throwable e) { /** Observes the boundary. */ static final class BoundarySubscriber extends Subscriber { final SourceSubscriber sub; - public BoundarySubscriber(Subscriber child, SourceSubscriber sub) { + public BoundarySubscriber(SourceSubscriber sub) { this.sub = sub; } - + @Override public void onStart() { request(Long.MAX_VALUE); } - + @Override public void onNext(U t) { sub.replaceWindow(); diff --git a/src/main/java/rx/internal/operators/OperatorWindowWithObservableFactory.java b/src/main/java/rx/internal/operators/OperatorWindowWithObservableFactory.java index 9dbfed2bb7..ff28e04a50 100644 --- a/src/main/java/rx/internal/operators/OperatorWindowWithObservableFactory.java +++ b/src/main/java/rx/internal/operators/OperatorWindowWithObservableFactory.java @@ -17,10 +17,10 @@ import java.util.*; -import rx.*; -import rx.Observable.Operator; import rx.Observable; +import rx.Observable.Operator; import rx.Observer; +import rx.Subscriber; import rx.functions.Func0; import rx.observers.SerializedSubscriber; import rx.subjects.UnicastSubject; @@ -29,32 +29,31 @@ /** * Creates non-overlapping windows of items where each window is terminated by * an event from a secondary observable and a new window is started immediately. - * + * * @param the value type * @param the boundary value type */ public final class OperatorWindowWithObservableFactory implements Operator, T> { final Func0> otherFactory; + /** Indicate the current subject should complete and a new subject be emitted. */ + static final Object NEXT_SUBJECT = new Object(); + public OperatorWindowWithObservableFactory(Func0> otherFactory) { this.otherFactory = otherFactory; } - + @Override public Subscriber call(Subscriber> child) { - + SourceSubscriber sub = new SourceSubscriber(child, otherFactory); - + child.add(sub); - + sub.replaceWindow(); - + return sub; } - /** Indicate the current subject should complete and a new subject be emitted. */ - static final Object NEXT_SUBJECT = new Object(); - /** For error and completion indication. */ - static final NotificationLite nl = NotificationLite.instance(); /** Observes the source. */ static final class SourceSubscriber extends Subscriber { final Subscriber> child; @@ -67,25 +66,25 @@ static final class SourceSubscriber extends Subscriber { boolean emitting; /** Guarded by guard. */ List queue; - - final SerialSubscription ssub; - + + final SerialSubscription serial; + final Func0> otherFactory; - - public SourceSubscriber(Subscriber> child, + + public SourceSubscriber(Subscriber> child, Func0> otherFactory) { this.child = new SerializedSubscriber>(child); this.guard = new Object(); - this.ssub = new SerialSubscription(); + this.serial = new SerialSubscription(); this.otherFactory = otherFactory; - this.add(ssub); + this.add(serial); } - + @Override public void onStart() { request(Long.MAX_VALUE); } - + @Override public void onNext(T t) { List localQueue; @@ -110,7 +109,7 @@ public void onNext(T t) { once = false; emitValue(t); } - + synchronized (guard) { localQueue = queue; queue = null; @@ -138,11 +137,11 @@ void drain(List queue) { if (o == NEXT_SUBJECT) { replaceSubject(); } else - if (nl.isError(o)) { - error(nl.getError(o)); + if (NotificationLite.isError(o)) { + error(NotificationLite.getError(o)); break; } else - if (nl.isCompleted(o)) { + if (NotificationLite.isCompleted(o)) { complete(); break; } else { @@ -172,9 +171,9 @@ void createNewWindow() { unsubscribe(); return; } - - BoundarySubscriber bs = new BoundarySubscriber(child, this); - ssub.set(bs); + + BoundarySubscriber bs = new BoundarySubscriber(this); + serial.set(bs); other.unsafeSubscribe(bs); } void emitValue(T t) { @@ -183,12 +182,12 @@ void emitValue(T t) { s.onNext(t); } } - + @Override public void onError(Throwable e) { synchronized (guard) { if (emitting) { - queue = Collections.singletonList(nl.error(e)); + queue = Collections.singletonList(NotificationLite.error(e)); return; } queue = null; @@ -205,7 +204,7 @@ public void onCompleted() { if (queue == null) { queue = new ArrayList(); } - queue.add(nl.completed()); + queue.add(NotificationLite.completed()); return; } localQueue = queue; @@ -265,7 +264,7 @@ void complete() { Observer s = consumer; consumer = null; producer = null; - + if (s != null) { s.onCompleted(); } @@ -276,7 +275,7 @@ void error(Throwable e) { Observer s = consumer; consumer = null; producer = null; - + if (s != null) { s.onError(e); } @@ -288,15 +287,15 @@ void error(Throwable e) { static final class BoundarySubscriber extends Subscriber { final SourceSubscriber sub; boolean done; - public BoundarySubscriber(Subscriber child, SourceSubscriber sub) { + public BoundarySubscriber(SourceSubscriber sub) { this.sub = sub; } - + @Override public void onStart() { request(Long.MAX_VALUE); } - + @Override public void onNext(U t) { if (!done) { diff --git a/src/main/java/rx/internal/operators/OperatorWindowWithSize.java b/src/main/java/rx/internal/operators/OperatorWindowWithSize.java index 4873a3109b..155d428426 100644 --- a/src/main/java/rx/internal/operators/OperatorWindowWithSize.java +++ b/src/main/java/rx/internal/operators/OperatorWindowWithSize.java @@ -28,14 +28,14 @@ /** * Creates windows of values into the source sequence with skip frequency and size bounds. - * + * * If skip == size then the windows are non-overlapping, otherwise, windows may overlap * or can be discontinuous. The returned Observable sequence is cold and need to be * consumed while the window operation is in progress. - * + * *

    Note that this conforms the Rx.NET behavior, but does not match former RxJava * behavior, which operated as a regular buffer and mapped its lists to Observables.

    - * + * * @param the value type */ public final class OperatorWindowWithSize implements Operator, T> { @@ -51,43 +51,43 @@ public OperatorWindowWithSize(int size, int skip) { public Subscriber call(Subscriber> child) { if (skip == size) { WindowExact parent = new WindowExact(child, size); - + child.add(parent.cancel); child.setProducer(parent.createProducer()); - + return parent; } else if (skip > size) { WindowSkip parent = new WindowSkip(child, size, skip); - + child.add(parent.cancel); child.setProducer(parent.createProducer()); - + return parent; } WindowOverlap parent = new WindowOverlap(child, size, skip); - + child.add(parent.cancel); child.setProducer(parent.createProducer()); - + return parent; - + } - + static final class WindowExact extends Subscriber implements Action0 { final Subscriber> actual; - + final int size; - + final AtomicInteger wip; - + final Subscription cancel; - + int index; - + Subject window; - + public WindowExact(Subscriber> actual, int size) { this.actual = actual; this.size = size; @@ -96,24 +96,24 @@ public WindowExact(Subscriber> actual, int size) { this.add(cancel); this.request(0); } - + @Override public void onNext(T t) { int i = index; - + Subject w = window; if (i == 0) { wip.getAndIncrement(); - + w = UnicastSubject.create(size, this); window = w; - + actual.onNext(w); } i++; - + w.onNext(t); - + if (i == size) { index = 0; window = null; @@ -122,29 +122,29 @@ public void onNext(T t) { index = i; } } - + @Override public void onError(Throwable e) { Subject w = window; - + if (w != null) { window = null; w.onError(e); } actual.onError(e); } - + @Override public void onCompleted() { Subject w = window; - + if (w != null) { window = null; w.onCompleted(); } actual.onCompleted(); } - + Producer createProducer() { return new Producer() { @Override @@ -159,7 +159,7 @@ public void request(long n) { } }; } - + @Override public void call() { if (wip.decrementAndGet() == 0) { @@ -167,22 +167,22 @@ public void call() { } } } - + static final class WindowSkip extends Subscriber implements Action0 { final Subscriber> actual; - + final int size; - + final int skip; - + final AtomicInteger wip; - + final Subscription cancel; - + int index; - + Subject window; - + public WindowSkip(Subscriber> actual, int size, int skip) { this.actual = actual; this.size = size; @@ -192,26 +192,26 @@ public WindowSkip(Subscriber> actual, int size, int skip) this.add(cancel); this.request(0); } - + @Override public void onNext(T t) { int i = index; - + Subject w = window; if (i == 0) { wip.getAndIncrement(); - + w = UnicastSubject.create(size, this); window = w; - + actual.onNext(w); } i++; - + if (w != null) { w.onNext(t); } - + if (i == size) { index = i; window = null; @@ -222,42 +222,42 @@ public void onNext(T t) { } else { index = i; } - + } - + @Override public void onError(Throwable e) { Subject w = window; - + if (w != null) { window = null; w.onError(e); } actual.onError(e); } - + @Override public void onCompleted() { Subject w = window; - + if (w != null) { window = null; w.onCompleted(); } actual.onCompleted(); } - + Producer createProducer() { return new WindowSkipProducer(); } - + @Override public void call() { if (wip.decrementAndGet() == 0) { unsubscribe(); } } - + final class WindowSkipProducer extends AtomicBoolean implements Producer { /** */ private static final long serialVersionUID = 4625807964358024108L; @@ -282,34 +282,34 @@ public void request(long n) { } } } - + static final class WindowOverlap extends Subscriber implements Action0 { final Subscriber> actual; - + final int size; - + final int skip; - + final AtomicInteger wip; - + final Subscription cancel; final ArrayDeque> windows; final AtomicLong requested; - + final AtomicInteger drainWip; - + final Queue> queue; - + Throwable error; - + volatile boolean done; - + int index; - + int produced; - + public WindowOverlap(Subscriber> actual, int size, int skip) { this.actual = actual; this.size = size; @@ -324,19 +324,19 @@ public WindowOverlap(Subscriber> actual, int size, int ski int maxWindows = (size + (skip - 1)) / skip; this.queue = new SpscLinkedArrayQueue>(maxWindows); } - + @Override public void onNext(T t) { int i = index; - + ArrayDeque> q = windows; - + if (i == 0 && !actual.isUnsubscribed()) { wip.getAndIncrement(); - + Subject w = UnicastSubject.create(16, this); q.offer(w); - + queue.offer(w); drain(); } @@ -346,10 +346,10 @@ public void onNext(T t) { } int p = produced + 1; - + if (p == size) { produced = p - skip; - + Subject w = q.poll(); if (w != null) { w.onCompleted(); @@ -357,7 +357,7 @@ public void onNext(T t) { } else { produced = p; } - + i++; if (i == skip) { index = 0; @@ -365,41 +365,41 @@ public void onNext(T t) { index = i; } } - + @Override public void onError(Throwable e) { for (Subject w : windows) { w.onError(e); } windows.clear(); - + error = e; done = true; drain(); } - + @Override public void onCompleted() { for (Subject w : windows) { w.onCompleted(); } windows.clear(); - + done = true; drain(); } - + Producer createProducer() { return new WindowOverlapProducer(); } - + @Override public void call() { if (wip.decrementAndGet() == 0) { unsubscribe(); } } - + void drain() { AtomicInteger dw = drainWip; if (dw.getAndIncrement() != 0) { @@ -408,49 +408,49 @@ void drain() { final Subscriber> a = actual; final Queue> q = queue; - + int missed = 1; - + for (;;) { - + long r = requested.get(); long e = 0L; - + while (e != r) { boolean d = done; Subject v = q.poll(); boolean empty = v == null; - + if (checkTerminated(d, empty, a, q)) { return; } - + if (empty) { break; } - + a.onNext(v); - + e++; } - + if (e == r) { if (checkTerminated(done, q.isEmpty(), a, q)) { return; } } - + if (e != 0 && r != Long.MAX_VALUE) { requested.addAndGet(-e); } - + missed = dw.addAndGet(-missed); if (missed == 0) { break; } } } - + boolean checkTerminated(boolean d, boolean empty, Subscriber> a, Queue> q) { if (a.isUnsubscribed()) { q.clear(); @@ -470,7 +470,7 @@ boolean checkTerminated(boolean d, boolean empty, Subscriber= 0 required but it was " + n); } if (n != 0L) { - + WindowOverlap parent = WindowOverlap.this; - + if (!get() && compareAndSet(false, true)) { long u = BackpressureUtils.multiplyCap(parent.skip, n - 1); long v = BackpressureUtils.addCap(u, parent.size); - + parent.request(v); } else { long u = BackpressureUtils.multiplyCap(parent.skip, n); WindowOverlap.this.request(u); } - + BackpressureUtils.getAndAddRequest(parent.requested, n); parent.drain(); } diff --git a/src/main/java/rx/internal/operators/OperatorWindowWithStartEndObservable.java b/src/main/java/rx/internal/operators/OperatorWindowWithStartEndObservable.java index 28a6fffc21..a3fce97b3f 100644 --- a/src/main/java/rx/internal/operators/OperatorWindowWithStartEndObservable.java +++ b/src/main/java/rx/internal/operators/OperatorWindowWithStartEndObservable.java @@ -17,10 +17,10 @@ import java.util.*; -import rx.*; -import rx.Observable.Operator; import rx.Observable; +import rx.Observable.Operator; import rx.Observer; +import rx.Subscriber; import rx.functions.Func1; import rx.observers.*; import rx.subjects.UnicastSubject; @@ -28,9 +28,9 @@ /** * Creates potentially overlapping windows of the source items where each window is - * started by a value emitted by an observable and closed when an associated Observable emits + * started by a value emitted by an observable and closed when an associated Observable emits * a value or completes. - * + * * @param the value type * @param the type of the window opening event * @param the type of the window closing event @@ -39,26 +39,26 @@ public final class OperatorWindowWithStartEndObservable implements Oper final Observable windowOpenings; final Func1> windowClosingSelector; - public OperatorWindowWithStartEndObservable(Observable windowOpenings, + public OperatorWindowWithStartEndObservable(Observable windowOpenings, Func1> windowClosingSelector) { this.windowOpenings = windowOpenings; this.windowClosingSelector = windowClosingSelector; } - + @Override public Subscriber call(Subscriber> child) { - CompositeSubscription csub = new CompositeSubscription(); - child.add(csub); - - final SourceSubscriber sub = new SourceSubscriber(child, csub); - + CompositeSubscription composite = new CompositeSubscription(); + child.add(composite); + + final SourceSubscriber sub = new SourceSubscriber(child, composite); + Subscriber open = new Subscriber() { @Override public void onStart() { request(Long.MAX_VALUE); } - + @Override public void onNext(U t) { sub.beginWindow(t); @@ -74,12 +74,12 @@ public void onCompleted() { sub.onCompleted(); } }; - - csub.add(sub); - csub.add(open); - + + composite.add(sub); + composite.add(open); + windowOpenings.unsafeSubscribe(open); - + return sub; } /** Serialized access to the subject. */ @@ -91,28 +91,28 @@ public SerializedSubject(Observer consumer, Observable producer) { this.consumer = new SerializedObserver(consumer); this.producer = producer; } - + } final class SourceSubscriber extends Subscriber { final Subscriber> child; - final CompositeSubscription csub; + final CompositeSubscription composite; final Object guard; /** Guarded by guard. */ final List> chunks; /** Guarded by guard. */ boolean done; - public SourceSubscriber(Subscriber> child, CompositeSubscription csub) { + public SourceSubscriber(Subscriber> child, CompositeSubscription composite) { this.child = new SerializedSubscriber>(child); this.guard = new Object(); this.chunks = new LinkedList>(); - this.csub = csub; + this.composite = composite; } - + @Override public void onStart() { request(Long.MAX_VALUE); } - + @Override public void onNext(T t) { List> list; @@ -144,7 +144,7 @@ public void onError(Throwable e) { } child.onError(e); } finally { - csub.unsubscribe(); + composite.unsubscribe(); } } @@ -165,10 +165,10 @@ public void onCompleted() { } child.onCompleted(); } finally { - csub.unsubscribe(); + composite.unsubscribe(); } } - + void beginWindow(U token) { final SerializedSubject s = createSerializedSubject(); synchronized (guard) { @@ -178,7 +178,7 @@ void beginWindow(U token) { chunks.add(s); } child.onNext(s.producer); - + Observable end; try { end = windowClosingSelector.call(token); @@ -186,7 +186,7 @@ void beginWindow(U token) { onError(e); return; } - + Subscriber v = new Subscriber() { boolean once = true; @Override @@ -196,7 +196,7 @@ public void onNext(V t) { @Override public void onError(Throwable e) { - + SourceSubscriber.this.onError(e); } @Override @@ -204,13 +204,13 @@ public void onCompleted() { if (once) { once = false; endWindow(s); - csub.remove(this); + composite.remove(this); } } - + }; - csub.add(v); - + composite.add(v); + end.unsafeSubscribe(v); } void endWindow(SerializedSubject window) { diff --git a/src/main/java/rx/internal/operators/OperatorWindowWithTime.java b/src/main/java/rx/internal/operators/OperatorWindowWithTime.java index 8ea74a3e1a..717b7b7067 100644 --- a/src/main/java/rx/internal/operators/OperatorWindowWithTime.java +++ b/src/main/java/rx/internal/operators/OperatorWindowWithTime.java @@ -19,10 +19,10 @@ import java.util.concurrent.TimeUnit; import rx.*; -import rx.Observable.Operator; -import rx.Scheduler.Worker; import rx.Observable; +import rx.Observable.Operator; import rx.Observer; +import rx.Scheduler.Worker; import rx.functions.Action0; import rx.observers.*; import rx.subjects.UnicastSubject; @@ -46,7 +46,10 @@ public final class OperatorWindowWithTime implements Operator, final TimeUnit unit; final Scheduler scheduler; final int size; - + /** Indicate the current subject should complete and a new subject be emitted. */ + static final Object NEXT_SUBJECT = new Object(); + + public OperatorWindowWithTime(long timespan, long timeshift, TimeUnit unit, int size, Scheduler scheduler) { this.timespan = timespan; this.timeshift = timeshift; @@ -54,37 +57,32 @@ public OperatorWindowWithTime(long timespan, long timeshift, TimeUnit unit, int this.size = size; this.scheduler = scheduler; } - - + + @Override public Subscriber call(Subscriber> child) { Worker worker = scheduler.createWorker(); - + if (timespan == timeshift) { ExactSubscriber s = new ExactSubscriber(child, worker); s.add(worker); s.scheduleExact(); return s; } - + InexactSubscriber s = new InexactSubscriber(child, worker); s.add(worker); s.startNewChunk(); s.scheduleChunk(); return s; } - /** Indicate the current subject should complete and a new subject be emitted. */ - static final Object NEXT_SUBJECT = new Object(); - /** For error and completion indication. */ - static final NotificationLite nl = NotificationLite.instance(); - /** The immutable windowing state with one subject. */ static final class State { final Observer consumer; final Observable producer; final int count; static final State EMPTY = new State(null, null, 0); - + public State(Observer consumer, Observable producer, int count) { this.consumer = consumer; this.producer = producer; @@ -114,7 +112,7 @@ final class ExactSubscriber extends Subscriber { /** Guarded by guard. */ boolean emitting; volatile State state; - + public ExactSubscriber(Subscriber> child, Worker worker) { this.child = new SerializedSubscriber>(child); this.worker = worker; @@ -130,12 +128,12 @@ public void call() { } })); } - + @Override public void onStart() { request(Long.MAX_VALUE); } - + @Override public void onNext(T t) { synchronized (guard) { @@ -187,11 +185,11 @@ boolean drain(List queue) { return false; } } else - if (nl.isError(o)) { - error(nl.getError(o)); + if (NotificationLite.isError(o)) { + error(NotificationLite.getError(o)); break; } else - if (nl.isCompleted(o)) { + if (NotificationLite.isCompleted(o)) { complete(); break; } else { @@ -238,13 +236,13 @@ boolean emitValue(T t) { state = s; return true; } - + @Override public void onError(Throwable e) { synchronized (guard) { if (emitting) { // drop any queued action and terminate asap - queue = Collections.singletonList(nl.error(e)); + queue = Collections.singletonList(NotificationLite.error(e)); return; } queue = null; @@ -278,7 +276,7 @@ public void onCompleted() { if (queue == null) { queue = new ArrayList(); } - queue.add(nl.completed()); + queue.add(NotificationLite.completed()); return; } localQueue = queue; @@ -293,15 +291,15 @@ public void onCompleted() { } complete(); } - + void scheduleExact() { worker.schedulePeriodically(new Action0() { - + @Override public void call() { nextWindow(); } - + }, 0, timespan, unit); } void nextWindow() { @@ -331,7 +329,7 @@ void nextWindow() { } queue = null; } - + if (!drain(localQueue)) { return; } @@ -345,8 +343,8 @@ void nextWindow() { } } } - /** - * Record to store the subject and the emission count. + /** + * Record to store the subject and the emission count. * @param the subject's in-out type */ static final class CountedSerializedSubject { @@ -380,7 +378,7 @@ public InexactSubscriber(Subscriber> child, Worker worker) public void onStart() { request(Long.MAX_VALUE); } - + @Override public void onNext(T t) { List> list; @@ -445,7 +443,7 @@ void scheduleChunk() { public void call() { startNewChunk(); } - + }, timeshift, timeshift, unit); } void startNewChunk() { @@ -462,14 +460,14 @@ void startNewChunk() { onError(e); return; } - + worker.schedule(new Action0() { @Override public void call() { terminateChunk(chunk); } - + }, timespan, unit); } void terminateChunk(CountedSerializedSubject chunk) { diff --git a/src/main/java/rx/internal/operators/OperatorWithLatestFrom.java b/src/main/java/rx/internal/operators/OperatorWithLatestFrom.java index 67b22fb982..c136e1a75e 100644 --- a/src/main/java/rx/internal/operators/OperatorWithLatestFrom.java +++ b/src/main/java/rx/internal/operators/OperatorWithLatestFrom.java @@ -34,7 +34,7 @@ public final class OperatorWithLatestFrom implements Operator { final Observable other; /** Indicates the other has not yet emitted a value. */ static final Object EMPTY = new Object(); - + public OperatorWithLatestFrom(Observable other, Func2 resultSelector) { this.other = other; this.resultSelector = resultSelector; @@ -44,9 +44,9 @@ public Subscriber call(Subscriber child) { // onError and onCompleted may happen either from the main or from other. final SerializedSubscriber s = new SerializedSubscriber(child, false); child.add(s); - + final AtomicReference current = new AtomicReference(EMPTY); - + final Subscriber subscriber = new Subscriber(s, true) { @Override public void onNext(T t) { @@ -56,7 +56,7 @@ public void onNext(T t) { @SuppressWarnings("unchecked") U u = (U)o; R result = resultSelector.call(t, u); - + s.onNext(result); } catch (Throwable e) { Exceptions.throwOrReport(e, this); @@ -74,7 +74,7 @@ public void onCompleted() { s.unsubscribe(); } }; - + Subscriber otherSubscriber = new Subscriber() { @Override public void onNext(U t) { @@ -95,9 +95,9 @@ public void onCompleted() { }; s.add(subscriber); s.add(otherSubscriber); - + other.unsafeSubscribe(otherSubscriber); - + return subscriber; } } diff --git a/src/main/java/rx/internal/operators/OperatorWithLatestFromMany.java b/src/main/java/rx/internal/operators/OperatorWithLatestFromMany.java index 91c390e63d..425cfcc4d5 100644 --- a/src/main/java/rx/internal/operators/OperatorWithLatestFromMany.java +++ b/src/main/java/rx/internal/operators/OperatorWithLatestFromMany.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -22,18 +22,18 @@ import rx.Observable.OnSubscribe; import rx.exceptions.Exceptions; import rx.functions.FuncN; -import rx.internal.util.RxJavaPluginUtils; import rx.observers.SerializedSubscriber; +import rx.plugins.RxJavaHooks; public final class OperatorWithLatestFromMany implements OnSubscribe { final Observable main; - + final Observable[] others; - + final Iterable> othersIterable; - + final FuncN combiner; - + public OperatorWithLatestFromMany(Observable main, Observable[] others, Iterable> othersIterable, FuncN combiner) { this.main = main; this.others = others; @@ -44,11 +44,11 @@ public OperatorWithLatestFromMany(Observable main, Observable[] others, It @Override public void call(Subscriber t) { SerializedSubscriber serial = new SerializedSubscriber(t); - - + + Observable[] sources; int n = 0; - + if (others != null) { sources = others; n = sources.length; @@ -61,72 +61,72 @@ public void call(Subscriber t) { sources[n++] = o; } } - + WithLatestMainSubscriber parent = new WithLatestMainSubscriber(t, combiner, n); - + serial.add(parent); - + for (int i = 0; i < n; i++) { - Observable o = sources[i]; if (serial.isUnsubscribed()) { return; } - + WithLatestOtherSubscriber inner = new WithLatestOtherSubscriber(parent, i + 1); parent.add(inner); - + + Observable o = sources[i]; o.unsafeSubscribe(inner); } - + main.unsafeSubscribe(parent); } - + static final class WithLatestMainSubscriber extends Subscriber { final Subscriber actual; - + final FuncN combiner; - + final AtomicReferenceArray current; - + static final Object EMPTY = new Object(); - + final AtomicInteger ready; - + boolean done; - + public WithLatestMainSubscriber(Subscriber actual, FuncN combiner, int n) { this.actual = actual; this.combiner = combiner; - + AtomicReferenceArray array = new AtomicReferenceArray(n + 1); for (int i = 0; i <= n; i++) { array.lazySet(i, EMPTY); } this.current = array; - + this.ready = new AtomicInteger(n); this.request(0); } - + @Override public void onNext(T t) { if (done) { return; } if (ready.get() == 0) { - + AtomicReferenceArray array = current; int n = array.length(); array.lazySet(0, t); - + Object[] copy = new Object[array.length()]; for (int i = 0; i < n; i++) { copy[i] = array.get(i); } - + R result; - + try { result = combiner.call(copy); } catch (Throwable ex) { @@ -134,24 +134,24 @@ public void onNext(T t) { onError(ex); return; } - + actual.onNext(result); } else { request(1); } } - + @Override public void onError(Throwable e) { if (done) { - RxJavaPluginUtils.handleException(e); + RxJavaHooks.onError(e); return; } done = true; unsubscribe(); actual.onError(e); } - + @Override public void onCompleted() { if (done) { @@ -161,51 +161,51 @@ public void onCompleted() { unsubscribe(); actual.onCompleted(); } - + @Override public void setProducer(Producer p) { super.setProducer(p); actual.setProducer(p); } - + void innerNext(int index, Object o) { Object last = current.getAndSet(index, o); if (last == EMPTY) { ready.decrementAndGet(); } } - + void innerError(int index, Throwable e) { onError(e); } - + void innerComplete(int index) { if (current.get(index) == EMPTY) { onCompleted(); } } } - + static final class WithLatestOtherSubscriber extends Subscriber { final WithLatestMainSubscriber parent; - + final int index; public WithLatestOtherSubscriber(WithLatestMainSubscriber parent, int index) { this.parent = parent; this.index = index; } - + @Override public void onNext(Object t) { parent.innerNext(index, t); } - + @Override public void onError(Throwable e) { parent.innerError(index, e); } - + @Override public void onCompleted() { parent.innerComplete(index); diff --git a/src/main/java/rx/internal/operators/OperatorZip.java b/src/main/java/rx/internal/operators/OperatorZip.java index 6f1280b3c3..a8e2cca2f0 100644 --- a/src/main/java/rx/internal/operators/OperatorZip.java +++ b/src/main/java/rx/internal/operators/OperatorZip.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -17,23 +17,10 @@ import java.util.concurrent.atomic.AtomicLong; -import rx.Observable; +import rx.*; import rx.Observable.Operator; -import rx.Observer; -import rx.Producer; -import rx.Subscriber; -import rx.exceptions.Exceptions; -import rx.exceptions.MissingBackpressureException; -import rx.functions.Func2; -import rx.functions.Func3; -import rx.functions.Func4; -import rx.functions.Func5; -import rx.functions.Func6; -import rx.functions.Func7; -import rx.functions.Func8; -import rx.functions.Func9; -import rx.functions.FuncN; -import rx.functions.Functions; +import rx.exceptions.*; +import rx.functions.*; import rx.internal.util.RxRingBuffer; import rx.subscriptions.CompositeSubscription; @@ -50,7 +37,7 @@ *

    * The resulting Observable returned from zip will invoke onNext as many times as the * number of onNext invocations of the source Observable that emits the fewest items. - * + * * @param * the result type */ @@ -115,25 +102,25 @@ public Subscriber call(final Subscriber child) child.add(subscriber); child.setProducer(producer); - + return subscriber; } @SuppressWarnings("rawtypes") - private final class ZipSubscriber extends Subscriber { + final class ZipSubscriber extends Subscriber { final Subscriber child; final Zip zipper; final ZipProducer producer; + boolean started; + public ZipSubscriber(Subscriber child, Zip zipper, ZipProducer producer) { this.child = child; this.zipper = zipper; this.producer = producer; } - boolean started = false; - @Override public void onCompleted() { if (!started) { @@ -159,10 +146,11 @@ public void onNext(Observable[] observables) { } - private static final class ZipProducer extends AtomicLong implements Producer { + static final class ZipProducer extends AtomicLong implements Producer { /** */ private static final long serialVersionUID = -1216676403723546796L; - private Zip zipper; + + final Zip zipper; public ZipProducer(Zip zipper) { this.zipper = zipper; @@ -180,13 +168,13 @@ public void request(long n) { static final class Zip extends AtomicLong { /** */ private static final long serialVersionUID = 5995274816189928317L; - + final Observer child; private final FuncN zipFunction; private final CompositeSubscription childSubscription = new CompositeSubscription(); static final int THRESHOLD = (int) (RxRingBuffer.SIZE * 0.7); - int emitted = 0; // not volatile/synchronized as accessed inside COUNTER_UPDATER block + int emitted; // not volatile/synchronized as accessed inside COUNTER_UPDATER block /* initialized when started in `start` */ private volatile Object[] subscribers; @@ -206,10 +194,10 @@ public void start(@SuppressWarnings("rawtypes") Observable[] os, AtomicLong requ subscribers[i] = io; childSubscription.add(io); } - + this.requested = requested; this.subscribers = subscribers; // full memory barrier: release all above - + for (int i = 0; i < os.length; i++) { os[i].unsafeSubscribe((InnerSubscriber) subscribers[i]); } @@ -217,10 +205,10 @@ public void start(@SuppressWarnings("rawtypes") Observable[] os, AtomicLong requ /** * check if we have values for each and emit if we do - * + * * This will only allow one thread at a time to do the work, but ensures via `counter` increment/decrement * that there is always once who acts on each `tick`. Same concept as used in OperationObserveOn. - * + * */ @SuppressWarnings("unchecked") void tick() { @@ -258,7 +246,7 @@ void tick() { } } // we only emit if requested > 0 and have all values available - if (requested.get() > 0 && allHaveValues) { + if (allHaveValues && requested.get() > 0) { try { // all have something so emit child.onNext(zipFunction.call(vs)); @@ -308,7 +296,7 @@ final class InnerSubscriber extends Subscriber { public void onStart() { request(RxRingBuffer.SIZE); } - + public void requestMore(long n) { request(n); } @@ -334,7 +322,7 @@ public void onNext(Object t) { } tick(); } - }; + } } } diff --git a/src/main/java/rx/internal/operators/OperatorZipIterable.java b/src/main/java/rx/internal/operators/OperatorZipIterable.java index a90e62f470..1bff49c910 100644 --- a/src/main/java/rx/internal/operators/OperatorZipIterable.java +++ b/src/main/java/rx/internal/operators/OperatorZipIterable.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -18,8 +18,8 @@ import java.util.Iterator; import rx.Observable.Operator; -import rx.exceptions.Exceptions; import rx.Subscriber; +import rx.exceptions.Exceptions; import rx.functions.Func2; import rx.observers.Subscribers; diff --git a/src/main/java/rx/internal/operators/SingleDelay.java b/src/main/java/rx/internal/operators/SingleDelay.java new file mode 100644 index 0000000000..512893f156 --- /dev/null +++ b/src/main/java/rx/internal/operators/SingleDelay.java @@ -0,0 +1,109 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * 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 rx.internal.operators; + +import java.util.concurrent.TimeUnit; + +import rx.*; +import rx.Scheduler.Worker; +import rx.Single.OnSubscribe; +import rx.functions.Action0; + +/** + * Signal the success or error value on the Scheduler's thread. + * + * @param the value type + */ +public final class SingleDelay implements Single.OnSubscribe { + + final Single.OnSubscribe source; + + final long delay; + + final TimeUnit unit; + + final Scheduler scheduler; + + public SingleDelay(OnSubscribe source, long delay, TimeUnit unit, Scheduler scheduler) { + this.source = source; + this.scheduler = scheduler; + this.delay = delay; + this.unit = unit; + } + + @Override + public void call(SingleSubscriber t) { + Worker w = scheduler.createWorker(); + + ObserveOnSingleSubscriber parent = new ObserveOnSingleSubscriber(t, w, delay, unit); + + t.add(w); + t.add(parent); + + source.call(parent); + } + + static final class ObserveOnSingleSubscriber extends SingleSubscriber + implements Action0 { + final SingleSubscriber actual; + + final Worker w; + + final long delay; + + final TimeUnit unit; + + T value; + Throwable error; + + public ObserveOnSingleSubscriber(SingleSubscriber actual, Worker w, long delay, TimeUnit unit) { + this.actual = actual; + this.w = w; + this.delay = delay; + this.unit = unit; + } + + @Override + public void onSuccess(T value) { + this.value = value; + w.schedule(this, delay, unit); + } + + @Override + public void onError(Throwable error) { + this.error = error; + w.schedule(this, delay, unit); + } + + @Override + public void call() { + try { + Throwable ex = error; + if (ex != null) { + error = null; + actual.onError(ex); + } else { + T v = value; + value = null; + actual.onSuccess(v); + } + } finally { + w.unsubscribe(); + } + } + } +} diff --git a/src/main/java/rx/internal/operators/SingleDoAfterTerminate.java b/src/main/java/rx/internal/operators/SingleDoAfterTerminate.java index ea9986ebac..d3173031d3 100644 --- a/src/main/java/rx/internal/operators/SingleDoAfterTerminate.java +++ b/src/main/java/rx/internal/operators/SingleDoAfterTerminate.java @@ -1,12 +1,12 @@ /** * Copyright 2015 Netflix, Inc. - * + * * 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. @@ -18,7 +18,7 @@ import rx.*; import rx.exceptions.Exceptions; import rx.functions.Action0; -import rx.internal.util.RxJavaPluginUtils; +import rx.plugins.RxJavaHooks; /** * Execute an action after onSuccess or onError has been delivered. @@ -27,31 +27,31 @@ */ public final class SingleDoAfterTerminate implements Single.OnSubscribe { final Single source; - + final Action0 action; public SingleDoAfterTerminate(Single source, Action0 action) { this.source = source; this.action = action; } - + @Override public void call(SingleSubscriber t) { SingleDoAfterTerminateSubscriber parent = new SingleDoAfterTerminateSubscriber(t, action); t.add(parent); source.subscribe(parent); } - + static final class SingleDoAfterTerminateSubscriber extends SingleSubscriber { final SingleSubscriber actual; final Action0 action; - + public SingleDoAfterTerminateSubscriber(SingleSubscriber actual, Action0 action) { this.actual = actual; this.action = action; } - + @Override public void onSuccess(T value) { try { @@ -60,7 +60,7 @@ public void onSuccess(T value) { doAction(); } } - + @Override public void onError(Throwable error) { try { @@ -69,16 +69,16 @@ public void onError(Throwable error) { doAction(); } } - + void doAction() { try { action.call(); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); - RxJavaPluginUtils.handleException(ex); + RxJavaHooks.onError(ex); } } } - - + + } diff --git a/src/main/java/rx/internal/operators/SingleDoOnEvent.java b/src/main/java/rx/internal/operators/SingleDoOnEvent.java new file mode 100644 index 0000000000..102d3ee8cc --- /dev/null +++ b/src/main/java/rx/internal/operators/SingleDoOnEvent.java @@ -0,0 +1,79 @@ +/** + * Copyright 2014 Netflix, Inc. + * + * 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 rx.internal.operators; + +import rx.Single; +import rx.SingleSubscriber; +import rx.exceptions.CompositeException; +import rx.exceptions.Exceptions; +import rx.functions.Action1; + +public final class SingleDoOnEvent implements Single.OnSubscribe { + final Single source; + final Action1 onSuccess; + final Action1 onError; + + public SingleDoOnEvent(Single source, Action1 onSuccess, Action1 onError) { + this.source = source; + this.onSuccess = onSuccess; + this.onError = onError; + } + + @Override + public void call(SingleSubscriber actual) { + SingleDoOnEventSubscriber parent = new SingleDoOnEventSubscriber(actual, onSuccess, onError); + actual.add(parent); + source.subscribe(parent); + } + + static final class SingleDoOnEventSubscriber extends SingleSubscriber { + final SingleSubscriber actual; + final Action1 onSuccess; + final Action1 onError; + + SingleDoOnEventSubscriber(SingleSubscriber actual, Action1 onSuccess, Action1 onError) { + this.actual = actual; + this.onSuccess = onSuccess; + this.onError = onError; + } + + @Override + public void onSuccess(T value) { + try { + onSuccess.call(value); + } catch (Throwable e) { + Exceptions.throwOrReport(e, this, value); + return; + } + + actual.onSuccess(value); + } + + @Override + public void onError(Throwable error) { + try { + onError.call(error); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + actual.onError(new CompositeException(error, e)); + return; + } + + actual.onError(error); + } + } +} diff --git a/src/main/java/rx/internal/operators/SingleDoOnSubscribe.java b/src/main/java/rx/internal/operators/SingleDoOnSubscribe.java new file mode 100644 index 0000000000..5731e232ef --- /dev/null +++ b/src/main/java/rx/internal/operators/SingleDoOnSubscribe.java @@ -0,0 +1,52 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * 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 rx.internal.operators; + +import rx.*; +import rx.exceptions.Exceptions; +import rx.functions.Action0; + +/** + * Call an Action0 when the subscription happens to the source. + * + * @param the value type + */ +public final class SingleDoOnSubscribe implements Single.OnSubscribe { + + final Single.OnSubscribe source; + + final Action0 onSubscribe; + + public SingleDoOnSubscribe(Single.OnSubscribe source, Action0 onSubscribe) { + this.source = source; + this.onSubscribe = onSubscribe; + } + + @Override + public void call(SingleSubscriber t) { + + try { + onSubscribe.call(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + t.onError(ex); + return; + } + + source.call(t); + } +} diff --git a/src/main/java/rx/internal/operators/SingleDoOnUnsubscribe.java b/src/main/java/rx/internal/operators/SingleDoOnUnsubscribe.java new file mode 100644 index 0000000000..72385811c1 --- /dev/null +++ b/src/main/java/rx/internal/operators/SingleDoOnUnsubscribe.java @@ -0,0 +1,44 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * 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 rx.internal.operators; + +import rx.*; +import rx.functions.Action0; +import rx.subscriptions.Subscriptions; + +/** + * Call an Action0 when the subscription happens to the source. + * + * @param the value type + */ +public final class SingleDoOnUnsubscribe implements Single.OnSubscribe { + + final Single.OnSubscribe source; + + final Action0 onUnsubscribe; + + public SingleDoOnUnsubscribe(Single.OnSubscribe source, Action0 onUnsubscribe) { + this.source = source; + this.onUnsubscribe = onUnsubscribe; + } + + @Override + public void call(SingleSubscriber t) { + t.add(Subscriptions.create(onUnsubscribe)); + source.call(t); + } +} diff --git a/src/main/java/rx/internal/operators/SingleFromCallable.java b/src/main/java/rx/internal/operators/SingleFromCallable.java new file mode 100644 index 0000000000..2e8f6940b2 --- /dev/null +++ b/src/main/java/rx/internal/operators/SingleFromCallable.java @@ -0,0 +1,50 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * 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 rx.internal.operators; + +import java.util.concurrent.Callable; + +import rx.*; +import rx.exceptions.Exceptions; + +/** + * Execute a callable and emit its resulting value. + * + * @param the value type + */ +public final class SingleFromCallable implements Single.OnSubscribe { + + final Callable callable; + + public SingleFromCallable(Callable callable) { + this.callable = callable; + } + + @Override + public void call(SingleSubscriber t) { + T v; + try { + v = callable.call(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + t.onError(ex); + return; + } + + t.onSuccess(v); + } +} diff --git a/src/main/java/rx/internal/operators/SingleFromEmitter.java b/src/main/java/rx/internal/operators/SingleFromEmitter.java new file mode 100644 index 0000000000..b87c90f054 --- /dev/null +++ b/src/main/java/rx/internal/operators/SingleFromEmitter.java @@ -0,0 +1,118 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * 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 rx.internal.operators; + +import java.util.concurrent.atomic.AtomicBoolean; + +import rx.*; +import rx.Single.OnSubscribe; +import rx.exceptions.Exceptions; +import rx.functions.*; +import rx.internal.subscriptions.*; +import rx.plugins.RxJavaHooks; + +/** + * Calls an action with a SingleEmitter instance for each individual subscribers that + * generates a terminal signal (eventually). + * + * @param the success value type + */ +public final class SingleFromEmitter implements OnSubscribe { + + final Action1> producer; + + public SingleFromEmitter(Action1> producer) { + this.producer = producer; + } + + @Override + public void call(SingleSubscriber t) { + SingleEmitterImpl parent = new SingleEmitterImpl(t); + t.add(parent); + + try { + producer.call(parent); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + parent.onError(ex); + } + } + + static final class SingleEmitterImpl + extends AtomicBoolean + implements SingleEmitter, Subscription { + private static final long serialVersionUID = 8082834163465882809L; + + final SingleSubscriber actual; + + final SequentialSubscription resource; + + SingleEmitterImpl(SingleSubscriber actual) { + this.actual = actual; + this.resource = new SequentialSubscription(); + } + + @Override + public void unsubscribe() { + if (compareAndSet(false, true)) { + resource.unsubscribe(); + } + } + + @Override + public boolean isUnsubscribed() { + return get(); + } + + @Override + public void onSuccess(T t) { + if (compareAndSet(false, true)) { + try { + actual.onSuccess(t); + } finally { + resource.unsubscribe(); + } + } + } + + @Override + public void onError(Throwable t) { + if (t == null) { + t = new NullPointerException(); + } + if (compareAndSet(false, true)) { + try { + actual.onError(t); + } finally { + resource.unsubscribe(); + } + } else { + RxJavaHooks.onError(t); + } + } + + @Override + public void setSubscription(Subscription s) { + resource.update(s); + } + + @Override + public void setCancellation(Cancellable c) { + setSubscription(new CancellableSubscription(c)); + } + } +} diff --git a/src/main/java/rx/internal/operators/SingleFromFuture.java b/src/main/java/rx/internal/operators/SingleFromFuture.java new file mode 100644 index 0000000000..cf66161e4a --- /dev/null +++ b/src/main/java/rx/internal/operators/SingleFromFuture.java @@ -0,0 +1,66 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * 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 rx.internal.operators; + +import java.util.concurrent.*; + +import rx.*; +import rx.exceptions.Exceptions; +import rx.subscriptions.Subscriptions; + +/** + * Wait and emit the value of the Future. + * + * @param the value type + */ +public final class SingleFromFuture implements Single.OnSubscribe { + + final Future future; + + final long timeout; + + final TimeUnit unit; + + public SingleFromFuture(Future future, long timeout, TimeUnit unit) { + this.future = future; + this.timeout = timeout; + this.unit = unit; + } + + @Override + public void call(SingleSubscriber t) { + Future f = future; + + t.add(Subscriptions.from(f)); + + T v; + + try { + if (timeout == 0L) { + v = f.get(); + } else { + v = f.get(timeout, unit); + } + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + t.onError(ex); + return; + } + + t.onSuccess(v); + } +} diff --git a/src/main/java/rx/internal/operators/SingleFromObservable.java b/src/main/java/rx/internal/operators/SingleFromObservable.java new file mode 100644 index 0000000000..ec6a82e35c --- /dev/null +++ b/src/main/java/rx/internal/operators/SingleFromObservable.java @@ -0,0 +1,94 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * 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 rx.internal.operators; + +import java.util.NoSuchElementException; + +import rx.*; +import rx.Observable.OnSubscribe; +import rx.plugins.RxJavaHooks; + +/** + * Wrap an Observable.OnSubscribe and expose it as a Single.OnSubscribe. + * + * @param the value type + */ +public final class SingleFromObservable implements Single.OnSubscribe { + + final Observable.OnSubscribe source; + + public SingleFromObservable(OnSubscribe source) { + this.source = source; + } + + @Override + public void call(SingleSubscriber t) { + WrapSingleIntoSubscriber parent = new WrapSingleIntoSubscriber(t); + t.add(parent); + source.call(parent); + } + + static final class WrapSingleIntoSubscriber extends Subscriber { + + final SingleSubscriber actual; + + T value; + int state; + + static final int STATE_EMPTY = 0; + static final int STATE_HAS_VALUE = 1; + static final int STATE_DONE = 2; + + WrapSingleIntoSubscriber(SingleSubscriber actual) { + this.actual = actual; + } + + @Override + public void onNext(T t) { + int s = state; + if (s == STATE_EMPTY) { + state = STATE_HAS_VALUE; + value = t; + } else if (s == STATE_HAS_VALUE) { + state = STATE_DONE; + actual.onError(new IndexOutOfBoundsException("The upstream produced more than one value")); + } + } + + @Override + public void onError(Throwable e) { + if (state == STATE_DONE) { + RxJavaHooks.onError(e); + } else { + value = null; + actual.onError(e); + } + } + + @Override + public void onCompleted() { + int s = state; + if (s == STATE_EMPTY) { + actual.onError(new NoSuchElementException()); + } else if (s == STATE_HAS_VALUE) { + state = STATE_DONE; + T v = value; + value = null; + actual.onSuccess(v); + } + } + } +} diff --git a/src/main/java/rx/internal/operators/SingleLiftObservableOperator.java b/src/main/java/rx/internal/operators/SingleLiftObservableOperator.java new file mode 100644 index 0000000000..921210fc87 --- /dev/null +++ b/src/main/java/rx/internal/operators/SingleLiftObservableOperator.java @@ -0,0 +1,84 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * 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 rx.internal.operators; + +import rx.*; +import rx.Observable.Operator; +import rx.Single.OnSubscribe; +import rx.exceptions.Exceptions; +import rx.internal.operators.SingleFromObservable.WrapSingleIntoSubscriber; +import rx.internal.producers.SingleProducer; +import rx.plugins.RxJavaHooks; + +/** + * Lift an Observable.Operator into the Single sequence. + * @param the input value type + * @param the output value type + */ +public final class SingleLiftObservableOperator implements Single.OnSubscribe { + + final Single.OnSubscribe source; + + final Operator lift; + + public SingleLiftObservableOperator(OnSubscribe source, Operator lift) { + this.source = source; + this.lift = lift; + } + + @Override + public void call(SingleSubscriber t) { + Subscriber outputAsSubscriber = new WrapSingleIntoSubscriber(t); + t.add(outputAsSubscriber); + + try { + Subscriber inputAsSubscriber = RxJavaHooks.onSingleLift(lift).call(outputAsSubscriber); + + SingleSubscriber input = wrap(inputAsSubscriber); + + inputAsSubscriber.onStart(); + + source.call(input); + } catch (Throwable ex) { + Exceptions.throwOrReport(ex, t); + } + } + + public static SingleSubscriber wrap(Subscriber subscriber) { + WrapSubscriberIntoSingle parent = new WrapSubscriberIntoSingle(subscriber); + subscriber.add(parent); + return parent; + } + + static final class WrapSubscriberIntoSingle extends SingleSubscriber { + final Subscriber actual; + + WrapSubscriberIntoSingle(Subscriber actual) { + this.actual = actual; + } + + @Override + public void onSuccess(T value) { + actual.setProducer(new SingleProducer(actual, value)); + } + + @Override + public void onError(Throwable error) { + actual.onError(error); + } + } +} diff --git a/src/main/java/rx/internal/operators/SingleObserveOn.java b/src/main/java/rx/internal/operators/SingleObserveOn.java new file mode 100644 index 0000000000..dc9c2fc299 --- /dev/null +++ b/src/main/java/rx/internal/operators/SingleObserveOn.java @@ -0,0 +1,95 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * 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 rx.internal.operators; + +import rx.*; +import rx.Scheduler.Worker; +import rx.Single.OnSubscribe; +import rx.functions.Action0; + +/** + * Signal the success or error value on the Scheduler's thread. + * + * @param the value type + */ +public final class SingleObserveOn implements Single.OnSubscribe { + + final Single.OnSubscribe source; + + final Scheduler scheduler; + + public SingleObserveOn(OnSubscribe source, Scheduler scheduler) { + this.source = source; + this.scheduler = scheduler; + } + + @Override + public void call(SingleSubscriber t) { + Worker w = scheduler.createWorker(); + + ObserveOnSingleSubscriber parent = new ObserveOnSingleSubscriber(t, w); + + t.add(w); + t.add(parent); + + source.call(parent); + } + + static final class ObserveOnSingleSubscriber extends SingleSubscriber + implements Action0 { + final SingleSubscriber actual; + + final Worker w; + + T value; + Throwable error; + + public ObserveOnSingleSubscriber(SingleSubscriber actual, Worker w) { + this.actual = actual; + this.w = w; + } + + @Override + public void onSuccess(T value) { + this.value = value; + w.schedule(this); + } + + @Override + public void onError(Throwable error) { + this.error = error; + w.schedule(this); + } + + @Override + public void call() { + try { + Throwable ex = error; + if (ex != null) { + error = null; + actual.onError(ex); + } else { + T v = value; + value = null; + actual.onSuccess(v); + } + } finally { + w.unsubscribe(); + } + } + } +} diff --git a/src/main/java/rx/internal/operators/SingleOnErrorReturn.java b/src/main/java/rx/internal/operators/SingleOnErrorReturn.java new file mode 100644 index 0000000000..124a24dfd7 --- /dev/null +++ b/src/main/java/rx/internal/operators/SingleOnErrorReturn.java @@ -0,0 +1,79 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * 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 rx.internal.operators; + +import rx.*; +import rx.Single.OnSubscribe; +import rx.exceptions.Exceptions; +import rx.functions.Func1; + +/** + * Signal a value returned by a resumeFunction when the source signals a Throwable. + * + * @param the value type + */ +public final class SingleOnErrorReturn implements Single.OnSubscribe { + + final Single.OnSubscribe source; + + final Func1 resumeFunction; + + public SingleOnErrorReturn(OnSubscribe source, Func1 resumeFunction) { + this.source = source; + this.resumeFunction = resumeFunction; + } + + @Override + public void call(SingleSubscriber t) { + OnErrorReturnsSingleSubscriber parent = new OnErrorReturnsSingleSubscriber(t, resumeFunction); + t.add(parent); + source.call(parent); + } + + static final class OnErrorReturnsSingleSubscriber extends SingleSubscriber { + + final SingleSubscriber actual; + + final Func1 resumeFunction; + + public OnErrorReturnsSingleSubscriber(SingleSubscriber actual, + Func1 resumeFunction) { + this.actual = actual; + this.resumeFunction = resumeFunction; + } + + @Override + public void onSuccess(T value) { + actual.onSuccess(value); + } + + @Override + public void onError(Throwable error) { + T v; + + try { + v = resumeFunction.call(error); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + actual.onError(ex); + return; + } + + actual.onSuccess(v); + } + } +} diff --git a/src/main/java/rx/internal/operators/SingleOnSubscribeMap.java b/src/main/java/rx/internal/operators/SingleOnSubscribeMap.java new file mode 100644 index 0000000000..a087c0d556 --- /dev/null +++ b/src/main/java/rx/internal/operators/SingleOnSubscribeMap.java @@ -0,0 +1,90 @@ +/** + * Copyright 2014 Netflix, Inc. + * + * 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 rx.internal.operators; + +import rx.*; +import rx.exceptions.*; +import rx.functions.Func1; +import rx.plugins.RxJavaHooks; + +/** + * Applies a function of your choosing to every item emitted by an {@code Single}, and emits the results of + * this transformation as a new {@code Single}. + * + * @param the input value type + * @param the return value type + */ +public final class SingleOnSubscribeMap implements Single.OnSubscribe { + + final Single source; + + final Func1 transformer; + + public SingleOnSubscribeMap(Single source, Func1 transformer) { + this.source = source; + this.transformer = transformer; + } + + @Override + public void call(final SingleSubscriber o) { + MapSubscriber parent = new MapSubscriber(o, transformer); + o.add(parent); + source.subscribe(parent); + } + + static final class MapSubscriber extends SingleSubscriber { + + final SingleSubscriber actual; + + final Func1 mapper; + + boolean done; + + public MapSubscriber(SingleSubscriber actual, Func1 mapper) { + this.actual = actual; + this.mapper = mapper; + } + + @Override + public void onSuccess(T t) { + R result; + + try { + result = mapper.call(t); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + unsubscribe(); + onError(OnErrorThrowable.addValueAsLastCause(ex, t)); + return; + } + + actual.onSuccess(result); + } + + @Override + public void onError(Throwable e) { + if (done) { + RxJavaHooks.onError(e); + return; + } + done = true; + + actual.onError(e); + } + } + +} + diff --git a/src/main/java/rx/internal/operators/SingleOnSubscribeUsing.java b/src/main/java/rx/internal/operators/SingleOnSubscribeUsing.java index 9d7319724c..a23a1dc362 100644 --- a/src/main/java/rx/internal/operators/SingleOnSubscribeUsing.java +++ b/src/main/java/rx/internal/operators/SingleOnSubscribeUsing.java @@ -32,7 +32,7 @@ public final class SingleOnSubscribeUsing implements Single.OnSubscribe { final Func0 resourceFactory; final Func1> singleFactory; - final Action1 disposeAction; + final Action1 disposeAction; final boolean disposeEagerly; public SingleOnSubscribeUsing(Func0 resourceFactory, @@ -43,11 +43,11 @@ public SingleOnSubscribeUsing(Func0 resourceFactory, this.disposeAction = disposeAction; this.disposeEagerly = disposeEagerly; } - + @Override public void call(final SingleSubscriber child) { - final Resource resource; - + final Resource resource; // NOPMD + try { resource = resourceFactory.call(); } catch (Throwable ex) { @@ -55,21 +55,21 @@ public void call(final SingleSubscriber child) { child.onError(ex); return; } - + Single single; - + try { single = singleFactory.call(resource); } catch (Throwable ex) { handleSubscriptionTimeError(child, resource, ex); return; } - + if (single == null) { handleSubscriptionTimeError(child, resource, new NullPointerException("The single")); return; } - + SingleSubscriber parent = new SingleSubscriber() { @Override public void onSuccess(T value) { @@ -78,14 +78,14 @@ public void onSuccess(T value) { disposeAction.call(resource); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); - + child.onError(ex); return; } } - + child.onSuccess(value); - + if (!disposeEagerly) { try { disposeAction.call(resource); @@ -95,14 +95,14 @@ public void onSuccess(T value) { } } } - + @Override public void onError(Throwable error) { handleSubscriptionTimeError(child, resource, error); } }; child.add(parent); - + single.subscribe(parent); } @@ -117,9 +117,9 @@ void handleSubscriptionTimeError(SingleSubscriber t, Resource resourc ex = new CompositeException(Arrays.asList(ex, ex2)); } } - + t.onError(ex); - + if (!disposeEagerly) { try { disposeAction.call(resource); diff --git a/src/main/java/rx/internal/operators/SingleOperatorCast.java b/src/main/java/rx/internal/operators/SingleOperatorCast.java new file mode 100644 index 0000000000..b77381b161 --- /dev/null +++ b/src/main/java/rx/internal/operators/SingleOperatorCast.java @@ -0,0 +1,37 @@ +/** + * Copyright 2014 Netflix, Inc. + * + * 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 rx.internal.operators; + +import rx.functions.Func1; + +/** + * Converts the element of a Single to the specified type. + * @param the input value type + * @param the output value type + */ +public class SingleOperatorCast implements Func1 { + + final Class castClass; + + public SingleOperatorCast(Class castClass) { + this.castClass = castClass; + } + + @Override + public R call(T t) { + return castClass.cast(t); + } +} diff --git a/src/main/java/rx/internal/operators/SingleOperatorOnErrorResumeNext.java b/src/main/java/rx/internal/operators/SingleOperatorOnErrorResumeNext.java index 393a72dfdd..e5c5dc5ee3 100644 --- a/src/main/java/rx/internal/operators/SingleOperatorOnErrorResumeNext.java +++ b/src/main/java/rx/internal/operators/SingleOperatorOnErrorResumeNext.java @@ -16,15 +16,14 @@ package rx.internal.operators; -import rx.Single; -import rx.SingleSubscriber; +import rx.*; import rx.exceptions.Exceptions; import rx.functions.Func1; -public class SingleOperatorOnErrorResumeNext implements Single.OnSubscribe { +public final class SingleOperatorOnErrorResumeNext implements Single.OnSubscribe { private final Single originalSingle; - private final Func1> resumeFunctionInCaseOfError; + final Func1> resumeFunctionInCaseOfError; private SingleOperatorOnErrorResumeNext(Single originalSingle, Func1> resumeFunctionInCaseOfError) { if (originalSingle == null) { diff --git a/src/main/java/rx/internal/operators/SingleOperatorZip.java b/src/main/java/rx/internal/operators/SingleOperatorZip.java index 93afe523d3..96215b9054 100644 --- a/src/main/java/rx/internal/operators/SingleOperatorZip.java +++ b/src/main/java/rx/internal/operators/SingleOperatorZip.java @@ -25,7 +25,12 @@ import rx.plugins.RxJavaHooks; import rx.subscriptions.CompositeSubscription; -public class SingleOperatorZip { +public final class SingleOperatorZip { + + /** Utility class. */ + private SingleOperatorZip() { + throw new IllegalStateException("No instances!"); + } public static Single zip(final Single[] singles, final FuncN zipper) { return Single.create(new Single.OnSubscribe() { diff --git a/src/main/java/rx/internal/operators/SingleTakeUntilCompletable.java b/src/main/java/rx/internal/operators/SingleTakeUntilCompletable.java new file mode 100644 index 0000000000..17111084e7 --- /dev/null +++ b/src/main/java/rx/internal/operators/SingleTakeUntilCompletable.java @@ -0,0 +1,92 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * 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 rx.internal.operators; + +import java.util.concurrent.CancellationException; +import java.util.concurrent.atomic.AtomicBoolean; + +import rx.*; +import rx.Single.OnSubscribe; +import rx.plugins.RxJavaHooks; + +/** + * Relay the source signals if the other doesn't terminate before. + * + * @param the value type + */ +public final class SingleTakeUntilCompletable implements Single.OnSubscribe { + + final Single.OnSubscribe source; + + final Completable other; + + public SingleTakeUntilCompletable(OnSubscribe source, Completable other) { + this.source = source; + this.other = other; + } + + @Override + public void call(SingleSubscriber t) { + TakeUntilSourceSubscriber parent = new TakeUntilSourceSubscriber(t); + t.add(parent); + + other.subscribe(parent); + source.call(parent); + } + + static final class TakeUntilSourceSubscriber extends SingleSubscriber + implements CompletableSubscriber { + + final SingleSubscriber actual; + + final AtomicBoolean once; + + TakeUntilSourceSubscriber(SingleSubscriber actual) { + this.actual = actual; + this.once = new AtomicBoolean(); + } + + @Override + public void onSubscribe(Subscription d) { + add(d); + } + + @Override + public void onSuccess(T value) { + if (once.compareAndSet(false, true)) { + unsubscribe(); + + actual.onSuccess(value); + } + } + + @Override + public void onError(Throwable error) { + if (once.compareAndSet(false, true)) { + unsubscribe(); + actual.onError(error); + } else { + RxJavaHooks.onError(error); + } + } + + @Override + public void onCompleted() { + onError(new CancellationException("Single::takeUntil(Completable) - Stream was canceled before emitting a terminal event.")); + } + } +} diff --git a/src/main/java/rx/internal/operators/SingleTakeUntilObservable.java b/src/main/java/rx/internal/operators/SingleTakeUntilObservable.java new file mode 100644 index 0000000000..604fda6f1f --- /dev/null +++ b/src/main/java/rx/internal/operators/SingleTakeUntilObservable.java @@ -0,0 +1,103 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * 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 rx.internal.operators; + +import java.util.concurrent.CancellationException; +import java.util.concurrent.atomic.AtomicBoolean; + +import rx.*; +import rx.Single.OnSubscribe; +import rx.plugins.RxJavaHooks; + +/** + * Relay the source signals if the other doesn't terminate before. + * + * @param the value type + * @param the other's value type (not relevant) + */ +public final class SingleTakeUntilObservable implements Single.OnSubscribe { + + final Single.OnSubscribe source; + + final Observable other; + + public SingleTakeUntilObservable(OnSubscribe source, Observable other) { + this.source = source; + this.other = other; + } + + @Override + public void call(SingleSubscriber t) { + TakeUntilSourceSubscriber parent = new TakeUntilSourceSubscriber(t); + t.add(parent); + + other.subscribe(parent.other); + source.call(parent); + } + + static final class TakeUntilSourceSubscriber extends SingleSubscriber { + + final SingleSubscriber actual; + + final AtomicBoolean once; + + final Subscriber other; + + TakeUntilSourceSubscriber(SingleSubscriber actual) { + this.actual = actual; + this.once = new AtomicBoolean(); + this.other = new OtherSubscriber(); + add(other); + } + + @Override + public void onSuccess(T value) { + if (once.compareAndSet(false, true)) { + unsubscribe(); + + actual.onSuccess(value); + } + } + + @Override + public void onError(Throwable error) { + if (once.compareAndSet(false, true)) { + unsubscribe(); + actual.onError(error); + } else { + RxJavaHooks.onError(error); + } + } + + final class OtherSubscriber extends Subscriber { + @Override + public void onNext(U value) { + onCompleted(); + } + + @Override + public void onError(Throwable error) { + TakeUntilSourceSubscriber.this.onError(error); + } + + @Override + public void onCompleted() { + onError(new CancellationException("Single::takeUntil(Observable) - Stream was canceled before emitting a terminal event.")); + } + } + } +} diff --git a/src/main/java/rx/internal/operators/SingleTakeUntilSingle.java b/src/main/java/rx/internal/operators/SingleTakeUntilSingle.java new file mode 100644 index 0000000000..24c0439817 --- /dev/null +++ b/src/main/java/rx/internal/operators/SingleTakeUntilSingle.java @@ -0,0 +1,98 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * 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 rx.internal.operators; + +import java.util.concurrent.CancellationException; +import java.util.concurrent.atomic.AtomicBoolean; + +import rx.*; +import rx.Single.OnSubscribe; +import rx.plugins.RxJavaHooks; + +/** + * Relay the source signals if the other doesn't terminate before. + * + * @param the value type + * @param the other's value type (not relevant) + */ +public final class SingleTakeUntilSingle implements Single.OnSubscribe { + + final Single.OnSubscribe source; + + final Single other; + + public SingleTakeUntilSingle(OnSubscribe source, Single other) { + this.source = source; + this.other = other; + } + + @Override + public void call(SingleSubscriber t) { + TakeUntilSourceSubscriber parent = new TakeUntilSourceSubscriber(t); + t.add(parent); + + other.subscribe(parent.other); + source.call(parent); + } + + static final class TakeUntilSourceSubscriber extends SingleSubscriber { + + final SingleSubscriber actual; + + final AtomicBoolean once; + + final SingleSubscriber other; + + TakeUntilSourceSubscriber(SingleSubscriber actual) { + this.actual = actual; + this.once = new AtomicBoolean(); + this.other = new OtherSubscriber(); + add(other); + } + + @Override + public void onSuccess(T value) { + if (once.compareAndSet(false, true)) { + unsubscribe(); + + actual.onSuccess(value); + } + } + + @Override + public void onError(Throwable error) { + if (once.compareAndSet(false, true)) { + unsubscribe(); + actual.onError(error); + } else { + RxJavaHooks.onError(error); + } + } + + final class OtherSubscriber extends SingleSubscriber { + @Override + public void onSuccess(U value) { + onError(new CancellationException("Single::takeUntil(Single) - Stream was canceled before emitting a terminal event.")); + } + + @Override + public void onError(Throwable error) { + TakeUntilSourceSubscriber.this.onError(error); + } + } + } +} diff --git a/src/main/java/rx/internal/operators/SingleTimeout.java b/src/main/java/rx/internal/operators/SingleTimeout.java new file mode 100644 index 0000000000..9376ada95e --- /dev/null +++ b/src/main/java/rx/internal/operators/SingleTimeout.java @@ -0,0 +1,137 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * 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 rx.internal.operators; + +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicBoolean; + +import rx.*; +import rx.functions.Action0; +import rx.plugins.RxJavaHooks; + +public final class SingleTimeout implements Single.OnSubscribe { + + final Single.OnSubscribe source; + + final long timeout; + + final TimeUnit unit; + + final Scheduler scheduler; + + final Single.OnSubscribe other; + + public SingleTimeout(Single.OnSubscribe source, long timeout, TimeUnit unit, Scheduler scheduler, + Single.OnSubscribe other) { + this.source = source; + this.timeout = timeout; + this.unit = unit; + this.scheduler = scheduler; + this.other = other; + } + + @Override + public void call(SingleSubscriber t) { + TimeoutSingleSubscriber parent = new TimeoutSingleSubscriber(t, other); + + Scheduler.Worker w = scheduler.createWorker(); + parent.add(w); + + t.add(parent); + + w.schedule(parent, timeout, unit); + + source.call(parent); + } + + static final class TimeoutSingleSubscriber extends SingleSubscriber implements Action0 { + + final SingleSubscriber actual; + + final AtomicBoolean once; + + final Single.OnSubscribe other; + + TimeoutSingleSubscriber(SingleSubscriber actual, Single.OnSubscribe other) { + this.actual = actual; + this.other = other; + this.once = new AtomicBoolean(); + } + + @Override + public void onSuccess(T value) { + if (once.compareAndSet(false, true)) { + try { + actual.onSuccess(value); + } finally { + unsubscribe(); + } + } + } + + @Override + public void onError(Throwable error) { + if (once.compareAndSet(false, true)) { + try { + actual.onError(error); + } finally { + unsubscribe(); + } + } else { + RxJavaHooks.onError(error); + } + } + + @Override + public void call() { + if (once.compareAndSet(false, true)) { + try { + Single.OnSubscribe o = other; + + if (o == null) { + actual.onError(new TimeoutException()); + } else { + OtherSubscriber p = new OtherSubscriber(actual); + actual.add(p); + o.call(p); + } + } finally { + unsubscribe(); + } + } + } + + static final class OtherSubscriber extends SingleSubscriber { + + final SingleSubscriber actual; + + OtherSubscriber(SingleSubscriber actual) { + this.actual = actual; + } + + @Override + public void onSuccess(T value) { + actual.onSuccess(value); + } + + @Override + public void onError(Throwable error) { + actual.onError(error); + } + } + } +} diff --git a/src/main/java/rx/internal/operators/SingleToObservable.java b/src/main/java/rx/internal/operators/SingleToObservable.java new file mode 100644 index 0000000000..50d9a9cf0e --- /dev/null +++ b/src/main/java/rx/internal/operators/SingleToObservable.java @@ -0,0 +1,41 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * 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 rx.internal.operators; + +import rx.*; +import rx.internal.operators.SingleLiftObservableOperator.WrapSubscriberIntoSingle; + +/** + * Expose a Single.OnSubscribe as an Observable.OnSubscribe. + * + * @param the value type + */ +public final class SingleToObservable implements Observable.OnSubscribe { + + final Single.OnSubscribe source; + + public SingleToObservable(Single.OnSubscribe source) { + this.source = source; + } + + @Override + public void call(Subscriber t) { + WrapSubscriberIntoSingle parent = new WrapSubscriberIntoSingle(t); + t.add(parent); + source.call(parent); + } +} diff --git a/src/main/java/rx/internal/producers/ProducerArbiter.java b/src/main/java/rx/internal/producers/ProducerArbiter.java index b23904103e..a734a38488 100644 --- a/src/main/java/rx/internal/producers/ProducerArbiter.java +++ b/src/main/java/rx/internal/producers/ProducerArbiter.java @@ -24,19 +24,19 @@ public final class ProducerArbiter implements Producer { long requested; Producer currentProducer; - + boolean emitting; long missedRequested; long missedProduced; Producer missedProducer; - + static final Producer NULL_PRODUCER = new Producer() { @Override public void request(long n) { - + // deliberately ignored } }; - + @Override public void request(long n) { if (n < 0) { @@ -60,12 +60,12 @@ public void request(long n) { u = Long.MAX_VALUE; } requested = u; - + Producer p = currentProducer; if (p != null) { p.request(n); } - + emitLoop(); skipFinal = true; } finally { @@ -76,7 +76,7 @@ public void request(long n) { } } } - + public void produced(long n) { if (n <= 0) { throw new IllegalArgumentException("n > 0 required"); @@ -88,7 +88,7 @@ public void produced(long n) { } emitting = true; } - + boolean skipFinal = false; try { long r = requested; @@ -99,7 +99,7 @@ public void produced(long n) { } requested = u; } - + emitLoop(); skipFinal = true; } finally { @@ -110,7 +110,7 @@ public void produced(long n) { } } } - + public void setProducer(Producer newProducer) { synchronized (this) { if (emitting) { @@ -125,7 +125,7 @@ public void setProducer(Producer newProducer) { if (newProducer != null) { newProducer.request(requested); } - + emitLoop(); skipFinal = true; } finally { @@ -136,7 +136,7 @@ public void setProducer(Producer newProducer) { } } } - + public void emitLoop() { for (;;) { long localRequested; @@ -146,7 +146,7 @@ public void emitLoop() { localRequested = missedRequested; localProduced = missedProduced; localProducer = missedProducer; - if (localRequested == 0L + if (localRequested == 0L && localProduced == 0L && localProducer == null) { emitting = false; @@ -156,9 +156,9 @@ public void emitLoop() { missedProduced = 0L; missedProducer = null; } - + long r = requested; - + if (r != Long.MAX_VALUE) { long u = r + localRequested; if (u < 0 || u == Long.MAX_VALUE) { diff --git a/src/main/java/rx/internal/producers/ProducerObserverArbiter.java b/src/main/java/rx/internal/producers/ProducerObserverArbiter.java index 985352a3f4..37c3faeb52 100644 --- a/src/main/java/rx/internal/producers/ProducerObserverArbiter.java +++ b/src/main/java/rx/internal/producers/ProducerObserverArbiter.java @@ -32,31 +32,31 @@ */ public final class ProducerObserverArbiter implements Producer, Observer { final Subscriber child; - + boolean emitting; - + List queue; - + Producer currentProducer; long requested; - + long missedRequested; Producer missedProducer; Object missedTerminal; - + volatile boolean hasError; - + static final Producer NULL_PRODUCER = new Producer() { @Override public void request(long n) { - + // deliberately ignored } }; - + public ProducerObserverArbiter(Subscriber child) { this.child = child; } - + @Override public void onNext(T t) { synchronized (this) { @@ -69,16 +69,17 @@ public void onNext(T t) { q.add(t); return; } + emitting = true; } boolean skipFinal = false; try { child.onNext(t); - + long r = requested; if (r != Long.MAX_VALUE) { requested = r - 1; } - + emitLoop(); skipFinal = true; } finally { @@ -89,7 +90,7 @@ public void onNext(T t) { } } } - + @Override public void onError(Throwable e) { boolean emit; @@ -108,7 +109,7 @@ public void onError(Throwable e) { hasError = true; } } - + @Override public void onCompleted() { synchronized (this) { @@ -120,7 +121,7 @@ public void onCompleted() { } child.onCompleted(); } - + @Override public void request(long n) { if (n < 0) { @@ -159,7 +160,7 @@ public void request(long n) { p.request(n); } } - + public void setProducer(Producer p) { synchronized (this) { if (emitting) { @@ -185,13 +186,13 @@ public void setProducer(Producer p) { p.request(r); } } - + void emitLoop() { final Subscriber c = child; long toRequest = 0L; Producer requestFrom = null; - + outer: for (;;) { long localRequested; @@ -221,7 +222,7 @@ void emitLoop() { } return; } - + boolean empty = q == null || q.isEmpty(); if (localTerminal != null) { if (localTerminal != Boolean.TRUE) { diff --git a/src/main/java/rx/internal/producers/QueuedProducer.java b/src/main/java/rx/internal/producers/QueuedProducer.java index ed892bab30..5f09d35822 100644 --- a/src/main/java/rx/internal/producers/QueuedProducer.java +++ b/src/main/java/rx/internal/producers/QueuedProducer.java @@ -25,32 +25,32 @@ import rx.internal.util.unsafe.*; /** - * Producer that holds an unbounded (or custom) queue, handles terminal events, + * Producer that holds an unbounded (or custom) queue, handles terminal events, * enqueues values and relays them to a child subscriber on request. * * @param the value type */ public final class QueuedProducer extends AtomicLong implements Producer, Observer { - + /** */ private static final long serialVersionUID = 7277121710709137047L; - + final Subscriber child; final Queue queue; final AtomicInteger wip; - + Throwable error; volatile boolean done; - + static final Object NULL_SENTINEL = new Object(); - + /** * Constructs an instance with the target child subscriber and an Spsc Linked (Atomic) Queue * as the queue implementation. * @param child the target child subscriber */ public QueuedProducer(Subscriber child) { - this(child, UnsafeAccess.isUnsafeAvailable() + this(child, UnsafeAccess.isUnsafeAvailable() ? new SpscLinkedQueue() : new SpscLinkedAtomicQueue()); } /** @@ -63,7 +63,7 @@ public QueuedProducer(Subscriber child, Queue queue) { this.queue = queue; this.wip = new AtomicInteger(); } - + @Override public void request(long n) { if (n < 0) { @@ -74,7 +74,7 @@ public void request(long n) { drain(); } } - + /** * Offers a value to this producer and tries to emit any queued values * if the child requests allow it. @@ -94,28 +94,28 @@ public boolean offer(T value) { drain(); return true; } - + @Override public void onNext(T value) { if (!offer(value)) { onError(new MissingBackpressureException()); } } - + @Override public void onError(Throwable e) { error = e; done = true; drain(); } - + @Override public void onCompleted() { done = true; drain(); } - - private boolean checkTerminated(boolean isDone, + + private boolean checkTerminated(boolean isDone, boolean isEmpty) { if (child.isUnsubscribed()) { return true; @@ -134,7 +134,7 @@ private boolean checkTerminated(boolean isDone, } return false; } - + private void drain() { if (wip.getAndIncrement() == 0) { final Subscriber c = child; @@ -144,12 +144,12 @@ private void drain() { if (checkTerminated(done, q.isEmpty())) { // (1) return; } - + wip.lazySet(1); - + long r = get(); long e = 0; - + while (r != 0) { boolean d = done; Object v = q.poll(); @@ -159,7 +159,7 @@ private void drain() { if (v == null) { break; } - + try { if (v == NULL_SENTINEL) { c.onNext(null); @@ -175,7 +175,7 @@ private void drain() { r--; e++; } - + if (e != 0 && get() != Long.MAX_VALUE) { addAndGet(-e); } diff --git a/src/main/java/rx/internal/producers/QueuedValueProducer.java b/src/main/java/rx/internal/producers/QueuedValueProducer.java index 53853f2ccc..e750cd2957 100644 --- a/src/main/java/rx/internal/producers/QueuedValueProducer.java +++ b/src/main/java/rx/internal/producers/QueuedValueProducer.java @@ -31,23 +31,23 @@ * @param the value type */ public final class QueuedValueProducer extends AtomicLong implements Producer { - + /** */ private static final long serialVersionUID = 7277121710709137047L; - + final Subscriber child; final Queue queue; final AtomicInteger wip; - + static final Object NULL_SENTINEL = new Object(); - + /** * Constructs an instance with the target child subscriber and an Spsc Linked (Atomic) Queue * as the queue implementation. * @param child the target child subscriber */ public QueuedValueProducer(Subscriber child) { - this(child, UnsafeAccess.isUnsafeAvailable() + this(child, UnsafeAccess.isUnsafeAvailable() ? new SpscLinkedQueue() : new SpscLinkedAtomicQueue()); } /** @@ -60,7 +60,7 @@ public QueuedValueProducer(Subscriber child, Queue queue) { this.queue = queue; this.wip = new AtomicInteger(); } - + @Override public void request(long n) { if (n < 0) { @@ -71,7 +71,7 @@ public void request(long n) { drain(); } } - + /** * Offers a value to this producer and tries to emit any queued values * if the child requests allow it. @@ -91,7 +91,7 @@ public boolean offer(T value) { drain(); return true; } - + private void drain() { if (wip.getAndIncrement() == 0) { final Subscriber c = child; @@ -100,13 +100,13 @@ private void drain() { if (c.isUnsubscribed()) { return; } - + wip.lazySet(1); - + long r = get(); long e = 0; Object v; - + while (r != 0 && (v = q.poll()) != null) { try { if (v == NULL_SENTINEL) { @@ -126,7 +126,7 @@ private void drain() { r--; e++; } - + if (e != 0 && get() != Long.MAX_VALUE) { addAndGet(-e); } diff --git a/src/main/java/rx/internal/producers/SingleDelayedProducer.java b/src/main/java/rx/internal/producers/SingleDelayedProducer.java index 12403fe21b..99e93c51ff 100644 --- a/src/main/java/rx/internal/producers/SingleDelayedProducer.java +++ b/src/main/java/rx/internal/producers/SingleDelayedProducer.java @@ -33,12 +33,12 @@ public final class SingleDelayedProducer extends AtomicInteger implements Pro final Subscriber child; /** The value to emit.*/ T value; - + static final int NO_REQUEST_NO_VALUE = 0; static final int NO_REQUEST_HAS_VALUE = 1; static final int HAS_REQUEST_NO_VALUE = 2; static final int HAS_REQUEST_HAS_VALUE = 3; - + /** * Constructor, wraps the target child subscriber. * @param child the child subscriber, not null @@ -46,7 +46,7 @@ public final class SingleDelayedProducer extends AtomicInteger implements Pro public SingleDelayedProducer(Subscriber child) { this.child = child; } - + @Override public void request(long n) { if (n < 0) { @@ -67,10 +67,10 @@ public void request(long n) { emit(child, value); } } - return; + return; // NOPMD } } - + public void setValue(T value) { for (;;) { int s = get(); @@ -85,14 +85,14 @@ public void setValue(T value) { emit(child, value); } } - return; + return; // NOPMD } } /** * Emits the given value to the child subscriber and completes it * and checks for unsubscriptions eagerly. - * @param c - * @param v + * @param c the target Subscriber to emit to + * @param v the value to emit */ private static void emit(Subscriber c, T v) { if (c.isUnsubscribed()) { @@ -108,6 +108,6 @@ private static void emit(Subscriber c, T v) { return; } c.onCompleted(); - + } } \ No newline at end of file diff --git a/src/main/java/rx/internal/producers/SingleProducer.java b/src/main/java/rx/internal/producers/SingleProducer.java index 337d815d91..51baf52547 100644 --- a/src/main/java/rx/internal/producers/SingleProducer.java +++ b/src/main/java/rx/internal/producers/SingleProducer.java @@ -55,11 +55,11 @@ public void request(long n) { if (compareAndSet(false, true)) { // avoid re-reading the instance fields final Subscriber c = child; - T v = value; // eagerly check for unsubscription if (c.isUnsubscribed()) { return; } + T v = value; // emit the value try { c.onNext(v); diff --git a/src/main/java/rx/internal/schedulers/CachedThreadScheduler.java b/src/main/java/rx/internal/schedulers/CachedThreadScheduler.java index c1655f0aa3..e5728540d5 100644 --- a/src/main/java/rx/internal/schedulers/CachedThreadScheduler.java +++ b/src/main/java/rx/internal/schedulers/CachedThreadScheduler.java @@ -24,16 +24,28 @@ import rx.subscriptions.*; public final class CachedThreadScheduler extends Scheduler implements SchedulerLifecycle { - private static final long KEEP_ALIVE_TIME = 60; + private static final long KEEP_ALIVE_TIME; private static final TimeUnit KEEP_ALIVE_UNIT = TimeUnit.SECONDS; - + static final ThreadWorker SHUTDOWN_THREADWORKER; + + static final CachedWorkerPool NONE; + + final ThreadFactory threadFactory; + + final AtomicReference pool; + static { SHUTDOWN_THREADWORKER = new ThreadWorker(RxThreadFactory.NONE); SHUTDOWN_THREADWORKER.unsubscribe(); + + NONE = new CachedWorkerPool(null, 0, null); + NONE.shutdown(); + + KEEP_ALIVE_TIME = Integer.getInteger("rx.io-scheduler.keepalive", 60); } - - private static final class CachedWorkerPool { + + static final class CachedWorkerPool { private final ThreadFactory threadFactory; private final long keepAliveTime; private final ConcurrentLinkedQueue expiringWorkerQueue; @@ -116,7 +128,7 @@ void evictExpiredWorkers() { long now() { return System.nanoTime(); } - + void shutdown() { try { if (evictorTask != null) { @@ -131,21 +143,12 @@ void shutdown() { } } - final ThreadFactory threadFactory; - final AtomicReference pool; - - static final CachedWorkerPool NONE; - static { - NONE = new CachedWorkerPool(null, 0, null); - NONE.shutdown(); - } - public CachedThreadScheduler(ThreadFactory threadFactory) { this.threadFactory = threadFactory; this.pool = new AtomicReference(NONE); start(); } - + @Override public void start() { CachedWorkerPool update = @@ -167,13 +170,13 @@ public void shutdown() { } } } - + @Override public Worker createWorker() { return new EventLoopWorker(pool.get()); } - private static final class EventLoopWorker extends Scheduler.Worker { + static final class EventLoopWorker extends Scheduler.Worker implements Action0 { private final CompositeSubscription innerSubscription = new CompositeSubscription(); private final CachedWorkerPool pool; private final ThreadWorker threadWorker; @@ -189,11 +192,18 @@ private static final class EventLoopWorker extends Scheduler.Worker { public void unsubscribe() { if (once.compareAndSet(false, true)) { // unsubscribe should be idempotent, so only do this once - pool.release(threadWorker); + + // Release the worker _after_ the previous action (if any) has completed + threadWorker.schedule(this); } innerSubscription.unsubscribe(); } + @Override + public void call() { + pool.release(threadWorker); + } + @Override public boolean isUnsubscribed() { return innerSubscription.isUnsubscribed(); @@ -226,7 +236,7 @@ public void call() { } } - private static final class ThreadWorker extends NewThreadWorker { + static final class ThreadWorker extends NewThreadWorker { private long expirationTime; ThreadWorker(ThreadFactory threadFactory) { diff --git a/src/main/java/rx/internal/schedulers/EventLoopsScheduler.java b/src/main/java/rx/internal/schedulers/EventLoopsScheduler.java index fb813412db..ed35e501e2 100644 --- a/src/main/java/rx/internal/schedulers/EventLoopsScheduler.java +++ b/src/main/java/rx/internal/schedulers/EventLoopsScheduler.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -33,22 +33,29 @@ public final class EventLoopsScheduler extends Scheduler implements SchedulerLif static final int MAX_THREADS; static { int maxThreads = Integer.getInteger(KEY_MAX_THREADS, 0); - int ncpu = Runtime.getRuntime().availableProcessors(); + int cpuCount = Runtime.getRuntime().availableProcessors(); int max; - if (maxThreads <= 0 || maxThreads > ncpu) { - max = ncpu; + if (maxThreads <= 0 || maxThreads > cpuCount) { + max = cpuCount; } else { max = maxThreads; } MAX_THREADS = max; } - + static final PoolWorker SHUTDOWN_WORKER; static { SHUTDOWN_WORKER = new PoolWorker(RxThreadFactory.NONE); SHUTDOWN_WORKER.unsubscribe(); } - + + /** This will indicate no pool is active. */ + static final FixedSchedulerPool NONE = new FixedSchedulerPool(null, 0); + + final ThreadFactory threadFactory; + + final AtomicReference pool; + static final class FixedSchedulerPool { final int cores; @@ -72,19 +79,14 @@ public PoolWorker getEventLoop() { // simple round robin, improvements to come return eventLoops[(int)(n++ % c)]; } - + public void shutdown() { for (PoolWorker w : eventLoops) { w.unsubscribe(); } } } - /** This will indicate no pool is active. */ - static final FixedSchedulerPool NONE = new FixedSchedulerPool(null, 0); - final ThreadFactory threadFactory; - final AtomicReference pool; - /** * Create a scheduler with pool size equal to the available processor * count and using least-recent worker selection policy. @@ -95,12 +97,12 @@ public EventLoopsScheduler(ThreadFactory threadFactory) { this.pool = new AtomicReference(NONE); start(); } - + @Override public Worker createWorker() { return new EventLoopWorker(pool.get().getEventLoop()); } - + @Override public void start() { FixedSchedulerPool update = new FixedSchedulerPool(threadFactory, MAX_THREADS); @@ -108,7 +110,7 @@ public void start() { update.shutdown(); } } - + @Override public void shutdown() { for (;;) { @@ -122,7 +124,7 @@ public void shutdown() { } } } - + /** * Schedules the action directly on one of the event loop workers * without the additional infrastructure and checking. @@ -134,7 +136,7 @@ public Subscription scheduleDirect(Action0 action) { return pw.scheduleActual(action, -1, TimeUnit.NANOSECONDS); } - private static class EventLoopWorker extends Scheduler.Worker { + static final class EventLoopWorker extends Scheduler.Worker { private final SubscriptionList serial = new SubscriptionList(); private final CompositeSubscription timed = new CompositeSubscription(); private final SubscriptionList both = new SubscriptionList(serial, timed); @@ -142,7 +144,7 @@ private static class EventLoopWorker extends Scheduler.Worker { EventLoopWorker(PoolWorker poolWorker) { this.poolWorker = poolWorker; - + } @Override diff --git a/src/main/java/rx/internal/schedulers/ExecutorScheduler.java b/src/main/java/rx/internal/schedulers/ExecutorScheduler.java index b4bcf19d7f..82a11c1bbb 100644 --- a/src/main/java/rx/internal/schedulers/ExecutorScheduler.java +++ b/src/main/java/rx/internal/schedulers/ExecutorScheduler.java @@ -46,11 +46,11 @@ static final class ExecutorSchedulerWorker extends Scheduler.Worker implements R // TODO: use a better performing structure for task tracking final CompositeSubscription tasks; // TODO: use MpscLinkedQueue once available - final ConcurrentLinkedQueue queue; + final ConcurrentLinkedQueue queue; final AtomicInteger wip; - + final ScheduledExecutorService service; - + public ExecutorSchedulerWorker(Executor executor) { this.executor = executor; this.queue = new ConcurrentLinkedQueue(); @@ -64,6 +64,9 @@ public Subscription schedule(Action0 action) { if (isUnsubscribed()) { return Subscriptions.unsubscribed(); } + + action = RxJavaHooks.onScheduledAction(action); + ScheduledAction ea = new ScheduledAction(action, tasks); tasks.add(ea); queue.offer(ea); @@ -84,7 +87,7 @@ public Subscription schedule(Action0 action) { throw t; } } - + return ea; } @@ -109,16 +112,18 @@ public void run() { } } while (wip.decrementAndGet() != 0); } - + @Override - public Subscription schedule(final Action0 action, long delayTime, TimeUnit unit) { + public Subscription schedule(Action0 action, long delayTime, TimeUnit unit) { if (delayTime <= 0) { return schedule(action); } if (isUnsubscribed()) { return Subscriptions.unsubscribed(); } - + + final Action0 decorated = RxJavaHooks.onScheduledAction(action); + final MultipleAssignmentSubscription first = new MultipleAssignmentSubscription(); final MultipleAssignmentSubscription mas = new MultipleAssignmentSubscription(); mas.set(first); @@ -129,15 +134,15 @@ public void call() { tasks.remove(mas); } }); - + ScheduledAction ea = new ScheduledAction(new Action0() { @Override public void call() { if (mas.isUnsubscribed()) { return; } - // schedule the real action untimed - Subscription s2 = schedule(action); + // schedule the real action non-delayed + Subscription s2 = schedule(decorated); mas.set(s2); // unless the worker is unsubscribed, we should get a new ScheduledAction if (s2.getClass() == ScheduledAction.class) { @@ -151,8 +156,8 @@ public void call() { // we don't override the current task in mas. first.set(ea); // we don't need to add ea to tasks because it will be tracked through mas/first - - + + try { Future f = service.schedule(ea, delayTime, unit); ea.add(f); @@ -161,7 +166,7 @@ public void call() { RxJavaHooks.onError(t); throw t; } - + /* * This allows cancelling either the delayed schedule or the actual schedule referenced * by mas and makes sure mas is removed from the tasks composite to avoid leaks. @@ -179,6 +184,6 @@ public void unsubscribe() { tasks.unsubscribe(); queue.clear(); } - + } } diff --git a/src/main/java/rx/internal/schedulers/GenericScheduledExecutorService.java b/src/main/java/rx/internal/schedulers/GenericScheduledExecutorService.java index 3bc60f076b..9ed0bbc046 100644 --- a/src/main/java/rx/internal/schedulers/GenericScheduledExecutorService.java +++ b/src/main/java/rx/internal/schedulers/GenericScheduledExecutorService.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -19,7 +19,6 @@ import java.util.concurrent.atomic.AtomicReference; import rx.Scheduler; -import rx.internal.util.RxThreadFactory; /** * A default {@link ScheduledExecutorService} that can be used for scheduling actions when a {@link Scheduler} implementation doesn't have that ability. @@ -32,9 +31,6 @@ */ public final class GenericScheduledExecutorService implements SchedulerLifecycle { - private static final String THREAD_NAME_PREFIX = "RxScheduledExecutorPool-"; - private static final RxThreadFactory THREAD_FACTORY = new RxThreadFactory(THREAD_NAME_PREFIX); - private static final ScheduledExecutorService[] NONE = new ScheduledExecutorService[0]; private static final ScheduledExecutorService SHUTDOWN; @@ -45,12 +41,12 @@ public final class GenericScheduledExecutorService implements SchedulerLifecycle /* Schedulers needs access to this in order to work with the lifecycle. */ public final static GenericScheduledExecutorService INSTANCE = new GenericScheduledExecutorService(); - + private final AtomicReference executor; /** We don't use atomics with this because thread-assignment is random anyway. */ private static int roundRobin; - + private GenericScheduledExecutorService() { executor = new AtomicReference(NONE); start(); @@ -66,13 +62,13 @@ public void start() { if (count > 8) { count = 8; } - + // A multi-threaded executor can reorder tasks, having a set of them // and handing one of those out on getInstance() ensures a proper order - + ScheduledExecutorService[] execs = new ScheduledExecutorService[count]; for (int i = 0; i < count; i++) { - execs[i] = Executors.newScheduledThreadPool(1, THREAD_FACTORY); + execs[i] = GenericScheduledExecutorServiceFactory.create(); } if (executor.compareAndSet(NONE, execs)) { for (ScheduledExecutorService exec : execs) { @@ -88,7 +84,7 @@ public void start() { } } } - + @Override public void shutdown() { for (;;) { @@ -105,10 +101,10 @@ public void shutdown() { } } } - + /** * Returns one of the single-threaded ScheduledExecutorService helper executors. - * + * * @return {@link ScheduledExecutorService} for generic use. */ public static ScheduledExecutorService getInstance() { diff --git a/src/main/java/rx/internal/schedulers/GenericScheduledExecutorServiceFactory.java b/src/main/java/rx/internal/schedulers/GenericScheduledExecutorServiceFactory.java new file mode 100644 index 0000000000..3d6ea1e598 --- /dev/null +++ b/src/main/java/rx/internal/schedulers/GenericScheduledExecutorServiceFactory.java @@ -0,0 +1,55 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * 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 rx.internal.schedulers; + +import java.util.concurrent.*; + +import rx.functions.Func0; +import rx.internal.util.RxThreadFactory; +import rx.plugins.RxJavaHooks; + +/** + * Utility class to create the individual ScheduledExecutorService instances for + * the GenericScheduledExecutorService class. + */ +enum GenericScheduledExecutorServiceFactory { + ; + + static final String THREAD_NAME_PREFIX = "RxScheduledExecutorPool-"; + static final RxThreadFactory THREAD_FACTORY = new RxThreadFactory(THREAD_NAME_PREFIX); + + static ThreadFactory threadFactory() { + return THREAD_FACTORY; + } + + /** + * Creates a ScheduledExecutorService (either the default or given by a hook). + * @return the ScheduledExecutorService created. + */ + public static ScheduledExecutorService create() { + Func0 f = RxJavaHooks.getOnGenericScheduledExecutorService(); + if (f == null) { + return createDefault(); + } + return f.call(); + } + + + static ScheduledExecutorService createDefault() { + return Executors.newScheduledThreadPool(1, threadFactory()); + } +} diff --git a/src/main/java/rx/internal/schedulers/ImmediateScheduler.java b/src/main/java/rx/internal/schedulers/ImmediateScheduler.java index c3dd7a6f85..800087f2ab 100644 --- a/src/main/java/rx/internal/schedulers/ImmediateScheduler.java +++ b/src/main/java/rx/internal/schedulers/ImmediateScheduler.java @@ -30,6 +30,7 @@ public final class ImmediateScheduler extends Scheduler { public static final ImmediateScheduler INSTANCE = new ImmediateScheduler(); private ImmediateScheduler() { + // the class is singleton } @Override @@ -37,7 +38,7 @@ public Worker createWorker() { return new InnerImmediateScheduler(); } - private class InnerImmediateScheduler extends Scheduler.Worker implements Subscription { + final class InnerImmediateScheduler extends Scheduler.Worker implements Subscription { final BooleanSubscription innerSubscription = new BooleanSubscription(); diff --git a/src/main/java/rx/internal/schedulers/NewThreadWorker.java b/src/main/java/rx/internal/schedulers/NewThreadWorker.java index 6521aadf46..23f8af90e2 100644 --- a/src/main/java/rx/internal/schedulers/NewThreadWorker.java +++ b/src/main/java/rx/internal/schedulers/NewThreadWorker.java @@ -15,8 +15,8 @@ */ package rx.internal.schedulers; -import java.lang.reflect.Method; -import java.util.Iterator; +import java.lang.reflect.*; +import java.util.*; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicReference; @@ -30,7 +30,8 @@ import static rx.internal.util.PlatformDependent.ANDROID_API_VERSION_IS_NOT_ANDROID; /** - * @warn class description missing + * Represents a Scheduler.Worker that runs on its own unique and single-threaded ScheduledExecutorService + * created via Executors. */ public class NewThreadWorker extends Scheduler.Worker implements Subscription { private final ScheduledExecutorService executor; @@ -45,6 +46,17 @@ public class NewThreadWorker extends Scheduler.Worker implements Subscription { public static final int PURGE_FREQUENCY; private static final ConcurrentHashMap EXECUTORS; private static final AtomicReference PURGE; + /** + * Improves performance of {@link #tryEnableCancelPolicy(ScheduledExecutorService)}. + * Also, it works even for inheritance: {@link Method} of base class can be invoked on the instance of child class. + */ + private static volatile Object cachedSetRemoveOnCancelPolicyMethod; + + /** + * Possible value of {@link #cachedSetRemoveOnCancelPolicyMethod} which means that cancel policy is not supported. + */ + private static final Object SET_REMOVE_ON_CANCEL_POLICY_METHOD_NOT_SUPPORTED = new Object(); + static { EXECUTORS = new ConcurrentHashMap(); PURGE = new AtomicReference(); @@ -60,10 +72,10 @@ public class NewThreadWorker extends Scheduler.Worker implements Subscription { SHOULD_TRY_ENABLE_CANCEL_POLICY = !purgeForce && (androidApiVersion == ANDROID_API_VERSION_IS_NOT_ANDROID || androidApiVersion >= 21); } - /** - * Registers the given executor service and starts the purge thread if not already started. + /** + * Registers the given executor service and starts the purge thread if not already started. *

    {@code public} visibility reason: called from other package(s) within RxJava - * @param service a scheduled thread pool executor instance + * @param service a scheduled thread pool executor instance */ public static void registerExecutor(ScheduledThreadPoolExecutor service) { do { @@ -79,19 +91,19 @@ public void run() { purgeExecutors(); } }, PURGE_FREQUENCY, PURGE_FREQUENCY, TimeUnit.MILLISECONDS); - + break; } else { exec.shutdownNow(); } } while (true); - + EXECUTORS.putIfAbsent(service, service); } - /** - * Deregisters the executor service. + /** + * Deregisters the executor service. *

    {@code public} visibility reason: called from other package(s) within RxJava - * @param service a scheduled thread pool executor instance + * @param service a scheduled thread pool executor instance */ public static void deregisterExecutor(ScheduledExecutorService service) { EXECUTORS.remove(service); @@ -100,7 +112,10 @@ public static void deregisterExecutor(ScheduledExecutorService service) { /** Purges each registered executor and eagerly evicts shutdown executors. */ static void purgeExecutors() { try { - Iterator it = EXECUTORS.keySet().iterator(); + // This prevents map.keySet to compile to a Java 8+ KeySetView return type + // and cause NoSuchMethodError on Java 6-7 runtimes. + Map map = EXECUTORS; + Iterator it = map.keySet().iterator(); while (it.hasNext()) { ScheduledThreadPoolExecutor exec = it.next(); if (!exec.isShutdown()) { @@ -115,30 +130,19 @@ static void purgeExecutors() { } } - /** - * Improves performance of {@link #tryEnableCancelPolicy(ScheduledExecutorService)}. - * Also, it works even for inheritance: {@link Method} of base class can be invoked on the instance of child class. - */ - private static volatile Object cachedSetRemoveOnCancelPolicyMethod; - - /** - * Possible value of {@link #cachedSetRemoveOnCancelPolicyMethod} which means that cancel policy is not supported. - */ - private static final Object SET_REMOVE_ON_CANCEL_POLICY_METHOD_NOT_SUPPORTED = new Object(); - /** * Tries to enable the Java 7+ setRemoveOnCancelPolicy. *

    {@code public} visibility reason: called from other package(s) within RxJava. * If the method returns false, the {@link #registerExecutor(ScheduledThreadPoolExecutor)} may * be called to enable the backup option of purging the executors. - * @param executor the executor to call setRemoveOnCaneclPolicy if available. - * @return true if the policy was successfully enabled + * @param executor the executor to call setRemoveOnCancelPolicy if available. + * @return true if the policy was successfully enabled */ public static boolean tryEnableCancelPolicy(ScheduledExecutorService executor) { - if (SHOULD_TRY_ENABLE_CANCEL_POLICY) { + if (SHOULD_TRY_ENABLE_CANCEL_POLICY) { // NOPMD final boolean isInstanceOfScheduledThreadPoolExecutor = executor instanceof ScheduledThreadPoolExecutor; - final Method methodToCall; + Method methodToCall; if (isInstanceOfScheduledThreadPoolExecutor) { final Object localSetRemoveOnCancelPolicyMethod = cachedSetRemoveOnCancelPolicyMethod; @@ -166,7 +170,11 @@ public static boolean tryEnableCancelPolicy(ScheduledExecutorService executor) { try { methodToCall.invoke(executor, true); return true; - } catch (Exception e) { + } catch (InvocationTargetException e) { + RxJavaHooks.onError(e); + } catch (IllegalAccessException e) { + RxJavaHooks.onError(e); + } catch (IllegalArgumentException e) { RxJavaHooks.onError(e); } } @@ -197,7 +205,7 @@ static Method findSetRemoveOnCancelPolicyMethod(ScheduledExecutorService executo return null; } - + /* package */ public NewThreadWorker(ThreadFactory threadFactory) { ScheduledExecutorService exec = Executors.newScheduledThreadPool(1, threadFactory); @@ -224,7 +232,7 @@ public Subscription schedule(final Action0 action, long delayTime, TimeUnit unit /** * Schedules the given action by wrapping it into a ScheduledAction on the - * underlying ExecutorService, returning the ScheduledAction. + * underlying ExecutorService, returning the ScheduledAction. * @param action the action to wrap and schedule * @param delayTime the delay in execution * @param unit the time unit of the delay @@ -258,12 +266,12 @@ public ScheduledAction scheduleActual(final Action0 action, long delayTime, Time return run; } - + public ScheduledAction scheduleActual(final Action0 action, long delayTime, TimeUnit unit, SubscriptionList parent) { Action0 decoratedAction = RxJavaHooks.onScheduledAction(action); ScheduledAction run = new ScheduledAction(decoratedAction, parent); parent.add(run); - + Future f; if (delayTime <= 0) { f = executor.submit(run); diff --git a/src/main/java/rx/internal/schedulers/SchedulePeriodicHelper.java b/src/main/java/rx/internal/schedulers/SchedulePeriodicHelper.java new file mode 100644 index 0000000000..e11e072c0a --- /dev/null +++ b/src/main/java/rx/internal/schedulers/SchedulePeriodicHelper.java @@ -0,0 +1,102 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * 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 rx.internal.schedulers; + +import java.util.concurrent.TimeUnit; + +import rx.Scheduler.Worker; +import rx.Subscription; +import rx.functions.Action0; +import rx.internal.subscriptions.SequentialSubscription; + +/** + * Utility method for scheduling tasks periodically (at a fixed rate) by using Worker.schedule(Action0, long, TimeUnit). + */ +public final class SchedulePeriodicHelper { + + /** Utility class. */ + private SchedulePeriodicHelper() { + throw new IllegalStateException("No instances!"); + } + + /** + * The tolerance for a clock drift in nanoseconds where the periodic scheduler will rebase. + *

    + * The associated system parameter, {@code rx.scheduler.drift-tolerance}, expects its value in minutes. + */ + public static final long CLOCK_DRIFT_TOLERANCE_NANOS; + static { + CLOCK_DRIFT_TOLERANCE_NANOS = TimeUnit.MINUTES.toNanos( + Long.getLong("rx.scheduler.drift-tolerance", 15)); + } + + /** + * Return the current time in nanoseconds. + */ + public interface NowNanoSupplier { + long nowNanos(); + } + + public static Subscription schedulePeriodically( + final Worker worker, + final Action0 action, + long initialDelay, long period, TimeUnit unit, + final NowNanoSupplier nowNanoSupplier) { + final long periodInNanos = unit.toNanos(period); + final long firstNowNanos = nowNanoSupplier != null ? nowNanoSupplier.nowNanos() : TimeUnit.MILLISECONDS.toNanos(worker.now()); + final long firstStartInNanos = firstNowNanos + unit.toNanos(initialDelay); + + final SequentialSubscription first = new SequentialSubscription(); + final SequentialSubscription mas = new SequentialSubscription(first); + + final Action0 recursiveAction = new Action0() { + long count; + long lastNowNanos = firstNowNanos; + long startInNanos = firstStartInNanos; + @Override + public void call() { + action.call(); + + if (!mas.isUnsubscribed()) { + + long nextTick; + + long nowNanos = nowNanoSupplier != null ? nowNanoSupplier.nowNanos() : TimeUnit.MILLISECONDS.toNanos(worker.now()); + // If the clock moved in a direction quite a bit, rebase the repetition period + if (nowNanos + CLOCK_DRIFT_TOLERANCE_NANOS < lastNowNanos + || nowNanos >= lastNowNanos + periodInNanos + CLOCK_DRIFT_TOLERANCE_NANOS) { + nextTick = nowNanos + periodInNanos; + /* + * Shift the start point back by the drift as if the whole thing + * started count periods ago. + */ + startInNanos = nextTick - (periodInNanos * (++count)); + } else { + nextTick = startInNanos + (++count * periodInNanos); + } + lastNowNanos = nowNanos; + + long delay = nextTick - nowNanos; + mas.replace(worker.schedule(this, delay, TimeUnit.NANOSECONDS)); + } + } + }; + first.replace(worker.schedule(recursiveAction, initialDelay, unit)); + return mas; + } + +} diff --git a/src/main/java/rx/internal/schedulers/ScheduledAction.java b/src/main/java/rx/internal/schedulers/ScheduledAction.java index 6ba16beaee..4fae47c704 100644 --- a/src/main/java/rx/internal/schedulers/ScheduledAction.java +++ b/src/main/java/rx/internal/schedulers/ScheduledAction.java @@ -53,22 +53,21 @@ public void run() { try { lazySet(Thread.currentThread()); action.call(); + } catch (OnErrorNotImplementedException e) { + signalError(new IllegalStateException("Exception thrown on Scheduler.Worker thread. Add `onError` handling.", e)); } catch (Throwable e) { - // nothing to do but print a System error as this is fatal and there is nowhere else to throw this - IllegalStateException ie = null; - if (e instanceof OnErrorNotImplementedException) { - ie = new IllegalStateException("Exception thrown on Scheduler.Worker thread. Add `onError` handling.", e); - } else { - ie = new IllegalStateException("Fatal Exception thrown on Scheduler.Worker thread.", e); - } - RxJavaHooks.onError(ie); - Thread thread = Thread.currentThread(); - thread.getUncaughtExceptionHandler().uncaughtException(thread, ie); + signalError(new IllegalStateException("Fatal Exception thrown on Scheduler.Worker thread.", e)); } finally { unsubscribe(); } } + void signalError(Throwable ie) { + RxJavaHooks.onError(ie); + Thread thread = Thread.currentThread(); + thread.getUncaughtExceptionHandler().uncaughtException(thread, ie); + } + @Override public boolean isUnsubscribed() { return cancel.isUnsubscribed(); @@ -99,7 +98,7 @@ public void add(Subscription s) { public void add(final Future f) { cancel.add(new FutureCompleter(f)); } - + /** * Adds a parent {@link CompositeSubscription} to this {@code ScheduledAction} so when the action is * cancelled or terminates, it can remove itself from this parent. @@ -128,7 +127,7 @@ public void addParent(SubscriptionList parent) { * prevent unnecessary self-interrupting if the unsubscription * happens from the same thread. */ - private final class FutureCompleter implements Subscription { + final class FutureCompleter implements Subscription { private final Future f; FutureCompleter(Future f) { @@ -150,7 +149,7 @@ public boolean isUnsubscribed() { } /** Remove a child subscription from a composite when unsubscribing. */ - private static final class Remover extends AtomicBoolean implements Subscription { + static final class Remover extends AtomicBoolean implements Subscription { /** */ private static final long serialVersionUID = 247232374289553518L; final ScheduledAction s; @@ -175,7 +174,7 @@ public void unsubscribe() { } /** Remove a child subscription from a composite when unsubscribing. */ - private static final class Remover2 extends AtomicBoolean implements Subscription { + static final class Remover2 extends AtomicBoolean implements Subscription { /** */ private static final long serialVersionUID = 247232374289553518L; final ScheduledAction s; diff --git a/src/main/java/rx/internal/schedulers/SchedulerLifecycle.java b/src/main/java/rx/internal/schedulers/SchedulerLifecycle.java index 498030b6ff..80453789da 100644 --- a/src/main/java/rx/internal/schedulers/SchedulerLifecycle.java +++ b/src/main/java/rx/internal/schedulers/SchedulerLifecycle.java @@ -21,16 +21,16 @@ * threads. */ public interface SchedulerLifecycle { - /** - * Allows the Scheduler instance to start threads + /** + * Allows the Scheduler instance to start threads * and accept tasks on them. - *

    Implementations should make sure the call is idempotent and threadsafe. + *

    Implementations should make sure the call is idempotent and thread-safe. */ void start(); - /** - * Instructs the Scheduler instance to stop threads - * and stop accepting tasks on any outstanding Workers. - *

    Implementations should make sure the call is idempotent and threadsafe. + /** + * Instructs the Scheduler instance to stop threads + * and stop accepting tasks on any outstanding Workers. + *

    Implementations should make sure the call is idempotent and thread-safe. */ void shutdown(); } \ No newline at end of file diff --git a/src/main/java/rx/internal/schedulers/SchedulerWhen.java b/src/main/java/rx/internal/schedulers/SchedulerWhen.java new file mode 100644 index 0000000000..f35528bb12 --- /dev/null +++ b/src/main/java/rx/internal/schedulers/SchedulerWhen.java @@ -0,0 +1,315 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * 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 rx.internal.schedulers; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + +import rx.Completable; +import rx.Completable.OnSubscribe; +import rx.CompletableSubscriber; +import rx.Observable; +import rx.Observer; +import rx.Scheduler; +import rx.Subscription; +import rx.functions.Action0; +import rx.functions.Func1; +import rx.internal.operators.BufferUntilSubscriber; +import rx.observers.SerializedObserver; +import rx.subjects.PublishSubject; +import rx.subscriptions.Subscriptions; + +/** + * Allows the use of operators for controlling the timing around when actions + * scheduled on workers are actually done. This makes it possible to layer + * additional behavior on this {@link Scheduler}. The only parameter is a + * function that flattens an {@link Observable} of {@link Observable} of + * {@link Completable}s into just one {@link Completable}. There must be a chain + * of operators connecting the returned value to the source {@link Observable} + * otherwise any work scheduled on the returned {@link Scheduler} will not be + * executed. + *

    + * When {@link Scheduler#createWorker()} is invoked a {@link Observable} of + * {@link Completable}s is onNext'd to the combinator to be flattened. If the + * inner {@link Observable} is not immediately subscribed to an calls to + * {@link Worker#schedule} are buffered. Once the {@link Observable} is + * subscribed to actions are then onNext'd as {@link Completable}s. + *

    + * Finally the actions scheduled on the parent {@link Scheduler} when the inner + * most {@link Completable}s are subscribed to. + *

    + * When the {@link rx.Scheduler.Worker} is unsubscribed the {@link Completable} emits an + * onComplete and triggers any behavior in the flattening operator. The + * {@link Observable} and all {@link Completable}s give to the flattening + * function never onError. + *

    + * Limit the amount concurrency two at a time without creating a new fix size + * thread pool: + * + *

    + * Scheduler limitScheduler = Schedulers.computation().when(workers -> {
    + *     // use merge max concurrent to limit the number of concurrent
    + *     // callbacks two at a time
    + *     return Completable.merge(Observable.merge(workers), 2);
    + * });
    + * 
    + *

    + * This is a slightly different way to limit the concurrency but it has some + * interesting benefits and drawbacks to the method above. It works by limited + * the number of concurrent {@link rx.Scheduler.Worker}s rather than individual actions. + * Generally each {@link Observable} uses its own {@link rx.Scheduler.Worker}. This means + * that this will essentially limit the number of concurrent subscribes. The + * danger comes from using operators like + * {@link Observable#zip(Observable, Observable, rx.functions.Func2)} where + * subscribing to the first {@link Observable} could deadlock the subscription + * to the second. + * + *

    + * Scheduler limitScheduler = Schedulers.computation().when(workers -> {
    + *     // use merge max concurrent to limit the number of concurrent
    + *     // Observables two at a time
    + *     return Completable.merge(Observable.merge(workers, 2));
    + * });
    + * 
    + * + * 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 Observable} operator + * that limits the rate without dropping the values (aka leaky bucket + * algorithm). + * + *
    + * Scheduler slowScheduler = Schedulers.computation().when(workers -> {
    + *     // use concatenate to make each worker happen one at a time.
    + *     return Completable.concat(workers.map(actions -> {
    + *         // delay the starting of the next worker by 1 second.
    + *         return Completable.merge(actions.delaySubscription(1, TimeUnit.SECONDS));
    + *     }));
    + * });
    + * 
    + * @since 1.3 + */ +public class SchedulerWhen extends Scheduler implements Subscription { + private final Scheduler actualScheduler; + private final Observer> workerObserver; + private final Subscription subscription; + + public SchedulerWhen(Func1>, Completable> combine, Scheduler actualScheduler) { + this.actualScheduler = actualScheduler; + // workers are converted into completables and put in this queue. + PublishSubject> workerSubject = PublishSubject.create(); + this.workerObserver = new SerializedObserver>(workerSubject); + // send it to a custom combinator to pick the order and rate at which + // workers are processed. + this.subscription = combine.call(workerSubject.onBackpressureBuffer()).subscribe(); + } + + @Override + public void unsubscribe() { + subscription.unsubscribe(); + } + + @Override + public boolean isUnsubscribed() { + return subscription.isUnsubscribed(); + } + + @Override + public Worker createWorker() { + final Worker actualWorker = actualScheduler.createWorker(); + // a queue for the actions submitted while worker is waiting to get to + // the subscribe to off the workerQueue. + BufferUntilSubscriber actionSubject = BufferUntilSubscriber. create(); + final Observer actionObserver = new SerializedObserver(actionSubject); + // convert the work of scheduling all the actions into a completable + Observable actions = actionSubject.map(new Func1() { + @Override + public Completable call(final ScheduledAction action) { + return Completable.create(new OnSubscribe() { + @Override + public void call(CompletableSubscriber actionCompletable) { + actionCompletable.onSubscribe(action); + action.call(actualWorker, actionCompletable); + } + }); + } + }); + + // a worker that queues the action to the actionQueue subject. + Worker worker = new Worker() { + private final AtomicBoolean unsubscribed = new AtomicBoolean(); + + @Override + public void unsubscribe() { + // complete the actionQueue when worker is unsubscribed to make + // room for the next worker in the workerQueue. + if (unsubscribed.compareAndSet(false, true)) { + actualWorker.unsubscribe(); + actionObserver.onCompleted(); + } + } + + @Override + public boolean isUnsubscribed() { + return unsubscribed.get(); + } + + @Override + public Subscription schedule(final Action0 action, final long delayTime, final TimeUnit unit) { + // send a scheduled action to the actionQueue + DelayedAction delayedAction = new DelayedAction(action, delayTime, unit); + actionObserver.onNext(delayedAction); + return delayedAction; + } + + @Override + public Subscription schedule(final Action0 action) { + // send a scheduled action to the actionQueue + ImmediateAction immediateAction = new ImmediateAction(action); + actionObserver.onNext(immediateAction); + return immediateAction; + } + }; + + // enqueue the completable that process actions put in reply subject + workerObserver.onNext(actions); + + // return the worker that adds actions to the reply subject + return worker; + } + + static final Subscription SUBSCRIBED = new Subscription() { + @Override + public void unsubscribe() { + } + + @Override + public boolean isUnsubscribed() { + return false; + } + }; + + static final Subscription UNSUBSCRIBED = Subscriptions.unsubscribed(); + + @SuppressWarnings("serial") + static abstract class ScheduledAction extends AtomicReferenceimplements Subscription { + public ScheduledAction() { + super(SUBSCRIBED); + } + + private void call(Worker actualWorker, CompletableSubscriber actionCompletable) { + Subscription oldState = get(); + // either SUBSCRIBED or UNSUBSCRIBED + if (oldState == UNSUBSCRIBED) { + // no need to schedule return + return; + } + if (oldState != SUBSCRIBED) { + // has already been scheduled return + // should not be able to get here but handle it anyway by not + // rescheduling. + return; + } + + Subscription newState = callActual(actualWorker, actionCompletable); + + if (!compareAndSet(SUBSCRIBED, newState)) { + // set would only fail if the new current state is some other + // subscription from a concurrent call to this method. + // Unsubscribe from the action just scheduled because it lost + // the race. + newState.unsubscribe(); + } + } + + protected abstract Subscription callActual(Worker actualWorker, CompletableSubscriber actionCompletable); + + @Override + public boolean isUnsubscribed() { + return get().isUnsubscribed(); + } + + @Override + public void unsubscribe() { + Subscription oldState; + // no matter what the current state is the new state is going to be + Subscription newState = UNSUBSCRIBED; + do { + oldState = get(); + if (oldState == UNSUBSCRIBED) { + // the action has already been unsubscribed + return; + } + } while (!compareAndSet(oldState, newState)); + + if (oldState != SUBSCRIBED) { + // the action was scheduled. stop it. + oldState.unsubscribe(); + } + } + } + + @SuppressWarnings("serial") + static class ImmediateAction extends ScheduledAction { + private final Action0 action; + + public ImmediateAction(Action0 action) { + this.action = action; + } + + @Override + protected Subscription callActual(Worker actualWorker, CompletableSubscriber actionCompletable) { + return actualWorker.schedule(new OnCompletedAction(action, actionCompletable)); + } + } + + @SuppressWarnings("serial") + static class DelayedAction extends ScheduledAction { + private final Action0 action; + private final long delayTime; + private final TimeUnit unit; + + public DelayedAction(Action0 action, long delayTime, TimeUnit unit) { + this.action = action; + this.delayTime = delayTime; + this.unit = unit; + } + + @Override + protected Subscription callActual(Worker actualWorker, CompletableSubscriber actionCompletable) { + return actualWorker.schedule(new OnCompletedAction(action, actionCompletable), delayTime, unit); + } + } + + static class OnCompletedAction implements Action0 { + private CompletableSubscriber actionCompletable; + private Action0 action; + + public OnCompletedAction(Action0 action, CompletableSubscriber actionCompletable) { + this.action = action; + this.actionCompletable = actionCompletable; + } + + @Override + public void call() { + try { + action.call(); + } finally { + actionCompletable.onCompleted(); + } + } + } +} diff --git a/src/main/java/rx/internal/schedulers/SleepingAction.java b/src/main/java/rx/internal/schedulers/SleepingAction.java index e3bdeb16ba..494439b11f 100644 --- a/src/main/java/rx/internal/schedulers/SleepingAction.java +++ b/src/main/java/rx/internal/schedulers/SleepingAction.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -16,6 +16,7 @@ package rx.internal.schedulers; import rx.Scheduler; +import rx.exceptions.Exceptions; import rx.functions.Action0; /* package */class SleepingAction implements Action0 { @@ -41,7 +42,7 @@ public void call() { Thread.sleep(delay); } catch (InterruptedException e) { Thread.currentThread().interrupt(); - throw new RuntimeException(e); + Exceptions.propagate(e); } } diff --git a/src/main/java/rx/internal/schedulers/TrampolineScheduler.java b/src/main/java/rx/internal/schedulers/TrampolineScheduler.java index 94fea1964a..fb03dcb58d 100644 --- a/src/main/java/rx/internal/schedulers/TrampolineScheduler.java +++ b/src/main/java/rx/internal/schedulers/TrampolineScheduler.java @@ -40,16 +40,13 @@ public Worker createWorker() { private TrampolineScheduler() { } - private static class InnerCurrentThreadScheduler extends Scheduler.Worker implements Subscription { + static final class InnerCurrentThreadScheduler extends Scheduler.Worker implements Subscription { final AtomicInteger counter = new AtomicInteger(); final PriorityBlockingQueue queue = new PriorityBlockingQueue(); private final BooleanSubscription innerSubscription = new BooleanSubscription(); private final AtomicInteger wip = new AtomicInteger(); - InnerCurrentThreadScheduler() { - } - @Override public Subscription schedule(Action0 action) { return enqueue(action, now()); @@ -102,7 +99,7 @@ public boolean isUnsubscribed() { } - private static final class TimedAction implements Comparable { + static final class TimedAction implements Comparable { final Action0 action; final Long execTime; final int count; // In case if time between enqueueing took less than 1ms diff --git a/src/main/java/rx/internal/subscriptions/CancellableSubscription.java b/src/main/java/rx/internal/subscriptions/CancellableSubscription.java new file mode 100644 index 0000000000..dcfd1bd075 --- /dev/null +++ b/src/main/java/rx/internal/subscriptions/CancellableSubscription.java @@ -0,0 +1,59 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * 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 rx.internal.subscriptions; + +import java.util.concurrent.atomic.AtomicReference; + +import rx.Subscription; +import rx.exceptions.Exceptions; +import rx.functions.Cancellable; +import rx.plugins.RxJavaHooks; + +/** + * A Subscription that wraps an Cancellable instance. + */ +public final class CancellableSubscription +extends AtomicReference +implements Subscription { + + /** */ + private static final long serialVersionUID = 5718521705281392066L; + + public CancellableSubscription(Cancellable cancellable) { + super(cancellable); + } + + @Override + public boolean isUnsubscribed() { + return get() == null; + } + + @Override + public void unsubscribe() { + if (get() != null) { + Cancellable c = getAndSet(null); + if (c != null) { + try { + c.cancel(); + } catch (Exception ex) { + Exceptions.throwIfFatal(ex); + RxJavaHooks.onError(ex); + } + } + } + } +} diff --git a/src/main/java/rx/internal/subscriptions/SequentialSubscription.java b/src/main/java/rx/internal/subscriptions/SequentialSubscription.java new file mode 100644 index 0000000000..4e77f42e7e --- /dev/null +++ b/src/main/java/rx/internal/subscriptions/SequentialSubscription.java @@ -0,0 +1,189 @@ +/** + * Copyright 2014 Netflix, Inc. + * + * 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 rx.internal.subscriptions; + +import java.util.concurrent.atomic.AtomicReference; + +import rx.Subscription; +import rx.subscriptions.Subscriptions; + +/** + * A container of a Subscription that supports operations of SerialSubscription + * and MultipleAssignmentSubscription via methods (update, replace) and extends + * AtomicReference to reduce allocation count (beware the API leak of AtomicReference!). + * @since 1.1.9 + */ +public final class SequentialSubscription extends AtomicReference implements Subscription { + + /** */ + private static final long serialVersionUID = 995205034283130269L; + + /** + * Create an empty SequentialSubscription. + */ + public SequentialSubscription() { + + } + + /** + * Create a SequentialSubscription with the given initial Subscription. + * @param initial the initial Subscription, may be null + */ + public SequentialSubscription(Subscription initial) { + lazySet(initial); + } + + /** + * Returns the current contained Subscription (may be null). + *

    (Remark: named as such because get() is final). + * @return the current contained Subscription (may be null) + */ + public Subscription current() { + Subscription current = super.get(); + if (current == Unsubscribed.INSTANCE) { + return Subscriptions.unsubscribed(); + } + return current; + } + + /** + * Atomically sets the contained Subscription to the provided next value and unsubscribes + * the previous value or unsubscribes the next value if this container is unsubscribed. + *

    (Remark: named as such because set() is final). + * @param next the next Subscription to contain, may be null + * @return true if the update succeeded, false if the container was unsubscribed + */ + public boolean update(Subscription next) { + for (;;) { + Subscription current = get(); + + if (current == Unsubscribed.INSTANCE) { + if (next != null) { + next.unsubscribe(); + } + return false; + } + + if (compareAndSet(current, next)) { + if (current != null) { + current.unsubscribe(); + } + return true; + } + } + } + + /** + * Atomically replaces the contained Subscription to the provided next value but + * does not unsubscribe the previous value or unsubscribes the next value if this + * container is unsubscribed. + * @param next the next Subscription to contain, may be null + * @return true if the update succeeded, false if the container was unsubscribed + */ + public boolean replace(Subscription next) { + for (;;) { + Subscription current = get(); + + if (current == Unsubscribed.INSTANCE) { + if (next != null) { + next.unsubscribe(); + } + return false; + } + + if (compareAndSet(current, next)) { + return true; + } + } + } + + /** + * Atomically tries to set the contained Subscription to the provided next value and unsubscribes + * the previous value or unsubscribes the next value if this container is unsubscribed. + *

    + * Unlike {@link #update(Subscription)}, this doesn't retry if the replace failed + * because a concurrent operation changed the underlying contained object. + * @param next the next Subscription to contain, may be null + * @return true if the update succeeded, false if the container was unsubscribed + */ + public boolean updateWeak(Subscription next) { + Subscription current = get(); + if (current == Unsubscribed.INSTANCE) { + if (next != null) { + next.unsubscribe(); + } + return false; + } + if (compareAndSet(current, next)) { + return true; + } + + current = get(); + + if (next != null) { + next.unsubscribe(); + } + return current == Unsubscribed.INSTANCE; + } + + /** + * Atomically tries to replace the contained Subscription to the provided next value but + * does not unsubscribe the previous value or unsubscribes the next value if this container + * is unsubscribed. + *

    + * Unlike {@link #replace(Subscription)}, this doesn't retry if the replace failed + * because a concurrent operation changed the underlying contained object. + * @param next the next Subscription to contain, may be null + * @return true if the update succeeded, false if the container was unsubscribed + */ + public boolean replaceWeak(Subscription next) { + Subscription current = get(); + if (current == Unsubscribed.INSTANCE) { + if (next != null) { + next.unsubscribe(); + } + return false; + } + if (compareAndSet(current, next)) { + return true; + } + + current = get(); + if (current == Unsubscribed.INSTANCE) { + if (next != null) { + next.unsubscribe(); + } + return false; + } + return true; + } + + @Override + public void unsubscribe() { + Subscription current = get(); + if (current != Unsubscribed.INSTANCE) { + current = getAndSet(Unsubscribed.INSTANCE); + if (current != null && current != Unsubscribed.INSTANCE) { + current.unsubscribe(); + } + } + } + + @Override + public boolean isUnsubscribed() { + return get() == Unsubscribed.INSTANCE; + } +} diff --git a/src/main/java/rx/internal/subscriptions/Unsubscribed.java b/src/main/java/rx/internal/subscriptions/Unsubscribed.java new file mode 100644 index 0000000000..a603313b49 --- /dev/null +++ b/src/main/java/rx/internal/subscriptions/Unsubscribed.java @@ -0,0 +1,35 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * 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 rx.internal.subscriptions; + +import rx.Subscription; + +/** + * Represents an unsubscribed Subscription via a singleton; don't leak it! + */ +public enum Unsubscribed implements Subscription { + INSTANCE; + + @Override + public boolean isUnsubscribed() { + return true; + } + + @Override + public void unsubscribe() { + // deliberately ignored + } +} \ No newline at end of file diff --git a/src/main/java/rx/internal/util/ActionNotificationObserver.java b/src/main/java/rx/internal/util/ActionNotificationObserver.java index 162d9371b4..b6d2732347 100644 --- a/src/main/java/rx/internal/util/ActionNotificationObserver.java +++ b/src/main/java/rx/internal/util/ActionNotificationObserver.java @@ -1,12 +1,12 @@ /** * Copyright 2016 Netflix, Inc. - * + * * 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. @@ -24,7 +24,7 @@ * @param the value type */ public final class ActionNotificationObserver implements Observer { - + final Action1> onNotification; public ActionNotificationObserver(Action1> onNotification) { diff --git a/src/main/java/rx/internal/util/ActionObserver.java b/src/main/java/rx/internal/util/ActionObserver.java new file mode 100644 index 0000000000..151a0c5f10 --- /dev/null +++ b/src/main/java/rx/internal/util/ActionObserver.java @@ -0,0 +1,51 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * 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 rx.internal.util; + +import rx.Observer; +import rx.functions.*; + +/** + * An Observer that forwards the onXXX method calls to callbacks. + * @param the value type + */ +public final class ActionObserver implements Observer { + + final Action1 onNext; + final Action1 onError; + final Action0 onCompleted; + + public ActionObserver(Action1 onNext, Action1 onError, Action0 onCompleted) { + this.onNext = onNext; + this.onError = onError; + this.onCompleted = onCompleted; + } + + @Override + public void onNext(T t) { + onNext.call(t); + } + + @Override + public void onError(Throwable e) { + onError.call(e); + } + + @Override + public void onCompleted() { + onCompleted.call(); + } +} \ No newline at end of file diff --git a/src/main/java/rx/internal/util/ActionSubscriber.java b/src/main/java/rx/internal/util/ActionSubscriber.java index 33a88fe5ed..344f0af291 100644 --- a/src/main/java/rx/internal/util/ActionSubscriber.java +++ b/src/main/java/rx/internal/util/ActionSubscriber.java @@ -1,12 +1,12 @@ /** * Copyright 2016 Netflix, Inc. - * + * * 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. diff --git a/src/main/java/rx/internal/util/BackpressureDrainManager.java b/src/main/java/rx/internal/util/BackpressureDrainManager.java index 08bb3beb58..da31c78178 100644 --- a/src/main/java/rx/internal/util/BackpressureDrainManager.java +++ b/src/main/java/rx/internal/util/BackpressureDrainManager.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -18,19 +18,26 @@ import java.util.concurrent.atomic.AtomicLong; import rx.Producer; -import rx.annotations.Experimental; /** * Manages the producer-backpressure-consumer interplay by * matching up available elements with requested elements and/or * terminal events. - * - * @since 1.1.0 + *

    History: 1.1.0 - experimental + * @since 1.3 */ -@Experimental public final class BackpressureDrainManager extends AtomicLong implements Producer { /** */ private static final long serialVersionUID = 2826241102729529449L; + /** Indicates if one is in emitting phase, guarded by this. */ + boolean emitting; + /** Indicates a terminal state. */ + volatile boolean terminated; + /** Indicates an error state, barrier is provided via terminated. */ + Throwable exception; + /** The callbacks to manage the drain. */ + final BackpressureQueueCallback actual; + /** * Interface representing the minimal callbacks required * to operate the drain part of a backpressure system. @@ -65,14 +72,6 @@ public interface BackpressureQueueCallback { void complete(Throwable exception); } - /** Indicates if one is in emitting phase, guarded by this. */ - protected boolean emitting; - /** Indicates a terminal state. */ - protected volatile boolean terminated; - /** Indicates an error state, barrier is provided via terminated. */ - protected Throwable exception; - /** The callbacks to manage the drain. */ - protected final BackpressureQueueCallback actual; /** * Constructs a backpressure drain manager with 0 requestedCount, * no terminal event and not emitting. @@ -85,14 +84,14 @@ public BackpressureDrainManager(BackpressureQueueCallback actual) { * Checks if a terminal state has been reached. * @return true if a terminal state has been reached */ - public final boolean isTerminated() { + public boolean isTerminated() { return terminated; } /** - * Move into a terminal state. + * Move into a terminal state. * Call drain() anytime after. */ - public final void terminate() { + public void terminate() { terminated = true; } /** @@ -102,16 +101,16 @@ public final void terminate() { * element emission. * @param error the exception to deliver */ - public final void terminate(Throwable error) { + public void terminate(Throwable error) { if (!terminated) { exception = error; terminated = true; } } /** - * Move into a terminal state and drain. + * Move into a terminal state and drain. */ - public final void terminateAndDrain() { + public void terminateAndDrain() { terminated = true; drain(); } @@ -121,7 +120,7 @@ public final void terminateAndDrain() { * element emission. * @param error the exception to deliver */ - public final void terminateAndDrain(Throwable error) { + public void terminateAndDrain(Throwable error) { if (!terminated) { exception = error; terminated = true; @@ -129,7 +128,7 @@ public final void terminateAndDrain(Throwable error) { } } @Override - public final void request(long n) { + public void request(long n) { if (n == 0) { return; } @@ -163,8 +162,7 @@ public final void request(long n) { * Try to drain the "queued" elements and terminal events * by considering the available and requested event counts. */ - public final void drain() { - long n; + public void drain() { boolean term; synchronized (this) { if (emitting) { @@ -173,7 +171,7 @@ public final void drain() { emitting = true; term = terminated; } - n = get(); + long n = get(); boolean skipFinal = false; try { BackpressureQueueCallback a = actual; diff --git a/src/main/java/rx/internal/util/BlockingUtils.java b/src/main/java/rx/internal/util/BlockingUtils.java index 951e49c83d..cb010af57e 100644 --- a/src/main/java/rx/internal/util/BlockingUtils.java +++ b/src/main/java/rx/internal/util/BlockingUtils.java @@ -17,7 +17,6 @@ package rx.internal.util; import rx.Subscription; -import rx.annotations.Experimental; import java.util.concurrent.CountDownLatch; @@ -25,8 +24,8 @@ * Utility functions relating to blocking types. *

    * Not intended to be part of the public API. + * @since 1.3 */ -@Experimental public final class BlockingUtils { private BlockingUtils() { } @@ -37,7 +36,6 @@ private BlockingUtils() { } * @param latch a CountDownLatch * @param subscription the Subscription to wait on. */ - @Experimental public static void awaitForComplete(CountDownLatch latch, Subscription subscription) { if (latch.getCount() == 0) { // Synchronous observable completes before awaiting for it. @@ -53,7 +51,7 @@ public static void awaitForComplete(CountDownLatch latch, Subscription subscript // for more information see https://github.com/ReactiveX/RxJava/pull/147#issuecomment-13624780 Thread.currentThread().interrupt(); // using Runtime so it is not checked - throw new RuntimeException("Interrupted while waiting for subscription to complete.", e); + throw new IllegalStateException("Interrupted while waiting for subscription to complete.", e); } } } diff --git a/src/main/java/rx/internal/util/ExceptionsUtils.java b/src/main/java/rx/internal/util/ExceptionsUtils.java index b714e7525a..d4cc3c8e3e 100644 --- a/src/main/java/rx/internal/util/ExceptionsUtils.java +++ b/src/main/java/rx/internal/util/ExceptionsUtils.java @@ -23,19 +23,19 @@ /** * Utility methods for terminal atomics with Throwables. - * + * * @since 1.1.2 */ public enum ExceptionsUtils { ; - + /** The single instance of a Throwable indicating a terminal state. */ private static final Throwable TERMINATED = new Throwable("Terminated"); - + /** * Atomically sets or combines the error with the contents of the field, wrapping multiple * errors into CompositeException if necessary. - * + * * @param field the target field * @param error the error to add * @return true if successful, false if the target field contains the terminal Throwable. @@ -46,7 +46,7 @@ public static boolean addThrowable(AtomicReference field, Throwable e if (current == TERMINATED) { return false; } - + Throwable next; if (current == null) { next = error; @@ -58,17 +58,17 @@ public static boolean addThrowable(AtomicReference field, Throwable e } else { next = new CompositeException(current, error); } - + if (field.compareAndSet(current, next)) { return true; } } } - + /** * Atomically swaps in the terminal Throwable and returns the previous * contents of the field - * + * * @param field the target field * @return the previous contents of the field before the swap, may be null */ @@ -79,10 +79,10 @@ public static Throwable terminate(AtomicReference field) { } return current; } - + /** * Checks if the given field holds the terminated Throwable instance. - * + * * @param field the target field * @return true if the given field holds the terminated Throwable instance */ @@ -92,7 +92,7 @@ public static boolean isTerminated(AtomicReference field) { /** * Returns true if the value is the terminated Throwable instance. - * + * * @param error the error to check * @return true if the value is the terminated Throwable instance */ diff --git a/src/main/java/rx/internal/util/IndexedRingBuffer.java b/src/main/java/rx/internal/util/IndexedRingBuffer.java index d434662866..30ba900b7e 100644 --- a/src/main/java/rx/internal/util/IndexedRingBuffer.java +++ b/src/main/java/rx/internal/util/IndexedRingBuffer.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -35,7 +35,7 @@ * - adds per second single-threaded => ~23,200,000 for 10,000 * - adds + removes per second single-threaded => 15,562,100 for 100 * - adds + removes per second single-threaded => 8,760,000 for 10,000 - * + * *

     {@code
      * Benchmark                                              (size)   Mode   Samples        Score  Score error    Units
      * r.i.IndexedRingBufferPerf.indexedRingBufferAdd            100  thrpt         5   263571.721     9856.994    ops/s
    @@ -43,104 +43,91 @@
      * r.i.IndexedRingBufferPerf.indexedRingBufferAddRemove      100  thrpt         5   139850.115    17143.705    ops/s
      * r.i.IndexedRingBufferPerf.indexedRingBufferAddRemove    10000  thrpt         5      809.982       72.931    ops/s
      * } 
    - * + * * @param */ public final class IndexedRingBuffer implements Subscription { - - private static final ObjectPool> POOL = new ObjectPool>() { - - @Override - protected IndexedRingBuffer createObject() { - return new IndexedRingBuffer(); - } - - }; - - @SuppressWarnings("unchecked") - public static IndexedRingBuffer getInstance() { - return (IndexedRingBuffer) POOL.borrowObject(); - } - private final ElementSection elements = new ElementSection(); private final IndexSection removed = new IndexSection(); /* package for unit testing */final AtomicInteger index = new AtomicInteger(); /* package for unit testing */final AtomicInteger removedIndex = new AtomicInteger(); - + + /* package for unit testing */static final int SIZE; + // default size of ring buffer /** * Set at 256 ... Android defaults far smaller which likely will never hit the use cases that require the higher buffers. *

    - * The 10000 size test represents something that should be a rare use case (merging 10000 concurrent Observables for example) - * + * The 10000 size test represents something that should be a rare use case (merging 10000 concurrent Observables for example) + * *

     {@code
          * ./gradlew benchmarks '-Pjmh=-f 1 -tu s -bm thrpt -wi 5 -i 5 -r 1 .*IndexedRingBufferPerf.*'
    -     * 
    +     *
          * 1024
    -     * 
    +     *
          * Benchmark                                              (size)   Mode   Samples        Score  Score error    Units
          * r.i.IndexedRingBufferPerf.indexedRingBufferAdd            100  thrpt         5   269292.006     6013.347    ops/s
          * r.i.IndexedRingBufferPerf.indexedRingBufferAdd          10000  thrpt         5     2217.103      163.396    ops/s
          * r.i.IndexedRingBufferPerf.indexedRingBufferAddRemove      100  thrpt         5   139349.608     9397.232    ops/s
          * r.i.IndexedRingBufferPerf.indexedRingBufferAddRemove    10000  thrpt         5     1045.323       30.991    ops/s
    -     * 
    +     *
          * 512
    -     * 
    +     *
          * Benchmark                                              (size)   Mode   Samples        Score  Score error    Units
          * r.i.IndexedRingBufferPerf.indexedRingBufferAdd            100  thrpt         5   270919.870     5381.793    ops/s
          * r.i.IndexedRingBufferPerf.indexedRingBufferAdd          10000  thrpt         5     1724.436       42.287    ops/s
          * r.i.IndexedRingBufferPerf.indexedRingBufferAddRemove      100  thrpt         5   141478.813     3696.030    ops/s
          * r.i.IndexedRingBufferPerf.indexedRingBufferAddRemove    10000  thrpt         5      719.447       75.629    ops/s
    -     * 
    -     * 
    +     *
    +     *
          * 256
    -     * 
    +     *
          * Benchmark                                              (size)   Mode   Samples        Score  Score error    Units
          * r.i.IndexedRingBufferPerf.indexedRingBufferAdd            100  thrpt         5   272042.605     7954.982    ops/s
          * r.i.IndexedRingBufferPerf.indexedRingBufferAdd          10000  thrpt         5     1101.329       23.566    ops/s
          * r.i.IndexedRingBufferPerf.indexedRingBufferAddRemove      100  thrpt         5   140479.804     6389.060    ops/s
          * r.i.IndexedRingBufferPerf.indexedRingBufferAddRemove    10000  thrpt         5      397.306       24.222    ops/s
    -     * 
    +     *
          * 128
    -     * 
    +     *
          * Benchmark                                              (size)   Mode   Samples        Score  Score error    Units
          * r.i.IndexedRingBufferPerf.indexedRingBufferAdd            100  thrpt         5   263065.312    11168.941    ops/s
          * r.i.IndexedRingBufferPerf.indexedRingBufferAdd          10000  thrpt         5      581.708       17.397    ops/s
          * r.i.IndexedRingBufferPerf.indexedRingBufferAddRemove      100  thrpt         5   138051.488     4618.935    ops/s
          * r.i.IndexedRingBufferPerf.indexedRingBufferAddRemove    10000  thrpt         5      176.873       35.669    ops/s
    -     * 
    +     *
          * 32
    -     * 
    +     *
          * Benchmark                                              (size)   Mode   Samples        Score  Score error    Units
          * r.i.IndexedRingBufferPerf.indexedRingBufferAdd            100  thrpt         5   250737.473    17260.148    ops/s
          * r.i.IndexedRingBufferPerf.indexedRingBufferAdd          10000  thrpt         5      144.725       26.284    ops/s
          * r.i.IndexedRingBufferPerf.indexedRingBufferAddRemove      100  thrpt         5   118832.832     9082.658    ops/s
          * r.i.IndexedRingBufferPerf.indexedRingBufferAddRemove    10000  thrpt         5       32.133        8.048    ops/s
    -     * 
    +     *
          * 8
    -     * 
    +     *
          * Benchmark                                              (size)   Mode   Samples        Score  Score error    Units
          * r.i.IndexedRingBufferPerf.indexedRingBufferAdd            100  thrpt         5   209192.847    25558.124    ops/s
          * r.i.IndexedRingBufferPerf.indexedRingBufferAdd          10000  thrpt         5       26.520        3.100    ops/s
          * r.i.IndexedRingBufferPerf.indexedRingBufferAddRemove      100  thrpt         5   100200.463     1854.259    ops/s
          * r.i.IndexedRingBufferPerf.indexedRingBufferAddRemove    10000  thrpt         5        8.456        2.114    ops/s
    -     * 
    +     *
          * 2
    -     * 
    +     *
          * Benchmark                                              (size)   Mode   Samples        Score  Score error    Units
          * r.i.IndexedRingBufferPerf.indexedRingBufferAdd            100  thrpt         5    96549.208     4427.239    ops/s
          * r.i.IndexedRingBufferPerf.indexedRingBufferAdd          10000  thrpt         5        6.637        2.025    ops/s
          * r.i.IndexedRingBufferPerf.indexedRingBufferAddRemove      100  thrpt         5    34553.169     4904.197    ops/s
          * r.i.IndexedRingBufferPerf.indexedRingBufferAddRemove    10000  thrpt         5        2.159        0.700    ops/s
          * } 
    - * + * * Impact of IndexedRingBuffer size on merge - * + * *
     {@code
          * ./gradlew benchmarks '-Pjmh=-f 1 -tu s -bm thrpt -wi 5 -i 5 -r 1 .*OperatorMergePerf.*'
    -     * 
    +     *
          * 512
    -     * 
    +     *
          * Benchmark                                          (size)   Mode   Samples        Score  Score error    Units
          * r.o.OperatorMergePerf.merge1SyncStreamOfN               1  thrpt         5  5282500.038   530541.761    ops/s
          * r.o.OperatorMergePerf.merge1SyncStreamOfN            1000  thrpt         5    49327.272     6382.189    ops/s
    @@ -157,10 +144,10 @@ public static  IndexedRingBuffer getInstance() {
          * r.o.OperatorMergePerf.oneStreamOfNthatMergesIn1         1  thrpt         5  5026522.098   364196.255    ops/s
          * r.o.OperatorMergePerf.oneStreamOfNthatMergesIn1      1000  thrpt         5    34926.819      938.612    ops/s
          * r.o.OperatorMergePerf.oneStreamOfNthatMergesIn1   1000000  thrpt         5       33.342        1.701    ops/s
    -     * 
    -     * 
    +     *
    +     *
          * 128
    -     * 
    +     *
          * Benchmark                                          (size)   Mode   Samples        Score  Score error    Units
          * r.o.OperatorMergePerf.merge1SyncStreamOfN               1  thrpt         5  5144891.776   271990.561    ops/s
          * r.o.OperatorMergePerf.merge1SyncStreamOfN            1000  thrpt         5    53580.161     2370.204    ops/s
    @@ -177,9 +164,9 @@ public static  IndexedRingBuffer getInstance() {
          * r.o.OperatorMergePerf.oneStreamOfNthatMergesIn1         1  thrpt         5  4953313.642   307512.126    ops/s
          * r.o.OperatorMergePerf.oneStreamOfNthatMergesIn1      1000  thrpt         5    35335.579     2368.377    ops/s
          * r.o.OperatorMergePerf.oneStreamOfNthatMergesIn1   1000000  thrpt         5       37.450        0.655    ops/s
    -     * 
    +     *
          * 32
    -     * 
    +     *
          * Benchmark                                          (size)   Mode   Samples        Score  Score error    Units
          * r.o.OperatorMergePerf.merge1SyncStreamOfN               1  thrpt         5  4975957.497   365423.694    ops/s
          * r.o.OperatorMergePerf.merge1SyncStreamOfN            1000  thrpt         5    52141.226     5056.658    ops/s
    @@ -196,9 +183,9 @@ public static  IndexedRingBuffer getInstance() {
          * r.o.OperatorMergePerf.oneStreamOfNthatMergesIn1         1  thrpt         5  5177255.256   150253.086    ops/s
          * r.o.OperatorMergePerf.oneStreamOfNthatMergesIn1      1000  thrpt         5    34772.490      909.967    ops/s
          * r.o.OperatorMergePerf.oneStreamOfNthatMergesIn1   1000000  thrpt         5       34.847        0.606    ops/s
    -     * 
    +     *
          * 8
    -     * 
    +     *
          * Benchmark                                          (size)   Mode   Samples        Score  Score error    Units
          * r.o.OperatorMergePerf.merge1SyncStreamOfN               1  thrpt         5  5027331.903   337986.410    ops/s
          * r.o.OperatorMergePerf.merge1SyncStreamOfN            1000  thrpt         5    51746.540     3585.450    ops/s
    @@ -215,10 +202,10 @@ public static  IndexedRingBuffer getInstance() {
          * r.o.OperatorMergePerf.oneStreamOfNthatMergesIn1         1  thrpt         5  4993609.293   267975.397    ops/s
          * r.o.OperatorMergePerf.oneStreamOfNthatMergesIn1      1000  thrpt         5    33228.972     1554.924    ops/s
          * r.o.OperatorMergePerf.oneStreamOfNthatMergesIn1   1000000  thrpt         5       32.994        3.615    ops/s
    -     * 
    -     * 
    +     *
    +     *
          * 2
    -     * 
    +     *
          * Benchmark                                          (size)   Mode   Samples        Score  Score error    Units
          * r.o.OperatorMergePerf.merge1SyncStreamOfN               1  thrpt         5  5103812.234   939461.192    ops/s
          * r.o.OperatorMergePerf.merge1SyncStreamOfN            1000  thrpt         5    51491.116     3790.056    ops/s
    @@ -235,28 +222,33 @@ public static  IndexedRingBuffer getInstance() {
          * r.o.OperatorMergePerf.oneStreamOfNthatMergesIn1         1  thrpt         5  5280829.290  1602542.493    ops/s
          * r.o.OperatorMergePerf.oneStreamOfNthatMergesIn1      1000  thrpt         5    35070.518     3565.672    ops/s
          * r.o.OperatorMergePerf.oneStreamOfNthatMergesIn1   1000000  thrpt         5       34.501        0.991    ops/s
    -     * 
    +     *
          * } 
    */ - static int _size = 256; static { + int defaultSize = 128; + // lower default for Android (https://github.com/ReactiveX/RxJava/issues/1820) if (PlatformDependent.isAndroid()) { - _size = 8; + defaultSize = 8; } // possible system property for overriding String sizeFromProperty = System.getProperty("rx.indexed-ring-buffer.size"); // also see RxRingBuffer if (sizeFromProperty != null) { try { - _size = Integer.parseInt(sizeFromProperty); - } catch (Exception e) { - System.err.println("Failed to set 'rx.indexed-ring-buffer.size' with value " + sizeFromProperty + " => " + e.getMessage()); + defaultSize = Integer.parseInt(sizeFromProperty); + } catch (NumberFormatException e) { + System.err.println("Failed to set 'rx.indexed-ring-buffer.size' with value " + sizeFromProperty + " => " + e.getMessage()); // NOPMD } } + + SIZE = defaultSize; + } + + public static IndexedRingBuffer getInstance() { + return new IndexedRingBuffer(); } - - /* package for unit testing */static final int SIZE = _size; /** * This resets the arrays, nulls out references and returns it to the pool. @@ -270,7 +262,6 @@ public void releaseToPool() { outer: while (section != null) { for (int i = 0; i < SIZE; i++, realIndex++) { if (realIndex >= maxIndex) { - section = null; break outer; } // we can use lazySet here because we are nulling things out and not accessing them again @@ -282,7 +273,6 @@ public void releaseToPool() { index.set(0); removedIndex.set(0); - POOL.returnObject(this); } @Override @@ -291,11 +281,12 @@ public void unsubscribe() { } IndexedRingBuffer() { + // nothing to do } /** * Add an element and return the index where it was added to allow removal. - * + * * @param e the element to add * @return the index where the element was added */ @@ -355,7 +346,7 @@ private ElementSection getElementSection(int index) { return a; } - private synchronized int getIndexForAdd() { + private synchronized int getIndexForAdd() { // NOPMD /* * Synchronized as I haven't yet figured out a way to do this in an atomic way that doesn't involve object allocation */ @@ -381,10 +372,10 @@ private synchronized int getIndexForAdd() { /** * Returns -1 if nothing, 0 or greater if the index should be used - * - * @return + * + * @return the index or -1 if none */ - private synchronized int getIndexFromPreviouslyRemoved() { + private synchronized int getIndexFromPreviouslyRemoved() { // NOPMD /* * Synchronized as I haven't yet figured out a way to do this in an atomic way that doesn't involve object allocation */ @@ -404,7 +395,7 @@ private synchronized int getIndexFromPreviouslyRemoved() { } } - private synchronized void pushRemovedIndex(int elementIndex) { + private synchronized void pushRemovedIndex(int elementIndex) { // NOPMD /* * Synchronized as I haven't yet figured out a way to do this in an atomic way that doesn't involve object allocation */ @@ -448,7 +439,7 @@ public int forEach(Func1 action, int startIndex) { } private int forEach(Func1 action, int startIndex, int endIndex) { - int lastIndex = startIndex; + int lastIndex; int maxIndex = index.get(); int realIndex = startIndex; ElementSection section = elements; @@ -462,7 +453,6 @@ private int forEach(Func1 action, int startIndex, int endInd outer: while (section != null) { for (int i = startIndex; i < SIZE; i++, realIndex++) { if (realIndex >= maxIndex || realIndex >= endIndex) { - section = null; break outer; } E element = section.array.get(i); @@ -483,13 +473,10 @@ private int forEach(Func1 action, int startIndex, int endInd return realIndex; } - private static class ElementSection { + static final class ElementSection { final AtomicReferenceArray array = new AtomicReferenceArray(SIZE); final AtomicReference> next = new AtomicReference>(); - ElementSection() { - } - ElementSection getNext() { if (next.get() != null) { return next.get(); @@ -506,12 +493,11 @@ ElementSection getNext() { } } - private static class IndexSection { + static class IndexSection { private final AtomicIntegerArray unsafeArray = new AtomicIntegerArray(SIZE); - IndexSection() { - } + private final AtomicReference _next = new AtomicReference(); public int getAndSet(int expected, int newValue) { return unsafeArray.getAndSet(expected, newValue); @@ -521,8 +507,6 @@ public void set(int i, int elementIndex) { unsafeArray.set(i, elementIndex); } - private final AtomicReference _next = new AtomicReference(); - IndexSection getNext() { if (_next.get() != null) { return _next.get(); diff --git a/src/main/java/rx/internal/util/InternalObservableUtils.java b/src/main/java/rx/internal/util/InternalObservableUtils.java index 0a72b2fd6d..93a215206c 100644 --- a/src/main/java/rx/internal/util/InternalObservableUtils.java +++ b/src/main/java/rx/internal/util/InternalObservableUtils.java @@ -1,12 +1,12 @@ /** * Copyright 2016 Netflix, Inc. - * + * * 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. @@ -32,24 +32,43 @@ */ public enum InternalObservableUtils { ; - + /** + * A BiFunction that expects a long as its first parameter and returns +1. + */ + public static final PlusOneLongFunc2 LONG_COUNTER = new PlusOneLongFunc2(); + + /** + * A two-argument function comparing two objects via null-safe equals. + */ + public static final ObjectEqualsFunc2 OBJECT_EQUALS = new ObjectEqualsFunc2(); + /** + * A function that converts a List of Observables into an array of Observables. + */ + public static final ToArrayFunc1 TO_ARRAY = new ToArrayFunc1(); + + static final ReturnsVoidFunc1 RETURNS_VOID = new ReturnsVoidFunc1(); + /** * A BiFunction that expects an integer as its first parameter and returns +1. */ public static final PlusOneFunc2 COUNTER = new PlusOneFunc2(); + static final NotificationErrorExtractor ERROR_EXTRACTOR = new NotificationErrorExtractor(); + + /** + * Throws an OnErrorNotImplementedException when called. + */ + public static final Action1 ERROR_NOT_IMPLEMENTED = new ErrorNotImplementedAction(); + + public static final Operator IS_EMPTY = new OperatorAny(UtilityFunctions.alwaysTrue(), true); + static final class PlusOneFunc2 implements Func2 { @Override public Integer call(Integer count, Object o) { return count + 1; } } - - /** - * A BiFunction that expects a long as its first parameter and returns +1. - */ - public static final PlusOneLongFunc2 LONG_COUNTER = new PlusOneLongFunc2(); - + static final class PlusOneLongFunc2 implements Func2 { @Override public Long call(Long count, Object o) { @@ -57,11 +76,6 @@ public Long call(Long count, Object o) { } } - /** - * A bifunction comparing two objects via null-safe equals. - */ - public static final ObjectEqualsFunc2 OBJECT_EQUALS = new ObjectEqualsFunc2(); - static final class ObjectEqualsFunc2 implements Func2 { @Override public Boolean call(Object first, Object second) { @@ -69,18 +83,13 @@ public Boolean call(Object first, Object second) { } } - /** - * A function that converts a List of Observables into an array of Observables. - */ - public static final ToArrayFunc1 TO_ARRAY = new ToArrayFunc1(); - static final class ToArrayFunc1 implements Func1>, Observable[]> { @Override public Observable[] call(List> o) { return o.toArray(new Observable[o.size()]); } } - + /** * Returns a Func1 that checks if its argument is null-safe equals with the given * constant reference. @@ -90,14 +99,14 @@ public Observable[] call(List> o) { public static Func1 equalsWith(Object other) { return new EqualsWithFunc1(other); } - + static final class EqualsWithFunc1 implements Func1 { final Object other; - + public EqualsWithFunc1(Object other) { this.other = other; } - + @Override public Boolean call(Object t) { return t == other || (t != null && t.equals(other)); @@ -113,14 +122,14 @@ public Boolean call(Object t) { public static Func1 isInstanceOf(Class clazz) { return new IsInstanceOfFunc1(clazz); } - + static final class IsInstanceOfFunc1 implements Func1 { final Class clazz; - + public IsInstanceOfFunc1(Class other) { this.clazz = other; } - + @Override public Boolean call(Object t) { return clazz.isInstance(t); @@ -133,26 +142,24 @@ public Boolean call(Object t) { * @param notificationHandler the handler to notify with nulls * @return the Func1 instance */ - public static final Func1>, Observable> createRepeatDematerializer(Func1, ? extends Observable> notificationHandler) { + public static Func1>, Observable> createRepeatDematerializer(Func1, ? extends Observable> notificationHandler) { return new RepeatNotificationDematerializer(notificationHandler); } - + static final class RepeatNotificationDematerializer implements Func1>, Observable> { - + final Func1, ? extends Observable> notificationHandler; - + public RepeatNotificationDematerializer(Func1, ? extends Observable> notificationHandler) { this.notificationHandler = notificationHandler; } - + @Override public Observable call(Observable> notifications) { return notificationHandler.call(notifications.map(RETURNS_VOID)); } - }; - - static final ReturnsVoidFunc1 RETURNS_VOID = new ReturnsVoidFunc1(); - + } + static final class ReturnsVoidFunc1 implements Func1 { @Override public Void call(Object t) { @@ -170,11 +177,11 @@ public Void call(Object t) { * @return the new Func1 instance */ public static Func1, Observable> createReplaySelectorAndObserveOn( - Func1, ? extends Observable> selector, + Func1, ? extends Observable> selector, Scheduler scheduler) { return new SelectorAndObserveOn(selector, scheduler); } - + static final class SelectorAndObserveOn implements Func1, Observable> { final Func1, ? extends Observable> selector; final Scheduler scheduler; @@ -200,32 +207,30 @@ public Observable call(Observable t) { * @param notificationHandler the handler to notify with Throwables * @return the Func1 instance */ - public static final Func1>, Observable> createRetryDematerializer(Func1, ? extends Observable> notificationHandler) { + public static Func1>, Observable> createRetryDematerializer(Func1, ? extends Observable> notificationHandler) { return new RetryNotificationDematerializer(notificationHandler); } static final class RetryNotificationDematerializer implements Func1>, Observable> { final Func1, ? extends Observable> notificationHandler; - + public RetryNotificationDematerializer(Func1, ? extends Observable> notificationHandler) { this.notificationHandler = notificationHandler; } - + @Override public Observable call(Observable> notifications) { return notificationHandler.call(notifications.map(ERROR_EXTRACTOR)); } } - - static final NotificationErrorExtractor ERROR_EXTRACTOR = new NotificationErrorExtractor(); - + static final class NotificationErrorExtractor implements Func1, Throwable> { @Override public Throwable call(Notification t) { return t.getThrowable(); } } - + /** * Returns a Func0 that supplies the ConnectableObservable returned by calling replay() on the source. * @param the input value type @@ -236,10 +241,10 @@ public static Func0> createReplaySupplier(final Obs return new ReplaySupplierNoParams(source); } - private static final class ReplaySupplierNoParams implements Func0> { + static final class ReplaySupplierNoParams implements Func0> { private final Observable source; - private ReplaySupplierNoParams(Observable source) { + ReplaySupplierNoParams(Observable source) { this.source = source; } @@ -264,7 +269,7 @@ static final class ReplaySupplierBuffer implements Func0 source; private final int bufferSize; - private ReplaySupplierBuffer(Observable source, int bufferSize) { + ReplaySupplierBuffer(Observable source, int bufferSize) { this.source = source; this.bufferSize = bufferSize; } @@ -286,7 +291,7 @@ public ConnectableObservable call() { * @param scheduler the scheduler to use for timing information * @return the new Func0 instance */ - public static Func0> createReplaySupplier(final Observable source, + public static Func0> createReplaySupplier(final Observable source, final long time, final TimeUnit unit, final Scheduler scheduler) { return new ReplaySupplierBufferTime(source, time, unit, scheduler); } @@ -297,7 +302,7 @@ static final class ReplaySupplierBufferTime implements Func0 source, long time, TimeUnit unit, Scheduler scheduler) { + ReplaySupplierBufferTime(Observable source, long time, TimeUnit unit, Scheduler scheduler) { this.unit = unit; this.source = source; this.time = time; @@ -323,7 +328,7 @@ public ConnectableObservable call() { * @param scheduler the scheduler to use for timing information * @return the new Func0 instance */ - public static Func0> createReplaySupplier(final Observable source, + public static Func0> createReplaySupplier(final Observable source, final int bufferSize, final long time, final TimeUnit unit, final Scheduler scheduler) { return new ReplaySupplierTime(source, bufferSize, time, unit, scheduler); } @@ -335,8 +340,8 @@ static final class ReplaySupplierTime implements Func0 source; - private ReplaySupplierTime(Observable source, int bufferSize, long time, TimeUnit unit, - Scheduler scheduler) { + ReplaySupplierTime(Observable source, int bufferSize, long time, TimeUnit unit, + Scheduler scheduler) { this.time = time; this.unit = unit; this.scheduler = scheduler; @@ -360,14 +365,14 @@ public ConnectableObservable call() { public static Func2 createCollectorCaller(Action2 collector) { return new CollectorCaller(collector); } - + static final class CollectorCaller implements Func2 { final Action2 collector; - + public CollectorCaller(Action2 collector) { this.collector = collector; } - + @Override public R call(R state, T value) { collector.call(state, value); @@ -375,17 +380,11 @@ public R call(R state, T value) { } } - /** - * Throws an OnErrorNotImplementedException when called. - */ - public static final Action1 ERROR_NOT_IMPLEMENTED = new ErrorNotImplementedAction(); - static final class ErrorNotImplementedAction implements Action1 { @Override public void call(Throwable t) { throw new OnErrorNotImplementedException(t); } } - - public static final Operator IS_EMPTY = new OperatorAny(UtilityFunctions.alwaysTrue(), true); + } diff --git a/src/main/java/rx/internal/util/LinkedArrayList.java b/src/main/java/rx/internal/util/LinkedArrayList.java index fb4ae68cc2..df7bf19d50 100644 --- a/src/main/java/rx/internal/util/LinkedArrayList.java +++ b/src/main/java/rx/internal/util/LinkedArrayList.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -18,7 +18,7 @@ import java.util.*; /** - * A list implementation which combines an ArrayList with a LinkedList to + * A list implementation which combines an ArrayList with a LinkedList to * avoid copying values when the capacity needs to be increased. *

    * The class is non final to allow embedding it directly and thus saving on object allocation. @@ -26,15 +26,15 @@ public class LinkedArrayList { /** The capacity of each array segment. */ final int capacityHint; - /** - * Contains the head of the linked array list if not null. The + /** + * Contains the head of the linked array list if not null. The * length is always capacityHint + 1 and the last element is an Object[] pointing * to the next element of the linked array list. */ Object[] head; /** The tail array where new elements will be added. */ Object[] tail; - /** + /** * The total size of the list; written after elements have been added (release) and * and when read, the value indicates how many elements can be safely read (acquire). */ @@ -43,7 +43,7 @@ public class LinkedArrayList { int indexInTail; /** * Constructor with the capacity hint of each array segment. - * @param capacityHint + * @param capacityHint the hint used for pre-sizing the internal array */ public LinkedArrayList(int capacityHint) { this.capacityHint = capacityHint; @@ -80,14 +80,14 @@ public void add(Object o) { * @return the head object array */ public Object[] head() { - return head; + return head; // NOPMD } /** * Returns the tail buffer segment or null if the list is empty. * @return the tail object array */ public Object[] tail() { - return tail; + return tail; // NOPMD } /** * Returns the total size of the list. @@ -114,7 +114,7 @@ public int capacityHint() { final int cap = capacityHint; final int s = size; final List list = new ArrayList(s + 1); - + Object[] h = head(); int j = 0; int k = 0; @@ -126,7 +126,7 @@ public int capacityHint() { h = (Object[])h[cap]; } } - + return list; } @Override diff --git a/src/main/java/rx/internal/util/ObjectPool.java b/src/main/java/rx/internal/util/ObjectPool.java deleted file mode 100644 index 35da79335a..0000000000 --- a/src/main/java/rx/internal/util/ObjectPool.java +++ /dev/null @@ -1,162 +0,0 @@ -/** - * Copyright 2014 Netflix, Inc. - * - * 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. - * - * Modified from http://www.javacodegeeks.com/2013/08/simple-and-lightweight-pool-implementation.html - */ -package rx.internal.util; - -import java.util.Queue; -import java.util.concurrent.*; -import java.util.concurrent.atomic.AtomicReference; - -import rx.internal.schedulers.*; -import rx.internal.util.unsafe.*; - -public abstract class ObjectPool implements SchedulerLifecycle { - Queue pool; - final int minSize; - final int maxSize; - private final long validationInterval; - - private final AtomicReference> periodicTask; - - public ObjectPool() { - this(0, 0, 67); - } - - /** - * Creates the pool. - * - * @param minIdle - * minimum number of objects residing in the pool - * @param maxIdle - * maximum number of objects residing in the pool - * @param validationInterval - * time in seconds for periodical checking of minIdle / maxIdle conditions in a separate thread. - * When the number of objects is less than minIdle, missing instances will be created. - * When the number of objects is greater than maxIdle, too many instances will be removed. - */ - private ObjectPool(final int min, final int max, final long validationInterval) { - this.minSize = min; - this.maxSize = max; - this.validationInterval = validationInterval; - this.periodicTask = new AtomicReference>(); - // initialize pool - initialize(min); - - start(); - } - - /** - * Gets the next free object from the pool. If the pool doesn't contain any objects, - * a new object will be created and given to the caller of this method back. - * - * @return T borrowed object - */ - public T borrowObject() { - T object; - if ((object = pool.poll()) == null) { - object = createObject(); - } - - return object; - } - - /** - * Returns object back to the pool. - * - * @param object - * object to be returned - */ - public void returnObject(T object) { - if (object == null) { - return; - } - - this.pool.offer(object); - } - - /** - * Shutdown this pool. - */ - @Override - public void shutdown() { - Future f = periodicTask.getAndSet(null); - if (f != null) { - f.cancel(false); - } - } - - @Override - public void start() { - for (;;) { - if (periodicTask.get() != null) { - return; - } - ScheduledExecutorService w = GenericScheduledExecutorService.getInstance(); - - Future f; - try { - f = w.scheduleAtFixedRate(new Runnable() { - - @Override - public void run() { - int size = pool.size(); - if (size < minSize) { - int sizeToBeAdded = maxSize - size; - for (int i = 0; i < sizeToBeAdded; i++) { - pool.add(createObject()); - } - } else if (size > maxSize) { - int sizeToBeRemoved = size - maxSize; - for (int i = 0; i < sizeToBeRemoved; i++) { - // pool.pollLast(); - pool.poll(); - } - } - } - - }, validationInterval, validationInterval, TimeUnit.SECONDS); - } catch (RejectedExecutionException ex) { - RxJavaPluginUtils.handleException(ex); - break; - } - if (!periodicTask.compareAndSet(null, f)) { - f.cancel(false); - } else { - break; - } - } - } - - /** - * Creates a new object. - * - * @return T new object - */ - protected abstract T createObject(); - - private void initialize(final int min) { - if (UnsafeAccess.isUnsafeAvailable()) { - pool = new MpmcArrayQueue(Math.max(maxSize, 1024)); - } else { - pool = new ConcurrentLinkedQueue(); - } - - for (int i = 0; i < min; i++) { - pool.add(createObject()); - } - } -} \ No newline at end of file diff --git a/src/main/java/rx/internal/util/ObserverSubscriber.java b/src/main/java/rx/internal/util/ObserverSubscriber.java index dbf519c263..6b6a62a0a6 100644 --- a/src/main/java/rx/internal/util/ObserverSubscriber.java +++ b/src/main/java/rx/internal/util/ObserverSubscriber.java @@ -1,12 +1,12 @@ /** * Copyright 2016 Netflix, Inc. - * + * * 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. @@ -28,17 +28,17 @@ public final class ObserverSubscriber extends Subscriber { public ObserverSubscriber(Observer observer) { this.observer = observer; } - + @Override public void onNext(T t) { observer.onNext(t); } - + @Override public void onError(Throwable e) { observer.onError(e); } - + @Override public void onCompleted() { observer.onCompleted(); diff --git a/src/main/java/rx/internal/util/OpenHashSet.java b/src/main/java/rx/internal/util/OpenHashSet.java index 1e79fdd4c1..238411656f 100644 --- a/src/main/java/rx/internal/util/OpenHashSet.java +++ b/src/main/java/rx/internal/util/OpenHashSet.java @@ -1,18 +1,18 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. - * + * * Modified from http://www.javacodegeeks.com/2013/08/simple-and-lightweight-pool-implementation.html */ @@ -40,11 +40,12 @@ public final class OpenHashSet { int size; int maxSize; T[] keys; - + private static final int INT_PHI = 0x9E3779B9; + public OpenHashSet() { this(16, 0.75f); } - + /** * Creates an OpenHashSet with the initial capacity and load factor of 0.75f. * @param capacity the initial capacity @@ -52,7 +53,7 @@ public OpenHashSet() { public OpenHashSet(int capacity) { this(capacity, 0.75f); } - + @SuppressWarnings("unchecked") public OpenHashSet(int capacity, float loadFactor) { this.loadFactor = loadFactor; @@ -61,11 +62,11 @@ public OpenHashSet(int capacity, float loadFactor) { this.maxSize = (int)(loadFactor * c); this.keys = (T[])new Object[c]; } - + public boolean add(T value) { final T[] a = keys; final int m = mask; - + int pos = mix(value.hashCode()) & m; T curr = a[pos]; if (curr != null) { @@ -111,10 +112,10 @@ public boolean remove(T value) { } } } - + boolean removeEntry(int pos, T[] a, int m) { size--; - + int last; int slot; T curr; @@ -128,17 +129,17 @@ boolean removeEntry(int pos, T[] a, int m) { return true; } slot = mix(curr.hashCode()) & m; - + if (last <= pos ? last >= slot || slot > pos : last >= slot && slot > pos) { break; } - + pos = (pos + 1) & m; } a[last] = curr; } } - + public void clear(Action1 clearAction) { if (size == 0) { return; @@ -154,25 +155,25 @@ public void clear(Action1 clearAction) { Arrays.fill(a, null); size = 0; } - + @SuppressWarnings("unchecked") public void terminate() { size = 0; keys = (T[])new Object[0]; } - + @SuppressWarnings("unchecked") void rehash() { T[] a = keys; int i = a.length; int newCap = i << 1; int m = newCap - 1; - + T[] b = (T[])new Object[newCap]; - - + + for (int j = size; j-- != 0; ) { - while (a[--i] == null); + while (a[--i] == null) { } // NOPMD int pos = mix(a[i].hashCode()) & m; if (b[pos] != null) { for (;;) { @@ -184,28 +185,26 @@ void rehash() { } b[pos] = a[i]; } - + this.mask = m; this.maxSize = (int)(newCap * loadFactor); this.keys = b; } - - private static final int INT_PHI = 0x9E3779B9; - + static int mix(int x) { final int h = x * INT_PHI; return h ^ (h >>> 16); } - + public boolean isEmpty() { return size == 0; } - + /** - * Returns the raw array of values of this set, watch out for null entires. + * Returns the raw array of values of this set, watch out for null entries. * @return the raw array of values of this set */ public T[] values() { - return keys; + return keys; // NOPMD } } diff --git a/src/main/java/rx/internal/util/PlatformDependent.java b/src/main/java/rx/internal/util/PlatformDependent.java index 2aa5f7e184..2b6d5c6e65 100644 --- a/src/main/java/rx/internal/util/PlatformDependent.java +++ b/src/main/java/rx/internal/util/PlatformDependent.java @@ -15,9 +15,6 @@ */ package rx.internal.util; -import java.security.AccessController; -import java.security.PrivilegedAction; - /** * Allow platform dependent logic such as checks for Android. * @@ -34,6 +31,11 @@ public final class PlatformDependent { private static final boolean IS_ANDROID = ANDROID_API_VERSION != ANDROID_API_VERSION_IS_NOT_ANDROID; + /** Utility class. */ + private PlatformDependent() { + throw new IllegalStateException("No instances!"); + } + /** * Returns {@code true} if and only if the current platform is Android. * @return {@code true} if and only if the current platform is Android @@ -62,29 +64,13 @@ public static int getAndroidApiVersion() { private static int resolveAndroidApiVersion() { try { return (Integer) Class - .forName("android.os.Build$VERSION", true, getSystemClassLoader()) + .forName("android.os.Build$VERSION") .getField("SDK_INT") .get(null); - } catch (Exception e) { + } catch (Exception e) { // NOPMD // Can not resolve version of Android API, maybe current platform is not Android // or API of resolving current Version of Android API has changed in some release of Android return ANDROID_API_VERSION_IS_NOT_ANDROID; } } - - /** - * Return the system {@link ClassLoader}. - */ - static ClassLoader getSystemClassLoader() { - if (System.getSecurityManager() == null) { - return ClassLoader.getSystemClassLoader(); - } else { - return AccessController.doPrivileged(new PrivilegedAction() { - @Override - public ClassLoader run() { - return ClassLoader.getSystemClassLoader(); - } - }); - } - } } diff --git a/src/main/java/rx/internal/util/RxJavaPluginUtils.java b/src/main/java/rx/internal/util/RxJavaPluginUtils.java deleted file mode 100644 index 4e459130e5..0000000000 --- a/src/main/java/rx/internal/util/RxJavaPluginUtils.java +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Copyright 2014 Netflix, Inc. - * - * 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 rx.internal.util; - -import rx.plugins.RxJavaHooks; - -public final class RxJavaPluginUtils { - - public static void handleException(Throwable e) { - try { - RxJavaHooks.onError(e); - } catch (Throwable pluginException) { - handlePluginException(pluginException); - } - } - - private static void handlePluginException(Throwable pluginException) { - /* - * We don't want errors from the plugin to affect normal flow. - * Since the plugin should never throw this is a safety net - * and will complain loudly to System.err so it gets fixed. - */ - System.err.println("RxJavaErrorHandler threw an Exception. It shouldn't. => " + pluginException.getMessage()); - pluginException.printStackTrace(); - } - -} diff --git a/src/main/java/rx/internal/util/RxRingBuffer.java b/src/main/java/rx/internal/util/RxRingBuffer.java index 5f35c7f6e5..366fffd4c2 100644 --- a/src/main/java/rx/internal/util/RxRingBuffer.java +++ b/src/main/java/rx/internal/util/RxRingBuffer.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -21,6 +21,7 @@ import rx.Subscription; import rx.exceptions.MissingBackpressureException; import rx.internal.operators.NotificationLite; +import rx.internal.util.atomic.SpscAtomicArrayQueue; import rx.internal.util.unsafe.SpmcArrayQueue; import rx.internal.util.unsafe.SpscArrayQueue; import rx.internal.util.unsafe.UnsafeAccess; @@ -32,121 +33,102 @@ */ public class RxRingBuffer implements Subscription { - public static RxRingBuffer getSpscInstance() { - if (UnsafeAccess.isUnsafeAvailable()) { - return new RxRingBuffer(SPSC_POOL, SIZE); - } else { - return new RxRingBuffer(); - } - } - - public static RxRingBuffer getSpmcInstance() { - if (UnsafeAccess.isUnsafeAvailable()) { - return new RxRingBuffer(SPMC_POOL, SIZE); - } else { - return new RxRingBuffer(); - } - } - /** * Queue implementation testing that led to current choices of data structures: - * + * * With synchronized LinkedList *
     {@code
          * Benchmark                                        Mode   Samples        Score  Score error    Units
          * r.i.RxRingBufferPerf.ringBufferAddRemove1       thrpt         5 19118392.046  1002814.238    ops/s
          * r.i.RxRingBufferPerf.ringBufferAddRemove1000    thrpt         5    17891.641      252.747    ops/s
    -     * 
    +     *
          * With MpscPaddedQueue (single consumer, so failing 1 unit test)
    -     * 
    +     *
          * Benchmark                                        Mode   Samples        Score  Score error    Units
          * r.i.RxRingBufferPerf.ringBufferAddRemove1       thrpt         5 22164483.238  3035027.348    ops/s
          * r.i.RxRingBufferPerf.ringBufferAddRemove1000    thrpt         5    23154.303      602.548    ops/s
    -     * 
    -     * 
    +     *
    +     *
          * With ConcurrentLinkedQueue (tracking count separately)
    -     * 
    +     *
          * Benchmark                                        Mode   Samples        Score  Score error    Units
          * r.i.RxRingBufferPerf.ringBufferAddRemove1       thrpt         5 17353906.092   378756.411    ops/s
          * r.i.RxRingBufferPerf.ringBufferAddRemove1000    thrpt         5    19224.411     1010.610    ops/s
    -     * 
    +     *
          * With ConcurrentLinkedQueue (using queue.size() method for count)
    -     * 
    +     *
          * Benchmark                                        Mode   Samples        Score  Score error    Units
          * r.i.RxRingBufferPerf.ringBufferAddRemove1       thrpt         5 23951121.098  1982380.330    ops/s
          * r.i.RxRingBufferPerf.ringBufferAddRemove1000    thrpt         5     1142.351       33.592    ops/s
    -     * 
    +     *
          * With SynchronizedQueue (synchronized LinkedList ... no object pooling)
    -     * 
    +     *
          * r.i.RxRingBufferPerf.createUseAndDestroy1       thrpt         5 33231667.136   685757.510    ops/s
          * r.i.RxRingBufferPerf.createUseAndDestroy1000    thrpt         5    74623.614     5493.766    ops/s
          * r.i.RxRingBufferPerf.ringBufferAddRemove1       thrpt         5 22907359.257   707026.632    ops/s
          * r.i.RxRingBufferPerf.ringBufferAddRemove1000    thrpt         5    22222.410      320.829    ops/s
    -     * 
    +     *
          * With ArrayBlockingQueue
    -     * 
    +     *
          * Benchmark                                            Mode   Samples        Score  Score error    Units
          * r.i.RxRingBufferPerf.createUseAndDestroy1       thrpt         5  2389804.664    68990.804    ops/s
          * r.i.RxRingBufferPerf.createUseAndDestroy1000    thrpt         5    27384.274     1411.789    ops/s
          * r.i.RxRingBufferPerf.ringBufferAddRemove1       thrpt         5 26497037.559    91176.247    ops/s
          * r.i.RxRingBufferPerf.ringBufferAddRemove1000    thrpt         5    17985.144      237.771    ops/s
    -     * 
    +     *
          * With ArrayBlockingQueue and Object Pool
    -     * 
    +     *
          * Benchmark                                            Mode   Samples        Score  Score error    Units
          * r.i.RxRingBufferPerf.createUseAndDestroy1       thrpt         5 12465685.522   399070.770    ops/s
          * r.i.RxRingBufferPerf.createUseAndDestroy1000    thrpt         5    27701.294      395.217    ops/s
          * r.i.RxRingBufferPerf.ringBufferAddRemove1       thrpt         5 26399625.086   695639.436    ops/s
          * r.i.RxRingBufferPerf.ringBufferAddRemove1000    thrpt         5    17985.427      253.190    ops/s
    -     * 
    +     *
          * With SpscArrayQueue (single consumer, so failing 1 unit test)
          *  - requires access to Unsafe
    -     * 
    +     *
          * Benchmark                                        Mode   Samples        Score  Score error    Units
          * r.i.RxRingBufferPerf.createUseAndDestroy1       thrpt         5  1922996.035    49183.766    ops/s
          * r.i.RxRingBufferPerf.createUseAndDestroy1000    thrpt         5    70890.186     1382.550    ops/s
          * r.i.RxRingBufferPerf.ringBufferAddRemove1       thrpt         5 80637811.605  3509706.954    ops/s
          * r.i.RxRingBufferPerf.ringBufferAddRemove1000    thrpt         5    71822.453     4127.660    ops/s
    -     * 
    -     * 
    +     *
    +     *
          * With SpscArrayQueue and Object Pool (object pool improves createUseAndDestroy1 by 10x)
    -     * 
    +     *
          * Benchmark                                        Mode   Samples        Score  Score error    Units
          * r.i.RxRingBufferPerf.createUseAndDestroy1       thrpt         5 25220069.264  1329078.785    ops/s
          * r.i.RxRingBufferPerf.createUseAndDestroy1000    thrpt         5    72313.457     3535.447    ops/s
          * r.i.RxRingBufferPerf.ringBufferAddRemove1       thrpt         5 81863840.884  2191416.069    ops/s
          * r.i.RxRingBufferPerf.ringBufferAddRemove1000    thrpt         5    73140.822     1528.764    ops/s
    -     * 
    +     *
          * With SpmcArrayQueue
          *  - requires access to Unsafe
    -     *  
    +     *
          * Benchmark                                            Mode   Samples        Score  Score error    Units
          * r.i.RxRingBufferPerf.spmcCreateUseAndDestroy1       thrpt         5 27630345.474   769219.142    ops/s
          * r.i.RxRingBufferPerf.spmcCreateUseAndDestroy1000    thrpt         5    80052.046     4059.541    ops/s
          * r.i.RxRingBufferPerf.spmcRingBufferAddRemove1       thrpt         5 44449524.222   563068.793    ops/s
          * r.i.RxRingBufferPerf.spmcRingBufferAddRemove1000    thrpt         5    65231.253     1805.732    ops/s
    -     * 
    +     *
          * With SpmcArrayQueue and ObjectPool (object pool improves createUseAndDestroy1 by 10x)
    -     * 
    +     *
          * Benchmark                                        Mode   Samples        Score  Score error    Units
          * r.i.RxRingBufferPerf.createUseAndDestroy1       thrpt         5 18489343.061  1011872.825    ops/s
          * r.i.RxRingBufferPerf.createUseAndDestroy1000    thrpt         5    46416.434     1439.144    ops/s
          * r.i.RxRingBufferPerf.ringBufferAddRemove        thrpt         5 38280945.847  1071801.279    ops/s
          * r.i.RxRingBufferPerf.ringBufferAddRemove1000    thrpt         5    42337.663     1052.231    ops/s
    -     * 
    +     *
          * --------------
    -     * 
    +     *
          * When UnsafeAccess.isUnsafeAvailable() == true we can use the Spmc/SpscArrayQueue implementations.
    -     * 
    +     *
          * } 
    */ - private static final NotificationLite on = NotificationLite.instance(); - private Queue queue; private final int size; - private final ObjectPool> pool; /** * We store the terminal state separately so it doesn't count against the size. @@ -161,12 +143,12 @@ public static RxRingBuffer getSpmcInstance() { // default size of ring buffer /** * 128 was chosen as the default based on the numbers below. A stream processing system may benefit from increasing to 512+. - * + * *
     {@code
          * ./gradlew benchmarks '-Pjmh=-f 1 -tu s -bm thrpt -wi 5 -i 5 -r 1 .*OperatorObserveOnPerf.*'
    -     * 
    +     *
          * 1024
    -     * 
    +     *
          * Benchmark                                         (size)   Mode   Samples        Score  Score error    Units
          * r.o.OperatorObserveOnPerf.observeOnComputation         1  thrpt         5   100642.874    24676.478    ops/s
          * r.o.OperatorObserveOnPerf.observeOnComputation      1000  thrpt         5     4095.901       90.730    ops/s
    @@ -177,9 +159,9 @@ public static RxRingBuffer getSpmcInstance() {
          * r.o.OperatorObserveOnPerf.observeOnNewThread           1  thrpt         5    16864.641     1826.877    ops/s
          * r.o.OperatorObserveOnPerf.observeOnNewThread        1000  thrpt         5     4269.317      169.480    ops/s
          * r.o.OperatorObserveOnPerf.observeOnNewThread     1000000  thrpt         5       13.393        1.047    ops/s
    -     * 
    +     *
          * 512
    -     * 
    +     *
          * Benchmark                                         (size)   Mode   Samples        Score  Score error    Units
          * r.o.OperatorObserveOnPerf.observeOnComputation         1  thrpt         5    98945.980    48050.282    ops/s
          * r.o.OperatorObserveOnPerf.observeOnComputation      1000  thrpt         5     4111.149       95.987    ops/s
    @@ -190,9 +172,9 @@ public static RxRingBuffer getSpmcInstance() {
          * r.o.OperatorObserveOnPerf.observeOnNewThread           1  thrpt         5    15813.984     8260.170    ops/s
          * r.o.OperatorObserveOnPerf.observeOnNewThread        1000  thrpt         5     4358.334      251.609    ops/s
          * r.o.OperatorObserveOnPerf.observeOnNewThread     1000000  thrpt         5       13.647        0.613    ops/s
    -     * 
    +     *
          * 256
    -     * 
    +     *
          * Benchmark                                         (size)   Mode   Samples        Score  Score error    Units
          * r.o.OperatorObserveOnPerf.observeOnComputation         1  thrpt         5   108489.834     2688.489    ops/s
          * r.o.OperatorObserveOnPerf.observeOnComputation      1000  thrpt         5     4526.674      728.019    ops/s
    @@ -203,9 +185,9 @@ public static RxRingBuffer getSpmcInstance() {
          * r.o.OperatorObserveOnPerf.observeOnNewThread           1  thrpt         5    16976.775      968.191    ops/s
          * r.o.OperatorObserveOnPerf.observeOnNewThread        1000  thrpt         5     6238.210     2060.387    ops/s
          * r.o.OperatorObserveOnPerf.observeOnNewThread     1000000  thrpt         5       13.465        0.566    ops/s
    -     * 
    +     *
          * 128
    -     * 
    +     *
          * Benchmark                                         (size)   Mode   Samples        Score  Score error    Units
          * r.o.OperatorObserveOnPerf.observeOnComputation         1  thrpt         5   106887.027    29307.913    ops/s
          * r.o.OperatorObserveOnPerf.observeOnComputation      1000  thrpt         5     6713.891      202.989    ops/s
    @@ -216,9 +198,9 @@ public static RxRingBuffer getSpmcInstance() {
          * r.o.OperatorObserveOnPerf.observeOnNewThread           1  thrpt         5    17172.274      236.816    ops/s
          * r.o.OperatorObserveOnPerf.observeOnNewThread        1000  thrpt         5     7073.555      595.990    ops/s
          * r.o.OperatorObserveOnPerf.observeOnNewThread     1000000  thrpt         5       11.855        1.093    ops/s
    -     * 
    +     *
          * 32
    -     * 
    +     *
          * Benchmark                                         (size)   Mode   Samples        Score  Score error    Units
          * r.o.OperatorObserveOnPerf.observeOnComputation         1  thrpt         5   106128.589    20986.201    ops/s
          * r.o.OperatorObserveOnPerf.observeOnComputation      1000  thrpt         5     6396.607       73.627    ops/s
    @@ -229,9 +211,9 @@ public static RxRingBuffer getSpmcInstance() {
          * r.o.OperatorObserveOnPerf.observeOnNewThread           1  thrpt         5    16927.513      606.692    ops/s
          * r.o.OperatorObserveOnPerf.observeOnNewThread        1000  thrpt         5     5191.084      244.876    ops/s
          * r.o.OperatorObserveOnPerf.observeOnNewThread     1000000  thrpt         5        8.288        0.217    ops/s
    -     * 
    +     *
          * 16
    -     * 
    +     *
          * Benchmark                                         (size)   Mode   Samples        Score  Score error    Units
          * r.o.OperatorObserveOnPerf.observeOnComputation         1  thrpt         5   109974.741      839.064    ops/s
          * r.o.OperatorObserveOnPerf.observeOnComputation      1000  thrpt         5     4538.912      173.561    ops/s
    @@ -242,9 +224,9 @@ public static RxRingBuffer getSpmcInstance() {
          * r.o.OperatorObserveOnPerf.observeOnNewThread           1  thrpt         5    14903.686     3325.205    ops/s
          * r.o.OperatorObserveOnPerf.observeOnNewThread        1000  thrpt         5     3784.776     1054.131    ops/s
          * r.o.OperatorObserveOnPerf.observeOnNewThread     1000000  thrpt         5        5.624        0.130    ops/s
    -     * 
    +     *
          * 2
    -     * 
    +     *
          * Benchmark                                         (size)   Mode   Samples        Score  Score error    Units
          * r.o.OperatorObserveOnPerf.observeOnComputation         1  thrpt         5   112663.216      899.005    ops/s
          * r.o.OperatorObserveOnPerf.observeOnComputation      1000  thrpt         5      899.737        9.460    ops/s
    @@ -257,65 +239,56 @@ public static RxRingBuffer getSpmcInstance() {
          * r.o.OperatorObserveOnPerf.observeOnNewThread     1000000  thrpt         5        1.173        0.100    ops/s
          * } 
    */ - static int _size = 128; static { + int defaultSize = 128; + // lower default for Android (https://github.com/ReactiveX/RxJava/issues/1820) if (PlatformDependent.isAndroid()) { - _size = 16; + defaultSize = 16; } // possible system property for overriding String sizeFromProperty = System.getProperty("rx.ring-buffer.size"); // also see IndexedRingBuffer if (sizeFromProperty != null) { try { - _size = Integer.parseInt(sizeFromProperty); - } catch (Exception e) { - System.err.println("Failed to set 'rx.buffer.size' with value " + sizeFromProperty + " => " + e.getMessage()); + defaultSize = Integer.parseInt(sizeFromProperty); + } catch (NumberFormatException e) { + System.err.println("Failed to set 'rx.buffer.size' with value " + sizeFromProperty + " => " + e.getMessage()); // NOPMD } } - } - public static final int SIZE = _size; - /* Public so Schedulers can manage the lifecycle of the inner worker. */ - public static ObjectPool> SPSC_POOL = new ObjectPool>() { + SIZE = defaultSize; + } + public static final int SIZE; - @Override - protected SpscArrayQueue createObject() { - return new SpscArrayQueue(SIZE); + public static RxRingBuffer getSpscInstance() { + if (UnsafeAccess.isUnsafeAvailable()) { + return new RxRingBuffer(false, SIZE); + } else { + return new RxRingBuffer(); } + } - }; - - /* Public so Schedulers can manage the lifecycle of the inner worker. */ - public static ObjectPool> SPMC_POOL = new ObjectPool>() { - - @Override - protected SpmcArrayQueue createObject() { - return new SpmcArrayQueue(SIZE); + public static RxRingBuffer getSpmcInstance() { + if (UnsafeAccess.isUnsafeAvailable()) { + return new RxRingBuffer(true, SIZE); + } else { + return new RxRingBuffer(); } + } - }; - private RxRingBuffer(Queue queue, int size) { this.queue = queue; - this.pool = null; this.size = size; } - private RxRingBuffer(ObjectPool> pool, int size) { - this.pool = pool; - this.queue = pool.borrowObject(); + private RxRingBuffer(boolean spmc, int size) { + this.queue = spmc ? new SpmcArrayQueue(size) : new SpscArrayQueue(size); this.size = size; } - public synchronized void release() { - Queue q = queue; - ObjectPool> p = pool; - if (p != null && q != null) { - q.clear(); - queue = null; - p.returnObject(q); - } + public synchronized void release() { // NOPMD + // 1.2.3: no longer pooling } @Override @@ -324,12 +297,12 @@ public void unsubscribe() { } /* package accessible for unit tests */RxRingBuffer() { - this(new SynchronizedQueue(SIZE), SIZE); + this(new SpscAtomicArrayQueue(SIZE), SIZE); } /** - * - * @param o + * + * @param o the value to buffer * @throws MissingBackpressureException * if more onNext are sent than have been requested */ @@ -339,12 +312,12 @@ public void onNext(Object o) throws MissingBackpressureException { synchronized (this) { Queue q = queue; if (q != null) { - mbe = !q.offer(on.next(o)); + mbe = !q.offer(NotificationLite.next(o)); } else { iae = true; } } - + if (iae) { throw new IllegalStateException("This instance has been unsubscribed and the queue is no longer usable."); } @@ -356,14 +329,14 @@ public void onNext(Object o) throws MissingBackpressureException { public void onCompleted() { // we ignore terminal events if we already have one if (terminalState == null) { - terminalState = on.completed(); + terminalState = NotificationLite.completed(); } } public void onError(Throwable t) { // we ignore terminal events if we already have one if (terminalState == null) { - terminalState = on.error(t); + terminalState = NotificationLite.error(t); } } @@ -385,10 +358,7 @@ public int count() { public boolean isEmpty() { Queue q = queue; - if (q == null) { - return true; - } - return q.isEmpty(); + return q == null || q.isEmpty(); } public Object poll() { @@ -400,7 +370,7 @@ public Object poll() { return null; } o = q.poll(); - + Object ts = terminalState; if (o == null && ts != null && q.peek() == null) { o = ts; @@ -429,24 +399,24 @@ public Object peek() { } public boolean isCompleted(Object o) { - return on.isCompleted(o); + return NotificationLite.isCompleted(o); } public boolean isError(Object o) { - return on.isError(o); + return NotificationLite.isError(o); } public Object getValue(Object o) { - return on.getValue(o); + return NotificationLite.getValue(o); } @SuppressWarnings({ "unchecked", "rawtypes" }) public boolean accept(Object o, Observer child) { - return on.accept(child, o); + return NotificationLite.accept(child, o); } public Throwable asError(Object o) { - return on.getError(o); + return NotificationLite.getError(o); } @Override diff --git a/src/main/java/rx/internal/util/ScalarSynchronousObservable.java b/src/main/java/rx/internal/util/ScalarSynchronousObservable.java index 8c56935596..141030266e 100644 --- a/src/main/java/rx/internal/util/ScalarSynchronousObservable.java +++ b/src/main/java/rx/internal/util/ScalarSynchronousObservable.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -34,9 +34,12 @@ * @param the value type */ public final class ScalarSynchronousObservable extends Observable { + /** The constant scalar value to emit on request. */ + final T t; + /** * Indicates that the Producer used by this Observable should be fully - * threadsafe. It is possible, but unlikely that multiple concurrent + * thread-safe. It is possible, but unlikely that multiple concurrent * requests will arrive to just(). */ static final boolean STRONG_MODE; @@ -58,7 +61,7 @@ static Producer createProducer(Subscriber s, T v) { } return new WeakSingleProducer(s, v); } - + /** * Constructs a ScalarSynchronousObservable with the given constant value. * @param the value type @@ -69,9 +72,6 @@ public static ScalarSynchronousObservable create(T t) { return new ScalarSynchronousObservable(t); } - /** The constant scalar value to emit on request. */ - final T t; - protected ScalarSynchronousObservable(final T t) { super(RxJavaHooks.onCreate(new JustOnSubscribe(t))); this.t = t; @@ -84,8 +84,8 @@ protected ScalarSynchronousObservable(final T t) { public T get() { return t; } - - + + /** * Customized observeOn/subscribeOn implementation which emits the scalar * value directly or with less overhead on the specified scheduler. @@ -93,7 +93,7 @@ public T get() { * @return the new observable */ public Observable scalarScheduleOn(final Scheduler scheduler) { - final Func1 onSchedule; + Func1 onSchedule; if (scheduler instanceof EventLoopsScheduler) { final EventLoopsScheduler els = (EventLoopsScheduler) scheduler; onSchedule = new Func1() { @@ -121,10 +121,10 @@ public void call() { } }; } - - return create(new ScalarAsyncOnSubscribe(t, onSchedule)); + + return unsafeCreate(new ScalarAsyncOnSubscribe(t, onSchedule)); } - + /** The OnSubscribe callback for the Observable constructor. */ static final class JustOnSubscribe implements OnSubscribe { final T value; @@ -172,7 +172,7 @@ static final class ScalarAsyncProducer extends AtomicBoolean implements Produ final Subscriber actual; final T value; final Func1 onSchedule; - + public ScalarAsyncProducer(Subscriber actual, T value, Func1 onSchedule) { this.actual = actual; this.value = value; @@ -188,7 +188,7 @@ public void request(long n) { actual.add(onSchedule.call(this)); } } - + @Override public void call() { Subscriber a = actual; @@ -207,13 +207,13 @@ public void call() { } a.onCompleted(); } - + @Override public String toString() { return "ScalarAsyncProducer[" + value + ", " + get() + "]"; } } - + /** * Given this scalar source as input to a flatMap, avoid one step of subscription * and subscribes to the single Observable returned by the function. @@ -225,7 +225,7 @@ public String toString() { * @return the new observable */ public Observable scalarFlatMap(final Func1> func) { - return create(new OnSubscribe() { + return unsafeCreate(new OnSubscribe() { @Override public void call(final Subscriber child) { Observable o = func.call(t); @@ -237,10 +237,10 @@ public void call(final Subscriber child) { } }); } - + /** * This is the weak version of SingleProducer that uses plain fields - * to avoid reentrancy and as such is not threadsafe for concurrent + * to avoid re-entrant invocation and as such is not thread-safe for concurrent * request() calls. * * @param the value type @@ -249,12 +249,12 @@ static final class WeakSingleProducer implements Producer { final Subscriber actual; final T value; boolean once; - + public WeakSingleProducer(Subscriber actual, T value) { this.actual = actual; this.value = value; } - + @Override public void request(long n) { if (once) { diff --git a/src/main/java/rx/internal/util/ScalarSynchronousSingle.java b/src/main/java/rx/internal/util/ScalarSynchronousSingle.java index 83b7d456a1..461f6e1c3b 100644 --- a/src/main/java/rx/internal/util/ScalarSynchronousSingle.java +++ b/src/main/java/rx/internal/util/ScalarSynchronousSingle.java @@ -15,23 +15,19 @@ */ package rx.internal.util; -import rx.Scheduler; +import rx.*; import rx.Scheduler.Worker; -import rx.Single; -import rx.SingleSubscriber; -import rx.Subscriber; -import rx.functions.Action0; -import rx.functions.Func1; +import rx.functions.*; import rx.internal.schedulers.EventLoopsScheduler; public final class ScalarSynchronousSingle extends Single { - public static final ScalarSynchronousSingle create(T t) { + final T value; + + public static ScalarSynchronousSingle create(T t) { return new ScalarSynchronousSingle(t); } - final T value; - protected ScalarSynchronousSingle(final T t) { super(new OnSubscribe() { @@ -133,23 +129,19 @@ public void call(final SingleSubscriber child) { if (o instanceof ScalarSynchronousSingle) { child.onSuccess(((ScalarSynchronousSingle) o).value); } else { - Subscriber subscriber = new Subscriber() { - @Override - public void onCompleted() { - } - + SingleSubscriber subscriber = new SingleSubscriber() { @Override public void onError(Throwable e) { child.onError(e); } @Override - public void onNext(R r) { + public void onSuccess(R r) { child.onSuccess(r); } }; child.add(subscriber); - o.unsafeSubscribe(subscriber); + o.subscribe(subscriber); } } }); diff --git a/src/main/java/rx/internal/util/SubscriptionList.java b/src/main/java/rx/internal/util/SubscriptionList.java index 6f6f391dde..8408cd2549 100644 --- a/src/main/java/rx/internal/util/SubscriptionList.java +++ b/src/main/java/rx/internal/util/SubscriptionList.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -22,21 +22,33 @@ /** * Subscription that represents a group of Subscriptions that are unsubscribed together. - * + * * @see Rx.Net equivalent CompositeDisposable */ public final class SubscriptionList implements Subscription { - private LinkedList subscriptions; + private List subscriptions; private volatile boolean unsubscribed; + /** + * Constructs an empty SubscriptionList. + */ public SubscriptionList() { + // nothing to do } + /** + * Constructs a SubscriptionList with the given initial child subscriptions. + * @param subscriptions the array of subscriptions to start with + */ public SubscriptionList(final Subscription... subscriptions) { this.subscriptions = new LinkedList(Arrays.asList(subscriptions)); } + /** + * Constructs a SubscriptionList with the given initial child subscription. + * @param s the initial subscription instance + */ public SubscriptionList(Subscription s) { this.subscriptions = new LinkedList(); this.subscriptions.add(s); @@ -62,7 +74,7 @@ public void add(final Subscription s) { if (!unsubscribed) { synchronized (this) { if (!unsubscribed) { - LinkedList subs = subscriptions; + List subs = subscriptions; if (subs == null) { subs = new LinkedList(); subscriptions = subs; @@ -78,9 +90,9 @@ public void add(final Subscription s) { public void remove(final Subscription s) { if (!unsubscribed) { - boolean unsubscribe = false; + boolean unsubscribe; synchronized (this) { - LinkedList subs = subscriptions; + List subs = subscriptions; if (unsubscribed || subs == null) { return; } diff --git a/src/test/java/rx/internal/util/SynchronizedQueueTest.java b/src/main/java/rx/internal/util/SuppressAnimalSniffer.java similarity index 70% rename from src/test/java/rx/internal/util/SynchronizedQueueTest.java rename to src/main/java/rx/internal/util/SuppressAnimalSniffer.java index aea871711a..38a0246bf7 100644 --- a/src/test/java/rx/internal/util/SynchronizedQueueTest.java +++ b/src/main/java/rx/internal/util/SuppressAnimalSniffer.java @@ -16,16 +16,14 @@ package rx.internal.util; -import static org.junit.Assert.assertTrue; +import java.lang.annotation.*; -import org.junit.Test; - -public class SynchronizedQueueTest { - - @Test - public void testEquals() { - SynchronizedQueue q = new SynchronizedQueue(); - assertTrue(q.equals(q)); - } +/** + * Suppress errors by the AnimalSniffer plugin. + */ +@Retention(RetentionPolicy.CLASS) +@Documented +@Target({ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.TYPE}) +public @interface SuppressAnimalSniffer { } diff --git a/src/main/java/rx/internal/util/SynchronizedQueue.java b/src/main/java/rx/internal/util/SynchronizedQueue.java deleted file mode 100644 index 8f0c4a7372..0000000000 --- a/src/main/java/rx/internal/util/SynchronizedQueue.java +++ /dev/null @@ -1,168 +0,0 @@ -/** - * Copyright 2014 Netflix, Inc. - * - * 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 rx.internal.util; - -import java.util.Collection; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.Queue; - -/** - * Intended for use when the `sun.misc.Unsafe` implementations can't be used. - * - * @param - */ -public class SynchronizedQueue implements Queue { - - private final LinkedList list = new LinkedList(); - private final int size; - - public SynchronizedQueue() { - this.size = -1; - } - - public SynchronizedQueue(int size) { - this.size = size; - } - - @Override - public synchronized boolean isEmpty() { - return list.isEmpty(); - } - - @Override - public synchronized boolean contains(Object o) { - return list.contains(o); - } - - @Override - public synchronized Iterator iterator() { - return list.iterator(); - } - - @Override - public synchronized int size() { - return list.size(); - } - - @Override - public synchronized boolean add(T e) { - return list.add(e); - } - - @Override - public synchronized boolean remove(Object o) { - return list.remove(o); - } - - @Override - public synchronized boolean containsAll(Collection c) { - return list.containsAll(c); - } - - @Override - public synchronized boolean addAll(Collection c) { - return list.addAll(c); - } - - @Override - public synchronized boolean removeAll(Collection c) { - return list.removeAll(c); - } - - @Override - public synchronized boolean retainAll(Collection c) { - return list.retainAll(c); - } - - @Override - public synchronized void clear() { - list.clear(); - } - - @Override - public synchronized String toString() { - return list.toString(); - } - - @Override - public int hashCode() { - return list.hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - SynchronizedQueue other = (SynchronizedQueue) obj; - if (list == null) { - if (other.list != null) - return false; - } else if (!list.equals(other.list)) - return false; - return true; - } - - @Override - public synchronized T peek() { - return list.peek(); - } - - @Override - public synchronized T element() { - return list.element(); - } - - @Override - public synchronized T poll() { - return list.poll(); - } - - @Override - public synchronized T remove() { - return list.remove(); - } - - @Override - public synchronized boolean offer(T e) { - if (size > -1 && list.size() + 1 > size) { - return false; - } - return list.offer(e); - } - - @Override - public synchronized Object clone() { - SynchronizedQueue q = new SynchronizedQueue(size); - q.addAll(list); - return q; - } - - @Override - public synchronized Object[] toArray() { - return list.toArray(); - } - - @Override - public synchronized R[] toArray(R[] a) { - return list.toArray(a); - } - -} diff --git a/src/main/java/rx/internal/util/SynchronizedSubscription.java b/src/main/java/rx/internal/util/SynchronizedSubscription.java deleted file mode 100644 index d8c1c9944f..0000000000 --- a/src/main/java/rx/internal/util/SynchronizedSubscription.java +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Copyright 2014 Netflix, Inc. - * - * 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 rx.internal.util; - -import rx.Subscription; - -public class SynchronizedSubscription implements Subscription { - - private final Subscription s; - - public SynchronizedSubscription(Subscription s) { - this.s = s; - } - - @Override - public synchronized void unsubscribe() { - s.unsubscribe(); - } - - @Override - public synchronized boolean isUnsubscribed() { - return s.isUnsubscribed(); - } - -} diff --git a/src/main/java/rx/internal/util/UtilityFunctions.java b/src/main/java/rx/internal/util/UtilityFunctions.java index 5fe24cb75a..2b8f306a16 100644 --- a/src/main/java/rx/internal/util/UtilityFunctions.java +++ b/src/main/java/rx/internal/util/UtilityFunctions.java @@ -1,34 +1,29 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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 rx.internal.util; -import rx.functions.Func0; import rx.functions.Func1; -import rx.functions.Func2; -import rx.functions.Func3; -import rx.functions.Func4; -import rx.functions.Func5; -import rx.functions.Func6; -import rx.functions.Func7; -import rx.functions.Func8; -import rx.functions.Func9; -import rx.functions.FuncN; /** - * Utility functions for internal use that we don't want part of the public API. + * Utility functions for internal use that we don't want part of the public API. */ public final class UtilityFunctions { + /** Utility class. */ + private UtilityFunctions() { + throw new IllegalStateException("No instances!"); + } + /** * Returns a function that always returns {@code true}. * @@ -55,16 +50,12 @@ public static Func1 alwaysFalse() { * @param the input and output value type * @return a {@link Func1} that accepts an Object and returns the same Object */ + @SuppressWarnings("unchecked") public static Func1 identity() { - return new Func1() { - @Override - public T call(T o) { - return o; - } - }; + return (Func1) Identity.INSTANCE; } - private enum AlwaysTrue implements Func1 { + enum AlwaysTrue implements Func1 { INSTANCE; @Override @@ -73,7 +64,7 @@ public Boolean call(Object o) { } } - private enum AlwaysFalse implements Func1 { + enum AlwaysFalse implements Func1 { INSTANCE; @Override @@ -82,99 +73,12 @@ public Boolean call(Object o) { } } - /** - * Returns a function that merely returns {@code null}, without side effects. - * - * @param the first argument type - * @param the second argument type - * @param the third argument type - * @param the fourth argument type - * @param the fifth argument type - * @param the sixth argument type - * @param the seventh argument type - * @param the eighth argument type - * @param the ninth argument type - * @param the tenth argument type - * @param the result type - * @return a function that returns {@code null} - */ - @SuppressWarnings("unchecked") - public static NullFunction returnNull() { - return NULL_FUNCTION; - } - - @SuppressWarnings("rawtypes") - private static final NullFunction NULL_FUNCTION = new NullFunction(); - - private static final class NullFunction implements - Func0, - Func1, - Func2, - Func3, - Func4, - Func5, - Func6, - Func7, - Func8, - Func9, - FuncN { - NullFunction() { - } - - @Override - public R call() { - return null; - } - - @Override - public R call(T0 t1) { - return null; - } - - @Override - public R call(T0 t1, T1 t2) { - return null; - } - - @Override - public R call(T0 t1, T1 t2, T2 t3) { - return null; - } - - @Override - public R call(T0 t1, T1 t2, T2 t3, T3 t4) { - return null; - } - - @Override - public R call(T0 t1, T1 t2, T2 t3, T3 t4, T4 t5) { - return null; - } - - @Override - public R call(T0 t1, T1 t2, T2 t3, T3 t4, T4 t5, T5 t6) { - return null; - } - - @Override - public R call(T0 t1, T1 t2, T2 t3, T3 t4, T4 t5, T5 t6, T6 t7) { - return null; - } - - @Override - public R call(T0 t1, T1 t2, T2 t3, T3 t4, T4 t5, T5 t6, T6 t7, T7 t8) { - return null; - } - - @Override - public R call(T0 t1, T1 t2, T2 t3, T3 t4, T4 t5, T5 t6, T6 t7, T7 t8, T8 t9) { - return null; - } + enum Identity implements Func1 { + INSTANCE; @Override - public R call(Object... args) { - return null; + public Object call(Object o) { + return o; } } - } diff --git a/src/main/java/rx/internal/util/atomic/AtomicReferenceArrayQueue.java b/src/main/java/rx/internal/util/atomic/AtomicReferenceArrayQueue.java index f7594ba20a..24691ca832 100644 --- a/src/main/java/rx/internal/util/atomic/AtomicReferenceArrayQueue.java +++ b/src/main/java/rx/internal/util/atomic/AtomicReferenceArrayQueue.java @@ -10,7 +10,7 @@ * 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. - * + * * 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/queues/atomic/AtomicReferenceArrayQueue.java */ @@ -36,8 +36,7 @@ public Iterator iterator() { @Override public void clear() { // we have to test isEmpty because of the weaker poll() guarantee - while (poll() != null || !isEmpty()) - ; + while (poll() != null || !isEmpty()) { } // NOPMD } protected final int calcElementOffset(long index, int mask) { return (int)index & mask; diff --git a/src/main/java/rx/internal/util/atomic/BaseLinkedAtomicQueue.java b/src/main/java/rx/internal/util/atomic/BaseLinkedAtomicQueue.java index 3775f550bf..64e5bd9c49 100644 --- a/src/main/java/rx/internal/util/atomic/BaseLinkedAtomicQueue.java +++ b/src/main/java/rx/internal/util/atomic/BaseLinkedAtomicQueue.java @@ -10,7 +10,7 @@ * 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. - * + * * 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/queues/atomic/BaseLinkedAtomicQueue.java */ @@ -42,7 +42,7 @@ protected final LinkedQueueNode xchgProducerNode(LinkedQueueNode node) { protected final LinkedQueueNode lvConsumerNode() { return consumerNode.get(); } - + protected final LinkedQueueNode lpConsumerNode() { return consumerNode.get(); } @@ -59,7 +59,7 @@ public final Iterator iterator() { *

    * IMPLEMENTATION NOTES:
    * This is an O(n) operation as we run through all the nodes and count them.
    - * + * * @see java.util.Queue#size() */ @Override @@ -70,7 +70,7 @@ public final int size() { // must chase the nodes all the way to the producer node, but there's no need to chase a moving target. while (chaserNode != producerNode && size < Integer.MAX_VALUE) { LinkedQueueNode next; - while((next = chaserNode.lvNext()) == null); + while ((next = chaserNode.lvNext()) == null) { } // NOPMD chaserNode = next; size++; } diff --git a/src/main/java/rx/internal/util/atomic/LinkedQueueNode.java b/src/main/java/rx/internal/util/atomic/LinkedQueueNode.java index d687460c64..a4f481147f 100644 --- a/src/main/java/rx/internal/util/atomic/LinkedQueueNode.java +++ b/src/main/java/rx/internal/util/atomic/LinkedQueueNode.java @@ -10,7 +10,7 @@ * 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. - * + * * 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/queues/LinkedQueueNode.java */ @@ -22,15 +22,17 @@ public final class LinkedQueueNode extends AtomicReference /** */ private static final long serialVersionUID = 2404266111789071508L; private E value; - + public LinkedQueueNode() { + // no initial value } + public LinkedQueueNode(E val) { spValue(val); } /** * Gets the current value and nulls out the reference to it from this node. - * + * * @return value */ public E getAndNullValue() { diff --git a/src/main/java/rx/internal/util/atomic/MpscLinkedAtomicQueue.java b/src/main/java/rx/internal/util/atomic/MpscLinkedAtomicQueue.java index 31f54bd5ce..54c4bbd1aa 100644 --- a/src/main/java/rx/internal/util/atomic/MpscLinkedAtomicQueue.java +++ b/src/main/java/rx/internal/util/atomic/MpscLinkedAtomicQueue.java @@ -10,7 +10,7 @@ * 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. - * + * * 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/queues/atomic/MpscLinkedAtomicQueue.java */ @@ -26,9 +26,9 @@ * * The queue is initialized with a stub node which is set to both the producer and consumer node references. From this * point follow the notes on offer/poll. - * + * * @author nitsanw - * + * * @param */ public final class MpscLinkedAtomicQueue extends BaseLinkedAtomicQueue { @@ -51,11 +51,11 @@ public MpscLinkedAtomicQueue() { * * This works because each producer is guaranteed to 'plant' a new node and link the old node. No 2 producers can * get the same producer node as part of XCHG guarantee. - * + * * @see java.util.Queue#offer(java.lang.Object) */ @Override - public final boolean offer(final E nextValue) { + public boolean offer(final E nextValue) { if (nextValue == null) { throw new NullPointerException("null elements not allowed"); } @@ -79,11 +79,11 @@ public final boolean offer(final E nextValue) { * * This means the consumerNode.value is always null, which is also the starting point for the queue. Because null * values are not allowed to be offered this is the only node with it's value set to null at any one time. - * + * * @see java.util.Queue#poll() */ @Override - public final E poll() { + public E poll() { LinkedQueueNode currConsumerNode = lpConsumerNode(); // don't load twice, it's alright LinkedQueueNode nextNode = currConsumerNode.lvNext(); if (nextNode != null) { @@ -94,9 +94,9 @@ public final E poll() { } else if (currConsumerNode != lvProducerNode()) { // spin, we are no longer wait free - while((nextNode = currConsumerNode.lvNext()) == null); + while ((nextNode = currConsumerNode.lvNext()) == null) { } // NOPMD // got the next node... - + // we have to null out the value because we are going to hang on to the node final E nextValue = nextNode.getAndNullValue(); spConsumerNode(nextNode); @@ -106,7 +106,7 @@ else if (currConsumerNode != lvProducerNode()) { } @Override - public final E peek() { + public E peek() { LinkedQueueNode currConsumerNode = lpConsumerNode(); // don't load twice, it's alright LinkedQueueNode nextNode = currConsumerNode.lvNext(); if (nextNode != null) { @@ -114,7 +114,7 @@ public final E peek() { } else if (currConsumerNode != lvProducerNode()) { // spin, we are no longer wait free - while((nextNode = currConsumerNode.lvNext()) == null); + while ((nextNode = currConsumerNode.lvNext()) == null) { } // NOPMD // got the next node... return nextNode.lpValue(); } diff --git a/src/main/java/rx/internal/util/atomic/SpscAtomicArrayQueue.java b/src/main/java/rx/internal/util/atomic/SpscAtomicArrayQueue.java index 6240a5f156..5bf922346c 100644 --- a/src/main/java/rx/internal/util/atomic/SpscAtomicArrayQueue.java +++ b/src/main/java/rx/internal/util/atomic/SpscAtomicArrayQueue.java @@ -10,7 +10,7 @@ * 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. - * + * * 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/queues/atomic/SpscAtomicArrayQueue.java */ @@ -29,13 +29,13 @@ * 2010 - Pisa - SPSC Queues on Shared Cache Multi-Core Systems.pdf
    * 2012 - Junchang- BQueue- Efficient and Practical Queuing.pdf
    *
    This implementation is wait free. - * + * * @param */ public final class SpscAtomicArrayQueue extends AtomicReferenceArrayQueue { private static final Integer MAX_LOOK_AHEAD_STEP = Integer.getInteger("jctools.spsc.max.lookahead.step", 4096); final AtomicLong producerIndex; - protected long producerLookAhead; + long producerLookAhead; final AtomicLong consumerIndex; final int lookAheadStep; public SpscAtomicArrayQueue(int capacity) { @@ -57,10 +57,10 @@ public boolean offer(E e) { final int offset = calcElementOffset(index, mask); if (index >= producerLookAhead) { int step = lookAheadStep; - if (null == lvElement(buffer, calcElementOffset(index + step, mask))) {// LoadLoad + if (null == lvElement(buffer, calcElementOffset(index + step, mask))) { // LoadLoad producerLookAhead = index + step; } - else if (null != lvElement(buffer, offset)){ + else if (null != lvElement(buffer, offset)) { return false; } } @@ -119,7 +119,7 @@ private void soProducerIndex(long newIndex) { private void soConsumerIndex(long newIndex) { consumerIndex.lazySet(newIndex); } - + private long lvConsumerIndex() { return consumerIndex.get(); } diff --git a/src/main/java/rx/internal/util/atomic/SpscExactAtomicArrayQueue.java b/src/main/java/rx/internal/util/atomic/SpscExactAtomicArrayQueue.java index 53f9b719ce..b3f71e264c 100644 --- a/src/main/java/rx/internal/util/atomic/SpscExactAtomicArrayQueue.java +++ b/src/main/java/rx/internal/util/atomic/SpscExactAtomicArrayQueue.java @@ -10,7 +10,7 @@ * 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. - * + * * 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/queues/atomic/SpscAtomicArrayQueue.java */ @@ -35,7 +35,7 @@ public final class SpscExactAtomicArrayQueue extends AtomicReferenceArray final int capacitySkip; final AtomicLong producerIndex; final AtomicLong consumerIndex; - + public SpscExactAtomicArrayQueue(int capacity) { super(Pow2.roundToPowerOfTwo(capacity)); int len = length(); @@ -44,17 +44,17 @@ public SpscExactAtomicArrayQueue(int capacity) { this.producerIndex = new AtomicLong(); this.consumerIndex = new AtomicLong(); } - - + + @Override public boolean offer(T value) { if (value == null) { throw new NullPointerException(); } - + long pi = producerIndex.get(); int m = mask; - + int fullCheck = (int)(pi + capacitySkip) & m; if (get(fullCheck) != null) { return false; @@ -82,13 +82,13 @@ public T peek() { } @Override public void clear() { - while (poll() != null || !isEmpty()); + while (poll() != null || !isEmpty()) { } // NOPMD } @Override public boolean isEmpty() { return producerIndex == consumerIndex; } - + @Override public int size() { long ci = consumerIndex.get(); @@ -161,5 +161,5 @@ public T remove() { public T element() { throw new UnsupportedOperationException(); } - + } diff --git a/src/main/java/rx/internal/util/atomic/SpscLinkedArrayQueue.java b/src/main/java/rx/internal/util/atomic/SpscLinkedArrayQueue.java index 4e4943dfcb..a796e1c373 100644 --- a/src/main/java/rx/internal/util/atomic/SpscLinkedArrayQueue.java +++ b/src/main/java/rx/internal/util/atomic/SpscLinkedArrayQueue.java @@ -23,26 +23,27 @@ /* - * The code was inspired by the similarly named JCTools class: + * The code was inspired by the similarly named JCTools class: * https://github.com/JCTools/JCTools/blob/master/jctools-core/src/main/java/org/jctools/queues/atomic */ /** * A single-producer single-consumer array-backed queue which can allocate new arrays in case the consumer is slower * than the producer. - * + * * @param the element type, not null */ public final class SpscLinkedArrayQueue implements Queue { static final int MAX_LOOK_AHEAD_STEP = Integer.getInteger("jctools.spsc.max.lookahead.step", 4096); - protected final AtomicLong producerIndex; - protected int producerLookAheadStep; - protected long producerLookAhead; - protected int producerMask; - protected AtomicReferenceArray producerBuffer; - protected int consumerMask; - protected AtomicReferenceArray consumerBuffer; - protected final AtomicLong consumerIndex; + final AtomicLong producerIndex; + int producerLookAheadStep; + long producerLookAhead; + int producerMask; + AtomicReferenceArray producerBuffer; + int consumerMask; + AtomicReferenceArray consumerBuffer; + final AtomicLong consumerIndex; + private static final Object HAS_NEXT = new Object(); public SpscLinkedArrayQueue(final int bufferSize) { @@ -65,7 +66,7 @@ public SpscLinkedArrayQueue(final int bufferSize) { * This implementation is correct for single producer thread use only. */ @Override - public final boolean offer(final T e) { + public boolean offer(final T e) { // local load of field to avoid repeated loads after volatile reads final AtomicReferenceArray buffer = producerBuffer; final long index = lpProducerIndex(); @@ -77,7 +78,7 @@ public final boolean offer(final T e) { final int lookAheadStep = producerLookAheadStep; // go around the buffer or resize if full (unless we hit max capacity) int lookAheadElementOffset = calcWrappedOffset(index + lookAheadStep, mask); - if (null == lvElement(buffer, lookAheadElementOffset)) {// LoadLoad + if (null == lvElement(buffer, lookAheadElementOffset)) { // LoadLoad producerLookAhead = index + lookAheadStep - 1; // joy, there's plenty of room return writeToQueue(buffer, e, index, offset); } else if (null == lvElement(buffer, calcWrappedOffset(index + 1, mask))) { // buffer is not full @@ -122,7 +123,7 @@ private AtomicReferenceArray lvNext(AtomicReferenceArray curr) { */ @SuppressWarnings("unchecked") @Override - public final T poll() { + public T poll() { // local load of field to avoid repeated loads after volatile reads final AtomicReferenceArray buffer = consumerBuffer; final long index = lpConsumerIndex(); @@ -162,7 +163,7 @@ private T newBufferPoll(AtomicReferenceArray nextBuffer, final long inde */ @SuppressWarnings("unchecked") @Override - public final T peek() { + public T peek() { final AtomicReferenceArray buffer = consumerBuffer; final long index = lpConsumerIndex(); final int mask = consumerMask; @@ -174,10 +175,10 @@ public final T peek() { return (T) e; } - + @Override public void clear() { - while (poll() != null || !isEmpty()); + while (poll() != null || !isEmpty()) { } // NOPMD } @SuppressWarnings("unchecked") @@ -188,7 +189,7 @@ private T newBufferPeek(AtomicReferenceArray nextBuffer, final long inde } @Override - public final int size() { + public int size() { /* * It is possible for a thread to be interrupted or reschedule between the read of the producer and * consumer indices, therefore protection is required to ensure size is within valid range. In the @@ -205,7 +206,7 @@ public final int size() { } } } - + @Override public boolean isEmpty() { return lvProducerIndex() == lvConsumerIndex(); @@ -254,7 +255,7 @@ private static Object lvElement(AtomicReferenceArray buffer, int off } @Override - public final Iterator iterator() { + public Iterator iterator() { throw new UnsupportedOperationException(); } @@ -312,21 +313,21 @@ public T remove() { public T element() { throw new UnsupportedOperationException(); } - + /** - * Offer two elements at the same time. + * Atomically offer two elements. *

    Don't use the regular offer() with this at all! - * @param first - * @param second + * @param first the first value + * @param second the second value * @return always true */ public boolean offer(T first, T second) { final AtomicReferenceArray buffer = producerBuffer; final long p = lvProducerIndex(); final int m = producerMask; - + int pi = calcWrappedOffset(p + 2, m); - + if (null == lvElement(buffer, pi)) { pi = calcWrappedOffset(p, m); soElement(buffer, pi + 1, second); @@ -336,14 +337,14 @@ public boolean offer(T first, T second) { final int capacity = buffer.length(); final AtomicReferenceArray newBuffer = new AtomicReferenceArray(capacity); producerBuffer = newBuffer; - + pi = calcWrappedOffset(p, m); 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 } diff --git a/src/main/java/rx/internal/util/atomic/SpscLinkedAtomicQueue.java b/src/main/java/rx/internal/util/atomic/SpscLinkedAtomicQueue.java index 9b73da962a..a2de19bf60 100644 --- a/src/main/java/rx/internal/util/atomic/SpscLinkedAtomicQueue.java +++ b/src/main/java/rx/internal/util/atomic/SpscLinkedAtomicQueue.java @@ -10,7 +10,7 @@ * 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. - * + * * 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/queues/atomic/SpscLinkedAtomicQueue.java */ @@ -26,9 +26,9 @@ * * The queue is initialized with a stub node which is set to both the producer and consumer node references. From this * point follow the notes on offer/poll. - * + * * @author nitsanw - * + * * @param */ public final class SpscLinkedAtomicQueue extends BaseLinkedAtomicQueue { @@ -43,7 +43,7 @@ public SpscLinkedAtomicQueue() { /** * {@inheritDoc}
    - * + * * IMPLEMENTATION NOTES:
    * Offer is allowed from a SINGLE thread.
    * Offer allocates a new node (holding the offered value) and: @@ -52,7 +52,7 @@ public SpscLinkedAtomicQueue() { *
  • Sets the new node as the producerNode * * From this follows that producerNode.next is always null and for all other nodes node.next is not null. - * + * * @see java.util.Queue#offer(java.lang.Object) */ @Override @@ -68,7 +68,7 @@ public boolean offer(final E nextValue) { /** * {@inheritDoc}
    - * + * * IMPLEMENTATION NOTES:
    * Poll is allowed from a SINGLE thread.
    * Poll reads the next node from the consumerNode and: @@ -78,7 +78,7 @@ public boolean offer(final E nextValue) { * * This means the consumerNode.value is always null, which is also the starting point for the queue. Because null * values are not allowed to be offered this is the only node with it's value set to null at any one time. - * + * */ @Override public E poll() { diff --git a/src/main/java/rx/internal/util/atomic/SpscUnboundedAtomicArrayQueue.java b/src/main/java/rx/internal/util/atomic/SpscUnboundedAtomicArrayQueue.java index 728591794e..87bf5afc9a 100644 --- a/src/main/java/rx/internal/util/atomic/SpscUnboundedAtomicArrayQueue.java +++ b/src/main/java/rx/internal/util/atomic/SpscUnboundedAtomicArrayQueue.java @@ -10,7 +10,7 @@ * 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. - * + * * 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/queues/atomic/SpscUnboundedAtomicArrayQueue.java */ @@ -33,12 +33,12 @@ public final class SpscUnboundedAtomicArrayQueue implements Queue { static final int MAX_LOOK_AHEAD_STEP = Integer.getInteger("jctools.spsc.max.lookahead.step", 4096); final AtomicLong producerIndex; - protected int producerLookAheadStep; - protected long producerLookAhead; - protected int producerMask; - protected AtomicReferenceArray producerBuffer; - protected int consumerMask; - protected AtomicReferenceArray consumerBuffer; + int producerLookAheadStep; + long producerLookAhead; + int producerMask; + AtomicReferenceArray producerBuffer; + int consumerMask; + AtomicReferenceArray consumerBuffer; final AtomicLong consumerIndex; private static final Object HAS_NEXT = new Object(); @@ -63,7 +63,7 @@ public SpscUnboundedAtomicArrayQueue(final int bufferSize) { * This implementation is correct for single producer thread use only. */ @Override - public final boolean offer(final T e) { + public boolean offer(final T e) { if (e == null) { throw new NullPointerException(); } @@ -78,7 +78,7 @@ public final boolean offer(final T e) { final int lookAheadStep = producerLookAheadStep; // go around the buffer or resize if full (unless we hit max capacity) int lookAheadElementOffset = calcWrappedOffset(index + lookAheadStep, mask); - if (null == lvElement(buffer, lookAheadElementOffset)) {// LoadLoad + if (null == lvElement(buffer, lookAheadElementOffset)) { // LoadLoad producerLookAhead = index + lookAheadStep - 1; // joy, there's plenty of room return writeToQueue(buffer, e, index, offset); } else if (null != lvElement(buffer, calcWrappedOffset(index + 1, mask))) { // buffer is not full @@ -123,7 +123,7 @@ private AtomicReferenceArray lvNext(AtomicReferenceArray curr) { */ @SuppressWarnings("unchecked") @Override - public final T poll() { + public T poll() { // local load of field to avoid repeated loads after volatile reads final AtomicReferenceArray buffer = consumerBuffer; final long index = lpConsumerIndex(); @@ -163,7 +163,7 @@ private T newBufferPoll(AtomicReferenceArray nextBuffer, final long inde */ @SuppressWarnings("unchecked") @Override - public final T peek() { + public T peek() { final AtomicReferenceArray buffer = consumerBuffer; final long index = lpConsumerIndex(); final int mask = consumerMask; @@ -175,10 +175,10 @@ public final T peek() { return (T) e; } - + @Override public void clear() { - while (poll() != null || !isEmpty()); + while (poll() != null || !isEmpty()) { } // NOPMD } @SuppressWarnings("unchecked") @@ -189,7 +189,7 @@ private T newBufferPeek(AtomicReferenceArray nextBuffer, final long inde } @Override - public final int size() { + public int size() { /* * It is possible for a thread to be interrupted or reschedule between the read of the producer and * consumer indices, therefore protection is required to ensure size is within valid range. In the @@ -206,7 +206,7 @@ public final int size() { } } } - + @Override public boolean isEmpty() { return lvProducerIndex() == lvConsumerIndex(); @@ -255,7 +255,7 @@ private static Object lvElement(AtomicReferenceArray buffer, int off } @Override - public final Iterator iterator() { + public Iterator iterator() { throw new UnsupportedOperationException(); } diff --git a/src/main/java/rx/internal/util/unsafe/BaseLinkedQueue.java b/src/main/java/rx/internal/util/unsafe/BaseLinkedQueue.java index 05bcb798fb..cdc5ed93d3 100644 --- a/src/main/java/rx/internal/util/unsafe/BaseLinkedQueue.java +++ b/src/main/java/rx/internal/util/unsafe/BaseLinkedQueue.java @@ -10,7 +10,7 @@ * 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. - * + * * 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/queues/atomic/BaseLinkedQueue.java */ @@ -20,6 +20,7 @@ import java.util.*; +import rx.internal.util.SuppressAnimalSniffer; import rx.internal.util.atomic.LinkedQueueNode; abstract class BaseLinkedQueuePad0 extends AbstractQueue { @@ -27,6 +28,7 @@ abstract class BaseLinkedQueuePad0 extends AbstractQueue { long p30, p31, p32, p33, p34, p35, p36, p37; } +@SuppressAnimalSniffer abstract class BaseLinkedQueueProducerNodeRef extends BaseLinkedQueuePad0 { protected final static long P_NODE_OFFSET = UnsafeAccess.addressOf(BaseLinkedQueueProducerNodeRef.class, "producerNode"); @@ -34,12 +36,12 @@ abstract class BaseLinkedQueueProducerNodeRef extends BaseLinkedQueuePad0 protected final void spProducerNode(LinkedQueueNode node) { producerNode = node; } - + @SuppressWarnings("unchecked") protected final LinkedQueueNode lvProducerNode() { return (LinkedQueueNode) UNSAFE.getObjectVolatile(this, P_NODE_OFFSET); } - + protected final LinkedQueueNode lpProducerNode() { return producerNode; } @@ -50,18 +52,19 @@ abstract class BaseLinkedQueuePad1 extends BaseLinkedQueueProducerNodeRef long p30, p31, p32, p33, p34, p35, p36, p37; } +@SuppressAnimalSniffer abstract class BaseLinkedQueueConsumerNodeRef extends BaseLinkedQueuePad1 { protected final static long C_NODE_OFFSET = UnsafeAccess.addressOf(BaseLinkedQueueConsumerNodeRef.class, "consumerNode"); protected LinkedQueueNode consumerNode; protected final void spConsumerNode(LinkedQueueNode node) { consumerNode = node; } - + @SuppressWarnings("unchecked") protected final LinkedQueueNode lvConsumerNode() { return (LinkedQueueNode) UNSAFE.getObjectVolatile(this, C_NODE_OFFSET); } - + protected final LinkedQueueNode lpConsumerNode() { return consumerNode; } @@ -69,11 +72,12 @@ protected final LinkedQueueNode lpConsumerNode() { /** * A base data structure for concurrent linked queues. - * + * * @author nitsanw - * - * @param + * + * @param the element type */ +@SuppressAnimalSniffer abstract class BaseLinkedQueue extends BaseLinkedQueueConsumerNodeRef { long p00, p01, p02, p03, p04, p05, p06, p07; long p30, p31, p32, p33, p34, p35, p36, p37; @@ -89,7 +93,7 @@ public final Iterator iterator() { *

    * IMPLEMENTATION NOTES:
    * This is an O(n) operation as we run through all the nodes and count them.
    - * + * * @see java.util.Queue#size() */ @Override @@ -102,13 +106,13 @@ public final int size() { // must chase the nodes all the way to the producer node, but there's no need to chase a moving target. while (chaserNode != producerNode && size < Integer.MAX_VALUE) { LinkedQueueNode next; - while((next = chaserNode.lvNext()) == null); + while ((next = chaserNode.lvNext()) == null) { } // NOPMD chaserNode = next; size++; } return size; } - + /** * {@inheritDoc}
    *

    @@ -116,7 +120,7 @@ public final int size() { * Queue is empty when producerNode is the same as consumerNode. An alternative implementation would be to observe * the producerNode.value is null, which also means an empty queue because only the consumerNode.value is allowed to * be null. - * + * * @see MessagePassingQueue#isEmpty() */ @Override diff --git a/src/main/java/rx/internal/util/unsafe/ConcurrentCircularArrayQueue.java b/src/main/java/rx/internal/util/unsafe/ConcurrentCircularArrayQueue.java index a8b9990b56..08456f0b0e 100644 --- a/src/main/java/rx/internal/util/unsafe/ConcurrentCircularArrayQueue.java +++ b/src/main/java/rx/internal/util/unsafe/ConcurrentCircularArrayQueue.java @@ -10,7 +10,7 @@ * 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. - * + * * 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/queues/ConcurrentCircularArrayQueue.java */ @@ -18,8 +18,9 @@ import static rx.internal.util.unsafe.UnsafeAccess.UNSAFE; -import java.util.AbstractQueue; -import java.util.Iterator; +import java.util.*; + +import rx.internal.util.SuppressAnimalSniffer; abstract class ConcurrentCircularArrayQueueL0Pad extends AbstractQueue implements MessagePassingQueue { long p00, p01, p02, p03, p04, p05, p06, p07; @@ -37,11 +38,12 @@ abstract class ConcurrentCircularArrayQueueL0Pad extends AbstractQueue imp * Load/Store methods using a buffer parameter are provided to allow the prevention of final field reload after a * LoadLoad barrier. *

    - * + * * @author nitsanw - * - * @param + * + * @param the element type */ +@SuppressAnimalSniffer public abstract class ConcurrentCircularArrayQueue extends ConcurrentCircularArrayQueueL0Pad { protected static final int SPARSE_SHIFT = Integer.getInteger("sparse.shift", 0); protected static final int BUFFER_PAD = 32; @@ -81,7 +83,7 @@ protected final long calcElementOffset(long index) { } /** * @param index desirable element index - * @param mask + * @param mask the binary mask to make the index wrap around * @return the offset in bytes within the array for a given index. */ protected final long calcElementOffset(long index, long mask) { @@ -89,7 +91,7 @@ protected final long calcElementOffset(long index, long mask) { } /** * A plain store (no ordering/fences) of an element to a given offset - * + * * @param offset computed via {@link ConcurrentCircularArrayQueue#calcElementOffset(long)} * @param e a kitty */ @@ -99,7 +101,7 @@ protected final void spElement(long offset, E e) { /** * A plain store (no ordering/fences) of an element to a given offset - * + * * @param buffer this.buffer * @param offset computed via {@link ConcurrentCircularArrayQueue#calcElementOffset(long)} * @param e an orderly kitty @@ -110,7 +112,7 @@ protected final void spElement(E[] buffer, long offset, E e) { /** * An ordered store(store + StoreStore barrier) of an element to a given offset - * + * * @param offset computed via {@link ConcurrentCircularArrayQueue#calcElementOffset(long)} * @param e an orderly kitty */ @@ -120,7 +122,7 @@ protected final void soElement(long offset, E e) { /** * An ordered store(store + StoreStore barrier) of an element to a given offset - * + * * @param buffer this.buffer * @param offset computed via {@link ConcurrentCircularArrayQueue#calcElementOffset(long)} * @param e an orderly kitty @@ -131,7 +133,7 @@ protected final void soElement(E[] buffer, long offset, E e) { /** * A plain load (no ordering/fences) of an element from a given offset. - * + * * @param offset computed via {@link ConcurrentCircularArrayQueue#calcElementOffset(long)} * @return the element at the offset */ @@ -141,7 +143,7 @@ protected final E lpElement(long offset) { /** * A plain load (no ordering/fences) of an element from a given offset. - * + * * @param buffer this.buffer * @param offset computed via {@link ConcurrentCircularArrayQueue#calcElementOffset(long)} * @return the element at the offset @@ -153,7 +155,7 @@ protected final E lpElement(E[] buffer, long offset) { /** * A volatile load (load + LoadLoad barrier) of an element from a given offset. - * + * * @param offset computed via {@link ConcurrentCircularArrayQueue#calcElementOffset(long)} * @return the element at the offset */ @@ -163,7 +165,7 @@ protected final E lvElement(long offset) { /** * A volatile load (load + LoadLoad barrier) of an element from a given offset. - * + * * @param buffer this.buffer * @param offset computed via {@link ConcurrentCircularArrayQueue#calcElementOffset(long)} * @return the element at the offset @@ -180,7 +182,6 @@ public Iterator iterator() { @Override public void clear() { // we have to test isEmpty because of the weaker poll() guarantee - while (poll() != null || !isEmpty()) - ; + while (poll() != null || !isEmpty()) { } // NOPMD } } diff --git a/src/main/java/rx/internal/util/unsafe/ConcurrentSequencedCircularArrayQueue.java b/src/main/java/rx/internal/util/unsafe/ConcurrentSequencedCircularArrayQueue.java index eaf824e95e..79c605a76a 100644 --- a/src/main/java/rx/internal/util/unsafe/ConcurrentSequencedCircularArrayQueue.java +++ b/src/main/java/rx/internal/util/unsafe/ConcurrentSequencedCircularArrayQueue.java @@ -10,7 +10,7 @@ * 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. - * + * * 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/queues/ConcurrentSequencedCircularArrayQueue.java */ @@ -18,6 +18,9 @@ import static rx.internal.util.unsafe.UnsafeAccess.UNSAFE; +import rx.internal.util.SuppressAnimalSniffer; + +@SuppressAnimalSniffer public abstract class ConcurrentSequencedCircularArrayQueue extends ConcurrentCircularArrayQueue { private static final long ARRAY_BASE; private static final int ELEMENT_SHIFT; diff --git a/src/main/java/rx/internal/util/unsafe/MessagePassingQueue.java b/src/main/java/rx/internal/util/unsafe/MessagePassingQueue.java index f8a87ffec5..8d9426efb6 100644 --- a/src/main/java/rx/internal/util/unsafe/MessagePassingQueue.java +++ b/src/main/java/rx/internal/util/unsafe/MessagePassingQueue.java @@ -2,15 +2,15 @@ * 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. - * + * * 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/queues/MessagePassingQueue.java */ @@ -24,18 +24,18 @@ * Message passing queues offer happens before semantics to messages passed through, namely that writes made by the * producer before offering the message are visible to the consuming thread after the message has been polled out of the * queue. - * + * * @author nitsanw - * + * * @param the event/message type */ public interface MessagePassingQueue { - + /** * Called from a producer thread subject to the restrictions appropriate to the implementation and according to the * {@link Queue#offer(Object)} interface. - * - * @param message + * + * @param message the Object to enqueue, not null * @return true if element was inserted into the queue, false iff full */ boolean offer(M message); @@ -43,7 +43,7 @@ public interface MessagePassingQueue { /** * Called from the consumer thread subject to the restrictions appropriate to the implementation and according to * the {@link Queue#poll()} interface. - * + * * @return a message from the queue if one is available, null iff empty */ M poll(); @@ -51,7 +51,7 @@ public interface MessagePassingQueue { /** * Called from the consumer thread subject to the restrictions appropriate to the implementation and according to * the {@link Queue#peek()} interface. - * + * * @return a message from the queue if one is available, null iff empty */ M peek(); @@ -59,14 +59,14 @@ public interface MessagePassingQueue { /** * This method's accuracy is subject to concurrent modifications happening as the size is estimated and as such is a * best effort rather than absolute value. For some implementations this method may be O(n) rather than O(1). - * + * * @return number of messages in the queue, between 0 and queue capacity or {@link Integer#MAX_VALUE} if not bounded */ int size(); - + /** * This method's accuracy is subject to concurrent modifications happening as the observation is carried out. - * + * * @return true if empty, false otherwise */ boolean isEmpty(); diff --git a/src/main/java/rx/internal/util/unsafe/MpmcArrayQueue.java b/src/main/java/rx/internal/util/unsafe/MpmcArrayQueue.java index a75c2a0028..a69c29d923 100644 --- a/src/main/java/rx/internal/util/unsafe/MpmcArrayQueue.java +++ b/src/main/java/rx/internal/util/unsafe/MpmcArrayQueue.java @@ -10,7 +10,7 @@ * 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. - * + * * 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/queues/MpmcArrayQueue.java */ @@ -18,6 +18,8 @@ import static rx.internal.util.unsafe.UnsafeAccess.UNSAFE; +import rx.internal.util.SuppressAnimalSniffer; + abstract class MpmcArrayQueueL1Pad extends ConcurrentSequencedCircularArrayQueue { long p10, p11, p12, p13, p14, p15, p16; long p30, p31, p32, p33, p34, p35, p36, p37; @@ -27,6 +29,7 @@ public MpmcArrayQueueL1Pad(int capacity) { } } +@SuppressAnimalSniffer abstract class MpmcArrayQueueProducerField extends MpmcArrayQueueL1Pad { private final static long P_INDEX_OFFSET = UnsafeAccess.addressOf(MpmcArrayQueueProducerField.class, "producerIndex"); private volatile long producerIndex; @@ -53,6 +56,7 @@ public MpmcArrayQueueL2Pad(int capacity) { } } +@SuppressAnimalSniffer abstract class MpmcArrayQueueConsumerField extends MpmcArrayQueueL2Pad { private final static long C_INDEX_OFFSET = UnsafeAccess.addressOf(MpmcArrayQueueConsumerField.class, "consumerIndex"); private volatile long consumerIndex; @@ -79,9 +83,9 @@ protected final boolean casConsumerIndex(long expect, long newValue) { * algorithm uses an array of structs which should offer nice locality properties but is sadly not possible in * Java (waiting on Value Types or similar). The alternative explored here utilizes 2 arrays, one for each * field of the struct. There is a further alternative in the experimental project which uses iteration phase - * markers to achieve the same algo and is closer structurally to the original, but sadly does not perform as + * markers to achieve the same algorithm and is closer structurally to the original, but sadly does not perform as * well as this implementation.
    - * Tradeoffs to keep in mind: + * Trade-offs to keep in mind: *

      *
    1. Padding for false sharing: counter fields and queue fields are all padded as well as either side of * both arrays. We are trading memory to avoid false sharing(active and passive). @@ -90,10 +94,11 @@ protected final boolean casConsumerIndex(long expect, long newValue) { *
    2. Power of 2 capacity: Actual elements buffer (and sequence buffer) is the closest power of 2 larger or * equal to the requested capacity. *
    - * + * * @param * type of the element stored in the {@link java.util.Queue} */ +@SuppressAnimalSniffer public class MpmcArrayQueue extends MpmcArrayQueueConsumerField { long p40, p41, p42, p43, p44, p45, p46; long p30, p31, p32, p33, p34, p35, p36, p37; diff --git a/src/main/java/rx/internal/util/unsafe/MpscLinkedQueue.java b/src/main/java/rx/internal/util/unsafe/MpscLinkedQueue.java index 2607c3a023..b69a5a0daa 100644 --- a/src/main/java/rx/internal/util/unsafe/MpscLinkedQueue.java +++ b/src/main/java/rx/internal/util/unsafe/MpscLinkedQueue.java @@ -10,13 +10,15 @@ * 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. - * + * * 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/queues/MpscLinkedQueue.java */ package rx.internal.util.unsafe; import static rx.internal.util.unsafe.UnsafeAccess.UNSAFE; + +import rx.internal.util.SuppressAnimalSniffer; import rx.internal.util.atomic.LinkedQueueNode; /** * This is a direct Java port of the MPSC algorithm as presented *
  • Use inheritance to ensure no false sharing occurs between producer/consumer node reference fields. - *
  • Use XCHG functionality to the best of the JDK ability (see differences in JDK7/8 impls). + *
  • Use XCHG functionality to the best of the JDK ability (see differences in JDK7/8 implementations). * * The queue is initialized with a stub node which is set to both the producer and consumer node references. From this * point follow the notes on offer/poll. - * + * * @author nitsanw - * + * * @param */ +@SuppressAnimalSniffer public final class MpscLinkedQueue extends BaseLinkedQueue { - + public MpscLinkedQueue() { consumerNode = new LinkedQueueNode(); xchgProducerNode(consumerNode);// this ensures correct construction: StoreLoad } - + @SuppressWarnings("unchecked") - protected final LinkedQueueNode xchgProducerNode(LinkedQueueNode newVal) { + protected LinkedQueueNode xchgProducerNode(LinkedQueueNode newVal) { Object oldVal; do { oldVal = producerNode; - } while(!UNSAFE.compareAndSwapObject(this, P_NODE_OFFSET, oldVal, newVal)); + } while (!UNSAFE.compareAndSwapObject(this, P_NODE_OFFSET, oldVal, newVal)); return (LinkedQueueNode) oldVal; } - + /** * {@inheritDoc}
    *

    @@ -62,12 +65,12 @@ protected final LinkedQueueNode xchgProducerNode(LinkedQueueNode newVal) { * * This works because each producer is guaranteed to 'plant' a new node and link the old node. No 2 producers can * get the same producer node as part of XCHG guarantee. - * + * * @see MessagePassingQueue#offer(Object) * @see java.util.Queue#offer(java.lang.Object) */ @Override - public final boolean offer(final E nextValue) { + public boolean offer(final E nextValue) { if (nextValue == null) { throw new NullPointerException("null elements not allowed"); } @@ -91,12 +94,12 @@ public final boolean offer(final E nextValue) { * * This means the consumerNode.value is always null, which is also the starting point for the queue. Because null * values are not allowed to be offered this is the only node with it's value set to null at any one time. - * + * * @see MessagePassingQueue#poll() * @see java.util.Queue#poll() */ @Override - public final E poll() { + public E poll() { LinkedQueueNode currConsumerNode = lpConsumerNode(); // don't load twice, it's alright LinkedQueueNode nextNode = currConsumerNode.lvNext(); if (nextNode != null) { @@ -107,9 +110,9 @@ public final E poll() { } else if (currConsumerNode != lvProducerNode()) { // spin, we are no longer wait free - while((nextNode = currConsumerNode.lvNext()) == null); + while ((nextNode = currConsumerNode.lvNext()) == null) { } // NOPMD // got the next node... - + // we have to null out the value because we are going to hang on to the node final E nextValue = nextNode.getAndNullValue(); consumerNode = nextNode; @@ -119,7 +122,7 @@ else if (currConsumerNode != lvProducerNode()) { } @Override - public final E peek() { + public E peek() { LinkedQueueNode currConsumerNode = consumerNode; // don't load twice, it's alright LinkedQueueNode nextNode = currConsumerNode.lvNext(); if (nextNode != null) { @@ -127,7 +130,7 @@ public final E peek() { } else if (currConsumerNode != lvProducerNode()) { // spin, we are no longer wait free - while((nextNode = currConsumerNode.lvNext()) == null); + while ((nextNode = currConsumerNode.lvNext()) == null) { } // NOPMD // got the next node... return nextNode.lpValue(); } diff --git a/src/main/java/rx/internal/util/unsafe/Pow2.java b/src/main/java/rx/internal/util/unsafe/Pow2.java index cbbd9c9c87..a71a3c2542 100644 --- a/src/main/java/rx/internal/util/unsafe/Pow2.java +++ b/src/main/java/rx/internal/util/unsafe/Pow2.java @@ -10,7 +10,7 @@ * 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. - * + * * 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 */ @@ -41,4 +41,4 @@ public static int roundToPowerOfTwo(final int value) { public static boolean isPowerOfTwo(final int value) { return (value & (value - 1)) == 0; } -} \ No newline at end of file +} diff --git a/src/main/java/rx/internal/util/unsafe/QueueProgressIndicators.java b/src/main/java/rx/internal/util/unsafe/QueueProgressIndicators.java index 185f0bd612..a81c19fccc 100644 --- a/src/main/java/rx/internal/util/unsafe/QueueProgressIndicators.java +++ b/src/main/java/rx/internal/util/unsafe/QueueProgressIndicators.java @@ -2,15 +2,15 @@ * 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. - * + * * 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/queues/QueueProgressIndicators.java */ @@ -20,7 +20,7 @@ * This interface is provided for monitoring purposes only and is only available on queues where it is easy to * provide it. The producer/consumer progress indicators usually correspond with the number of elements * offered/polled, but they are not guaranteed to maintain that semantic. - * + * * @author nitsanw * */ @@ -34,10 +34,10 @@ public interface QueueProgressIndicators { * This value will normally indicate number of elements passed into the queue, but may under some * circumstances be a derivative of that figure. This method should not be used to derive size or * emptiness. - * + * * @return the current value of the producer progress index */ - public long currentProducerIndex(); + long currentProducerIndex(); /** * This method has no concurrent visibility semantics. The value returned may be negative. Under normal @@ -47,8 +47,8 @@ public interface QueueProgressIndicators { * This value will normally indicate number of elements taken out of the queue, but may under some * circumstances be a derivative of that figure. This method should not be used to derive size or * emptiness. - * + * * @return the current value of the consumer progress index */ - public long currentConsumerIndex(); + long currentConsumerIndex(); } \ No newline at end of file diff --git a/src/main/java/rx/internal/util/unsafe/README.md b/src/main/java/rx/internal/util/unsafe/README.md index 1bff119cd7..c847ea77e5 100644 --- a/src/main/java/rx/internal/util/unsafe/README.md +++ b/src/main/java/rx/internal/util/unsafe/README.md @@ -2,7 +2,7 @@ This package contains code that relies on sun.misc.Unsafe. Before using it you M Much of the code in this package comes from or is inspired by the JCTools project: https://github.com/JCTools/JCTools -JCTools has now published artifacts (https://github.com/JCTools/JCTools/issues/17) so RxJava could add JCTools as a "shadow" dependency (https://github.com/ReactiveX/RxJava/issues/1735). +JCTools has now published artifacts (https://github.com/JCTools/JCTools/issues/17) so RxJava could add JCTools as a "shadow" dependency (https://github.com/ReactiveX/RxJava/issues/1735). RxJava has a "zero dependency" policy for the core library, so if we do add it as a dependency, it won't be an externally visible dependency that results in a separate jar. The license for the JCTools code is https://github.com/JCTools/JCTools/blob/master/LICENSE diff --git a/src/main/java/rx/internal/util/unsafe/SpmcArrayQueue.java b/src/main/java/rx/internal/util/unsafe/SpmcArrayQueue.java index 8a4251872d..043511cac8 100644 --- a/src/main/java/rx/internal/util/unsafe/SpmcArrayQueue.java +++ b/src/main/java/rx/internal/util/unsafe/SpmcArrayQueue.java @@ -2,15 +2,15 @@ * 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. - * + * * 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/queues/SpmcArrayQueue.java */ @@ -18,6 +18,8 @@ import static rx.internal.util.unsafe.UnsafeAccess.UNSAFE; +import rx.internal.util.SuppressAnimalSniffer; + abstract class SpmcArrayQueueL1Pad extends ConcurrentCircularArrayQueue { long p10, p11, p12, p13, p14, p15, p16; long p30, p31, p32, p33, p34, p35, p36, p37; @@ -27,6 +29,7 @@ public SpmcArrayQueueL1Pad(int capacity) { } } +@SuppressAnimalSniffer abstract class SpmcArrayQueueProducerField extends SpmcArrayQueueL1Pad { protected final static long P_INDEX_OFFSET = UnsafeAccess.addressOf(SpmcArrayQueueProducerField.class, "producerIndex"); private volatile long producerIndex; @@ -53,6 +56,7 @@ public SpmcArrayQueueL2Pad(int capacity) { } } +@SuppressAnimalSniffer abstract class SpmcArrayQueueConsumerField extends SpmcArrayQueueL2Pad { protected final static long C_INDEX_OFFSET = UnsafeAccess.addressOf(SpmcArrayQueueConsumerField.class, "consumerIndex"); private volatile long consumerIndex; @@ -79,6 +83,7 @@ public SpmcArrayQueueMidPad(int capacity) { } } +@SuppressAnimalSniffer abstract class SpmcArrayQueueProducerIndexCacheField extends SpmcArrayQueueMidPad { // This is separated from the consumerIndex which will be highly contended in the hope that this value spends most // of it's time in a cache line that is Shared(and rarely invalidated) @@ -106,6 +111,7 @@ public SpmcArrayQueueL3Pad(int capacity) { } } +@SuppressAnimalSniffer public final class SpmcArrayQueue extends SpmcArrayQueueL3Pad { public SpmcArrayQueue(final int capacity) { @@ -123,13 +129,13 @@ public boolean offer(final E e) { final long offset = calcElementOffset(currProducerIndex); if (null != lvElement(lb, offset)) { long size = currProducerIndex - lvConsumerIndex(); - - if(size > lMask) { + + if (size > lMask) { return false; } else { // spin wait for slot to clear, buggers wait freedom - while(null != lvElement(lb, offset)); + while (null != lvElement(lb, offset)) { } // NOPMD } } spElement(lb, offset, e); @@ -201,10 +207,10 @@ public int size() { } } } - + @Override public boolean isEmpty() { - // Order matters! + // Order matters! // Loading consumer before producer allows for producer increments after consumer index is read. // This ensures the correctness of this method at least for the consumer thread. Other threads POV is not really // something we can fix here. diff --git a/src/main/java/rx/internal/util/unsafe/SpscArrayQueue.java b/src/main/java/rx/internal/util/unsafe/SpscArrayQueue.java index ae9a5b771a..f5bab6f124 100644 --- a/src/main/java/rx/internal/util/unsafe/SpscArrayQueue.java +++ b/src/main/java/rx/internal/util/unsafe/SpscArrayQueue.java @@ -2,15 +2,15 @@ * 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. - * + * * 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/queues/SpscArrayQueue.java */ @@ -18,12 +18,14 @@ import static rx.internal.util.unsafe.UnsafeAccess.UNSAFE; +import rx.internal.util.SuppressAnimalSniffer; + abstract class SpscArrayQueueColdField extends ConcurrentCircularArrayQueue { private static final Integer MAX_LOOK_AHEAD_STEP = Integer.getInteger("jctools.spsc.max.lookahead.step", 4096); protected final int lookAheadStep; public SpscArrayQueueColdField(int capacity) { super(capacity); - lookAheadStep = Math.min(capacity/4, MAX_LOOK_AHEAD_STEP); + lookAheadStep = Math.min(capacity / 4, MAX_LOOK_AHEAD_STEP); } } abstract class SpscArrayQueueL1Pad extends SpscArrayQueueColdField { @@ -35,6 +37,7 @@ public SpscArrayQueueL1Pad(int capacity) { } } +@SuppressAnimalSniffer abstract class SpscArrayQueueProducerFields extends SpscArrayQueueL1Pad { protected final static long P_INDEX_OFFSET = UnsafeAccess.addressOf(SpscArrayQueueProducerFields.class, "producerIndex"); protected long producerIndex; @@ -54,6 +57,7 @@ public SpscArrayQueueL2Pad(int capacity) { } } +@SuppressAnimalSniffer abstract class SpscArrayQueueConsumerField extends SpscArrayQueueL2Pad { protected long consumerIndex; protected final static long C_INDEX_OFFSET = UnsafeAccess.addressOf(SpscArrayQueueConsumerField.class, "consumerIndex"); @@ -82,11 +86,12 @@ public SpscArrayQueueL3Pad(int capacity) { * 2010 - Pisa - SPSC Queues on Shared Cache Multi-Core Systems.pdf
    * 2012 - Junchang- BQueue- Efficient and Practical Queuing.pdf
    *
    This implementation is wait free. - * + * * @author nitsanw - * + * * @param */ +@SuppressAnimalSniffer public final class SpscArrayQueue extends SpscArrayQueueL3Pad { public SpscArrayQueue(final int capacity) { @@ -107,14 +112,14 @@ public boolean offer(final E e) { final E[] lElementBuffer = buffer; final long index = producerIndex; final long offset = calcElementOffset(index); - if (null != lvElement(lElementBuffer, offset)){ + if (null != lvElement(lElementBuffer, offset)) { return false; } soElement(lElementBuffer, offset, e); // StoreStore soProducerIndex(index + 1); // ordered store -> atomic and ordered for size() return true; } - + /** * {@inheritDoc} *

    @@ -162,7 +167,7 @@ public int size() { } } } - + @Override public boolean isEmpty() { return lvProducerIndex() == lvConsumerIndex(); @@ -175,11 +180,11 @@ private void soProducerIndex(long v) { private void soConsumerIndex(long v) { UNSAFE.putOrderedLong(this, C_INDEX_OFFSET, v); } - + private long lvProducerIndex() { return UNSAFE.getLongVolatile(this, P_INDEX_OFFSET); } - + private long lvConsumerIndex() { return UNSAFE.getLongVolatile(this, C_INDEX_OFFSET); } diff --git a/src/main/java/rx/internal/util/unsafe/SpscLinkedQueue.java b/src/main/java/rx/internal/util/unsafe/SpscLinkedQueue.java index b9a037a986..2a391f5e6b 100644 --- a/src/main/java/rx/internal/util/unsafe/SpscLinkedQueue.java +++ b/src/main/java/rx/internal/util/unsafe/SpscLinkedQueue.java @@ -10,7 +10,7 @@ * 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. - * + * * 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/queues/SpscLinkedQueue.java */ @@ -31,9 +31,9 @@ * * The queue is initialized with a stub node which is set to both the producer and consumer node references. From this * point follow the notes on offer/poll. - * + * * @author nitsanw - * + * * @param */ public final class SpscLinkedQueue extends BaseLinkedQueue { @@ -46,7 +46,7 @@ public SpscLinkedQueue() { /** * {@inheritDoc}
    - * + * * IMPLEMENTATION NOTES:
    * Offer is allowed from a SINGLE thread.
    * Offer allocates a new node (holding the offered value) and: @@ -55,7 +55,7 @@ public SpscLinkedQueue() { *

  • Sets the new node as the producerNode * * From this follows that producerNode.next is always null and for all other nodes node.next is not null. - * + * * @see MessagePassingQueue#offer(Object) * @see java.util.Queue#offer(java.lang.Object) */ @@ -72,7 +72,7 @@ public boolean offer(final E nextValue) { /** * {@inheritDoc}
    - * + * * IMPLEMENTATION NOTES:
    * Poll is allowed from a SINGLE thread.
    * Poll reads the next node from the consumerNode and: @@ -82,7 +82,7 @@ public boolean offer(final E nextValue) { * * This means the consumerNode.value is always null, which is also the starting point for the queue. Because null * values are not allowed to be offered this is the only node with it's value set to null at any one time. - * + * */ @Override public E poll() { diff --git a/src/main/java/rx/internal/util/unsafe/SpscUnboundedArrayQueue.java b/src/main/java/rx/internal/util/unsafe/SpscUnboundedArrayQueue.java index 6175bab455..aa7baed670 100644 --- a/src/main/java/rx/internal/util/unsafe/SpscUnboundedArrayQueue.java +++ b/src/main/java/rx/internal/util/unsafe/SpscUnboundedArrayQueue.java @@ -2,15 +2,15 @@ * 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. - * + * * 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/queues/SpscUnboundedArrayQueue.java */ @@ -22,6 +22,8 @@ import java.util.AbstractQueue; import java.util.Iterator; +import rx.internal.util.SuppressAnimalSniffer; + abstract class SpscUnboundedArrayQueueProducerFields extends AbstractQueue { protected long producerIndex; } @@ -46,8 +48,9 @@ abstract class SpscUnboundedArrayQueueConsumerField extends SpscUnboundedArra protected long consumerIndex; } +@SuppressAnimalSniffer public class SpscUnboundedArrayQueue extends SpscUnboundedArrayQueueConsumerField - implements QueueProgressIndicators{ + implements QueueProgressIndicators { static final int MAX_LOOK_AHEAD_STEP = Integer.getInteger("jctools.spsc.max.lookahead.step", 4096); private final static long P_INDEX_OFFSET; private final static long C_INDEX_OFFSET; @@ -69,13 +72,17 @@ public class SpscUnboundedArrayQueue extends SpscUnboundedArrayQueueConsumerF Field iField = SpscUnboundedArrayQueueProducerFields.class.getDeclaredField("producerIndex"); P_INDEX_OFFSET = UNSAFE.objectFieldOffset(iField); } catch (NoSuchFieldException e) { - throw new RuntimeException(e); + InternalError ex = new InternalError(); + ex.initCause(e); + throw ex; } try { Field iField = SpscUnboundedArrayQueueConsumerField.class.getDeclaredField("consumerIndex"); C_INDEX_OFFSET = UNSAFE.objectFieldOffset(iField); } catch (NoSuchFieldException e) { - throw new RuntimeException(e); + InternalError ex = new InternalError(); + ex.initCause(e); + throw ex; } } @@ -90,7 +97,7 @@ public SpscUnboundedArrayQueue(final int bufferSize) { consumerBuffer = buffer; consumerMask = mask; producerLookAhead = mask - 1; // we know it's all empty to start with - soProducerIndex(0l); + soProducerIndex(0L); } @Override @@ -119,7 +126,7 @@ public final boolean offer(final E e) { final int lookAheadStep = producerLookAheadStep; // go around the buffer or resize if full (unless we hit max capacity) long lookAheadElementOffset = calcWrappedOffset(index + lookAheadStep, mask); - if (null == lvElement(buffer, lookAheadElementOffset)) {// LoadLoad + if (null == lvElement(buffer, lookAheadElementOffset)) { // LoadLoad producerLookAhead = index + lookAheadStep - 1; // joy, there's plenty of room return writeToQueue(buffer, e, index, offset); } else if (null != lvElement(buffer, calcWrappedOffset(index + 1, mask))) { // buffer is not full @@ -152,11 +159,11 @@ private void resize(final E[] oldBuffer, final long currIndex, final long offset } private void soNext(E[] curr, E[] next) { - soElement(curr, calcDirectOffset(curr.length -1), next); + soElement(curr, calcDirectOffset(curr.length - 1), next); } @SuppressWarnings("unchecked") private E[] lvNext(E[] curr) { - return (E[]) lvElement(curr, calcDirectOffset(curr.length -1)); + return (E[]) lvElement(curr, calcDirectOffset(curr.length - 1)); } /** * {@inheritDoc} @@ -277,12 +284,12 @@ private static void soElement(Object[] buffer, long offset, Object e) { private static Object lvElement(E[] buffer, long offset) { return UNSAFE.getObjectVolatile(buffer, offset); } - + @Override public long currentProducerIndex() { return lvProducerIndex(); } - + @Override public long currentConsumerIndex() { return lvConsumerIndex(); diff --git a/src/main/java/rx/internal/util/unsafe/UnsafeAccess.java b/src/main/java/rx/internal/util/unsafe/UnsafeAccess.java index 076112982f..d5576b77e7 100644 --- a/src/main/java/rx/internal/util/unsafe/UnsafeAccess.java +++ b/src/main/java/rx/internal/util/unsafe/UnsafeAccess.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -17,6 +17,7 @@ import java.lang.reflect.Field; +import rx.internal.util.SuppressAnimalSniffer; import sun.misc.Unsafe; /** @@ -26,28 +27,30 @@ * Note that you can force RxJava to not use Unsafe API by setting any value to System Property * {@code rx.unsafe-disable}. */ +@SuppressAnimalSniffer public final class UnsafeAccess { - private UnsafeAccess() { - throw new IllegalStateException("No instances!"); - } public static final Unsafe UNSAFE; private static final boolean DISABLED_BY_USER = System.getProperty("rx.unsafe-disable") != null; + private UnsafeAccess() { + throw new IllegalStateException("No instances!"); + } + static { Unsafe u = null; try { /* * This mechanism for getting UNSAFE originally from: - * + * * 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/UnsafeAccess.java */ Field field = Unsafe.class.getDeclaredField("theUnsafe"); field.setAccessible(true); u = (Unsafe) field.get(null); - } catch (Throwable e) { + } catch (Throwable e) { // NOPMD // do nothing, hasUnsafe() will return false } UNSAFE = u; @@ -65,8 +68,9 @@ public static int getAndIncrementInt(Object obj, long offset) { for (;;) { int current = UNSAFE.getIntVolatile(obj, offset); int next = current + 1; - if (UNSAFE.compareAndSwapInt(obj, offset, current, next)) + if (UNSAFE.compareAndSwapInt(obj, offset, current, next)) { return current; + } } } @@ -74,23 +78,25 @@ public static int getAndAddInt(Object obj, long offset, int n) { for (;;) { int current = UNSAFE.getIntVolatile(obj, offset); int next = current + n; - if (UNSAFE.compareAndSwapInt(obj, offset, current, next)) + if (UNSAFE.compareAndSwapInt(obj, offset, current, next)) { return current; + } } } public static int getAndSetInt(Object obj, long offset, int newValue) { for (;;) { int current = UNSAFE.getIntVolatile(obj, offset); - if (UNSAFE.compareAndSwapInt(obj, offset, current, newValue)) + if (UNSAFE.compareAndSwapInt(obj, offset, current, newValue)) { return current; + } } } public static boolean compareAndSwapInt(Object obj, long offset, int expected, int newValue) { return UNSAFE.compareAndSwapInt(obj, offset, expected, newValue); } - + /** * Returns the address of the specific field on the class and * wraps a NoSuchFieldException into an internal error. @@ -111,4 +117,4 @@ public static long addressOf(Class clazz, String fieldName) { throw ie; } } -} \ No newline at end of file +} diff --git a/src/main/java/rx/observables/AsyncOnSubscribe.java b/src/main/java/rx/observables/AsyncOnSubscribe.java index a32175fd9c..7a1eef7112 100644 --- a/src/main/java/rx/observables/AsyncOnSubscribe.java +++ b/src/main/java/rx/observables/AsyncOnSubscribe.java @@ -1,12 +1,12 @@ /** * Copyright 2015 Netflix, Inc. - * + * * 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. @@ -23,7 +23,7 @@ import rx.Observable; import rx.Observable.OnSubscribe; import rx.Observer; -import rx.annotations.Experimental; +import rx.annotations.Beta; import rx.functions.*; import rx.internal.operators.BufferUntilSubscriber; import rx.observers.SerializedObserver; @@ -33,7 +33,7 @@ /** * A utility class to create {@code OnSubscribe} functions that respond correctly to back * pressure requests from subscribers. This is an improvement over - * {@link rx.Observable#create(OnSubscribe) Observable.create(OnSubscribe)} which does not provide + * {@link rx.Observable#unsafeCreate(OnSubscribe) Observable.create(OnSubscribe)} which does not provide * any means of managing back pressure requests out-of-the-box. This variant of an OnSubscribe * function allows for the asynchronous processing of requests. * @@ -43,8 +43,9 @@ * {@link #onUnsubscribe(Object) onUnsubscribe(S)}. * @param * the type of {@code Subscribers} that will be compatible with {@code this}. + * @since 1.3 - beta */ -@Experimental +@Beta public abstract class AsyncOnSubscribe implements OnSubscribe { /** @@ -52,7 +53,7 @@ public abstract class AsyncOnSubscribe implements OnSubscribe { * to produce a state value. This value is passed into {@link #next(Object, long, Observer) * next(S state, Observer observer)} on the first iteration. Subsequent iterations of * {@code next} will receive the state returned by the previous invocation of {@code next}. - * + * * @return the initial state value */ protected abstract S generateState(); @@ -63,15 +64,15 @@ public abstract class AsyncOnSubscribe implements OnSubscribe { * {@code observer.onError(throwable)} or throw an Exception. To signal the end of a data stream * call {@code observer.onCompleted()}. Implementations of this method must follow the following * rules. - * + * *
      *
    • Must not call {@code observer.onNext(t)} more than 1 time per invocation.
    • *
    • Must not call {@code observer.onNext(t)} concurrently.
    • *
    - * + * * The value returned from an invocation of this method will be passed in as the {@code state} * argument of the next invocation of this method. - * + * * @param state * the state value (from {@link #generateState()} on the first invocation or the * previous invocation of this method. @@ -87,20 +88,20 @@ public abstract class AsyncOnSubscribe implements OnSubscribe { /** * Clean up behavior that is executed after the downstream subscriber's subscription is * unsubscribed. This method will be invoked exactly once. - * + * * @param state * the last state value returned from {@code next(S, Long, Observer)} or * {@code generateState()} at the time when a terminal event is emitted from * {@link #next(Object, long, Observer)} or unsubscribing. */ protected void onUnsubscribe(S state) { - + // default behavior is no-op } /** * Generates a synchronous {@link AsyncOnSubscribe} that calls the provided {@code next} * function to generate data to downstream subscribers. - * + * * @param the type of the generated values * @param the type of the associated state with each Subscriber * @param generator @@ -110,10 +111,9 @@ protected void onUnsubscribe(S state) { * {@link #next(Object, long, Observer) next(S, long, Observer)}) * @return an AsyncOnSubscribe that emits data in a protocol compatible with back-pressure. */ - @Experimental - public static AsyncOnSubscribe createSingleState(Func0 generator, + public static AsyncOnSubscribe createSingleState(Func0 generator, final Action3>> next) { - Func3>, S> nextFunc = + Func3>, S> nextFunc = new Func3>, S>() { @Override public S call(S state, Long requested, Observer> subscriber) { @@ -126,9 +126,9 @@ public S call(S state, Long requested, Observer> subscri /** * Generates a synchronous {@link AsyncOnSubscribe} that calls the provided {@code next} * function to generate data to downstream subscribers. - * + * * This overload creates a AsyncOnSubscribe without an explicit clean up step. - * + * * @param the type of the generated values * @param the type of the associated state with each Subscriber * @param generator @@ -141,11 +141,10 @@ public S call(S state, Long requested, Observer> subscri * @return an AsyncOnSubscribe that emits data downstream in a protocol compatible with * back-pressure. */ - @Experimental - public static AsyncOnSubscribe createSingleState(Func0 generator, - final Action3>> next, + public static AsyncOnSubscribe createSingleState(Func0 generator, + final Action3>> next, final Action1 onUnsubscribe) { - Func3>, S> nextFunc = + Func3>, S> nextFunc = new Func3>, S>() { @Override public S call(S state, Long requested, Observer> subscriber) { @@ -158,7 +157,7 @@ public S call(S state, Long requested, Observer> subscri /** * Generates a synchronous {@link AsyncOnSubscribe} that calls the provided {@code next} * function to generate data to downstream subscribers. - * + * * @param the type of the generated values * @param the type of the associated state with each Subscriber * @param generator @@ -171,9 +170,8 @@ public S call(S state, Long requested, Observer> subscri * @return an AsyncOnSubscribe that emits data downstream in a protocol compatible with * back-pressure. */ - @Experimental - public static AsyncOnSubscribe createStateful(Func0 generator, - Func3>, ? extends S> next, + public static AsyncOnSubscribe createStateful(Func0 generator, + Func3>, ? extends S> next, Action1 onUnsubscribe) { return new AsyncOnSubscribeImpl(generator, next, onUnsubscribe); } @@ -181,7 +179,7 @@ public static AsyncOnSubscribe createStateful(Func0 ge /** * Generates a synchronous {@link AsyncOnSubscribe} that calls the provided {@code next} * function to generate data to downstream subscribers. - * + * * @param the type of the generated values * @param the type of the associated state with each Subscriber * @param generator @@ -192,8 +190,7 @@ public static AsyncOnSubscribe createStateful(Func0 ge * @return an AsyncOnSubscribe that emits data downstream in a protocol compatible with * back-pressure. */ - @Experimental - public static AsyncOnSubscribe createStateful(Func0 generator, + public static AsyncOnSubscribe createStateful(Func0 generator, Func3>, ? extends S> next) { return new AsyncOnSubscribeImpl(generator, next); } @@ -201,10 +198,10 @@ public static AsyncOnSubscribe createStateful(Func0 ge /** * Generates a synchronous {@link AsyncOnSubscribe} that calls the provided {@code next} * function to generate data to downstream subscribers. - * + * * This overload creates a "state-less" AsyncOnSubscribe which does not have an explicit state * value. This should be used when the {@code next} function closes over it's state. - * + * * @param the type of the generated values * @param next * produces data to the downstream subscriber (see @@ -212,9 +209,8 @@ public static AsyncOnSubscribe createStateful(Func0 ge * @return an AsyncOnSubscribe that emits data downstream in a protocol compatible with * back-pressure. */ - @Experimental public static AsyncOnSubscribe createStateless(final Action2>> next) { - Func3>, Void> nextFunc = + Func3>, Void> nextFunc = new Func3>, Void>() { @Override public Void call(Void state, Long requested, Observer> subscriber) { @@ -227,10 +223,10 @@ public Void call(Void state, Long requested, Observer> s /** * Generates a synchronous {@link AsyncOnSubscribe} that calls the provided {@code next} * function to generate data to downstream subscribers. - * + * * This overload creates a "state-less" AsyncOnSubscribe which does not have an explicit state * value. This should be used when the {@code next} function closes over it's state. - * + * * @param the type of the generated values * @param next * produces data to the downstream subscriber (see @@ -240,10 +236,9 @@ public Void call(Void state, Long requested, Observer> s * @return an AsyncOnSubscribe that emits data downstream in a protocol compatible with * back-pressure. */ - @Experimental - public static AsyncOnSubscribe createStateless(final Action2>> next, + public static AsyncOnSubscribe createStateless(final Action2>> next, final Action0 onUnsubscribe) { - Func3>, Void> nextFunc = + Func3>, Void> nextFunc = new Func3>, Void>() { @Override public Void call(Void state, Long requested, Observer> subscriber) { @@ -269,7 +264,7 @@ public void call(Void t) { * @param * the type of compatible Subscribers */ - private static final class AsyncOnSubscribeImpl extends AsyncOnSubscribe { + static final class AsyncOnSubscribeImpl extends AsyncOnSubscribe { private final Func0 generator; private final Func3>, ? extends S> next; private final Action1 onUnsubscribe; @@ -304,8 +299,9 @@ protected S next(S state, long requested, Observer> obse @Override protected void onUnsubscribe(S state) { - if (onUnsubscribe != null) + if (onUnsubscribe != null) { onUnsubscribe.call(state); + } } } @@ -319,38 +315,38 @@ public final void call(final Subscriber actualSubscriber) { return; } UnicastSubject> subject = UnicastSubject.> create(); - + final AsyncOuterManager outerProducer = new AsyncOuterManager(this, state, subject); - + Subscriber concatSubscriber = new Subscriber() { @Override public void onNext(T t) { actualSubscriber.onNext(t); } - + @Override public void onError(Throwable e) { actualSubscriber.onError(e); } - + @Override public void onCompleted() { actualSubscriber.onCompleted(); } - + @Override public void setProducer(Producer p) { outerProducer.setConcatProducer(p); } }; - + subject.onBackpressureBuffer().concatMap(new Func1, Observable>() { @Override public Observable call(Observable v) { return v.onBackpressureBuffer(); } }).unsafeSubscribe(concatSubscriber); - + actualSubscriber.add(concatSubscriber); actualSubscriber.add(outerProducer); actualSubscriber.setProducer(outerProducer); @@ -371,11 +367,11 @@ static final class AsyncOuterManager implements Producer, Subscription, Ob private S state; private final UnicastSubject> merger; - + boolean emitting; List requests; Producer concatProducer; - + long expectedDelivery; public AsyncOuterManager(AsyncOnSubscribe parent, S initialState, UnicastSubject> merger) { @@ -407,7 +403,7 @@ void setConcatProducer(Producer p) { } concatProducer = p; } - + @Override public boolean isUnsubscribed() { return isUnsubscribed.get(); @@ -416,7 +412,7 @@ public boolean isUnsubscribed() { public void nextIteration(long requestCount) { state = parent.next(state, requestCount, serializedSubscriber); } - + void cleanup() { subscriptions.unsubscribe(); try { @@ -443,19 +439,19 @@ public void request(long n) { requests = q; } q.add(n); - - quit = true; + + quit = true; } else { emitting = true; } } - + concatProducer.request(n); - + if (quit) { return; } - + if (tryEmit(n)) { return; } @@ -469,7 +465,7 @@ public void request(long n) { } requests = null; } - + for (long r : q) { if (tryEmit(r)) { return; @@ -498,12 +494,12 @@ public void requestRemaining(long n) { requests = q; } q.add(n); - + return; } emitting = true; } - + if (tryEmit(n)) { return; } @@ -517,7 +513,7 @@ public void requestRemaining(long n) { } requests = null; } - + for (long r : q) { if (tryEmit(r)) { return; @@ -531,13 +527,16 @@ boolean tryEmit(long n) { cleanup(); return true; } - + try { onNextCalled = false; expectedDelivery = n; nextIteration(n); - - if (hasTerminated || isUnsubscribed()) { + + //hasTerminated will be true when onCompleted was already emitted from the request callback + //even if the the observer has not seen onCompleted from the requested observable, + //so we should not clean up while there are active subscriptions + if (hasTerminated && !subscriptions.hasSubscriptions() || isUnsubscribed()) { cleanup(); return true; } @@ -586,8 +585,9 @@ public void onNext(final Observable t) { throw new IllegalStateException("onNext called multiple times!"); } onNextCalled = true; - if (hasTerminated) + if (hasTerminated) { return; + } subscribeBufferToObservable(t); } @@ -623,7 +623,7 @@ public void onCompleted() { public void call() { subscriptions.remove(s); }}); - + ((Observable)doOnTerminate).subscribe(s); merger.onNext(buffer); @@ -631,12 +631,12 @@ public void call() { } static final class UnicastSubject extends Observableimplements Observer { + private final State state; + public static UnicastSubject create() { return new UnicastSubject(new State()); } - private State state; - protected UnicastSubject(final State state) { super(state); this.state = state; diff --git a/src/main/java/rx/observables/BlockingObservable.java b/src/main/java/rx/observables/BlockingObservable.java index 53991409af..ebde4486b0 100644 --- a/src/main/java/rx/observables/BlockingObservable.java +++ b/src/main/java/rx/observables/BlockingObservable.java @@ -22,12 +22,10 @@ import rx.*; import rx.Observable; import rx.Observer; -import rx.annotations.Experimental; -import rx.exceptions.OnErrorNotImplementedException; +import rx.exceptions.*; import rx.functions.*; import rx.internal.operators.*; -import rx.internal.util.BlockingUtils; -import rx.internal.util.UtilityFunctions; +import rx.internal.util.*; import rx.subscriptions.Subscriptions; /** @@ -53,6 +51,15 @@ public final class BlockingObservable { private final Observable o; + /** Constant to indicate the onStart method should be called. */ + static final Object ON_START = new Object(); + + /** Constant indicating the setProducer method should be called. */ + static final Object SET_PRODUCER = new Object(); + + /** Indicates an unsubscription happened */ + static final Object UNSUBSCRIBE = new Object(); + private BlockingObservable(Observable o) { this.o = o; } @@ -81,10 +88,10 @@ public static BlockingObservable from(final Observable o) { * need the {@link Subscriber#onCompleted()} or {@link Subscriber#onError(Throwable)} methods. If the * underlying Observable terminates with an error, rather than calling {@code onError}, this method will * throw an exception. - * + * *

    The difference between this method and {@link #subscribe(Action1)} is that the {@code onNext} action * is executed on the emission thread instead of the current thread. - * + * * @param onNext * the {@link Action1} to invoke for each item emitted by the {@code BlockingObservable} * @throws RuntimeException @@ -113,7 +120,7 @@ public void onError(Throwable e) { * If we receive an onError event we set the reference on the * outer thread so we can git it and throw after the * latch.await(). - * + * * We do this instead of throwing directly since this may be on * a different thread and the latch is still waiting. */ @@ -129,11 +136,7 @@ public void onNext(T args) { BlockingUtils.awaitForComplete(latch, subscription); if (exceptionFromOnError.get() != null) { - if (exceptionFromOnError.get() instanceof RuntimeException) { - throw (RuntimeException) exceptionFromOnError.get(); - } else { - throw new RuntimeException(exceptionFromOnError.get()); - } + Exceptions.propagate(exceptionFromOnError.get()); } } @@ -457,20 +460,16 @@ public void onNext(final T item) { BlockingUtils.awaitForComplete(latch, subscription); if (returnException.get() != null) { - if (returnException.get() instanceof RuntimeException) { - throw (RuntimeException) returnException.get(); - } else { - throw new RuntimeException(returnException.get()); - } + Exceptions.propagate(returnException.get()); } return returnItem.get(); } - + /** * Runs the source observable to a terminal event, ignoring any values and rethrowing any exception. + * @since 1.3 */ - @Experimental public void subscribe() { final CountDownLatch cdl = new CountDownLatch(1); final Throwable[] error = { null }; @@ -478,63 +477,58 @@ public void subscribe() { Subscription s = ((Observable)o).subscribe(new Subscriber() { @Override public void onNext(T t) { - + // deliberately ignored } @Override public void onError(Throwable e) { error[0] = e; cdl.countDown(); } - + @Override public void onCompleted() { cdl.countDown(); } }); - + BlockingUtils.awaitForComplete(cdl, s); Throwable e = error[0]; if (e != null) { - if (e instanceof RuntimeException) { - throw (RuntimeException)e; - } else { - throw new RuntimeException(e); - } + Exceptions.propagate(e); } } - + /** * Subscribes to the source and calls back the Observer methods on the current thread. * @param observer the observer to call event methods on + * @since 1.3 */ - @Experimental public void subscribe(Observer observer) { - final NotificationLite nl = NotificationLite.instance(); final BlockingQueue queue = new LinkedBlockingQueue(); - + @SuppressWarnings("unchecked") Subscription s = ((Observable)o).subscribe(new Subscriber() { @Override public void onNext(T t) { - queue.offer(nl.next(t)); + queue.offer(NotificationLite.next(t)); } @Override public void onError(Throwable e) { - queue.offer(nl.error(e)); + queue.offer(NotificationLite.error(e)); } @Override public void onCompleted() { - queue.offer(nl.completed()); + queue.offer(NotificationLite.completed()); } }); - + try { for (;;) { Object o = queue.poll(); if (o == null) { o = queue.take(); } - if (nl.accept(observer, o)) { + if (NotificationLite.accept(observer, o)) { return; } } @@ -545,55 +539,45 @@ public void onCompleted() { s.unsubscribe(); } } - - /** Constant to indicate the onStart method should be called. */ - static final Object ON_START = new Object(); - - /** Constant indicating the setProducer method should be called. */ - static final Object SET_PRODUCER = new Object(); - - /** Indicates an unsubscription happened */ - static final Object UNSUBSCRIBE = new Object(); /** * Subscribes to the source and calls the Subscriber methods on the current thread. *

    * The unsubscription and backpressure is composed through. * @param subscriber the subscriber to forward events and calls to in the current thread + * @since 1.3 */ @SuppressWarnings("unchecked") - @Experimental public void subscribe(Subscriber subscriber) { - final NotificationLite nl = NotificationLite.instance(); final BlockingQueue queue = new LinkedBlockingQueue(); - final Producer[] theProducer = { null }; - + final Producer[] theProducer = { null }; + Subscriber s = new Subscriber() { @Override public void onNext(T t) { - queue.offer(nl.next(t)); + queue.offer(NotificationLite.next(t)); } @Override public void onError(Throwable e) { - queue.offer(nl.error(e)); + queue.offer(NotificationLite.error(e)); } @Override public void onCompleted() { - queue.offer(nl.completed()); + queue.offer(NotificationLite.completed()); } - + @Override public void setProducer(Producer p) { theProducer[0] = p; queue.offer(SET_PRODUCER); } - + @Override public void onStart() { queue.offer(ON_START); } }; - + subscriber.add(s); subscriber.add(Subscriptions.create(new Action0() { @Override @@ -601,9 +585,9 @@ public void call() { queue.offer(UNSUBSCRIBE); } })); - + ((Observable)o).subscribe(s); - + try { for (;;) { if (subscriber.isUnsubscribed()) { @@ -622,7 +606,7 @@ public void call() { if (o == SET_PRODUCER) { subscriber.setProducer(theProducer[0]); } else - if (nl.accept(subscriber, o)) { + if (NotificationLite.accept(subscriber, o)) { return; } } @@ -633,18 +617,18 @@ public void call() { s.unsubscribe(); } } - + /** * Subscribes to the source and calls the given action on the current thread and rethrows any exception wrapped * into OnErrorNotImplementedException. - * + * *

    The difference between this method and {@link #forEach(Action1)} is that the * action is always executed on the current thread. - * + * * @param onNext the callback action for each source value * @see #forEach(Action1) + * @since 1.3 */ - @Experimental public void subscribe(final Action1 onNext) { subscribe(onNext, new Action1() { @Override @@ -653,36 +637,36 @@ public void call(Throwable t) { } }, Actions.empty()); } - + /** * Subscribes to the source and calls the given actions on the current thread. * @param onNext the callback action for each source value * @param onError the callback action for an error event + * @since 1.3 */ - @Experimental public void subscribe(final Action1 onNext, final Action1 onError) { subscribe(onNext, onError, Actions.empty()); } - + /** * Subscribes to the source and calls the given actions on the current thread. * @param onNext the callback action for each source value * @param onError the callback action for an error event * @param onCompleted the callback action for the completion event. + * @since 1.3 */ - @Experimental public void subscribe(final Action1 onNext, final Action1 onError, final Action0 onCompleted) { subscribe(new Observer() { @Override public void onNext(T t) { onNext.call(t); } - + @Override public void onError(Throwable e) { onError.call(e); } - + @Override public void onCompleted() { onCompleted.call(); diff --git a/src/main/java/rx/observables/ConnectableObservable.java b/src/main/java/rx/observables/ConnectableObservable.java index fe78ae55cd..f1fffceb5f 100644 --- a/src/main/java/rx/observables/ConnectableObservable.java +++ b/src/main/java/rx/observables/ConnectableObservable.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -16,7 +16,6 @@ package rx.observables; import rx.*; -import rx.annotations.Beta; import rx.functions.*; import rx.internal.operators.*; @@ -27,7 +26,7 @@ * before the {@code Observable} begins emitting items. *

    * - * + * * @see RxJava Wiki: * Connectable Observable Operators * @param @@ -72,63 +71,60 @@ public void call(Subscription t1) { /** * Returns an {@code Observable} that stays connected to this {@code ConnectableObservable} as long as there * is at least one subscription to this {@code ConnectableObservable}. - * + * * @return a {@link Observable} * @see ReactiveX documentation: RefCount */ public Observable refCount() { - return create(new OnSubscribeRefCount(this)); + return unsafeCreate(new OnSubscribeRefCount(this)); } - + /** * Returns an Observable that automatically connects to this ConnectableObservable * when the first Subscriber subscribes. - * + * * @return an Observable that automatically connects to this ConnectableObservable * when the first Subscriber subscribes - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.2 */ - @Beta public Observable autoConnect() { return autoConnect(1); } /** * Returns an Observable that automatically connects to this ConnectableObservable * when the specified number of Subscribers subscribe to it. - * + * * @param numberOfSubscribers the number of subscribers to await before calling connect * on the ConnectableObservable. A non-positive value indicates * an immediate connection. * @return an Observable that automatically connects to this ConnectableObservable * when the specified number of Subscribers subscribe to it - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.2 */ - @Beta public Observable autoConnect(int numberOfSubscribers) { return autoConnect(numberOfSubscribers, Actions.empty()); } - + /** * Returns an Observable that automatically connects to this ConnectableObservable - * when the specified number of Subscribers subscribe to it and calls the + * when the specified number of Subscribers subscribe to it and calls the * specified callback with the Subscription associated with the established connection. - * + * * @param numberOfSubscribers the number of subscribers to await before calling connect * on the ConnectableObservable. A non-positive value indicates * an immediate connection. * @param connection the callback Action1 that will receive the Subscription representing the * established connection * @return an Observable that automatically connects to this ConnectableObservable - * when the specified number of Subscribers subscribe to it and calls the + * when the specified number of Subscribers subscribe to it and calls the * specified callback with the Subscription associated with the established connection - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.2 */ - @Beta public Observable autoConnect(int numberOfSubscribers, Action1 connection) { if (numberOfSubscribers <= 0) { this.connect(connection); return this; } - return create(new OnSubscribeAutoConnect(this, numberOfSubscribers, connection)); + return unsafeCreate(new OnSubscribeAutoConnect(this, numberOfSubscribers, connection)); } } diff --git a/src/main/java/rx/observables/GroupedObservable.java b/src/main/java/rx/observables/GroupedObservable.java index 98b5a3e836..c68a0526f3 100644 --- a/src/main/java/rx/observables/GroupedObservable.java +++ b/src/main/java/rx/observables/GroupedObservable.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -27,7 +27,7 @@ * 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 * may discard their buffers by applying an operator like {@link Observable#take take}{@code (0)} to them. - * + * * @param * the type of the key * @param @@ -40,7 +40,7 @@ public class GroupedObservable extends Observable { /** * Converts an {@link Observable} into a {@code GroupedObservable} with a particular key. - * + * * @param the key type * @param the value type * @param key @@ -77,7 +77,7 @@ public void call(Subscriber s) { *

    Scheduler:
    *
    {@code create} does not operate by default on a particular {@link Scheduler}.
    * - * + * * @param * the type of the key * @param @@ -99,7 +99,7 @@ protected GroupedObservable(K key, OnSubscribe onSubscribe) { /** * Returns the key that identifies the group of items emitted by this {@code GroupedObservable} - * + * * @return the key that the items emitted by this {@code GroupedObservable} were grouped by */ public K getKey() { diff --git a/src/main/java/rx/observables/SyncOnSubscribe.java b/src/main/java/rx/observables/SyncOnSubscribe.java index 34745fb118..9d4b5245f4 100644 --- a/src/main/java/rx/observables/SyncOnSubscribe.java +++ b/src/main/java/rx/observables/SyncOnSubscribe.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -20,16 +20,15 @@ import rx.*; import rx.Observable.OnSubscribe; -import rx.annotations.Beta; import rx.exceptions.Exceptions; import rx.functions.*; import rx.internal.operators.BackpressureUtils; import rx.plugins.RxJavaHooks; /** - * A utility class to create {@code OnSubscribe} functions that respond correctly to back + * A utility class to create {@code OnSubscribe} functions that responds correctly to back * pressure requests from subscribers. This is an improvement over - * {@link rx.Observable#create(OnSubscribe) Observable.create(OnSubscribe)} which does not provide + * {@link rx.Observable#unsafeCreate(OnSubscribe) Observable.create(OnSubscribe)} which does not provide * any means of managing back pressure requests out-of-the-box. * * @param @@ -38,17 +37,17 @@ * {@link #onUnsubscribe(Object) onUnsubscribe(S)}. * @param * the type of {@code Subscribers} that will be compatible with {@code this}. + * @since 1.2 */ -@Beta public abstract class SyncOnSubscribe implements OnSubscribe { - + /* (non-Javadoc) * @see rx.functions.Action1#call(java.lang.Object) */ @Override public final void call(final Subscriber subscriber) { S state; - + try { state = generateState(); } catch (Throwable e) { @@ -56,7 +55,7 @@ public final void call(final Subscriber subscriber) { subscriber.onError(e); return; } - + SubscriptionProducer p = new SubscriptionProducer(subscriber, this, state); subscriber.add(p); subscriber.setProducer(p); @@ -67,7 +66,7 @@ public final void call(final Subscriber subscriber) { * to produce a state value. This value is passed into {@link #next(Object, Observer) next(S * state, Observer observer)} on the first iteration. Subsequent iterations of {@code next} * will receive the state returned by the previous invocation of {@code next}. - * + * * @return the initial state value */ protected abstract S generateState(); @@ -76,17 +75,17 @@ public final void call(final Subscriber subscriber) { * Called to produce data to the downstream subscribers. To emit data to a downstream subscriber * call {@code observer.onNext(t)}. To signal an error condition call * {@code observer.onError(throwable)} or throw an Exception. To signal the end of a data stream - * call {@code + * call {@code * observer.onCompleted()}. Implementations of this method must follow the following rules. - * + * *
      *
    • Must not call {@code observer.onNext(t)} more than 1 time per invocation.
    • *
    • Must not call {@code observer.onNext(t)} concurrently.
    • *
    - * + * * The value returned from an invocation of this method will be passed in as the {@code state} * argument of the next invocation of this method. - * + * * @param state * the state value (from {@link #generateState()} on the first invocation or the * previous invocation of this method. @@ -99,19 +98,19 @@ public final void call(final Subscriber subscriber) { /** * Clean up behavior that is executed after the downstream subscriber's subscription is * unsubscribed. This method will be invoked exactly once. - * + * * @param state * the last state value prior from {@link #generateState()} or * {@link #next(Object, Observer) next(S, Observer<T>)} before unsubscribe. */ protected void onUnsubscribe(S state) { - + // default behavior is no-op } /** * Generates a synchronous {@link SyncOnSubscribe} that calls the provided {@code next} function * to generate data to downstream subscribers. - * + * * @param the type of the generated values * @param the type of the associated state with each Subscriber * @param generator @@ -120,9 +119,9 @@ protected void onUnsubscribe(S state) { * produces data to the downstream subscriber (see {@link #next(Object, Observer) * next(S, Subscriber)}) * @return a SyncOnSubscribe that emits data in a protocol compatible with back-pressure. + * @since 1.3 */ - @Beta - public static SyncOnSubscribe createSingleState(Func0 generator, + public static SyncOnSubscribe createSingleState(Func0 generator, final Action2> next) { Func2, S> nextFunc = new Func2, S>() { @Override @@ -137,9 +136,9 @@ public S call(S state, Observer subscriber) { /** * Generates a synchronous {@link SyncOnSubscribe} that calls the provided {@code next} function * to generate data to downstream subscribers. - * + * * This overload creates a SyncOnSubscribe without an explicit clean up step. - * + * * @param the type of the generated values * @param the type of the associated state with each Subscriber * @param generator @@ -151,10 +150,10 @@ public S call(S state, Observer subscriber) { * clean up behavior (see {@link #onUnsubscribe(Object) onUnsubscribe(S)}) * @return a SyncOnSubscribe that emits data downstream in a protocol compatible with * back-pressure. + * @since 1.3 */ - @Beta - public static SyncOnSubscribe createSingleState(Func0 generator, - final Action2> next, + public static SyncOnSubscribe createSingleState(Func0 generator, + final Action2> next, final Action1 onUnsubscribe) { Func2, S> nextFunc = new Func2, S>() { @Override @@ -169,7 +168,7 @@ public S call(S state, Observer subscriber) { /** * Generates a synchronous {@link SyncOnSubscribe} that calls the provided {@code next} function * to generate data to downstream subscribers. - * + * * @param the type of the generated values * @param the type of the associated state with each Subscriber * @param generator @@ -181,18 +180,18 @@ public S call(S state, Observer subscriber) { * clean up behavior (see {@link #onUnsubscribe(Object) onUnsubscribe(S)}) * @return a SyncOnSubscribe that emits data downstream in a protocol compatible with * back-pressure. + * @since 1.3 */ - @Beta - public static SyncOnSubscribe createStateful(Func0 generator, - Func2, ? extends S> next, + public static SyncOnSubscribe createStateful(Func0 generator, + Func2, ? extends S> next, Action1 onUnsubscribe) { return new SyncOnSubscribeImpl(generator, next, onUnsubscribe); } - + /** * Generates a synchronous {@link SyncOnSubscribe} that calls the provided {@code next} function * to generate data to downstream subscribers. - * + * * @param the type of the generated values * @param the type of the associated state with each Subscriber * @param generator @@ -202,9 +201,9 @@ public static SyncOnSubscribe createStateful(Func0 gen * next(S, Subscriber)}) * @return a SyncOnSubscribe that emits data downstream in a protocol compatible with * back-pressure. + * @since 1.3 */ - @Beta - public static SyncOnSubscribe createStateful(Func0 generator, + public static SyncOnSubscribe createStateful(Func0 generator, Func2, ? extends S> next) { return new SyncOnSubscribeImpl(generator, next); } @@ -212,18 +211,18 @@ public static SyncOnSubscribe createStateful(Func0 gen /** * Generates a synchronous {@link SyncOnSubscribe} that calls the provided {@code next} function * to generate data to downstream subscribers. - * + * * This overload creates a "state-less" SyncOnSubscribe which does not have an explicit state * value. This should be used when the {@code next} function closes over it's state. - * + * * @param the type of the generated values * @param next * produces data to the downstream subscriber (see {@link #next(Object, Observer) * next(S, Subscriber)}) * @return a SyncOnSubscribe that emits data downstream in a protocol compatible with * back-pressure. + * @since 1.3 */ - @Beta public static SyncOnSubscribe createStateless(final Action1> next) { Func2, Void> nextFunc = new Func2, Void>() { @Override @@ -238,10 +237,10 @@ public Void call(Void state, Observer subscriber) { /** * Generates a synchronous {@link SyncOnSubscribe} that calls the provided {@code next} function * to generate data to downstream subscribers. - * + * * This overload creates a "state-less" SyncOnSubscribe which does not have an explicit state * value. This should be used when the {@code next} function closes over it's state. - * + * * @param the type of the generated values * @param next * produces data to the downstream subscriber (see {@link #next(Object, Observer) @@ -250,9 +249,9 @@ public Void call(Void state, Observer subscriber) { * clean up behavior (see {@link #onUnsubscribe(Object) onUnsubscribe(S)}) * @return a SyncOnSubscribe that emits data downstream in a protocol compatible with * back-pressure. + * @since 1.3 */ - @Beta - public static SyncOnSubscribe createStateless(final Action1> next, + public static SyncOnSubscribe createStateless(final Action1> next, final Action0 onUnsubscribe) { Func2, Void> nextFunc = new Func2, Void>() { @Override @@ -261,7 +260,7 @@ public Void call(Void state, Observer subscriber) { return null; } }; - Action1 wrappedOnUnsubscribe = new Action1(){ + Action1 wrappedOnUnsubscribe = new Action1() { @Override public void call(Void t) { onUnsubscribe.call(); @@ -271,7 +270,7 @@ public void call(Void t) { /** * An implementation of SyncOnSubscribe that delegates - * {@link SyncOnSubscribe#next(Object, Subscriber)}, {@link SyncOnSubscribe#generateState()}, + * {@link SyncOnSubscribe#next(Object, Observer)}, {@link SyncOnSubscribe#generateState()}, * and {@link SyncOnSubscribe#onUnsubscribe(Object)} to provided functions/closures. * * @param @@ -279,7 +278,7 @@ public void call(Void t) { * @param * the type of compatible Subscribers */ - private static final class SyncOnSubscribeImpl extends SyncOnSubscribe { + static final class SyncOnSubscribeImpl extends SyncOnSubscribe { private final Func0 generator; private final Func2, ? extends S> next; private final Action1 onUnsubscribe; @@ -314,8 +313,9 @@ protected S next(S state, Observer observer) { @Override protected void onUnsubscribe(S state) { - if (onUnsubscribe != null) + if (onUnsubscribe != null) { onUnsubscribe.call(state); + } } } @@ -325,7 +325,7 @@ protected void onUnsubscribe(S state) { * @param * the type of compatible Subscribers */ - private static class SubscriptionProducer + static final class SubscriptionProducer extends AtomicLong implements Producer, Subscription, Observer { /** */ private static final long serialVersionUID = -3736864024352728072L; @@ -333,7 +333,7 @@ private static class SubscriptionProducer private final SyncOnSubscribe parent; private boolean onNextCalled; private boolean hasTerminated; - + private S state; SubscriptionProducer(final Subscriber subscriber, SyncOnSubscribe parent, S state) { @@ -346,23 +346,24 @@ private static class SubscriptionProducer public boolean isUnsubscribed() { return get() < 0L; } - + @Override public void unsubscribe() { - while(true) { + while (true) { long requestCount = get(); if (compareAndSet(0L, -1L)) { doUnsubscribe(); return; } - else if (compareAndSet(requestCount, -2L)) + else if (compareAndSet(requestCount, -2L)) { // the loop is iterating concurrently // need to check if requestCount == -1 - // and unsub if so after loop iteration + // and unsubscribe if so after loop iteration return; + } } } - + private boolean tryUnsubscribe() { // only one thread at a time can iterate over request count // therefore the requestCount atomic cannot be decrement concurrently here @@ -388,17 +389,17 @@ private void doUnsubscribe() { public void request(long n) { if (n > 0 && BackpressureUtils.getAndAddRequest(this, n) == 0L) { if (n == Long.MAX_VALUE) { - fastpath(); + fastPath(); } else { slowPath(n); } } } - private void fastpath() { + private void fastPath() { final SyncOnSubscribe p = parent; Subscriber a = actualSubscriber; - + for (;;) { try { onNextCalled = false; @@ -440,12 +441,14 @@ private void slowPath(long n) { if (tryUnsubscribe()) { return; } - if (onNextCalled) + if (onNextCalled) { numRemaining--; + } } while (numRemaining != 0L); numRequested = addAndGet(-numRequested); - if (numRequested <= 0L) + if (numRequested <= 0L) { break; + } } // catches cases where unsubscribe is called before decrementing atomic request count tryUnsubscribe(); @@ -454,7 +457,7 @@ private void slowPath(long n) { private void nextIteration(final SyncOnSubscribe parent) { state = parent.next(state, this); } - + @Override public void onCompleted() { if (hasTerminated) { @@ -487,5 +490,5 @@ public void onNext(T value) { } } - + } \ No newline at end of file diff --git a/src/main/java/rx/observers/AssertableSubscriber.java b/src/main/java/rx/observers/AssertableSubscriber.java new file mode 100644 index 0000000000..21180c7eb2 --- /dev/null +++ b/src/main/java/rx/observers/AssertableSubscriber.java @@ -0,0 +1,276 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * 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 rx.observers; + +import java.util.List; +import java.util.concurrent.TimeUnit; + +import rx.*; +import rx.functions.Action0; + +/** + * Interface for asserting the state of a sequence under testing with a {@code test()} + * method of a reactive base class. + *

    + * This interface is not intended to be implemented outside of RxJava. + *

    + * This interface extends {@link Observer} and allows injecting onXXX signals into + * the testing process. + *

    History: 1.2.3 - experimental + * @param the value type consumed by this Observer + * @since 1.3 + */ +public interface AssertableSubscriber extends Observer, Subscription { + + /** + * Allows manually calling the {@code onStart} method of the underlying Subscriber. + */ + void onStart(); + + /** + * Allows manually calling the {@code setProducer} method of the underlying Subscriber. + * @param p the producer to use, not null + */ + void setProducer(Producer p); + + /** + * Returns the number of {@code onCompleted} signals received by this Observer. + * @return the number of {@code onCompleted} signals received + */ + int getCompletions(); + + /** + * Returns a list of received {@code onError} signals. + * @return this + */ + List getOnErrorEvents(); + + /** + * Returns the number of {@code onNext} signals received by this Observer in + * a thread-safe manner; one can read up to this number of elements from + * the {@code List} returned by {@link #getOnNextEvents()}. + * @return the number of {@code onNext} signals received. + */ + int getValueCount(); + + /** + * Requests the specified amount of items from upstream. + * @param n the amount requested, non-negative + * @return this + */ + AssertableSubscriber requestMore(long n); + + /** + * Returns the list of received {@code onNext} events. + *

    If the sequence hasn't completed yet and is asynchronous, use the + * {@link #getValueCount()} method to determine how many elements are safe + * to be read from the list returned by this method. + * @return the List of received {@code onNext} events. + */ + List getOnNextEvents(); + + /** + * Assert that this Observer received the given list of items as {@code onNext} signals + * in the same order and with the default null-safe object equals comparison. + * @param items the List of items expected + * @return this + */ + AssertableSubscriber assertReceivedOnNext(List items); + + /** + * Assert that this Observer receives at least the given number of {@code onNext} + * signals within the specified timeout period. + *

    + * Note that it is possible the AssertionError thrown by this method will + * contain an actual value >= to the expected one in case there is an emission + * race or unexpected delay on the emitter side. In this case, increase the timeout + * amount to avoid false positives. + * + * @param expected the expected (at least) number of {@code onNext} signals + * @param timeout the timeout to wait to receive the given number of {@code onNext} events + * @param unit the time unit + * @return this + */ + AssertableSubscriber awaitValueCount(int expected, long timeout, TimeUnit unit); + + /** + * Assert that this Observer received either an {@code onError} or {@code onCompleted} signal. + * @return this + */ + AssertableSubscriber assertTerminalEvent(); + + /** + * Assert that this Observer has been unsubscribed via {@code unsubscribe()} or by a wrapping + * {@code SafeSubscriber}. + * @return this + */ + AssertableSubscriber assertUnsubscribed(); + + /** + * Assert that this Observer has not received any {@code onError} signal. + * @return this + */ + AssertableSubscriber assertNoErrors(); + + /** + * Waits for an {@code onError} or {code onCompleted} terminal event indefinitely. + * @return this + */ + AssertableSubscriber awaitTerminalEvent(); + + + /** + * Waits for an {@code onError} or {code onCompleted} terminal event for the given + * amount of timeout. + * @param timeout the time to wait for the terminal event + * @param unit the time unit of the wait time + * @return this + */ + AssertableSubscriber awaitTerminalEvent(long timeout, TimeUnit unit); + + /** + * Waits for an {@code onError} or {code onCompleted} terminal event for the given + * amount of timeout and unsubscribes the sequence if the timeout passed or the + * wait itself is interrupted. + * @param timeout the time to wait for the terminal event + * @param unit the time unit of the wait time + * @return this + */ + AssertableSubscriber awaitTerminalEventAndUnsubscribeOnTimeout(long timeout, + TimeUnit unit); + + /** + * Returns the Thread that has called the last {@code onNext}, {@code onError} or + * {@code onCompleted} methods of this Observer. + * @return this + */ + Thread getLastSeenThread(); + + /** + * Assert that this Observer received exaclty one {@code onCompleted} signal. + * @return this + */ + AssertableSubscriber assertCompleted(); + + /** + * Assert that this Observer received no {@code onCompleted} signal. + * @return this + */ + AssertableSubscriber assertNotCompleted(); + + /** + * Assert that this Observer received one {@code onError} signal with + * the given subclass of a Throwable as type. + * @param clazz the expected type of the {@code onError} signal received + * @return this + */ + AssertableSubscriber assertError(Class clazz); + + /** + * Assert that this Observer received one {@code onError} signal with the + * object-equals of the given Throwable instance + * @param throwable the Throwable instance expected + * @return this + */ + AssertableSubscriber assertError(Throwable throwable); + + /** + * Assert that no {@code onError} or {@code onCompleted} signals were received so far. + * @return this + */ + AssertableSubscriber assertNoTerminalEvent(); + + /** + * Assert that no {@code onNext} signals were received so far. + * @return this + */ + AssertableSubscriber assertNoValues(); + + /** + * Assert that this Observer received exactly the given count of + * {@code onNext} signals. + * @param count the expected number of {@code onNext} signals + * @return this + */ + AssertableSubscriber assertValueCount(int count); + + /** + * Assert that this Observer received exactly the given expected values + * (compared via null-safe object equals) in the given order. + * @param values the expected values + * @return this + */ + AssertableSubscriber assertValues(T... values); + + /** + * Assert that this Observer received exactly the given single expected value + * (compared via null-safe object equals). + * @param value the single value expected + * @return this + */ + AssertableSubscriber assertValue(T value); + + /** + * Assert that this Observer received exactly the given values (compared via + * null-safe object equals) and if so, clears the internal buffer of the + * underlying Subscriber of these values. + * @param expectedFirstValue the first value expected + * @param expectedRestValues the rest of the values expected + * @return this + */ + AssertableSubscriber assertValuesAndClear(T expectedFirstValue, + T... expectedRestValues); + + /** + * Performs an action given by the Action0 callback in a fluent manner. + * @param action the action to perform, not null + * @return this + */ + AssertableSubscriber perform(Action0 action); + + @Override + void unsubscribe(); + + @Override + boolean isUnsubscribed(); + + /** + * Assert that this Observer received the specified items in the given order followed + * by a completion signal and no errors. + * @param values the values expected + * @return this + */ + AssertableSubscriber assertResult(T... values); + + /** + * Assert that this Observer received the specified items in the given order followed + * by an error signal of the given type (but no completion signal). + * @param errorClass the expected Throwable subclass type + * @param values the expected values + * @return this + */ + AssertableSubscriber assertFailure(Class errorClass, T... values); + + /** + * Assert that this Observer received the specified items in the given order followed + * by an error signal of the given type and with the exact error message (but no completion signal). + * @param errorClass the expected Throwable subclass type + * @param message the expected error message returned by {@link Throwable#getMessage()} + * @param values the expected values + * @return this + */ + AssertableSubscriber assertFailureAndMessage(Class errorClass, String message, T... values); +} \ No newline at end of file diff --git a/src/main/java/rx/observers/AsyncCompletableSubscriber.java b/src/main/java/rx/observers/AsyncCompletableSubscriber.java index d61585e4fa..81933b80e6 100644 --- a/src/main/java/rx/observers/AsyncCompletableSubscriber.java +++ b/src/main/java/rx/observers/AsyncCompletableSubscriber.java @@ -1,12 +1,12 @@ /** * Copyright 2016 Netflix, Inc. - * + * * 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. @@ -17,21 +17,20 @@ import java.util.concurrent.atomic.AtomicReference; -import rx.Completable.CompletableSubscriber; +import rx.CompletableSubscriber; import rx.Subscription; -import rx.annotations.Experimental; -import rx.internal.util.RxJavaPluginUtils; +import rx.plugins.RxJavaHooks; /** * An abstract base class for CompletableSubscriber implementations that want to expose an unsubscription * capability. *

    - * Calling {@link #unsubscribe()} and {@link #isUnsubscribed()} is threadsafe and can happen at any time, even + * Calling {@link #unsubscribe()} and {@link #isUnsubscribed()} is thread-safe and can happen at any time, even * before or during an active {@link rx.Completable#subscribe(CompletableSubscriber)} call. *

    * Override the {@link #onStart()} method to execute custom logic on the very first successful onSubscribe call. *

    - * If one wants to remain consistent regarding {@link #isUnsubscribed()} and being terminated, + * If one wants to remain consistent regarding {@link #isUnsubscribed()} and being terminated, * the {@link #clear()} method should be called from the implementing onError and onCompleted methods. *

    *

    
    @@ -40,13 +39,13 @@
      *     public void onStart() {
      *         System.out.println("Started!");
      *     }
    - *     
    + *
      *     @Override
      *     public void onCompleted() {
      *         System.out.println("Completed!");
      *         clear();
      *     }
    - *     
    + *
      *     @Override
      *     public void onError(Throwable e) {
      *         e.printStackTrace();
    @@ -54,47 +53,51 @@
      *     }
      * }
      * 
    - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ -@Experimental public abstract class AsyncCompletableSubscriber implements CompletableSubscriber, Subscription { + /** + * Indicates the unsubscribed state. + */ + static final Unsubscribed UNSUBSCRIBED = new Unsubscribed(); - /** + /** * Holds onto a deferred subscription and allows asynchronous cancellation before the call - * to onSubscribe() by the upstream. + * to onSubscribe() by the upstream. */ private final AtomicReference upstream = new AtomicReference(); - + @Override public final void onSubscribe(Subscription d) { if (!upstream.compareAndSet(null, d)) { d.unsubscribe(); if (upstream.get() != UNSUBSCRIBED) { - RxJavaPluginUtils.handleException(new IllegalStateException("Subscription already set!")); + RxJavaHooks.onError(new IllegalStateException("Subscription already set!")); } } else { onStart(); } } - + /** * Called before the first onSubscribe() call succeeds. */ protected void onStart() { + // default behavior is no op } - + @Override public final boolean isUnsubscribed() { return upstream.get() == UNSUBSCRIBED; } - + /** * Call to clear the upstream's subscription without unsubscribing it. */ protected final void clear() { upstream.set(UNSUBSCRIBED); } - + @Override public final void unsubscribe() { Subscription current = upstream.get(); @@ -104,14 +107,9 @@ public final void unsubscribe() { current.unsubscribe(); } } - + } - - /** - * Indicates the unsubscribed state. - */ - static final Unsubscribed UNSUBSCRIBED = new Unsubscribed(); - + static final class Unsubscribed implements Subscription { @Override @@ -123,6 +121,6 @@ public void unsubscribe() { public boolean isUnsubscribed() { return true; } - + } } diff --git a/src/main/java/rx/observers/Observers.java b/src/main/java/rx/observers/Observers.java index 864d98fbc1..1fe078920d 100644 --- a/src/main/java/rx/observers/Observers.java +++ b/src/main/java/rx/observers/Observers.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -17,8 +17,7 @@ import rx.Observer; import rx.exceptions.OnErrorNotImplementedException; -import rx.functions.Action0; -import rx.functions.Action1; +import rx.functions.*; /** * Helper methods and utilities for creating and working with {@link Observer} objects. @@ -61,7 +60,7 @@ public static Observer empty() { /** * Creates an {@link Observer} that receives the emissions of any {@code Observable} it subscribes to via - * {@link Observer#onNext onNext} but ignores {@link Observer#onCompleted onCompleted} notifications; + * {@link Observer#onNext onNext} but ignores {@link Observer#onCompleted onCompleted} notifications; * it will throw an {@link OnErrorNotImplementedException} if {@link Observer#onError onError} is invoked. * * @param the observed value type @@ -101,7 +100,7 @@ public final void onNext(T args) { * Creates an {@link Observer} that receives the emissions of any {@code Observable} it subscribes to via * {@link Observer#onNext onNext} and handles any {@link Observer#onError onError} notification but ignores * an {@link Observer#onCompleted onCompleted} notification. - * + * * @param the observed value type * @param onNext * a function that handles each item emitted by an {@code Observable} @@ -145,7 +144,7 @@ public final void onNext(T args) { * Creates an {@link Observer} that receives the emissions of any {@code Observable} it subscribes to via * {@link Observer#onNext onNext} and handles any {@link Observer#onError onError} or * {@link Observer#onCompleted onCompleted} notifications. - * + * * @param the observed value type * @param onNext * a function that handles each item emitted by an {@code Observable} diff --git a/src/main/java/rx/observers/SafeCompletableSubscriber.java b/src/main/java/rx/observers/SafeCompletableSubscriber.java index 12258115c7..7430fa371a 100644 --- a/src/main/java/rx/observers/SafeCompletableSubscriber.java +++ b/src/main/java/rx/observers/SafeCompletableSubscriber.java @@ -1,12 +1,12 @@ /** * Copyright 2016 Netflix, Inc. - * + * * 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. @@ -15,26 +15,24 @@ */ package rx.observers; -import rx.Completable.CompletableSubscriber; +import rx.CompletableSubscriber; import rx.Subscription; -import rx.annotations.Experimental; import rx.exceptions.*; -import rx.internal.util.RxJavaPluginUtils; +import rx.plugins.RxJavaHooks; /** * Wraps another CompletableSubscriber and handles exceptions thrown * from onError and onCompleted. - * - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * + * @since 1.3 */ -@Experimental public final class SafeCompletableSubscriber implements CompletableSubscriber, Subscription { final CompletableSubscriber actual; Subscription s; - + boolean done; - + public SafeCompletableSubscriber(CompletableSubscriber actual) { this.actual = actual; } @@ -49,15 +47,15 @@ public void onCompleted() { actual.onCompleted(); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); - + throw new OnCompletedFailedException(ex); } } @Override public void onError(Throwable e) { - RxJavaPluginUtils.handleException(e); if (done) { + RxJavaHooks.onError(e); return; } done = true; @@ -65,7 +63,7 @@ public void onError(Throwable e) { actual.onError(e); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); - + throw new OnErrorFailedException(new CompositeException(e, ex)); } } @@ -81,12 +79,12 @@ public void onSubscribe(Subscription d) { onError(ex); } } - + @Override public void unsubscribe() { s.unsubscribe(); } - + @Override public boolean isUnsubscribed() { return done || s.isUnsubscribed(); diff --git a/src/main/java/rx/observers/SafeSubscriber.java b/src/main/java/rx/observers/SafeSubscriber.java index e2df6ffe96..16027b0d52 100644 --- a/src/main/java/rx/observers/SafeSubscriber.java +++ b/src/main/java/rx/observers/SafeSubscriber.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -18,13 +18,8 @@ import java.util.Arrays; import rx.Subscriber; -import rx.exceptions.CompositeException; -import rx.exceptions.Exceptions; -import rx.exceptions.OnCompletedFailedException; -import rx.exceptions.OnErrorFailedException; -import rx.exceptions.OnErrorNotImplementedException; -import rx.exceptions.UnsubscribeFailedException; -import rx.internal.util.RxJavaPluginUtils; +import rx.exceptions.*; +import rx.plugins.*; /** * {@code SafeSubscriber} is a wrapper around {@code Subscriber} that ensures that the {@code Subscriber} @@ -56,7 +51,7 @@ * * {@code SafeSubscriber} will not synchronize {@code onNext} execution. Use {@link SerializedSubscriber} to do * that. - * + * * @param * the type of item expected by the {@link Subscriber} */ @@ -64,7 +59,7 @@ public class SafeSubscriber extends Subscriber { private final Subscriber actual; - boolean done = false; + boolean done; public SafeSubscriber(Subscriber actual) { super(actual); @@ -86,15 +81,15 @@ public void onCompleted() { // we handle here instead of another method so we don't add stacks to the frame // which can prevent it from being able to handle StackOverflow Exceptions.throwIfFatal(e); - RxJavaPluginUtils.handleException(e); + RxJavaHooks.onError(e); throw new OnCompletedFailedException(e.getMessage(), e); - } finally { + } finally { // NOPMD try { // Similarly to onError if failure occurs in unsubscribe then Rx contract is broken // and we throw an UnsubscribeFailureException. unsubscribe(); } catch (Throwable e) { - RxJavaPluginUtils.handleException(e); + RxJavaHooks.onError(e); throw new UnsubscribeFailedException(e.getMessage(), e); } } @@ -106,7 +101,7 @@ public void onCompleted() { *

    * If the {@code Observable} calls this method, it will not thereafter call {@link #onNext} or * {@link #onCompleted}. - * + * * @param e * the exception encountered by the Observable */ @@ -128,15 +123,15 @@ public void onError(Throwable e) { *

    * The {@code Observable} will not call this method again after it calls either {@link #onCompleted} or * {@link #onError}. - * - * @param args + * + * @param t * the item emitted by the Observable */ @Override - public void onNext(T args) { + public void onNext(T t) { try { if (!done) { - actual.onNext(args); + actual.onNext(t); } } catch (Throwable e) { // we handle here instead of another method so we don't add stacks to the frame @@ -148,55 +143,54 @@ public void onNext(T args) { /** * The logic for {@code onError} without the {@code isFinished} check so it can be called from within * {@code onCompleted}. - * + * * @see the report of this bug */ - protected void _onError(Throwable e) { - RxJavaPluginUtils.handleException(e); + @SuppressWarnings("deprecation") + protected void _onError(Throwable e) { // NOPMD + RxJavaPlugins.getInstance().getErrorHandler().handleError(e); try { actual.onError(e); + } catch (OnErrorNotImplementedException e2) { // NOPMD + /* + * onError isn't implemented so throw + * + * https://github.com/ReactiveX/RxJava/issues/198 + * + * Rx Design Guidelines 5.2 + * + * "when calling the Subscribe method that only has an onNext argument, the OnError behavior + * will be to rethrow the exception on the thread that the message comes out from the observable + * sequence. The OnCompleted behavior in this case is to do nothing." + */ + try { + unsubscribe(); + } catch (Throwable unsubscribeException) { + RxJavaHooks.onError(unsubscribeException); + throw new OnErrorNotImplementedException("Observer.onError not implemented and error while unsubscribing.", new CompositeException(Arrays.asList(e, unsubscribeException))); // NOPMD + } + throw e2; } catch (Throwable e2) { - if (e2 instanceof OnErrorNotImplementedException) { - /* - * onError isn't implemented so throw - * - * https://github.com/ReactiveX/RxJava/issues/198 - * - * Rx Design Guidelines 5.2 - * - * "when calling the Subscribe method that only has an onNext argument, the OnError behavior - * will be to rethrow the exception on the thread that the message comes out from the observable - * sequence. The OnCompleted behavior in this case is to do nothing." - */ - try { - unsubscribe(); - } catch (Throwable unsubscribeException) { - RxJavaPluginUtils.handleException(unsubscribeException); - throw new RuntimeException("Observer.onError not implemented and error while unsubscribing.", new CompositeException(Arrays.asList(e, unsubscribeException))); - } - throw (OnErrorNotImplementedException) e2; - } else { - /* - * throw since the Rx contract is broken if onError failed - * - * https://github.com/ReactiveX/RxJava/issues/198 - */ - RxJavaPluginUtils.handleException(e2); - try { - unsubscribe(); - } catch (Throwable unsubscribeException) { - RxJavaPluginUtils.handleException(unsubscribeException); - throw new OnErrorFailedException("Error occurred when trying to propagate error to Observer.onError and during unsubscription.", new CompositeException(Arrays.asList(e, e2, unsubscribeException))); - } - - throw new OnErrorFailedException("Error occurred when trying to propagate error to Observer.onError", new CompositeException(Arrays.asList(e, e2))); + /* + * throw since the Rx contract is broken if onError failed + * + * https://github.com/ReactiveX/RxJava/issues/198 + */ + RxJavaHooks.onError(e2); + try { + unsubscribe(); + } catch (Throwable unsubscribeException) { + RxJavaHooks.onError(unsubscribeException); + throw new OnErrorFailedException("Error occurred when trying to propagate error to Observer.onError and during unsubscription.", new CompositeException(Arrays.asList(e, e2, unsubscribeException))); } + + throw new OnErrorFailedException("Error occurred when trying to propagate error to Observer.onError", new CompositeException(Arrays.asList(e, e2))); } // if we did not throw above we will unsubscribe here, if onError failed then unsubscribe happens in the catch try { unsubscribe(); - } catch (RuntimeException unsubscribeException) { - RxJavaPluginUtils.handleException(unsubscribeException); + } catch (Throwable unsubscribeException) { + RxJavaHooks.onError(unsubscribeException); throw new OnErrorFailedException(unsubscribeException); } } diff --git a/src/main/java/rx/observers/SerializedObserver.java b/src/main/java/rx/observers/SerializedObserver.java index 1629efe85f..eb3afcf109 100644 --- a/src/main/java/rx/observers/SerializedObserver.java +++ b/src/main/java/rx/observers/SerializedObserver.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -29,7 +29,7 @@ *

  • Adding notifications to a queue if another thread is already emitting
  • *
  • Not holding any locks or blocking any threads while emitting
  • * - * + * * @param * the type of items expected to be observed by the {@code Observer} */ @@ -41,7 +41,6 @@ public class SerializedObserver implements Observer { private volatile boolean terminated; /** If not null, it indicates more work. */ private FastList queue; - private final NotificationLite nl = NotificationLite.instance(); static final class FastList { Object[] array; @@ -83,7 +82,7 @@ public void onNext(T t) { list = new FastList(); queue = list; } - list.add(nl.next(t)); + list.add(NotificationLite.next(t)); return; } emitting = true; @@ -110,7 +109,7 @@ public void onNext(T t) { break; } try { - if (nl.accept(actual, o)) { + if (NotificationLite.accept(actual, o)) { terminated = true; return; } @@ -123,7 +122,7 @@ public void onNext(T t) { } } } - + @Override public void onError(final Throwable e) { Exceptions.throwIfFatal(e); @@ -136,16 +135,16 @@ public void onError(final Throwable e) { } terminated = true; if (emitting) { - /* - * FIXME: generally, errors jump the queue but this wasn't true - * for SerializedObserver and may break existing expectations. + /* + * FIXME: generally, errors jump the queue but this wasn't true + * for SerializedObserver and may break existing expectations. */ FastList list = queue; if (list == null) { list = new FastList(); queue = list; } - list.add(nl.error(e)); + list.add(NotificationLite.error(e)); return; } emitting = true; @@ -169,7 +168,7 @@ public void onCompleted() { list = new FastList(); queue = list; } - list.add(nl.completed()); + list.add(NotificationLite.completed()); return; } emitting = true; diff --git a/src/main/java/rx/observers/SerializedSubscriber.java b/src/main/java/rx/observers/SerializedSubscriber.java index 0b16549da3..b4ee838acc 100644 --- a/src/main/java/rx/observers/SerializedSubscriber.java +++ b/src/main/java/rx/observers/SerializedSubscriber.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -15,8 +15,7 @@ */ package rx.observers; -import rx.Observer; -import rx.Subscriber; +import rx.*; /** * Enforces single-threaded, serialized, ordered execution of {@link #onNext}, {@link #onCompleted}, and @@ -28,7 +27,7 @@ *
  • Adding notifications to a queue if another thread is already emitting
  • *
  • Not holding any locks or blocking any threads while emitting
  • * - * + * * @param * the type of items expected to be emitted to the {@code Subscriber} */ @@ -70,7 +69,7 @@ public void onCompleted() { *

    * If the {@code Observable} calls this method, it will not thereafter call {@link #onNext} or * {@link #onCompleted}. - * + * * @param e * the exception encountered by the Observable */ @@ -86,7 +85,7 @@ public void onError(Throwable e) { *

    * The {@code Observable} will not call this method again after it calls either {@link #onCompleted} or * {@link #onError}. - * + * * @param t * the item emitted by the Observable */ diff --git a/src/main/java/rx/observers/Subscribers.java b/src/main/java/rx/observers/Subscribers.java index 1fd99e3f1c..28176f87e0 100644 --- a/src/main/java/rx/observers/Subscribers.java +++ b/src/main/java/rx/observers/Subscribers.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -28,8 +28,8 @@ private Subscribers() { } /** - * Returns an inert {@link Subscriber} that does nothing in response to the emissions or notifications - * from any {@code Observable} it subscribes to. Will throw an {@link OnErrorNotImplementedException} if {@link Subscriber#onError onError} + * Returns an inert {@link Subscriber} that does nothing in response to the emissions or notifications + * from any {@code Observable} it subscribes to. Will throw an {@link OnErrorNotImplementedException} if {@link Subscriber#onError onError} * method is called * * @param the observed value type @@ -110,7 +110,7 @@ public final void onNext(T args) { * Creates an {@link Subscriber} that receives the emissions of any {@code Observable} it subscribes to via * {@link Subscriber#onNext onNext} and handles any {@link Subscriber#onError onError} notification but * ignores an {@link Subscriber#onCompleted onCompleted} notification. - * + * * @param the observed value type * @param onNext * a function that handles each item emitted by an {@code Observable} @@ -154,7 +154,7 @@ public final void onNext(T args) { * Creates an {@link Subscriber} that receives the emissions of any {@code Observable} it subscribes to via * {@link Subscriber#onNext onNext} and handles any {@link Subscriber#onError onError} or * {@link Subscriber#onCompleted onCompleted} notifications. - * + * * @param the observed value type * @param onNext * a function that handles each item emitted by an {@code Observable} @@ -206,11 +206,11 @@ public final void onNext(T args) { * subscriber and uses the subscription list of * subscriber when {@link Subscriber#add(rx.Subscription)} is * called. - * + * * @param the observed value type * @param subscriber * the Subscriber to wrap. - * + * * @return a new Subscriber that passes all events to * subscriber, has backpressure controlled by * subscriber and uses subscriber to @@ -234,7 +234,7 @@ public void onError(Throwable e) { public void onNext(T t) { subscriber.onNext(t); } - + }; } } diff --git a/src/main/java/rx/observers/TestObserver.java b/src/main/java/rx/observers/TestObserver.java index fa7d19dd61..dfe5dcad38 100644 --- a/src/main/java/rx/observers/TestObserver.java +++ b/src/main/java/rx/observers/TestObserver.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -24,15 +24,15 @@ /** * Observer usable for unit testing to perform assertions, inspect received events or wrap a mocked Observer. * @param the observed value type - * @deprecated use the {@link TestSubscriber} insteand. + * @deprecated use the {@link TestSubscriber} instead. */ @Deprecated public class TestObserver implements Observer { private final Observer delegate; - private final ArrayList onNextEvents = new ArrayList(); - private final ArrayList onErrorEvents = new ArrayList(); - private final ArrayList> onCompletedEvents = new ArrayList>(); + private final List onNextEvents = new ArrayList(); + private final List onErrorEvents = new ArrayList(); + private final List> onCompletedEvents = new ArrayList>(); public TestObserver(Observer delegate) { this.delegate = delegate; @@ -132,8 +132,8 @@ public void assertReceivedOnNext(List items) { assertionError("Value at index: " + i + " expected to be [null] but was: [" + actual + "]\n"); } } else if (!expected.equals(actual)) { - assertionError("Value at index: " + i - + " expected to be [" + expected + "] (" + expected.getClass().getSimpleName() + assertionError("Value at index: " + i + + " expected to be [" + expected + "] (" + expected.getClass().getSimpleName() + ") but was: [" + actual + "] (" + (actual != null ? actual.getClass().getSimpleName() : "null") + ")\n"); } @@ -160,7 +160,7 @@ public void assertTerminalEvent() { assertionError("Received both an onError and onCompleted. Should be one or the other."); } - if (onCompletedEvents.size() == 0 && onErrorEvents.size() == 0) { + if (onCompletedEvents.isEmpty() && onErrorEvents.isEmpty()) { assertionError("No terminal events received."); } } @@ -172,30 +172,29 @@ public void assertTerminalEvent() { */ final void assertionError(String message) { StringBuilder b = new StringBuilder(message.length() + 32); - - b.append(message); - - - b.append(" ("); + + b.append(message) + .append(" ("); + int c = onCompletedEvents.size(); - b.append(c); - b.append(" completion"); + b.append(c) + .append(" completion"); if (c != 1) { - b.append("s"); + b.append('s'); } - b.append(")"); - + b.append(')'); + if (!onErrorEvents.isEmpty()) { int size = onErrorEvents.size(); b.append(" (+") .append(size) .append(" error"); if (size != 1) { - b.append("s"); + b.append('s'); } - b.append(")"); + b.append(')'); } - + AssertionError ae = new AssertionError(b.toString()); if (!onErrorEvents.isEmpty()) { if (onErrorEvents.size() == 1) { @@ -206,24 +205,24 @@ final void assertionError(String message) { } throw ae; } - + // do nothing ... including swallowing errors - private static Observer INERT = new Observer() { + private static final Observer INERT = new Observer() { @Override public void onCompleted() { - + // deliberately ignored } @Override public void onError(Throwable e) { - + // deliberately ignored } @Override public void onNext(Object t) { - + // deliberately ignored } - + }; } diff --git a/src/main/java/rx/observers/TestSubscriber.java b/src/main/java/rx/observers/TestSubscriber.java index ce973fe861..d35602f572 100644 --- a/src/main/java/rx/observers/TestSubscriber.java +++ b/src/main/java/rx/observers/TestSubscriber.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -20,7 +20,6 @@ import rx.*; import rx.Observer; -import rx.annotations.Experimental; import rx.exceptions.CompositeException; /** @@ -31,19 +30,19 @@ public class TestSubscriber extends Subscriber { private final Observer delegate; - + private final List values; - + private final List errors; - + /** The number of onCompleted() calls. */ private int completions; - + private final CountDownLatch latch = new CountDownLatch(1); - + /** Written after an onNext value has been added to the {@link #values} list. */ private volatile int valueCount; - + private volatile Thread lastSeenThread; /** The shared no-op observer. */ private static final Observer INERT = new Observer() { @@ -75,7 +74,7 @@ public void onNext(Object t) { public TestSubscriber(long initialRequest) { this((Observer)INERT, initialRequest); } - + /** * Constructs a TestSubscriber with the initial request to be requested from upstream * and a delegate Observer to wrap. @@ -93,7 +92,7 @@ public TestSubscriber(Observer delegate, long initialRequest) { if (initialRequest >= 0L) { this.request(initialRequest); } - + this.values = new ArrayList(); this.errors = new ArrayList(); } @@ -136,7 +135,7 @@ public TestSubscriber() { public static TestSubscriber create() { return new TestSubscriber(); } - + /** * Factory method to construct a TestSubscriber with the given initial request amount and no delegation. * @param the value type @@ -147,7 +146,7 @@ public static TestSubscriber create() { public static TestSubscriber create(long initialRequest) { return new TestSubscriber(initialRequest); } - + /** * Factory method to construct a TestSubscriber which delegates events to the given Observer and * issues the given initial request amount. @@ -174,7 +173,7 @@ public static TestSubscriber create(Observer delegate, long initialReq public static TestSubscriber create(Subscriber delegate) { return new TestSubscriber(delegate); } - + /** * Factory method to construct a TestSubscriber which delegates events to the given Observer and * an issues an initial request of Long.MAX_VALUE. @@ -187,7 +186,7 @@ public static TestSubscriber create(Subscriber delegate) { public static TestSubscriber create(Observer delegate) { return new TestSubscriber(delegate); } - + /** * Notifies the Subscriber that the {@code Observable} has finished sending push-based notifications. *

    @@ -209,7 +208,7 @@ public void onCompleted() { * completion via {@link #onCompleted}, as a {@link List}. * * @return a list of Notifications representing calls to this Subscriber's {@link #onCompleted} method - * + * * @deprecated use {@link #getCompletions()} instead. */ @Deprecated @@ -221,13 +220,12 @@ public List> getOnCompletedEvents() { } return result; } - + /** * Returns the number of times onCompleted was called on this TestSubscriber. * @return the number of times onCompleted was called on this TestSubscriber. - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Experimental public final int getCompletions() { return completions; } @@ -237,7 +235,7 @@ public final int getCompletions() { *

    * If the {@code Observable} calls this method, it will not thereafter call {@link #onNext} or * {@link #onCompleted}. - * + * * @param e * the exception encountered by the Observable */ @@ -269,7 +267,7 @@ public List getOnErrorEvents() { *

    * The {@code Observable} will not call this method again after it calls either {@link #onCompleted} or * {@link #onError}. - * + * * @param t * the item emitted by the Observable */ @@ -280,7 +278,7 @@ public void onNext(T t) { valueCount = values.size(); delegate.onNext(t); } - + /** * Returns the committed number of onNext elements that are safe to be * read from {@link #getOnNextEvents()} other threads. @@ -289,7 +287,7 @@ public void onNext(T t) { public final int getValueCount() { return valueCount; } - + /** * Allows calling the protected {@link #request(long)} from unit tests. * @@ -328,19 +326,22 @@ public void assertReceivedOnNext(List items) { } for (int i = 0; i < items.size(); i++) { - T expected = items.get(i); - T actual = values.get(i); - if (expected == null) { - // check for null equality - if (actual != null) { - assertionError("Value at index: " + i + " expected to be [null] but was: [" + actual + "]\n"); - } - } else if (!expected.equals(actual)) { - assertionError("Value at index: " + i - + " expected to be [" + expected + "] (" + expected.getClass().getSimpleName() - + ") but was: [" + actual + "] (" + (actual != null ? actual.getClass().getSimpleName() : "null") + ")\n"); + assertItem(items.get(i), i); + } + } + private void assertItem(T expected, int i) { + T actual = values.get(i); + if (expected == null) { + // check for null equality + if (actual != null) { + assertionError("Value at index: " + i + " expected: [null] but was: [" + actual + "]\n"); } + } else if (!expected.equals(actual)) { + assertionError("Value at index: " + i + + " expected: [" + expected + "] (" + expected.getClass().getSimpleName() + + ") but was: [" + actual + "] (" + (actual != null ? actual.getClass().getSimpleName() : "null") + ")\n"); + } } @@ -352,18 +353,21 @@ public void assertReceivedOnNext(List items) { * @param timeout the time to wait for the events * @param unit the time unit of waiting * @return true if the expected number of onNext events happened - * @throws InterruptedException if the sleep is interrupted - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @throws RuntimeException if the sleep is interrupted + * @since 1.3 */ - @Experimental - public final boolean awaitValueCount(int expected, long timeout, TimeUnit unit) throws InterruptedException { + public final boolean awaitValueCount(int expected, long timeout, TimeUnit unit) { while (timeout != 0 && valueCount < expected) { - unit.sleep(1); + try { + unit.sleep(1); + } catch (InterruptedException e) { + throw new IllegalStateException("Interrupted", e); + } timeout--; } return valueCount >= expected; } - + /** * Asserts that a single terminal event occurred, either {@link #onCompleted} or {@link #onError}. * @@ -383,7 +387,7 @@ public void assertTerminalEvent() { assertionError("Received both an onError and onCompleted. Should be one or the other."); } - if (completions == 0 && errors.size() == 0) { + if (completions == 0 && errors.isEmpty()) { assertionError("No terminal events received."); } } @@ -402,18 +406,18 @@ public void assertUnsubscribed() { /** * Asserts that this {@code Subscriber} has received no {@code onError} notifications. - * + * * @throws AssertionError * if this {@code Subscriber} has received one or more {@code onError} notifications */ public void assertNoErrors() { List onErrorEvents = getOnErrorEvents(); - if (onErrorEvents.size() > 0) { + if (!onErrorEvents.isEmpty()) { assertionError("Unexpected onError events"); } } - + /** * Blocks until this {@link Subscriber} receives a notification that the {@code Observable} is complete * (either an {@code onCompleted} or {@code onError} notification). @@ -425,7 +429,7 @@ public void awaitTerminalEvent() { try { latch.await(); } catch (InterruptedException e) { - throw new RuntimeException("Interrupted", e); + throw new IllegalStateException("Interrupted", e); } } @@ -444,7 +448,7 @@ public void awaitTerminalEvent(long timeout, TimeUnit unit) { try { latch.await(timeout, unit); } catch (InterruptedException e) { - throw new RuntimeException("Interrupted", e); + throw new IllegalStateException("Interrupted", e); } } @@ -481,7 +485,7 @@ public void awaitTerminalEventAndUnsubscribeOnTimeout(long timeout, TimeUnit uni public Thread getLastSeenThread() { return lastSeenThread; } - + /** * Asserts that there is exactly one completion event. * @@ -524,7 +528,7 @@ public void assertNotCompleted() { */ public void assertError(Class clazz) { List err = errors; - if (err.size() == 0) { + if (err.isEmpty()) { assertionError("No errors"); } else if (err.size() > 1) { @@ -549,7 +553,7 @@ public void assertError(Class clazz) { */ public void assertError(Throwable throwable) { List err = errors; - if (err.size() == 0) { + if (err.isEmpty()) { assertionError("No errors"); } else if (err.size() > 1) { @@ -569,7 +573,7 @@ public void assertError(Throwable throwable) { public void assertNoTerminalEvent() { List err = errors; int s = completions; - if (err.size() > 0 || s > 0) { + if (!err.isEmpty() || s > 0) { if (err.isEmpty()) { assertionError("Found " + err.size() + " errors and " + s + " completion events instead of none"); } else @@ -607,7 +611,7 @@ public void assertValueCount(int count) { assertionError("Number of onNext events differ; expected: " + count + ", actual: " + s); } } - + /** * Asserts that the received onNext events, in order, are the specified items. * @@ -629,7 +633,7 @@ public void assertValues(T... values) { public void assertValue(T value) { assertReceivedOnNext(Collections.singletonList(value)); } - + /** * Combines an assertion error message with the current completion and error state of this * TestSubscriber, giving more information when some assertXXX check fails. @@ -637,30 +641,29 @@ public void assertValue(T value) { */ final void assertionError(String message) { StringBuilder b = new StringBuilder(message.length() + 32); - - b.append(message); - - - b.append(" ("); + + b.append(message) + .append(" ("); + int c = completions; - b.append(c); - b.append(" completion"); + b.append(c) + .append(" completion"); if (c != 1) { - b.append("s"); + b.append('s'); } - b.append(")"); - + b.append(')'); + if (!errors.isEmpty()) { int size = errors.size(); b.append(" (+") .append(size) .append(" error"); if (size != 1) { - b.append("s"); + b.append('s'); } - b.append(")"); + b.append(')'); } - + AssertionError ae = new AssertionError(b.toString()); if (!errors.isEmpty()) { if (errors.size() == 1) { @@ -671,4 +674,35 @@ final void assertionError(String message) { } throw ae; } + + /** + * Assert that the TestSubscriber contains the given first and optional rest values exactly + * and if so, clears the internal list of values. + *

    + *

    +     * TestSubscriber ts = new TestSubscriber();
    +     *
    +     * ts.onNext(1);
    +     *
    +     * ts.assertValuesAndClear(1);
    +     *
    +     * ts.onNext(2);
    +     * ts.onNext(3);
    +     *
    +     * ts.assertValuesAndClear(2, 3); // no mention of 1
    +     * 
    + * @param expectedFirstValue the expected first value + * @param expectedRestValues the optional rest values + * @since 1.3 + */ + public final void assertValuesAndClear(T expectedFirstValue, T... expectedRestValues) { + int n = 1 + expectedRestValues.length; + assertValueCount(n); + assertItem(expectedFirstValue, 0); + for (int i = 0; i < expectedRestValues.length; i++) { + assertItem(expectedRestValues[i], i + 1); + } + values.clear(); + valueCount = 0; + } } diff --git a/src/main/java/rx/package-info.java b/src/main/java/rx/package-info.java index cae651eab2..9a3ff1a612 100644 --- a/src/main/java/rx/package-info.java +++ b/src/main/java/rx/package-info.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -16,7 +16,7 @@ /** * Base reactive classes: Observable, Single and Completable; base reactive consumers; * other common base interfaces. - * + * *

    A library that enables subscribing to and composing asynchronous events and * callbacks.

    *

    The Observable/Observer interfaces and associated operators (in @@ -26,8 +26,8 @@ * More information can be found at http://msdn.microsoft.com/en-us/data/gg577609. *

    - * - * + * + * *

    Compared with the Microsoft implementation: *

      *
    • Observable == IObservable
    • diff --git a/src/main/java/rx/plugins/RxJavaCompletableExecutionHook.java b/src/main/java/rx/plugins/RxJavaCompletableExecutionHook.java index 287289242d..d759d0d021 100644 --- a/src/main/java/rx/plugins/RxJavaCompletableExecutionHook.java +++ b/src/main/java/rx/plugins/RxJavaCompletableExecutionHook.java @@ -16,7 +16,6 @@ package rx.plugins; import rx.*; -import rx.annotations.Experimental; import rx.functions.Func1; /** @@ -35,23 +34,22 @@ * should be fast. If anything time-consuming is to be done it should be spawned asynchronously onto separate * worker threads. * - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ -@Experimental -public abstract class RxJavaCompletableExecutionHook { +public abstract class RxJavaCompletableExecutionHook { // NOPMD /** - * Invoked during the construction by {@link Completable#create(Completable.CompletableOnSubscribe)} + * Invoked during the construction by {@link Completable#create(Completable.OnSubscribe)} *

      * This can be used to decorate or replace the onSubscribe function or just perform extra * logging, metrics and other such things and pass through the function. * * @param f - * original {@link rx.Completable.CompletableOnSubscribe}<{@code T}> to be executed - * @return {@link rx.Completable.CompletableOnSubscribe} function that can be modified, decorated, replaced or just + * original {@link rx.Completable.OnSubscribe}<{@code T}> to be executed + * @return {@link rx.Completable.OnSubscribe} function that can be modified, decorated, replaced or just * returned as a pass through */ @Deprecated - public Completable.CompletableOnSubscribe onCreate(Completable.CompletableOnSubscribe f) { + public Completable.OnSubscribe onCreate(Completable.OnSubscribe f) { return f; } @@ -63,12 +61,12 @@ public Completable.CompletableOnSubscribe onCreate(Completable.CompletableOnSubs * * @param completableInstance the target completable instance * @param onSubscribe - * original {@link rx.Completable.CompletableOnSubscribe}<{@code T}> to be executed - * @return {@link rx.Completable.CompletableOnSubscribe}<{@code T}> function that can be modified, decorated, replaced or just + * original {@link rx.Completable.OnSubscribe}<{@code T}> to be executed + * @return {@link rx.Completable.OnSubscribe}<{@code T}> function that can be modified, decorated, replaced or just * returned as a pass through */ @Deprecated - public Completable.CompletableOnSubscribe onSubscribeStart(Completable completableInstance, final Completable.CompletableOnSubscribe onSubscribe) { + public Completable.OnSubscribe onSubscribeStart(Completable completableInstance, final Completable.OnSubscribe onSubscribe) { // pass through by default return onSubscribe; } @@ -93,16 +91,16 @@ public Throwable onSubscribeError(Throwable e) { * Invoked just as the operator functions is called to bind two operations together into a new * {@link Completable} and the return value is used as the lifted function *

      - * This can be used to decorate or replace the {@link rx.Completable.CompletableOperator} instance or just perform extra + * This can be used to decorate or replace the {@link rx.Completable.Operator} instance or just perform extra * logging, metrics and other such things and pass through the onSubscribe. * * @param lift - * original {@link rx.Completable.CompletableOperator}{@code } - * @return {@link rx.Completable.CompletableOperator}{@code } function that can be modified, decorated, replaced or just + * original {@link rx.Completable.Operator}{@code } + * @return {@link rx.Completable.Operator}{@code } function that can be modified, decorated, replaced or just * returned as a pass through */ @Deprecated - public Completable.CompletableOperator onLift(final Completable.CompletableOperator lift) { + public Completable.Operator onLift(final Completable.Operator lift) { return lift; } } diff --git a/src/main/java/rx/plugins/RxJavaErrorHandler.java b/src/main/java/rx/plugins/RxJavaErrorHandler.java index 8107fab457..4c0dfba9d0 100644 --- a/src/main/java/rx/plugins/RxJavaErrorHandler.java +++ b/src/main/java/rx/plugins/RxJavaErrorHandler.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -16,7 +16,6 @@ package rx.plugins; import rx.*; -import rx.annotations.Beta; import rx.exceptions.Exceptions; /** @@ -31,7 +30,9 @@ * See {@link RxJavaPlugins} or the RxJava GitHub Wiki for information on configuring plugins: https://github.com/ReactiveX/RxJava/wiki/Plugins. */ -public abstract class RxJavaErrorHandler { +public abstract class RxJavaErrorHandler { // NOPMD + + protected static final String ERROR_IN_RENDERING_SUFFIX = ".errorRendering"; /** * Receives all {@code Exception}s from an {@link Observable} passed to @@ -39,7 +40,7 @@ public abstract class RxJavaErrorHandler { *

      * This should never throw an {@code Exception}. Make sure to try/catch({@code Throwable}) all code * inside this method implementation. - * + * * @param e * the {@code Exception} */ @@ -48,8 +49,6 @@ public void handleError(Throwable e) { // do nothing by default } - protected static final String ERROR_IN_RENDERING_SUFFIX = ".errorRendering"; - /** * Receives items causing {@code OnErrorThrowable.OnNextValue} and gives a chance to choose the String * representation of the item in the {@code OnNextValue} stacktrace rendering. Returns {@code null} if this @@ -57,17 +56,15 @@ public void handleError(Throwable e) { *

      * Note that primitive types are always rendered as their {@code toString()} value. *

      - * If a {@code Throwable} is caught when rendering, this will fallback to the item's classname suffixed by + * If a {@code Throwable} is caught when rendering, this will fallback to the item's class name suffixed by * {@code ERROR_IN_RENDERING_SUFFIX}. * * @param item the last emitted item, that caused the exception wrapped in * {@code OnErrorThrowable.OnNextValue} * @return a short {@link String} representation of the item if one is known for its type, or null for * default - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the - * release number) + * @since 1.3 */ - @Beta public final String handleOnNextValueRendering(Object item) { try { @@ -87,7 +84,7 @@ public final String handleOnNextValueRendering(Object item) { * {@code String} (as large renderings will bloat up the stacktrace). Prefer to try/catch({@code Throwable}) * all code inside this method implementation. *

      - * If a {@code Throwable} is caught when rendering, this will fallback to the item's classname suffixed by + * If a {@code Throwable} is caught when rendering, this will fallback to the item's class name suffixed by * {@value #ERROR_IN_RENDERING_SUFFIX}. * * @param item the last emitted item, that caused the exception wrapped in @@ -95,10 +92,8 @@ public final String handleOnNextValueRendering(Object item) { * @return a short {@link String} representation of the item if one is known for its type, or null for * default * @throws InterruptedException if the rendering thread is interrupted - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the - * release number) + * @since 1.3 */ - @Beta protected String render (Object item) throws InterruptedException { //do nothing by default return null; diff --git a/src/main/java/rx/plugins/RxJavaHooks.java b/src/main/java/rx/plugins/RxJavaHooks.java index a5b621ea19..106b9e627f 100644 --- a/src/main/java/rx/plugins/RxJavaHooks.java +++ b/src/main/java/rx/plugins/RxJavaHooks.java @@ -1,12 +1,12 @@ /** * Copyright 2016 Netflix, Inc. - * + * * 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. @@ -16,11 +16,9 @@ package rx.plugins; import java.lang.Thread.UncaughtExceptionHandler; +import java.util.concurrent.ScheduledExecutorService; import rx.*; -import rx.Completable.CompletableOnSubscribe; -import rx.Observable.*; -import rx.annotations.Experimental; import rx.functions.*; import rx.internal.operators.*; @@ -29,55 +27,72 @@ * points as well as Scheduler hooks. *

      * The class features a lockdown state, see {@link #lockdown()} and {@link #isLockdown()}, to - * prevent further changes to the hooks. + * prevent further changes to the hooks. + * @since 1.3 */ -@Experimental public final class RxJavaHooks { - /** Utility class. */ - private RxJavaHooks() { - throw new IllegalStateException("No instances!"); - } - /** * Prevents changing the hook callbacks when set to true. */ /* test */ static volatile boolean lockdown; - + static volatile Action1 onError; - + @SuppressWarnings("rawtypes") static volatile Func1 onObservableCreate; @SuppressWarnings("rawtypes") static volatile Func1 onSingleCreate; - static volatile Func1 onCompletableCreate; + static volatile Func1 onCompletableCreate; @SuppressWarnings("rawtypes") static volatile Func2 onObservableStart; @SuppressWarnings("rawtypes") - static volatile Func2 onSingleStart; + static volatile Func2 onSingleStart; - static volatile Func2 onCompletableStart; + static volatile Func2 onCompletableStart; static volatile Func1 onComputationScheduler; static volatile Func1 onIOScheduler; static volatile Func1 onNewThreadScheduler; - + static volatile Func1 onScheduleAction; static volatile Func1 onObservableReturn; static volatile Func1 onSingleReturn; + static volatile Func0 onGenericScheduledExecutorService; + + static volatile Func1 onObservableSubscribeError; + + static volatile Func1 onSingleSubscribeError; + + static volatile Func1 onCompletableSubscribeError; + + @SuppressWarnings("rawtypes") + static volatile Func1 onObservableLift; + + @SuppressWarnings("rawtypes") + static volatile Func1 onSingleLift; + + static volatile Func1 onCompletableLift; + /** Initialize with the default delegation to the original RxJavaPlugins. */ static { init(); } - + + /** Utility class. */ + private RxJavaHooks() { + throw new IllegalStateException("No instances!"); + } + + /** * Initialize the hooks via delegating to RxJavaPlugins. */ @@ -89,65 +104,46 @@ public void call(Throwable e) { RxJavaPlugins.getInstance().getErrorHandler().handleError(e); } }; - - RxJavaPlugins.getInstance().getObservableExecutionHook(); - - onObservableCreate = new Func1() { - @Override - public OnSubscribe call(OnSubscribe f) { - return RxJavaPlugins.getInstance().getObservableExecutionHook().onCreate(f); - } - }; - - onObservableStart = new Func2() { + + onObservableStart = new Func2() { @Override - public OnSubscribe call(Observable t1, OnSubscribe t2) { + public Observable.OnSubscribe call(Observable t1, Observable.OnSubscribe t2) { return RxJavaPlugins.getInstance().getObservableExecutionHook().onSubscribeStart(t1, t2); } }; - + onObservableReturn = new Func1() { @Override public Subscription call(Subscription f) { return RxJavaPlugins.getInstance().getObservableExecutionHook().onSubscribeReturn(f); } }; - - RxJavaPlugins.getInstance().getSingleExecutionHook(); - onSingleCreate = new Func1() { + onSingleStart = new Func2() { @Override - public rx.Single.OnSubscribe call(rx.Single.OnSubscribe f) { - return RxJavaPlugins.getInstance().getSingleExecutionHook().onCreate(f); - } - }; - - onSingleStart = new Func2() { - @Override - public Observable.OnSubscribe call(Single t1, Observable.OnSubscribe t2) { - return RxJavaPlugins.getInstance().getSingleExecutionHook().onSubscribeStart(t1, t2); + public Single.OnSubscribe call(Single t1, Single.OnSubscribe t2) { + + RxJavaSingleExecutionHook hook = RxJavaPlugins.getInstance().getSingleExecutionHook(); + + if (hook == RxJavaSingleExecutionHookDefault.getInstance()) { + return t2; + } + + return new SingleFromObservable(hook.onSubscribeStart(t1, + new SingleToObservable(t2))); } }; - + onSingleReturn = new Func1() { @Override public Subscription call(Subscription f) { return RxJavaPlugins.getInstance().getSingleExecutionHook().onSubscribeReturn(f); } }; - - RxJavaPlugins.getInstance().getCompletableExecutionHook(); - onCompletableCreate = new Func1() { + onCompletableStart = new Func2() { @Override - public CompletableOnSubscribe call(CompletableOnSubscribe f) { - return RxJavaPlugins.getInstance().getCompletableExecutionHook().onCreate(f); - } - }; - - onCompletableStart = new Func2() { - @Override - public Completable.CompletableOnSubscribe call(Completable t1, Completable.CompletableOnSubscribe t2) { + public Completable.OnSubscribe call(Completable t1, Completable.OnSubscribe t2) { return RxJavaPlugins.getInstance().getCompletableExecutionHook().onSubscribeStart(t1, t2); } }; @@ -158,11 +154,79 @@ public Action0 call(Action0 a) { return RxJavaPlugins.getInstance().getSchedulersHook().onSchedule(a); } }; + + onObservableSubscribeError = new Func1() { + @Override + public Throwable call(Throwable t) { + return RxJavaPlugins.getInstance().getObservableExecutionHook().onSubscribeError(t); + } + }; + + onObservableLift = new Func1() { + @Override + public Observable.Operator call(Observable.Operator t) { + return RxJavaPlugins.getInstance().getObservableExecutionHook().onLift(t); + } + }; + + onSingleSubscribeError = new Func1() { + @Override + public Throwable call(Throwable t) { + return RxJavaPlugins.getInstance().getSingleExecutionHook().onSubscribeError(t); + } + }; + + onSingleLift = new Func1() { + @Override + public Observable.Operator call(Observable.Operator t) { + return RxJavaPlugins.getInstance().getSingleExecutionHook().onLift(t); + } + }; + + onCompletableSubscribeError = new Func1() { + @Override + public Throwable call(Throwable t) { + return RxJavaPlugins.getInstance().getCompletableExecutionHook().onSubscribeError(t); + } + }; + + onCompletableLift = new Func1() { + @Override + public Completable.Operator call(Completable.Operator t) { + return RxJavaPlugins.getInstance().getCompletableExecutionHook().onLift(t); + } + }; + + initCreate(); } - + + @SuppressWarnings({ "rawtypes", "unchecked", "deprecation" }) + static void initCreate() { + onObservableCreate = new Func1() { + @Override + public Observable.OnSubscribe call(Observable.OnSubscribe f) { + return RxJavaPlugins.getInstance().getObservableExecutionHook().onCreate(f); + } + }; + + onSingleCreate = new Func1() { + @Override + public rx.Single.OnSubscribe call(rx.Single.OnSubscribe f) { + return RxJavaPlugins.getInstance().getSingleExecutionHook().onCreate(f); + } + }; + + onCompletableCreate = new Func1() { + @Override + public Completable.OnSubscribe call(Completable.OnSubscribe f) { + return RxJavaPlugins.getInstance().getCompletableExecutionHook().onCreate(f); + } + }; + } + /** * Reset all hook callbacks to those of the current RxJavaPlugins handlers. - * + * * @see #clear() */ public static void reset() { @@ -170,13 +234,18 @@ public static void reset() { return; } init(); + + onComputationScheduler = null; + onIOScheduler = null; + onNewThreadScheduler = null; + onGenericScheduledExecutorService = null; } /** - * Clears all hooks to be no-operations (and passthroughs) + * Clears all hooks to be no-op (and pass-through) * and onError hook to signal errors to the caller thread's * UncaughtExceptionHandler. - * + * * @see #reset() */ public static void clear() { @@ -184,30 +253,39 @@ public static void clear() { return; } onError = null; - + onObservableCreate = null; onObservableStart = null; onObservableReturn = null; - + onObservableSubscribeError = null; + onObservableLift = null; + onSingleCreate = null; onSingleStart = null; onSingleReturn = null; - + onSingleSubscribeError = null; + onSingleLift = null; + onCompletableCreate = null; onCompletableStart = null; - + onCompletableSubscribeError = null; + onCompletableLift = null; + onComputationScheduler = null; onIOScheduler = null; onNewThreadScheduler = null; + + onScheduleAction = null; + onGenericScheduledExecutorService = null; } /** - * Prevents changing a hooks. + * Prevents changing the hooks. */ public static void lockdown() { lockdown = true; } - + /** * Returns true if the hooks can no longer be changed. * @return true if the hooks can no longer be changed @@ -222,13 +300,30 @@ public static boolean isLockdown() { public static void onError(Throwable ex) { Action1 f = onError; if (f != null) { - f.call(ex); - return; + try { + f.call(ex); + return; + } catch (Throwable pluginException) { + /* + * We don't want errors from the plugin to affect normal flow. + * Since the plugin should never throw this is a safety net + * and will complain loudly to System.err so it gets fixed. + */ + System.err.println("The onError handler threw an Exception. It shouldn't. => " + pluginException.getMessage()); // NOPMD + pluginException.printStackTrace(); // NOPMD + + signalUncaught(pluginException); + } } + signalUncaught(ex); + } + + static void signalUncaught(Throwable ex) { Thread current = Thread.currentThread(); - current.getUncaughtExceptionHandler().uncaughtException(current, ex); + UncaughtExceptionHandler handler = current.getUncaughtExceptionHandler(); + handler.uncaughtException(current, ex); } - + /** * Hook to call when an Observable is created. * @param the value type @@ -237,7 +332,7 @@ public static void onError(Throwable ex) { */ @SuppressWarnings({ "rawtypes", "unchecked" }) public static Observable.OnSubscribe onCreate(Observable.OnSubscribe onSubscribe) { - Func1 f = onObservableCreate; + Func1 f = onObservableCreate; if (f != null) { return f.call(onSubscribe); } @@ -264,18 +359,18 @@ public static Single.OnSubscribe onCreate(Single.OnSubscribe onSubscri * @param onSubscribe the original OnSubscribe logic * @return the original or replacement OnSubscribe instance */ - public static Completable.CompletableOnSubscribe onCreate(Completable.CompletableOnSubscribe onSubscribe) { - Func1 f = onCompletableCreate; + public static Completable.OnSubscribe onCreate(Completable.OnSubscribe onSubscribe) { + Func1 f = onCompletableCreate; if (f != null) { return f.call(onSubscribe); } return onSubscribe; } - + /** * Hook to call when the Schedulers.computation() is called. * @param scheduler the default computation scheduler - * @return the default of alternative scheduler + * @return the default of alternative scheduler */ public static Scheduler onComputationScheduler(Scheduler scheduler) { Func1 f = onComputationScheduler; @@ -288,7 +383,7 @@ public static Scheduler onComputationScheduler(Scheduler scheduler) { /** * Hook to call when the Schedulers.io() is called. * @param scheduler the default io scheduler - * @return the default of alternative scheduler + * @return the default of alternative scheduler */ public static Scheduler onIOScheduler(Scheduler scheduler) { Func1 f = onIOScheduler; @@ -301,7 +396,7 @@ public static Scheduler onIOScheduler(Scheduler scheduler) { /** * Hook to call when the Schedulers.newThread() is called. * @param scheduler the default new thread scheduler - * @return the default of alternative scheduler + * @return the default of alternative scheduler */ public static Scheduler onNewThreadScheduler(Scheduler scheduler) { Func1 f = onNewThreadScheduler; @@ -333,14 +428,14 @@ public static Action0 onScheduledAction(Action0 action) { * @return the original or alternative action that will be subscribed to */ @SuppressWarnings({ "rawtypes", "unchecked" }) - public static OnSubscribe onObservableStart(Observable instance, OnSubscribe onSubscribe) { - Func2 f = onObservableStart; + public static Observable.OnSubscribe onObservableStart(Observable instance, Observable.OnSubscribe onSubscribe) { + Func2 f = onObservableStart; if (f != null) { return f.call(instance, onSubscribe); } return onSubscribe; } - + /** * Hook to call before the Observable.subscribe() method is about to return a Subscription. * @param subscription the original subscription @@ -360,7 +455,10 @@ public static Subscription onObservableReturn(Subscription subscription) { * @return the original error or alternative Throwable to be thrown */ public static Throwable onObservableError(Throwable error) { - // TODO add hook + Func1 f = onObservableSubscribeError; + if (f != null) { + return f.call(error); + } return error; } @@ -371,8 +469,12 @@ public static Throwable onObservableError(Throwable error) { * @param operator the original operator * @return the original or alternative operator that will be subscribed to */ - public static Operator onObservableLift(Operator operator) { - // TODO add hook + @SuppressWarnings({ "rawtypes", "unchecked" }) + public static Observable.Operator onObservableLift(Observable.Operator operator) { + Func1 f = onObservableLift; + if (f != null) { + return f.call(operator); + } return operator; } @@ -384,8 +486,8 @@ public static Operator onObservableLift(Operator operator) { * @return the original or alternative action that will be subscribed to */ @SuppressWarnings({ "rawtypes", "unchecked" }) - public static Observable.OnSubscribe onSingleStart(Single instance, Observable.OnSubscribe onSubscribe) { - Func2 f = onSingleStart; + public static Single.OnSubscribe onSingleStart(Single instance, Single.OnSubscribe onSubscribe) { + Func2 f = onSingleStart; if (f != null) { return f.call(instance, onSubscribe); } @@ -411,7 +513,10 @@ public static Subscription onSingleReturn(Subscription subscription) { * @return the original error or alternative Throwable to be thrown */ public static Throwable onSingleError(Throwable error) { - // TODO add hook + Func1 f = onSingleSubscribeError; + if (f != null) { + return f.call(error); + } return error; } @@ -422,8 +527,12 @@ public static Throwable onSingleError(Throwable error) { * @param operator the original operator * @return the original or alternative operator that will be subscribed to */ - public static Operator onSingleLift(Operator operator) { - // TODO add hook + @SuppressWarnings({ "rawtypes", "unchecked" }) + public static Observable.Operator onSingleLift(Observable.Operator operator) { + Func1 f = onSingleLift; + if (f != null) { + return f.call(operator); + } return operator; } @@ -434,8 +543,8 @@ public static Operator onSingleLift(Operator operator) { * @param onSubscribe the original OnSubscribe action * @return the original or alternative action that will be subscribed to */ - public static Completable.CompletableOnSubscribe onCompletableStart(Completable instance, Completable.CompletableOnSubscribe onSubscribe) { - Func2 f = onCompletableStart; + public static Completable.OnSubscribe onCompletableStart(Completable instance, Completable.OnSubscribe onSubscribe) { + Func2 f = onCompletableStart; if (f != null) { return f.call(instance, onSubscribe); } @@ -448,7 +557,10 @@ public static Completable.CompletableOnSubscribe onCompletableStart(Completa * @return the original error or alternative Throwable to be thrown */ public static Throwable onCompletableError(Throwable error) { - // TODO add hook + Func1 f = onCompletableSubscribeError; + if (f != null) { + return f.call(error); + } return error; } @@ -459,18 +571,21 @@ public static Throwable onCompletableError(Throwable error) { * @param operator the original operator * @return the original or alternative operator that will be subscribed to */ - public static Completable.CompletableOperator onCompletableLift(Completable.CompletableOperator operator) { - // TODO add hook + public static Completable.Operator onCompletableLift(Completable.Operator operator) { + Func1 f = onCompletableLift; + if (f != null) { + return f.call(operator); + } return operator; } - + /** * Sets the global error consumer action unless a lockdown is in effect. *

      - * This operation is threadsafe. + * This operation is thread-safe. *

      - * Calling with a {@code null} parameter restores the default behavior: + * Calling with a {@code null} parameter has the effect that * errors are routed to the current thread's {@link UncaughtExceptionHandler}. * @param onError the action that will receive undeliverable Throwables */ @@ -480,11 +595,11 @@ public static void setOnError(Action1 onError) { } RxJavaHooks.onError = onError; } - + /** * Sets the Completable's onCreate hook function unless a lockdown is in effect. *

      - * This operation is threadsafe. + * This operation is thread-safe. *

      * Calling with a {@code null} parameter restores the default behavior: * the hook returns the same object. @@ -492,17 +607,17 @@ public static void setOnError(Action1 onError) { * and should return a CompletableOnSubscribe. */ public static void setOnCompletableCreate( - Func1 onCompletableCreate) { + Func1 onCompletableCreate) { if (lockdown) { return; } RxJavaHooks.onCompletableCreate = onCompletableCreate; } - + /** * Sets the Observable onCreate hook function unless a lockdown is in effect. *

      - * This operation is threadsafe. + * This operation is thread-safe. *

      * Calling with a {@code null} parameter restores the default behavior: * the hook returns the same object. @@ -517,11 +632,11 @@ public static void setOnObservableCreate( } RxJavaHooks.onObservableCreate = onObservableCreate; } - + /** * Sets the Single onCreate hook function unless a lockdown is in effect. *

      - * This operation is threadsafe. + * This operation is thread-safe. *

      * Calling with a {@code null} parameter restores the default behavior: * the hook returns the same object. @@ -535,12 +650,12 @@ public static void setOnSingleCreate(Func1 - * This operation is threadsafe. + * This operation is thread-safe. *

      * Calling with a {@code null} parameter restores the default behavior: * the hook returns the same object. @@ -553,12 +668,12 @@ public static void setOnComputationScheduler(Func1 onCompu } RxJavaHooks.onComputationScheduler = onComputationScheduler; } - + /** * Sets the hook function for returning a scheduler when the Schedulers.io() is called * unless a lockdown is in effect. *

      - * This operation is threadsafe. + * This operation is thread-safe. *

      * Calling with a {@code null} parameter restores the default behavior: * the hook returns the same object. @@ -571,12 +686,12 @@ public static void setOnIOScheduler(Func1 onIOScheduler) { } RxJavaHooks.onIOScheduler = onIOScheduler; } - + /** * Sets the hook function for returning a scheduler when the Schedulers.newThread() is called * unless a lockdown is in effect. *

      - * This operation is threadsafe. + * This operation is thread-safe. *

      * Calling with a {@code null} parameter restores the default behavior: * the hook returns the same object. @@ -589,12 +704,12 @@ public static void setOnNewThreadScheduler(Func1 onNewThre } RxJavaHooks.onNewThreadScheduler = onNewThreadScheduler; } - + /** * Sets the hook function that is called before an action is scheduled, allowing * decorating that function, unless a lockdown is in effect. *

      - * This operation is threadsafe. + * This operation is thread-safe. *

      * Calling with a {@code null} parameter restores the default behavior: * the hook returns the same object. @@ -607,12 +722,12 @@ public static void setOnScheduleAction(Func1 onScheduleAction) } RxJavaHooks.onScheduleAction = onScheduleAction; } - + /** * Sets the hook function that is called when a subscriber subscribes to a Completable * unless a lockdown is in effect. *

      - * This operation is threadsafe. + * This operation is thread-safe. *

      * Calling with a {@code null} parameter restores the default behavior: * the hook returns the same CompletableOnSubscribe object. @@ -621,18 +736,18 @@ public static void setOnScheduleAction(Func1 onScheduleAction) * that gets actually subscribed to. */ public static void setOnCompletableStart( - Func2 onCompletableStart) { + Func2 onCompletableStart) { if (lockdown) { return; } RxJavaHooks.onCompletableStart = onCompletableStart; } - + /** * Sets the hook function that is called when a subscriber subscribes to a Observable * unless a lockdown is in effect. *

      - * This operation is threadsafe. + * This operation is thread-safe. *

      * Calling with a {@code null} parameter restores the default behavior: * the hook returns the same OnSubscribe object. @@ -648,12 +763,12 @@ public static void setOnObservableStart( } RxJavaHooks.onObservableStart = onObservableStart; } - + /** * Sets the hook function that is called when a subscriber subscribes to a Single * unless a lockdown is in effect. *

      - * This operation is threadsafe. + * This operation is thread-safe. *

      * Calling with a {@code null} parameter restores the default behavior: * the hook returns the same OnSubscribe object. @@ -662,18 +777,18 @@ public static void setOnObservableStart( * that gets actually subscribed to. */ @SuppressWarnings("rawtypes") - public static void setOnSingleStart(Func2 onSingleStart) { + public static void setOnSingleStart(Func2 onSingleStart) { if (lockdown) { return; } RxJavaHooks.onSingleStart = onSingleStart; } - + /** - * Sets a hook function that is called when the Observable.subscribe() call + * Sets a hook function that is called when the Observable.subscribe() call * is about to return a Subscription unless a lockdown is in effect. *

      - * This operation is threadsafe. + * This operation is thread-safe. *

      * Calling with a {@code null} parameter restores the default behavior: * the hook returns the same object. @@ -687,12 +802,12 @@ public static void setOnObservableReturn(Func1 onObs } RxJavaHooks.onObservableReturn = onObservableReturn; } - + /** - * Sets a hook function that is called when the Single.subscribe() call + * Sets a hook function that is called when the Single.subscribe() call * is about to return a Subscription unless a lockdown is in effect. *

      - * This operation is threadsafe. + * This operation is thread-safe. *

      * Calling with a {@code null} parameter restores the default behavior: * the hook returns the same object. @@ -706,149 +821,327 @@ public static void setOnSingleReturn(Func1 onSingleR } RxJavaHooks.onSingleReturn = onSingleReturn; } - + + /** + * Sets a hook function that is called when the Single.subscribe() call + * fails with an exception. + *

      + * This operation is thread-safe. + *

      + * Calling with a {@code null} parameter restores the default behavior: + * the hook returns the same object. + * @param onSingleSubscribeError the function that is called with the crash exception and should return + * an exception. + */ + public static void setOnSingleSubscribeError(Func1 onSingleSubscribeError) { + if (lockdown) { + return; + } + RxJavaHooks.onSingleSubscribeError = onSingleSubscribeError; + } + + /** + * Returns the current Single onSubscribeError hook function or null if it is + * set to the default pass-through. + *

      + * This operation is thread-safe. + * @return the current hook function + */ + public static Func1 getOnSingleSubscribeError() { + return onSingleSubscribeError; + } + + /** + * Sets a hook function that is called when the Completable.subscribe() call + * fails with an exception. + *

      + * This operation is thread-safe. + *

      + * Calling with a {@code null} parameter restores the default behavior: + * the hook returns the same object. + * @param onCompletableSubscribeError the function that is called with the crash exception and should return + * an exception. + */ + public static void setOnCompletableSubscribeError(Func1 onCompletableSubscribeError) { + if (lockdown) { + return; + } + RxJavaHooks.onCompletableSubscribeError = onCompletableSubscribeError; + } + + /** + * Returns the current Completable onSubscribeError hook function or null if it is + * set to the default pass-through. + *

      + * This operation is thread-safe. + * @return the current hook function + */ + public static Func1 getOnCompletableSubscribeError() { + return onCompletableSubscribeError; + } + + /** + * Sets a hook function that is called when the Observable.subscribe() call + * fails with an exception. + *

      + * This operation is thread-safe. + *

      + * Calling with a {@code null} parameter restores the default behavior: + * the hook returns the same object. + * @param onObservableSubscribeError the function that is called with the crash exception and should return + * an exception. + */ + public static void setOnObservableSubscribeError(Func1 onObservableSubscribeError) { + if (lockdown) { + return; + } + RxJavaHooks.onObservableSubscribeError = onObservableSubscribeError; + } + + /** + * Returns the current Observable onSubscribeError hook function or null if it is + * set to the default pass-through. + *

      + * This operation is thread-safe. + * @return the current hook function + */ + public static Func1 getOnObservableSubscribeError() { + return onObservableSubscribeError; + } + + /** + * Sets a hook function that is called with an operator when an Observable operator built with + * lift() gets subscribed to. + *

      + * This operation is thread-safe. + *

      + * Calling with a {@code null} parameter restores the default behavior: + * the hook returns the same object. + * @param onObservableLift the function that is called with original Operator and should + * return an Operator instance. + */ + @SuppressWarnings("rawtypes") + public static void setOnObservableLift(Func1 onObservableLift) { + if (lockdown) { + return; + } + RxJavaHooks.onObservableLift = onObservableLift; + } + + /** + * Returns the current Observable onLift hook function or null if it is + * set to the default pass-through. + *

      + * This operation is thread-safe. + * @return the current hook function + */ + @SuppressWarnings("rawtypes") + public static Func1 getOnObservableLift() { + return onObservableLift; + } + + /** + * Sets a hook function that is called with an operator when an Single operator built with + * lift() gets subscribed to. + *

      + * This operation is thread-safe. + *

      + * Calling with a {@code null} parameter restores the default behavior: + * the hook returns the same object. + * @param onSingleLift the function that is called with original Operator and should + * return an Operator instance. + */ + @SuppressWarnings("rawtypes") + public static void setOnSingleLift(Func1 onSingleLift) { + if (lockdown) { + return; + } + RxJavaHooks.onSingleLift = onSingleLift; + } + + /** + * Returns the current Single onLift hook function or null if it is + * set to the default pass-through. + *

      + * This operation is thread-safe. + * @return the current hook function + */ + @SuppressWarnings("rawtypes") + public static Func1 getOnSingleLift() { + return onSingleLift; + } + + /** + * Sets a hook function that is called with an operator when a Completable operator built with + * lift() gets subscribed to. + *

      + * This operation is thread-safe. + *

      + * Calling with a {@code null} parameter restores the default behavior: + * the hook returns the same object. + * @param onCompletableLift the function that is called with original Operator and should + * return an Operator instance. + */ + public static void setOnCompletableLift(Func1 onCompletableLift) { + if (lockdown) { + return; + } + RxJavaHooks.onCompletableLift = onCompletableLift; + } + + /** + * Returns the current Completable onLift hook function or null if it is + * set to the default pass-through. + *

      + * This operation is thread-safe. + * @return the current hook function + */ + public static Func1 getOnCompletableLift() { + return onCompletableLift; + } + /** * Returns the current computation scheduler hook function or null if it is * set to the default pass-through. *

      - * This operation is threadsafe. + * This operation is thread-safe. * @return the current hook function */ public static Func1 getOnComputationScheduler() { return onComputationScheduler; } - + /** * Returns the current global error handler hook action or null if it is * set to the default one that signals errors to the current threads * UncaughtExceptionHandler. *

      - * This operation is threadsafe. + * This operation is thread-safe. * @return the current hook action */ public static Action1 getOnError() { return onError; } - + /** * Returns the current io scheduler hook function or null if it is * set to the default pass-through. *

      - * This operation is threadsafe. + * This operation is thread-safe. * @return the current hook function */ public static Func1 getOnIOScheduler() { return onIOScheduler; } - + /** * Returns the current new thread scheduler hook function or null if it is * set to the default pass-through. *

      - * This operation is threadsafe. + * This operation is thread-safe. * @return the current hook function */ public static Func1 getOnNewThreadScheduler() { return onNewThreadScheduler; } - + /** * Returns the current Observable onCreate hook function or null if it is * set to the default pass-through. *

      - * This operation is threadsafe. + * This operation is thread-safe. * @return the current hook function */ @SuppressWarnings("rawtypes") public static Func1 getOnObservableCreate() { return onObservableCreate; } - + /** * Returns the current schedule action hook function or null if it is * set to the default pass-through. *

      - * This operation is threadsafe. + * This operation is thread-safe. * @return the current hook function */ public static Func1 getOnScheduleAction() { return onScheduleAction; } - + /** * Returns the current Single onCreate hook function or null if it is * set to the default pass-through. *

      - * This operation is threadsafe. + * This operation is thread-safe. * @return the current hook function */ @SuppressWarnings("rawtypes") public static Func1 getOnSingleCreate() { return onSingleCreate; } - + /** * Returns the current Completable onCreate hook function or null if it is * set to the default pass-through. *

      - * This operation is threadsafe. + * This operation is thread-safe. * @return the current hook function */ - public static Func1 getOnCompletableCreate() { + public static Func1 getOnCompletableCreate() { return onCompletableCreate; } - + /** * Returns the current Completable onStart hook function or null if it is * set to the default pass-through. *

      - * This operation is threadsafe. + * This operation is thread-safe. * @return the current hook function */ - public static Func2 getOnCompletableStart() { + public static Func2 getOnCompletableStart() { return onCompletableStart; } - + /** * Returns the current Observable onStart hook function or null if it is * set to the default pass-through. *

      - * This operation is threadsafe. + * This operation is thread-safe. * @return the current hook function */ @SuppressWarnings("rawtypes") public static Func2 getOnObservableStart() { return onObservableStart; } - + /** * Returns the current Single onStart hook function or null if it is * set to the default pass-through. *

      - * This operation is threadsafe. + * This operation is thread-safe. * @return the current hook function */ @SuppressWarnings("rawtypes") - public static Func2 getOnSingleStart() { + public static Func2 getOnSingleStart() { return onSingleStart; } - + /** * Returns the current Observable onReturn hook function or null if it is * set to the default pass-through. *

      - * This operation is threadsafe. + * This operation is thread-safe. * @return the current hook function */ public static Func1 getOnObservableReturn() { return onObservableReturn; } - + /** * Returns the current Single onReturn hook function or null if it is * set to the default pass-through. *

      - * This operation is threadsafe. + * This operation is thread-safe. * @return the current hook function */ public static Func1 getOnSingleReturn() { @@ -859,47 +1152,18 @@ public static Func1 getOnSingleReturn() { * Resets the assembly tracking hooks to their default delegates to * RxJavaPlugins. */ - @SuppressWarnings({ "rawtypes", "unchecked", "deprecation" }) public static void resetAssemblyTracking() { if (lockdown) { return; } - RxJavaPlugins plugin = RxJavaPlugins.getInstance(); - - final RxJavaObservableExecutionHook observableExecutionHook = plugin.getObservableExecutionHook(); - - onObservableCreate = new Func1() { - @Override - public OnSubscribe call(OnSubscribe f) { - return observableExecutionHook.onCreate(f); - } - }; - - final RxJavaSingleExecutionHook singleExecutionHook = plugin.getSingleExecutionHook(); - - onSingleCreate = new Func1() { - @Override - public rx.Single.OnSubscribe call(rx.Single.OnSubscribe f) { - return singleExecutionHook.onCreate(f); - } - }; - - final RxJavaCompletableExecutionHook completableExecutionHook = plugin.getCompletableExecutionHook(); - - onCompletableCreate = new Func1() { - @Override - public CompletableOnSubscribe call(CompletableOnSubscribe f) { - return completableExecutionHook.onCreate(f); - } - }; - + initCreate(); } /** * Clears the assembly tracking hooks to their default pass-through behavior. */ - public static void crearAssemblyTracking() { + public static void clearAssemblyTracking() { if (lockdown) { return; } @@ -911,17 +1175,17 @@ public static void crearAssemblyTracking() { /** * Sets up hooks that capture the current stacktrace when a source or an * operator is instantiated, keeping it in a field for debugging purposes - * and alters exceptions passign along to hold onto this stacktrace. + * and alters exceptions passing along to hold onto this stacktrace. */ @SuppressWarnings({ "rawtypes", "unchecked" }) public static void enableAssemblyTracking() { if (lockdown) { return; } - - onObservableCreate = new Func1() { + + onObservableCreate = new Func1() { @Override - public OnSubscribe call(OnSubscribe f) { + public Observable.OnSubscribe call(Observable.OnSubscribe f) { return new OnSubscribeOnAssembly(f); } }; @@ -933,12 +1197,42 @@ public rx.Single.OnSubscribe call(rx.Single.OnSubscribe f) { } }; - onCompletableCreate = new Func1() { + onCompletableCreate = new Func1() { @Override - public CompletableOnSubscribe call(CompletableOnSubscribe f) { + public Completable.OnSubscribe call(Completable.OnSubscribe f) { return new OnSubscribeOnAssemblyCompletable(f); } }; } + /** + * Sets the hook function for returning a ScheduledExecutorService used + * by the GenericScheduledExecutorService for background tasks. + *

      + * This operation is thread-safe. + *

      + * Calling with a {@code null} parameter restores the default behavior: + * create the default with {@link java.util.concurrent.Executors#newScheduledThreadPool(int, java.util.concurrent.ThreadFactory)}. + *

      + * For the changes to take effect, the Schedulers has to be restarted. + * @param factory the supplier that is called when the GenericScheduledExecutorService + * is (re)started + */ + public static void setOnGenericScheduledExecutorService(Func0 factory) { + if (lockdown) { + return; + } + onGenericScheduledExecutorService = factory; + } + + /** + * Returns the current factory for creating ScheduledExecutorServices in + * GenericScheduledExecutorService utility. + *

      + * This operation is thread-safe. + * @return the current factory function + */ + public static Func0 getOnGenericScheduledExecutorService() { + return onGenericScheduledExecutorService; + } } diff --git a/src/main/java/rx/plugins/RxJavaObservableExecutionHook.java b/src/main/java/rx/plugins/RxJavaObservableExecutionHook.java index 8c08851ed5..47ab6e4095 100644 --- a/src/main/java/rx/plugins/RxJavaObservableExecutionHook.java +++ b/src/main/java/rx/plugins/RxJavaObservableExecutionHook.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -37,15 +37,15 @@ * Methods are also invoked synchronously and will add to execution time of the observable so all behavior * should be fast. If anything time-consuming is to be done it should be spawned asynchronously onto separate * worker threads. - * + * */ -public abstract class RxJavaObservableExecutionHook { +public abstract class RxJavaObservableExecutionHook { // NOPMD /** - * Invoked during the construction by {@link Observable#create(OnSubscribe)} + * Invoked during the construction by {@link Observable#unsafeCreate(OnSubscribe)} *

      * This can be used to decorate or replace the onSubscribe function or just perform extra * logging, metrics and other such things and pass through the function. - * + * * @param the value type * @param f * original {@link OnSubscribe}<{@code T}> to be executed @@ -62,7 +62,7 @@ public OnSubscribe onCreate(OnSubscribe f) { *

      * This can be used to decorate or replace the onSubscribe function or just perform extra * logging, metrics and other such things and pass through the function. - * + * * @param the value type * @param observableInstance the parent observable instance * @param onSubscribe @@ -82,7 +82,7 @@ public OnSubscribe onSubscribeStart(Observable observableIns *

      * This can be used to decorate or replace the {@link Subscription} instance or just perform extra logging, * metrics and other such things and pass through the subscription. - * + * * @param the value type * @param subscription * original {@link Subscription} @@ -100,7 +100,7 @@ public Subscription onSubscribeReturn(Subscription subscription) { *

      * This is not errors emitted via {@link Subscriber#onError(Throwable)} but exceptions thrown when * attempting to subscribe to a {@link Func1}<{@link Subscriber}{@code }, {@link Subscription}>. - * + * * @param the value type * @param e * Throwable thrown by {@link Observable#subscribe(Subscriber)} @@ -118,7 +118,7 @@ public Throwable onSubscribeError(Throwable e) { *

      * This can be used to decorate or replace the {@link Operator} instance or just perform extra * logging, metrics and other such things and pass through the onSubscribe. - * + * * @param the upstream's value type (input) * @param the downstream's value type (output) * @param lift diff --git a/src/main/java/rx/plugins/RxJavaObservableExecutionHookDefault.java b/src/main/java/rx/plugins/RxJavaObservableExecutionHookDefault.java index 43dde33a26..2bc2a788aa 100644 --- a/src/main/java/rx/plugins/RxJavaObservableExecutionHookDefault.java +++ b/src/main/java/rx/plugins/RxJavaObservableExecutionHookDefault.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -18,9 +18,14 @@ /** * Default no-op implementation of {@link RxJavaObservableExecutionHook} */ -/* package */class RxJavaObservableExecutionHookDefault extends RxJavaObservableExecutionHook { +/* package */final class RxJavaObservableExecutionHookDefault extends RxJavaObservableExecutionHook { - private static RxJavaObservableExecutionHookDefault INSTANCE = new RxJavaObservableExecutionHookDefault(); + private static final RxJavaObservableExecutionHookDefault INSTANCE = new RxJavaObservableExecutionHookDefault(); + + /** Utility class. */ + private RxJavaObservableExecutionHookDefault() { + + } public static RxJavaObservableExecutionHook getInstance() { return INSTANCE; diff --git a/src/main/java/rx/plugins/RxJavaPlugins.java b/src/main/java/rx/plugins/RxJavaPlugins.java index 9271a42ce2..cac5b167c6 100644 --- a/src/main/java/rx/plugins/RxJavaPlugins.java +++ b/src/main/java/rx/plugins/RxJavaPlugins.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -17,7 +17,6 @@ import java.util.*; import java.util.concurrent.atomic.AtomicReference; -import rx.annotations.Experimental; /** * Registry for plugin implementations that allows global override and handles the retrieval of correct @@ -28,14 +27,14 @@ * property names) *

    • default implementation
    • * - *

      In addition to the {@code rxjava.plugin.[simple classname].implementation} system properties, + *

      In addition to the {@code rxjava.plugin.[simple class name].implementation} system properties, * you can define two system property:
      *

      
        * rxjava.plugin.[index].class}
        * rxjava.plugin.[index].impl}
        * 
      - * - * Where the {@code .class} property contains the simple classname from above and the {@code .impl} + * + * Where the {@code .class} property contains the simple class name from above and the {@code .impl} * contains the fully qualified name of the implementation class. The {@code [index]} can be * any short string or number of your choosing. For example, you can now define a custom * {@code RxJavaErrorHandler} via two system property: @@ -43,9 +42,9 @@ * rxjava.plugin.1.class=RxJavaErrorHandler * rxjava.plugin.1.impl=some.package.MyRxJavaErrorHandler * - * + * * @see RxJava Wiki: Plugins - * + * * Use the {@link RxJavaHooks} features instead which let's you change individual * handlers at runtime. */ @@ -58,11 +57,14 @@ public class RxJavaPlugins { private final AtomicReference completableExecutionHook = new AtomicReference(); private final AtomicReference schedulersHook = new AtomicReference(); + static final RxJavaErrorHandler DEFAULT_ERROR_HANDLER = new RxJavaErrorHandler() { + }; + /** * Retrieves the single {@code RxJavaPlugins} instance. * * @return the single {@code RxJavaPlugins} instance - * + * * @deprecated use the static methods of {@link RxJavaHooks}. */ @Deprecated @@ -71,43 +73,41 @@ public static RxJavaPlugins getInstance() { } /* package accessible for unit tests */RxJavaPlugins() { - + // nothing to do } - + /** * Reset {@code RxJavaPlugins} instance *

      - * This API is experimental. Resetting the plugins is dangerous - * during application runtime and also bad code could invoke it in + * This API is experimental. Resetting the plugins is dangerous + * during application runtime and also bad code could invoke it in * the middle of an application life-cycle and really break applications * if not used cautiously. For more detailed discussions: - * * @see Make RxJavaPlugins.reset() public + * @see Make RxJavaPlugins.reset() public + * @since 1.3 */ - @Experimental public void reset() { INSTANCE.errorHandler.set(null); INSTANCE.observableExecutionHook.set(null); INSTANCE.singleExecutionHook.set(null); + INSTANCE.completableExecutionHook.set(null); INSTANCE.schedulersHook.set(null); } - static final RxJavaErrorHandler DEFAULT_ERROR_HANDLER = new RxJavaErrorHandler() { - }; - /** * Retrieves an instance of {@link RxJavaErrorHandler} to use based on order of precedence as defined in * {@link RxJavaPlugins} class header. *

      * Override the default by calling {@link #registerErrorHandler(RxJavaErrorHandler)} or by setting the - * property {@code rxjava.plugin.RxJavaErrorHandler.implementation} with the full classname to load. + * property {@code rxjava.plugin.RxJavaErrorHandler.implementation} with the full class name to load. * @return {@link RxJavaErrorHandler} implementation to use */ public RxJavaErrorHandler getErrorHandler() { if (errorHandler.get() == null) { // check for an implementation from System.getProperty first - Object impl = getPluginImplementationViaProperty(RxJavaErrorHandler.class, System.getProperties()); + Object impl = getPluginImplementationViaProperty(RxJavaErrorHandler.class, getSystemPropertiesSafe()); if (impl == null) { - // nothing set via properties so initialize with default + // nothing set via properties so initialize with default errorHandler.compareAndSet(null, DEFAULT_ERROR_HANDLER); // we don't return from here but call get() again in case of thread-race so the winner will always get returned } else { @@ -121,7 +121,7 @@ public RxJavaErrorHandler getErrorHandler() { /** * Registers an {@link RxJavaErrorHandler} implementation as a global override of any injected or default * implementations. - * + * * @param impl * {@link RxJavaErrorHandler} implementation * @throws IllegalStateException @@ -140,16 +140,16 @@ public void registerErrorHandler(RxJavaErrorHandler impl) { *

      * Override the default by calling {@link #registerObservableExecutionHook(RxJavaObservableExecutionHook)} * or by setting the property {@code rxjava.plugin.RxJavaObservableExecutionHook.implementation} with the - * full classname to load. - * + * full class name to load. + * * @return {@link RxJavaObservableExecutionHook} implementation to use */ public RxJavaObservableExecutionHook getObservableExecutionHook() { if (observableExecutionHook.get() == null) { // check for an implementation from System.getProperty first - Object impl = getPluginImplementationViaProperty(RxJavaObservableExecutionHook.class, System.getProperties()); + Object impl = getPluginImplementationViaProperty(RxJavaObservableExecutionHook.class, getSystemPropertiesSafe()); if (impl == null) { - // nothing set via properties so initialize with default + // nothing set via properties so initialize with default observableExecutionHook.compareAndSet(null, RxJavaObservableExecutionHookDefault.getInstance()); // we don't return from here but call get() again in case of thread-race so the winner will always get returned } else { @@ -163,7 +163,7 @@ public RxJavaObservableExecutionHook getObservableExecutionHook() { /** * Register an {@link RxJavaObservableExecutionHook} implementation as a global override of any injected or * default implementations. - * + * * @param impl * {@link RxJavaObservableExecutionHook} implementation * @throws IllegalStateException @@ -182,14 +182,14 @@ public void registerObservableExecutionHook(RxJavaObservableExecutionHook impl) *

      * Override the default by calling {@link #registerSingleExecutionHook(RxJavaSingleExecutionHook)} * or by setting the property {@code rxjava.plugin.RxJavaSingleExecutionHook.implementation} with the - * full classname to load. + * full class name to load. * * @return {@link RxJavaSingleExecutionHook} implementation to use */ public RxJavaSingleExecutionHook getSingleExecutionHook() { if (singleExecutionHook.get() == null) { // check for an implementation from System.getProperty first - Object impl = getPluginImplementationViaProperty(RxJavaSingleExecutionHook.class, System.getProperties()); + Object impl = getPluginImplementationViaProperty(RxJavaSingleExecutionHook.class, getSystemPropertiesSafe()); if (impl == null) { // nothing set via properties so initialize with default singleExecutionHook.compareAndSet(null, RxJavaSingleExecutionHookDefault.getInstance()); @@ -224,16 +224,15 @@ public void registerSingleExecutionHook(RxJavaSingleExecutionHook impl) { *

      * Override the default by calling {@link #registerCompletableExecutionHook(RxJavaCompletableExecutionHook)} * or by setting the property {@code rxjava.plugin.RxJavaCompletableExecutionHook.implementation} with the - * full classname to load. + * full class name to load. * * @return {@link RxJavaCompletableExecutionHook} implementation to use - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Experimental public RxJavaCompletableExecutionHook getCompletableExecutionHook() { if (completableExecutionHook.get() == null) { // check for an implementation from System.getProperty first - Object impl = getPluginImplementationViaProperty(RxJavaCompletableExecutionHook.class, System.getProperties()); + Object impl = getPluginImplementationViaProperty(RxJavaCompletableExecutionHook.class, getSystemPropertiesSafe()); if (impl == null) { // nothing set via properties so initialize with default completableExecutionHook.compareAndSet(null, new RxJavaCompletableExecutionHook() { }); @@ -255,20 +254,32 @@ public RxJavaCompletableExecutionHook getCompletableExecutionHook() { * @throws IllegalStateException * if called more than once or after the default was initialized (if usage occurs before trying * to register) - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Experimental public void registerCompletableExecutionHook(RxJavaCompletableExecutionHook impl) { if (!completableExecutionHook.compareAndSet(null, impl)) { throw new IllegalStateException("Another strategy was already registered: " + singleExecutionHook.get()); } } + /** + * A security manager may prevent accessing the System properties entirely, + * therefore, the SecurityException is turned into an empty properties. + * @return the Properties to use for looking up settings + */ + /* test */ static Properties getSystemPropertiesSafe() { + try { + return System.getProperties(); + } catch (SecurityException ex) { + return new Properties(); + } + } + /* test */ static Object getPluginImplementationViaProperty(Class pluginClass, Properties propsIn) { // Make a defensive clone because traversal may fail with ConcurrentModificationException // if the properties get changed by something outside RxJava. Properties props = (Properties)propsIn.clone(); - + final String classSimpleName = pluginClass.getSimpleName(); /* * Check system properties for plugin class. @@ -276,35 +287,42 @@ public void registerCompletableExecutionHook(RxJavaCompletableExecutionHook impl * This will only happen during system startup thus it's okay to use the synchronized * System.getProperties as it will never get called in normal operations. */ - - final String pluginPrefix = "rxjava.plugin."; - + + String pluginPrefix = "rxjava.plugin."; + String defaultKey = pluginPrefix + classSimpleName + ".implementation"; String implementingClass = props.getProperty(defaultKey); if (implementingClass == null) { - final String classSuffix = ".class"; - final String implSuffix = ".impl"; - - for (Map.Entry e : props.entrySet()) { - String key = e.getKey().toString(); - if (key.startsWith(pluginPrefix) && key.endsWith(classSuffix)) { - String value = e.getValue().toString(); - - if (classSimpleName.equals(value)) { - String index = key.substring(0, key.length() - classSuffix.length()).substring(pluginPrefix.length()); - - String implKey = pluginPrefix + index + implSuffix; - - implementingClass = props.getProperty(implKey); - - if (implementingClass == null) { - throw new RuntimeException("Implementing class declaration for " + classSimpleName + " missing: " + implKey); + String classSuffix = ".class"; + String implSuffix = ".impl"; + + try { + for (Map.Entry e : props.entrySet()) { + String key = e.getKey().toString(); + if (key.startsWith(pluginPrefix) && key.endsWith(classSuffix)) { + String value = e.getValue().toString(); + + if (classSimpleName.equals(value)) { + String index = key.substring(0, key.length() - classSuffix.length()).substring(pluginPrefix.length()); + + String implKey = pluginPrefix + index + implSuffix; + + implementingClass = props.getProperty(implKey); + + if (implementingClass == null) { + throw new IllegalStateException("Implementing class declaration for " + classSimpleName + " missing: " + implKey); + } + + break; } - - break; } } + } catch (SecurityException ex) { + // https://github.com/ReactiveX/RxJava/issues/5819 + // We don't seem to have access to all properties. + // At least print the exception to the console. + ex.printStackTrace(); } } @@ -315,13 +333,13 @@ public void registerCompletableExecutionHook(RxJavaCompletableExecutionHook impl cls = cls.asSubclass(pluginClass); return cls.newInstance(); } catch (ClassCastException e) { - throw new RuntimeException(classSimpleName + " implementation is not an instance of " + classSimpleName + ": " + implementingClass); + throw new IllegalStateException(classSimpleName + " implementation is not an instance of " + classSimpleName + ": " + implementingClass, e); } catch (ClassNotFoundException e) { - throw new RuntimeException(classSimpleName + " implementation class not found: " + implementingClass, e); + throw new IllegalStateException(classSimpleName + " implementation class not found: " + implementingClass, e); } catch (InstantiationException e) { - throw new RuntimeException(classSimpleName + " implementation not able to be instantiated: " + implementingClass, e); + throw new IllegalStateException(classSimpleName + " implementation not able to be instantiated: " + implementingClass, e); } catch (IllegalAccessException e) { - throw new RuntimeException(classSimpleName + " implementation not able to be accessed: " + implementingClass, e); + throw new IllegalStateException(classSimpleName + " implementation not able to be accessed: " + implementingClass, e); } } @@ -333,7 +351,7 @@ public void registerCompletableExecutionHook(RxJavaCompletableExecutionHook impl * in the {@link RxJavaPlugins} class header. *

      * Override the default by calling {@link #registerSchedulersHook(RxJavaSchedulersHook)} or by setting - * the property {@code rxjava.plugin.RxJavaSchedulersHook.implementation} with the full classname to + * the property {@code rxjava.plugin.RxJavaSchedulersHook.implementation} with the full class name to * load. * * @return the {@link RxJavaSchedulersHook} implementation in use @@ -341,7 +359,7 @@ public void registerCompletableExecutionHook(RxJavaCompletableExecutionHook impl public RxJavaSchedulersHook getSchedulersHook() { if (schedulersHook.get() == null) { // check for an implementation from System.getProperty first - Object impl = getPluginImplementationViaProperty(RxJavaSchedulersHook.class, System.getProperties()); + Object impl = getPluginImplementationViaProperty(RxJavaSchedulersHook.class, getSystemPropertiesSafe()); if (impl == null) { // nothing set via properties so initialize with default schedulersHook.compareAndSet(null, RxJavaSchedulersHook.getDefaultInstance()); diff --git a/src/main/java/rx/plugins/RxJavaSchedulersHook.java b/src/main/java/rx/plugins/RxJavaSchedulersHook.java index a8158e2f5e..e65ab1f3bc 100644 --- a/src/main/java/rx/plugins/RxJavaSchedulersHook.java +++ b/src/main/java/rx/plugins/RxJavaSchedulersHook.java @@ -18,7 +18,6 @@ import java.util.concurrent.ThreadFactory; import rx.Scheduler; -import rx.annotations.Experimental; import rx.functions.Action0; import rx.internal.schedulers.CachedThreadScheduler; import rx.internal.schedulers.EventLoopsScheduler; @@ -42,11 +41,13 @@ */ public class RxJavaSchedulersHook { + private final static RxJavaSchedulersHook DEFAULT_INSTANCE = new RxJavaSchedulersHook(); + /** * Create an instance of the default {@link Scheduler} used for {@link Schedulers#computation()}. * @return the created Scheduler instance + * @since 1.3 */ - @Experimental public static Scheduler createComputationScheduler() { return createComputationScheduler(new RxThreadFactory("RxComputationScheduler-")); } @@ -56,18 +57,20 @@ public static Scheduler createComputationScheduler() { * except using {@code threadFactory} for thread creation. * @param threadFactory the factory to use for each worker thread * @return the created Scheduler instance + * @since 1.3 */ - @Experimental public static Scheduler createComputationScheduler(ThreadFactory threadFactory) { - if (threadFactory == null) throw new NullPointerException("threadFactory == null"); + if (threadFactory == null) { + throw new NullPointerException("threadFactory == null"); + } return new EventLoopsScheduler(threadFactory); } /** * Create an instance of the default {@link Scheduler} used for {@link Schedulers#io()}. * @return the created Scheduler instance + * @since 1.3 */ - @Experimental public static Scheduler createIoScheduler() { return createIoScheduler(new RxThreadFactory("RxIoScheduler-")); } @@ -77,18 +80,20 @@ public static Scheduler createIoScheduler() { * except using {@code threadFactory} for thread creation. * @param threadFactory the factory to use for each worker thread * @return the created Scheduler instance + * @since 1.3 */ - @Experimental public static Scheduler createIoScheduler(ThreadFactory threadFactory) { - if (threadFactory == null) throw new NullPointerException("threadFactory == null"); + if (threadFactory == null) { + throw new NullPointerException("threadFactory == null"); + } return new CachedThreadScheduler(threadFactory); } /** * Create an instance of the default {@link Scheduler} used for {@link Schedulers#newThread()}. * @return the created Scheduler instance + * @since 1.3 */ - @Experimental public static Scheduler createNewThreadScheduler() { return createNewThreadScheduler(new RxThreadFactory("RxNewThreadScheduler-")); } @@ -98,19 +103,15 @@ public static Scheduler createNewThreadScheduler() { * except using {@code threadFactory} for thread creation. * @param threadFactory the factory to use for each worker thread * @return the created Scheduler instance + * @since 1.3 */ - @Experimental public static Scheduler createNewThreadScheduler(ThreadFactory threadFactory) { - if (threadFactory == null) throw new NullPointerException("threadFactory == null"); + if (threadFactory == null) { + throw new NullPointerException("threadFactory == null"); + } return new NewThreadScheduler(threadFactory); } - protected RxJavaSchedulersHook() { - - } - - private final static RxJavaSchedulersHook DEFAULT_INSTANCE = new RxJavaSchedulersHook(); - /** * Scheduler to return from {@link rx.schedulers.Schedulers#computation()} or null if default should be * used. diff --git a/src/main/java/rx/plugins/RxJavaSingleExecutionHook.java b/src/main/java/rx/plugins/RxJavaSingleExecutionHook.java index 4e023f615e..ad3d3bfc85 100644 --- a/src/main/java/rx/plugins/RxJavaSingleExecutionHook.java +++ b/src/main/java/rx/plugins/RxJavaSingleExecutionHook.java @@ -38,7 +38,7 @@ * worker threads. * */ -public abstract class RxJavaSingleExecutionHook { +public abstract class RxJavaSingleExecutionHook { // NOPMD /** * Invoked during the construction by {@link Single#create(Single.OnSubscribe)} *

      diff --git a/src/main/java/rx/plugins/RxJavaSingleExecutionHookDefault.java b/src/main/java/rx/plugins/RxJavaSingleExecutionHookDefault.java index 60a382589f..d84eff34cc 100644 --- a/src/main/java/rx/plugins/RxJavaSingleExecutionHookDefault.java +++ b/src/main/java/rx/plugins/RxJavaSingleExecutionHookDefault.java @@ -1,12 +1,12 @@ /** * Copyright 2016 Netflix, Inc. - * + * * 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. @@ -18,10 +18,16 @@ /** * Default no-op implementation of {@link RxJavaSingleExecutionHook} */ -class RxJavaSingleExecutionHookDefault extends RxJavaSingleExecutionHook { +final class RxJavaSingleExecutionHookDefault extends RxJavaSingleExecutionHook { private static final RxJavaSingleExecutionHookDefault INSTANCE = new RxJavaSingleExecutionHookDefault(); + /** + * Utility class. + */ + private RxJavaSingleExecutionHookDefault() { + } + public static RxJavaSingleExecutionHook getInstance() { return INSTANCE; } diff --git a/src/main/java/rx/schedulers/ImmediateScheduler.java b/src/main/java/rx/schedulers/ImmediateScheduler.java index 2696859987..728dce77f5 100644 --- a/src/main/java/rx/schedulers/ImmediateScheduler.java +++ b/src/main/java/rx/schedulers/ImmediateScheduler.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -22,9 +22,9 @@ */ @Deprecated // Class was part of public API. -public final class ImmediateScheduler extends Scheduler { +public final class ImmediateScheduler extends Scheduler { // NOPMD private ImmediateScheduler() { - throw new AssertionError(); + throw new IllegalStateException("No instances!"); } @Override diff --git a/src/main/java/rx/schedulers/NewThreadScheduler.java b/src/main/java/rx/schedulers/NewThreadScheduler.java index fc709243f0..7834d5499f 100644 --- a/src/main/java/rx/schedulers/NewThreadScheduler.java +++ b/src/main/java/rx/schedulers/NewThreadScheduler.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -22,9 +22,9 @@ */ @Deprecated // Class was part of public API. -public final class NewThreadScheduler extends Scheduler { +public final class NewThreadScheduler extends Scheduler { // NOPMD private NewThreadScheduler() { - throw new AssertionError(); + throw new IllegalStateException("No instances!"); } @Override diff --git a/src/main/java/rx/schedulers/Schedulers.java b/src/main/java/rx/schedulers/Schedulers.java index 6dc89f0178..1327c6ba93 100644 --- a/src/main/java/rx/schedulers/Schedulers.java +++ b/src/main/java/rx/schedulers/Schedulers.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -15,19 +15,38 @@ */ package rx.schedulers; -import rx.Scheduler; -import rx.annotations.Experimental; -import rx.internal.schedulers.ExecutorScheduler; -import rx.internal.schedulers.GenericScheduledExecutorService; -import rx.internal.schedulers.SchedulerLifecycle; -import rx.internal.util.RxRingBuffer; -import rx.plugins.*; - import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicReference; +import rx.Scheduler; +import rx.internal.schedulers.*; +import rx.plugins.*; + /** * Static factory methods for creating Schedulers. + *

      + * System configuration properties: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
      Property nameDescriptionDefault
      {@code rx.io-scheduler.keepalive}time (in seconds) to keep an unused backing + * thread-pool60
      {@code rx.scheduler.max-computation-threads}number of threads the + * computation scheduler uses (between 1 and number of available processors) + * number of available processors.
      {@code rx.scheduler.jdk6.purge-frequency-millis}time (in milliseconds) between calling + * purge on any active backing thread-pool on a Java 6 runtime1000
      {@code rx.scheduler.jdk6.purge-force} boolean forcing the call to purge on any active + * backing thread-poolfalse
      */ public final class Schedulers { @@ -137,7 +156,7 @@ public static Scheduler computation() { * @return a {@link Scheduler} meant for IO-bound work */ public static Scheduler io() { - return RxJavaHooks.onComputationScheduler(getInstance().ioScheduler); + return RxJavaHooks.onIOScheduler(getInstance().ioScheduler); } /** @@ -146,7 +165,7 @@ public static Scheduler io() { * * @return a {@code TestScheduler} meant for debugging */ - public static TestScheduler test() { + public static TestScheduler test() { // NOPMD return new TestScheduler(); } @@ -165,35 +184,31 @@ public static Scheduler from(Executor executor) { * Resets the current {@link Schedulers} instance. * This will re-init the cached schedulers on the next usage, * which can be useful in testing. + * @since 1.3 */ - @Experimental public static void reset() { Schedulers s = INSTANCE.getAndSet(null); if (s != null) { s.shutdownInstance(); } } - + /** * Starts those standard Schedulers which support the SchedulerLifecycle interface. - *

      The operation is idempotent and threadsafe. + *

      The operation is idempotent and thread-safe. */ - /* public test only */ static void start() { + public static void start() { Schedulers s = getInstance(); - + s.startInstance(); - + synchronized (s) { GenericScheduledExecutorService.INSTANCE.start(); - - RxRingBuffer.SPSC_POOL.start(); - - RxRingBuffer.SPMC_POOL.start(); } } /** * Shuts down those standard Schedulers which support the SchedulerLifecycle interface. - *

      The operation is idempotent and threadsafe. + *

      The operation is idempotent and thread-safe. */ public static void shutdown() { Schedulers s = getInstance(); @@ -201,17 +216,13 @@ public static void shutdown() { synchronized (s) { GenericScheduledExecutorService.INSTANCE.shutdown(); - - RxRingBuffer.SPSC_POOL.shutdown(); - - RxRingBuffer.SPMC_POOL.shutdown(); } } - + /** * Start the instance-specific schedulers. */ - synchronized void startInstance() { + synchronized void startInstance() { // NOPMD if (computationScheduler instanceof SchedulerLifecycle) { ((SchedulerLifecycle) computationScheduler).start(); } @@ -222,11 +233,11 @@ synchronized void startInstance() { ((SchedulerLifecycle) newThreadScheduler).start(); } } - + /** * Start the instance-specific schedulers. */ - synchronized void shutdownInstance() { + synchronized void shutdownInstance() { // NOPMD if (computationScheduler instanceof SchedulerLifecycle) { ((SchedulerLifecycle) computationScheduler).shutdown(); } diff --git a/src/main/java/rx/schedulers/TestScheduler.java b/src/main/java/rx/schedulers/TestScheduler.java index fec8bbcd75..0106cdbc67 100644 --- a/src/main/java/rx/schedulers/TestScheduler.java +++ b/src/main/java/rx/schedulers/TestScheduler.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -23,6 +23,8 @@ import rx.Scheduler; import rx.Subscription; import rx.functions.Action0; +import rx.internal.schedulers.SchedulePeriodicHelper; +import rx.internal.schedulers.SchedulePeriodicHelper.NowNanoSupplier; import rx.subscriptions.BooleanSubscription; import rx.subscriptions.Subscriptions; @@ -32,9 +34,13 @@ */ public class TestScheduler extends Scheduler { final Queue queue = new PriorityQueue(11, new CompareActionsByTime()); - static long counter = 0; - private static final class TimedAction { + static long counter; + + // Storing time in nanoseconds internally. + long time; + + static final class TimedAction { final long time; final Action0 action; @@ -53,9 +59,7 @@ public String toString() { } } - private static class CompareActionsByTime implements Comparator { - CompareActionsByTime() { - } + static final class CompareActionsByTime implements Comparator { @Override public int compare(TimedAction action1, TimedAction action2) { @@ -67,9 +71,6 @@ public int compare(TimedAction action1, TimedAction action2) { } } - // Storing time in nanoseconds internally. - long time; - @Override public long now() { return TimeUnit.NANOSECONDS.toMillis(time); @@ -131,13 +132,10 @@ public Worker createWorker() { return new InnerTestScheduler(); } - private final class InnerTestScheduler extends Worker { + final class InnerTestScheduler extends Worker implements NowNanoSupplier { private final BooleanSubscription s = new BooleanSubscription(); - InnerTestScheduler() { - } - @Override public void unsubscribe() { s.unsubscribe(); @@ -176,11 +174,22 @@ public void call() { }); } + @Override + public Subscription schedulePeriodically(Action0 action, long initialDelay, long period, TimeUnit unit) { + return SchedulePeriodicHelper.schedulePeriodically(this, + action, initialDelay, period, unit, this); + } + @Override public long now() { return TestScheduler.this.now(); } + @Override + public long nowNanos() { + return TestScheduler.this.time; + } + } } diff --git a/src/main/java/rx/schedulers/TimeInterval.java b/src/main/java/rx/schedulers/TimeInterval.java index 2123abaa0b..d8065b677f 100644 --- a/src/main/java/rx/schedulers/TimeInterval.java +++ b/src/main/java/rx/schedulers/TimeInterval.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -42,7 +42,7 @@ public TimeInterval(long intervalInMilliseconds, T value) { /** * Returns the time interval, expressed in milliseconds. - * + * * @return the time interval in milliseconds */ public long getIntervalInMilliseconds() { @@ -61,7 +61,7 @@ public T getValue() { // The following methods are generated by eclipse automatically. @Override public int hashCode() { - final int prime = 31; + int prime = 31; int result = 1; result = prime * result @@ -72,20 +72,26 @@ public int hashCode() { @Override public boolean equals(Object obj) { - if (this == obj) + if (this == obj) { return true; - if (obj == null) + } + if (obj == null) { return false; - if (getClass() != obj.getClass()) + } + if (getClass() != obj.getClass()) { return false; + } TimeInterval other = (TimeInterval) obj; - if (intervalInMilliseconds != other.intervalInMilliseconds) + if (intervalInMilliseconds != other.intervalInMilliseconds) { return false; + } if (value == null) { - if (other.value != null) + if (other.value != null) { return false; - } else if (!value.equals(other.value)) + } + } else if (!value.equals(other.value)) { return false; + } return true; } diff --git a/src/main/java/rx/schedulers/Timestamped.java b/src/main/java/rx/schedulers/Timestamped.java index fee18b7976..5a3cac829d 100644 --- a/src/main/java/rx/schedulers/Timestamped.java +++ b/src/main/java/rx/schedulers/Timestamped.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -30,7 +30,7 @@ public Timestamped(long timestampMillis, T value) { /** * Returns the timestamp, expressed in milliseconds. - * + * * @return timestamp in milliseconds */ public long getTimestampMillis() { @@ -39,7 +39,7 @@ public long getTimestampMillis() { /** * Returns the value embedded in the {@code Timestamped} object. - * + * * @return the value */ public T getValue() { @@ -58,22 +58,12 @@ public boolean equals(Object obj) { return false; } Timestamped other = (Timestamped) obj; - if (timestampMillis != other.timestampMillis) { - return false; - } - if (value == null) { - if (other.value != null) { - return false; - } - } else if (!value.equals(other.value)) { - return false; - } - return true; + return timestampMillis == other.timestampMillis && ((value == other.value) || (value != null && value.equals(other.value))); } @Override public int hashCode() { - final int prime = 31; + int prime = 31; int result = 1; result = prime * result + (int) (timestampMillis ^ (timestampMillis >>> 32)); result = prime * result + ((value == null) ? 0 : value.hashCode()); diff --git a/src/main/java/rx/schedulers/TrampolineScheduler.java b/src/main/java/rx/schedulers/TrampolineScheduler.java index bb28916123..4a7edc2edd 100644 --- a/src/main/java/rx/schedulers/TrampolineScheduler.java +++ b/src/main/java/rx/schedulers/TrampolineScheduler.java @@ -22,9 +22,9 @@ */ @Deprecated // Class was part of public API. -public final class TrampolineScheduler extends Scheduler { +public final class TrampolineScheduler extends Scheduler { // NOPMD private TrampolineScheduler() { - throw new AssertionError(); + throw new IllegalStateException("No instances!"); } @Override diff --git a/src/main/java/rx/schedulers/package-info.java b/src/main/java/rx/schedulers/package-info.java index 7dc59c5083..d9e76afba0 100644 --- a/src/main/java/rx/schedulers/package-info.java +++ b/src/main/java/rx/schedulers/package-info.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -14,7 +14,7 @@ * limitations under the License. */ /** - * Scheduler implementations, value+time record classes and the standard factory class to + * Scheduler implementations, value+time record classes and the standard factory class to * return standard RxJava schedulers or wrap any Executor-based (thread pool) instances. */ package rx.schedulers; \ No newline at end of file diff --git a/src/main/java/rx/singles/BlockingSingle.java b/src/main/java/rx/singles/BlockingSingle.java index 1cd0605a33..a113f272ba 100644 --- a/src/main/java/rx/singles/BlockingSingle.java +++ b/src/main/java/rx/singles/BlockingSingle.java @@ -16,28 +16,25 @@ package rx.singles; -import rx.Single; -import rx.SingleSubscriber; -import rx.Subscription; -import rx.annotations.Experimental; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicReference; + +import rx.*; +import rx.exceptions.Exceptions; import rx.internal.operators.BlockingOperatorToFuture; import rx.internal.util.BlockingUtils; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.Future; -import java.util.concurrent.atomic.AtomicReference; - /** * {@code BlockingSingle} is a blocking "version" of {@link Single} that provides blocking * operators. *

      * You construct a {@code BlockingSingle} from a {@code Single} with {@link #from(Single)} * or {@link Single#toBlocking()}. - * + * * @param the value type of the sequence + * @since 1.3 */ -@Experimental -public class BlockingSingle { +public final class BlockingSingle { private final Single single; private BlockingSingle(Single single) { @@ -51,7 +48,6 @@ private BlockingSingle(Single single) { * @param single the {@link Single} you want to convert * @return a {@code BlockingSingle} version of {@code single} */ - @Experimental public static BlockingSingle from(Single single) { return new BlockingSingle(single); } @@ -66,7 +62,6 @@ public static BlockingSingle from(Single single) { * * @return the value emitted by this {@code BlockingSingle} */ - @Experimental public T value() { final AtomicReference returnItem = new AtomicReference(); final AtomicReference returnException = new AtomicReference(); @@ -88,10 +83,7 @@ public void onError(Throwable error) { BlockingUtils.awaitForComplete(latch, subscription); Throwable throwable = returnException.get(); if (throwable != null) { - if (throwable instanceof RuntimeException) { - throw (RuntimeException) throwable; - } - throw new RuntimeException(throwable); + throw Exceptions.propagate(throwable); } return returnItem.get(); } @@ -102,7 +94,6 @@ public void onError(Throwable error) { * @return a {@link Future} that returns the value */ @SuppressWarnings("unchecked") - @Experimental public Future toFuture() { return BlockingOperatorToFuture.toFuture(((Single)single).toObservable()); } diff --git a/src/main/java/rx/subjects/AsyncSubject.java b/src/main/java/rx/subjects/AsyncSubject.java index 26057b6821..f7bd7a21be 100644 --- a/src/main/java/rx/subjects/AsyncSubject.java +++ b/src/main/java/rx/subjects/AsyncSubject.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -18,7 +18,6 @@ import java.util.*; import rx.Observer; -import rx.annotations.Beta; import rx.exceptions.Exceptions; import rx.functions.Action1; import rx.internal.operators.NotificationLite; @@ -53,11 +52,13 @@ subject.onCompleted(); } - * + * * @param * the type of item expected to be observed by the Subject */ public final class AsyncSubject extends Subject { + final SubjectSubscriptionManager state; + volatile Object lastValue; /** * Creates and returns a new {@code AsyncSubject}. @@ -70,24 +71,19 @@ public static AsyncSubject create() { @Override public void call(SubjectObserver o) { Object v = state.getLatest(); - NotificationLite nl = state.nl; - if (v == null || nl.isCompleted(v)) { + if (v == null || NotificationLite.isCompleted(v)) { o.onCompleted(); } else - if (nl.isError(v)) { - o.onError(nl.getError(v)); + if (NotificationLite.isError(v)) { + o.onError(NotificationLite.getError(v)); } else { - o.actual.setProducer(new SingleProducer(o.actual, nl.getValue(v))); + o.actual.setProducer(new SingleProducer(o.actual, NotificationLite.getValue(v))); } } }; return new AsyncSubject(state, state); } - final SubjectSubscriptionManager state; - volatile Object lastValue; - private final NotificationLite nl = NotificationLite.instance(); - protected AsyncSubject(OnSubscribe onSubscribe, SubjectSubscriptionManager state) { super(onSubscribe); this.state = state; @@ -98,13 +94,13 @@ public void onCompleted() { if (state.active) { Object last = lastValue; if (last == null) { - last = nl.completed(); + last = NotificationLite.completed(); } for (SubjectObserver bo : state.terminate(last)) { - if (last == nl.completed()) { + if (last == NotificationLite.completed()) { bo.onCompleted(); } else { - bo.actual.setProducer(new SingleProducer(bo.actual, nl.getValue(last))); + bo.actual.setProducer(new SingleProducer(bo.actual, NotificationLite.getValue(last))); } } } @@ -113,7 +109,7 @@ public void onCompleted() { @Override public void onError(final Throwable e) { if (state.active) { - Object n = nl.error(e); + Object n = NotificationLite.error(e); List errors = null; for (SubjectObserver bo : state.terminate(n)) { try { @@ -132,7 +128,7 @@ public void onError(final Throwable e) { @Override public void onNext(T v) { - lastValue = nl.next(v); + lastValue = NotificationLite.next(v); } @Override @@ -145,46 +141,46 @@ public boolean hasObservers() { *

      Note that unless {@link #hasCompleted()} or {@link #hasThrowable()} returns true, the value * retrieved by {@code getValue()} may get outdated. * @return true if and only if the subject has some value but not an error + * @since 1.2 */ - @Beta public boolean hasValue() { Object v = lastValue; Object o = state.getLatest(); - return !nl.isError(o) && nl.isNext(v); + return !NotificationLite.isError(o) && NotificationLite.isNext(v); } /** * Check if the Subject has terminated with an exception. * @return true if the subject has received a throwable through {@code onError}. + * @since 1.2 */ - @Beta public boolean hasThrowable() { Object o = state.getLatest(); - return nl.isError(o); + return NotificationLite.isError(o); } /** * Check if the Subject has terminated normally. * @return true if the subject completed normally via {@code onCompleted()} + * @since 1.2 */ - @Beta public boolean hasCompleted() { Object o = state.getLatest(); - return o != null && !nl.isError(o); + return o != null && !NotificationLite.isError(o); } /** * Returns the current value of the Subject if there is such a value and * the subject hasn't terminated with an exception. *

      The method can return {@code null} for various reasons. Use {@link #hasValue()}, {@link #hasThrowable()} * and {@link #hasCompleted()} to determine if such {@code null} is a valid value, there was an - * exception or the Subject terminated without receiving any value. + * exception or the Subject terminated without receiving any value. * @return the current value or {@code null} if the Subject doesn't have a value, * has terminated with an exception or has an actual {@code null} as a value. + * @since 1.2 */ - @Beta public T getValue() { Object v = lastValue; Object o = state.getLatest(); - if (!nl.isError(o) && nl.isNext(v)) { - return nl.getValue(v); + if (!NotificationLite.isError(o) && NotificationLite.isNext(v)) { + return NotificationLite.getValue(v); } return null; } @@ -192,12 +188,12 @@ public T getValue() { * Returns the Throwable that terminated the Subject. * @return the Throwable that terminated the Subject or {@code null} if the * subject hasn't terminated yet or it terminated normally. + * @since 1.2 */ - @Beta public Throwable getThrowable() { Object o = state.getLatest(); - if (nl.isError(o)) { - return nl.getError(o); + if (NotificationLite.isError(o)) { + return NotificationLite.getError(o); } return null; } diff --git a/src/main/java/rx/subjects/BehaviorSubject.java b/src/main/java/rx/subjects/BehaviorSubject.java index ad8bd448f6..8a45cf1df9 100644 --- a/src/main/java/rx/subjects/BehaviorSubject.java +++ b/src/main/java/rx/subjects/BehaviorSubject.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -20,7 +20,6 @@ import java.util.*; import rx.Observer; -import rx.annotations.Beta; import rx.exceptions.Exceptions; import rx.functions.Action1; import rx.internal.operators.NotificationLite; @@ -36,14 +35,14 @@ *

      *

       {@code
       
      -  // observer will receive all events.
      +  // observer will receive all 4 events (including "default").
         BehaviorSubject subject = BehaviorSubject.create("default");
         subject.subscribe(observer);
         subject.onNext("one");
         subject.onNext("two");
         subject.onNext("three");
       
      -  // observer will receive the "one", "two" and "three" events, but not "zero"
      +  // observer will receive the "one", "two" and "three" events, but not "default" and "zero"
         BehaviorSubject subject = BehaviorSubject.create("default");
         subject.onNext("zero");
         subject.onNext("one");
      @@ -57,7 +56,7 @@
         subject.onNext("one");
         subject.onCompleted();
         subject.subscribe(observer);
      -  
      +
         // observer will receive only onError
         BehaviorSubject subject = BehaviorSubject.create("default");
         subject.onNext("zero");
      @@ -65,11 +64,15 @@
         subject.onError(new RuntimeException("error"));
         subject.subscribe(observer);
         } 
      - * 
      + *
        * @param 
        *          the type of item expected to be observed by the Subject
        */
       public final class BehaviorSubject extends Subject {
      +    /** An empty array to trigger getValues() to return a new array. */
      +    private static final Object[] EMPTY_ARRAY = new Object[0];
      +    private final SubjectSubscriptionManager state;
      +
           /**
            * Creates a {@link BehaviorSubject} without a default item.
            *
      @@ -83,7 +86,7 @@ public static  BehaviorSubject create() {
           /**
            * Creates a {@link BehaviorSubject} that emits the last item it observed and all subsequent items to each
            * {@link Observer} that subscribes to it.
      -     * 
      +     *
            * @param 
            *            the type of item the Subject will emit
            * @param defaultValue
      @@ -97,23 +100,20 @@ public static  BehaviorSubject create(T defaultValue) {
           private static  BehaviorSubject create(T defaultValue, boolean hasDefault) {
               final SubjectSubscriptionManager state = new SubjectSubscriptionManager();
               if (hasDefault) {
      -            state.setLatest(NotificationLite.instance().next(defaultValue));
      +            state.setLatest(NotificationLite.next(defaultValue));
               }
               state.onAdded = new Action1>() {
       
                   @Override
                   public void call(SubjectObserver o) {
      -                o.emitFirst(state.getLatest(), state.nl);
      +                o.emitFirst(state.getLatest());
                   }
      -            
      +
               };
               state.onTerminated = state.onAdded;
      -        return new BehaviorSubject(state, state); 
      +        return new BehaviorSubject(state, state);
           }
       
      -    private final SubjectSubscriptionManager state;
      -    private final NotificationLite nl = NotificationLite.instance();
      -
           protected BehaviorSubject(OnSubscribe onSubscribe, SubjectSubscriptionManager state) {
               super(onSubscribe);
               this.state = state;
      @@ -123,9 +123,9 @@ protected BehaviorSubject(OnSubscribe onSubscribe, SubjectSubscriptionManager
           public void onCompleted() {
               Object last = state.getLatest();
               if (last == null || state.active) {
      -            Object n = nl.completed();
      +            Object n = NotificationLite.completed();
                   for (SubjectObserver bo : state.terminate(n)) {
      -                bo.emitNext(n, state.nl);
      +                bo.emitNext(n);
                   }
               }
           }
      @@ -134,11 +134,11 @@ public void onCompleted() {
           public void onError(Throwable e) {
               Object last = state.getLatest();
               if (last == null || state.active) {
      -            Object n = nl.error(e);
      +            Object n = NotificationLite.error(e);
                   List errors = null;
                   for (SubjectObserver bo : state.terminate(n)) {
                       try {
      -                    bo.emitNext(n, state.nl);
      +                    bo.emitNext(n);
                       } catch (Throwable e2) {
                           if (errors == null) {
                               errors = new ArrayList();
      @@ -155,9 +155,9 @@ public void onError(Throwable e) {
           public void onNext(T v) {
               Object last = state.getLatest();
               if (last == null || state.active) {
      -            Object n = nl.next(v);
      +            Object n = NotificationLite.next(v);
                   for (SubjectObserver bo : state.next(n)) {
      -                bo.emitNext(n, state.nl);
      +                bo.emitNext(n);
                   }
               }
           }
      @@ -176,44 +176,44 @@ public boolean hasObservers() {
            * 

      Note that unless {@link #hasCompleted()} or {@link #hasThrowable()} returns true, the value * retrieved by {@code getValue()} may get outdated. * @return true if and only if the subject has some value and hasn't terminated yet. + * @since 1.2 */ - @Beta public boolean hasValue() { Object o = state.getLatest(); - return nl.isNext(o); + return NotificationLite.isNext(o); } /** * Check if the Subject has terminated with an exception. * @return true if the subject has received a throwable through {@code onError}. + * @since 1.2 */ - @Beta public boolean hasThrowable() { Object o = state.getLatest(); - return nl.isError(o); + return NotificationLite.isError(o); } /** * Check if the Subject has terminated normally. * @return true if the subject completed normally via {@code onCompleted()} + * @since 1.2 */ - @Beta public boolean hasCompleted() { Object o = state.getLatest(); - return nl.isCompleted(o); + return NotificationLite.isCompleted(o); } /** * Returns the current value of the Subject if there is such a value and * the subject hasn't terminated yet. *

      The method can return {@code null} for various reasons. Use {@link #hasValue()}, {@link #hasThrowable()} * and {@link #hasCompleted()} to determine if such {@code null} is a valid value, there was an - * exception or the Subject terminated (with or without receiving any value). + * exception or the Subject terminated (with or without receiving any value). * @return the current value or {@code null} if the Subject doesn't have a value, * has terminated or has an actual {@code null} as a valid value. + * @since 1.2 */ - @Beta public T getValue() { Object o = state.getLatest(); - if (nl.isNext(o)) { - return nl.getValue(o); + if (NotificationLite.isNext(o)) { + return NotificationLite.getValue(o); } return null; } @@ -221,30 +221,30 @@ public T getValue() { * Returns the Throwable that terminated the Subject. * @return the Throwable that terminated the Subject or {@code null} if the * subject hasn't terminated yet or it terminated normally. + * @since 1.2 */ - @Beta public Throwable getThrowable() { Object o = state.getLatest(); - if (nl.isError(o)) { - return nl.getError(o); + if (NotificationLite.isError(o)) { + return NotificationLite.getError(o); } return null; } /** - * Returns a snapshot of the currently buffered non-terminal events into + * Returns a snapshot of the currently buffered non-terminal events into * the provided {@code a} array or creates a new array if it has not enough capacity. * @param a the array to fill in - * @return the array {@code a} if it had enough capacity or a new array containing the available values + * @return the array {@code a} if it had enough capacity or a new array containing the available values + * @since 1.2 */ - @Beta @SuppressWarnings("unchecked") public T[] getValues(T[] a) { Object o = state.getLatest(); - if (nl.isNext(o)) { + if (NotificationLite.isNext(o)) { if (a.length == 0) { a = (T[])Array.newInstance(a.getClass().getComponentType(), 1); } - a[0] = nl.getValue(o); + a[0] = NotificationLite.getValue(o); if (a.length > 1) { a[1] = null; } @@ -254,19 +254,16 @@ public T[] getValues(T[] a) { } return a; } - - /** An empty array to trigger getValues() to return a new array. */ - private static final Object[] EMPTY_ARRAY = new Object[0]; - + /** * Returns a snapshot of the currently buffered non-terminal events. - *

      The operation is threadsafe. + *

      The operation is thread-safe. * * @return a snapshot of the currently buffered non-terminal events. * @since (If this graduates from being an Experimental class method, replace this parenthetical with the release number) + * @since 1.2 */ @SuppressWarnings("unchecked") - @Beta public Object[] getValues() { T[] r = getValues((T[])EMPTY_ARRAY); if (r == EMPTY_ARRAY) { diff --git a/src/main/java/rx/subjects/PublishSubject.java b/src/main/java/rx/subjects/PublishSubject.java index 42a4a18c7c..eda6945a45 100644 --- a/src/main/java/rx/subjects/PublishSubject.java +++ b/src/main/java/rx/subjects/PublishSubject.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -16,13 +16,12 @@ package rx.subjects; import java.util.*; +import java.util.concurrent.atomic.*; +import rx.*; import rx.Observer; -import rx.annotations.Beta; -import rx.exceptions.Exceptions; -import rx.functions.Action1; -import rx.internal.operators.NotificationLite; -import rx.subjects.SubjectSubscriptionManager.SubjectObserver; +import rx.exceptions.*; +import rx.internal.operators.BackpressureUtils; /** * Subject that, once an {@link Observer} has subscribed, emits all subsequently observed items to the @@ -45,12 +44,14 @@ subject.onCompleted(); } - * + * * @param * the type of items observed and emitted by the Subject */ public final class PublishSubject extends Subject { + final PublishSubjectState state; + /** * Creates and returns a new {@code PublishSubject}. * @@ -58,97 +59,268 @@ public final class PublishSubject extends Subject { * @return the new {@code PublishSubject} */ public static PublishSubject create() { - final SubjectSubscriptionManager state = new SubjectSubscriptionManager(); - state.onTerminated = new Action1>() { - - @Override - public void call(SubjectObserver o) { - o.emitFirst(state.getLatest(), state.nl); - } - - }; - return new PublishSubject(state, state); + return new PublishSubject(new PublishSubjectState()); } - final SubjectSubscriptionManager state; - private final NotificationLite nl = NotificationLite.instance(); - - protected PublishSubject(OnSubscribe onSubscribe, SubjectSubscriptionManager state) { - super(onSubscribe); + protected PublishSubject(PublishSubjectState state) { + super(state); this.state = state; } @Override - public void onCompleted() { - if (state.active) { - Object n = nl.completed(); - for (SubjectObserver bo : state.terminate(n)) { - bo.emitNext(n, state.nl); - } - } - + public void onNext(T v) { + state.onNext(v); } @Override - public void onError(final Throwable e) { - if (state.active) { - Object n = nl.error(e); - List errors = null; - for (SubjectObserver bo : state.terminate(n)) { - try { - bo.emitNext(n, state.nl); - } catch (Throwable e2) { - if (errors == null) { - errors = new ArrayList(); - } - errors.add(e2); - } - } - Exceptions.throwIfAny(errors); - } + public void onError(Throwable e) { + state.onError(e); } @Override - public void onNext(T v) { - for (SubjectObserver bo : state.observers()) { - bo.onNext(v); - } + public void onCompleted() { + state.onCompleted(); } + @Override public boolean hasObservers() { - return state.observers().length > 0; + return state.get().length != 0; } - + /** * Check if the Subject has terminated with an exception. * @return true if the subject has received a throwable through {@code onError}. + * @since 1.2 */ - @Beta public boolean hasThrowable() { - Object o = state.getLatest(); - return nl.isError(o); + return state.get() == PublishSubjectState.TERMINATED && state.error != null; } /** * Check if the Subject has terminated normally. * @return true if the subject completed normally via {@code onCompleted} + * @since 1.2 */ - @Beta public boolean hasCompleted() { - Object o = state.getLatest(); - return o != null && !nl.isError(o); + return state.get() == PublishSubjectState.TERMINATED && state.error == null; } /** * Returns the Throwable that terminated the Subject. * @return the Throwable that terminated the Subject or {@code null} if the * subject hasn't terminated yet or it terminated normally. + * @since 1.2 */ - @Beta public Throwable getThrowable() { - Object o = state.getLatest(); - if (nl.isError(o)) { - return nl.getError(o); + if (state.get() == PublishSubjectState.TERMINATED) { + return state.error; } return null; } + + static final class PublishSubjectState + extends AtomicReference[]> + implements OnSubscribe, Observer { + + /** */ + private static final long serialVersionUID = -7568940796666027140L; + + @SuppressWarnings("rawtypes") + static final PublishSubjectProducer[] EMPTY = new PublishSubjectProducer[0]; + @SuppressWarnings("rawtypes") + static final PublishSubjectProducer[] TERMINATED = new PublishSubjectProducer[0]; + + Throwable error; + + @SuppressWarnings("unchecked") + public PublishSubjectState() { + lazySet(EMPTY); + } + + @Override + public void call(Subscriber t) { + PublishSubjectProducer pp = new PublishSubjectProducer(this, t); + t.add(pp); + t.setProducer(pp); + + if (add(pp)) { + if (pp.isUnsubscribed()) { + remove(pp); + } + } else { + Throwable ex = error; + if (ex != null) { + t.onError(ex); + } else { + t.onCompleted(); + } + } + } + + + boolean add(PublishSubjectProducer inner) { + for (;;) { + PublishSubjectProducer[] curr = get(); + if (curr == TERMINATED) { + return false; + } + + int n = curr.length; + + @SuppressWarnings("unchecked") + PublishSubjectProducer[] next = new PublishSubjectProducer[n + 1]; + System.arraycopy(curr, 0, next, 0, n); + + next[n] = inner; + if (compareAndSet(curr, next)) { + return true; + } + } + } + + @SuppressWarnings("unchecked") + void remove(PublishSubjectProducer inner) { + for (;;) { + PublishSubjectProducer[] curr = get(); + if (curr == TERMINATED || curr == EMPTY) { + return; + } + + int n = curr.length; + int j = -1; + for (int i = 0; i < n; i++) { + if (curr[i] == inner) { + j = i; + break; + } + } + + if (j < 0) { + return; + } + + PublishSubjectProducer[] next; + if (n == 1) { + next = EMPTY; + } else { + next = new PublishSubjectProducer[n - 1]; + System.arraycopy(curr, 0, next, 0, j); + System.arraycopy(curr, j + 1, next, j, n - j - 1); + } + + if (compareAndSet(curr, next)) { + return; + } + } + } + + @Override + public void onNext(T t) { + for (PublishSubjectProducer pp : get()) { + pp.onNext(t); + } + } + + @SuppressWarnings("unchecked") + @Override + public void onError(Throwable e) { + error = e; + List errors = null; + for (PublishSubjectProducer pp : getAndSet(TERMINATED)) { + try { + pp.onError(e); + } catch (Throwable ex) { + if (errors == null) { + errors = new ArrayList(1); + } + errors.add(ex); + } + } + + Exceptions.throwIfAny(errors); + } + + @SuppressWarnings("unchecked") + @Override + public void onCompleted() { + for (PublishSubjectProducer pp : getAndSet(TERMINATED)) { + pp.onCompleted(); + } + } + + } + + static final class PublishSubjectProducer + extends AtomicLong + implements Producer, Subscription, Observer { + /** */ + private static final long serialVersionUID = 6451806817170721536L; + + final PublishSubjectState parent; + + final Subscriber actual; + + long produced; + + public PublishSubjectProducer(PublishSubjectState parent, Subscriber actual) { + this.parent = parent; + this.actual = actual; + } + + @Override + public void request(long n) { + if (BackpressureUtils.validate(n)) { + for (;;) { + long r = get(); + if (r == Long.MIN_VALUE) { + return; + } + long u = BackpressureUtils.addCap(r, n); + if (compareAndSet(r, u)) { + return; + } + } + } + } + + @Override + public boolean isUnsubscribed() { + return get() == Long.MIN_VALUE; + } + + @Override + public void unsubscribe() { + if (getAndSet(Long.MIN_VALUE) != Long.MIN_VALUE) { + parent.remove(this); + } + } + + @Override + public void onNext(T t) { + long r = get(); + if (r != Long.MIN_VALUE) { + long p = produced; + if (r != p) { + produced = p + 1; + actual.onNext(t); + } else { + unsubscribe(); + actual.onError(new MissingBackpressureException("PublishSubject: could not emit value due to lack of requests")); + } + } + } + + @Override + public void onError(Throwable e) { + if (get() != Long.MIN_VALUE) { + actual.onError(e); + } + } + + @Override + public void onCompleted() { + if (get() != Long.MIN_VALUE) { + actual.onCompleted(); + } + } + } } diff --git a/src/main/java/rx/subjects/ReplaySubject.java b/src/main/java/rx/subjects/ReplaySubject.java index a842baedbf..0bf2ed7f90 100644 --- a/src/main/java/rx/subjects/ReplaySubject.java +++ b/src/main/java/rx/subjects/ReplaySubject.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -22,10 +22,9 @@ import rx.*; import rx.Observer; -import rx.annotations.Beta; import rx.exceptions.Exceptions; import rx.internal.operators.BackpressureUtils; -import rx.internal.util.RxJavaPluginUtils; +import rx.plugins.RxJavaHooks; import rx.schedulers.Schedulers; /** @@ -48,11 +47,15 @@ subject.subscribe(observer2); } - * + * * @param * the type of items observed and emitted by the Subject */ public final class ReplaySubject extends Subject { + /** The state storing the history and the references. */ + final ReplayState state; + /** An empty array to trigger getValues() to return a new array. */ + private static final Object[] EMPTY_ARRAY = new Object[0]; /** * Creates an unbounded replay subject. *

      @@ -136,7 +139,7 @@ public static ReplaySubject create(int capacity) { * discards the oldest item. *

      * When observers subscribe to a terminated {@code ReplaySubject}, they are guaranteed to see at most - * {@code size} {@code onNext} events followed by a termination event. + * {@code size} {@code onNext} events followed by a termination event. *

      * If an observer subscribes while the {@code ReplaySubject} 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 @@ -160,10 +163,10 @@ public static ReplaySubject createWithSize(int size) { * In this setting, the {@code ReplaySubject} internally tags each observed item with a timestamp value * supplied by the {@link Scheduler} and keeps only those whose age is less than the supplied time value * 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. + * this first item is then evicted by any subsequent item or termination event, leaving the buffer empty. *

      * Once the subject is terminated, observers subscribing to it will receive items that remained in the - * buffer after the terminal event, regardless of their age. + * buffer after the terminal event, regardless of their age. *

      * If an observer subscribes while the {@code ReplaySubject} 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, @@ -229,24 +232,21 @@ public static ReplaySubject createWithTimeAndSize(long time, TimeUnit uni return new ReplaySubject(state); } - /** The state storing the history and the references. */ - final ReplayState state; - ReplaySubject(ReplayState state) { super(state); this.state = state; } - + @Override public void onNext(T t) { state.onNext(t); } - + @Override public void onError(final Throwable e) { state.onError(e); } - + @Override public void onCompleted() { state.onCompleted(); @@ -266,16 +266,16 @@ public boolean hasObservers() { /** * Check if the Subject has terminated with an exception. * @return true if the subject has received a throwable through {@code onError}. + * @since 1.2 */ - @Beta public boolean hasThrowable() { return state.isTerminated() && state.buffer.error() != null; } /** * Check if the Subject has terminated normally. * @return true if the subject completed normally via {@code onCompleted} + * @since 1.2 */ - @Beta public boolean hasCompleted() { return state.isTerminated() && state.buffer.error() == null; } @@ -283,8 +283,8 @@ public boolean hasCompleted() { * Returns the Throwable that terminated the Subject. * @return the Throwable that terminated the Subject or {@code null} if the * subject hasn't terminated yet or it terminated normally. + * @since 1.2 */ - @Beta public Throwable getThrowable() { if (state.isTerminated()) { return state.buffer.error(); @@ -294,45 +294,46 @@ public Throwable getThrowable() { /** * Returns the current number of items (non-terminal events) available for replay. * @return the number of items available + * @since 1.2 */ - @Beta public int size() { return state.buffer.size(); } + /** * @return true if the Subject holds at least one non-terminal event available for replay + * @since 1.2 */ - @Beta public boolean hasAnyValue() { return !state.buffer.isEmpty(); } - @Beta + + /** + * @return true if the Subject holds at least one non-terminal event available for replay + * @since 1.2 + */ public boolean hasValue() { return hasAnyValue(); } /** - * Returns a snapshot of the currently buffered non-terminal events into + * Returns a snapshot of the currently buffered non-terminal events into * the provided {@code a} array or creates a new array if it has not enough capacity. * @param a the array to fill in - * @return the array {@code a} if it had enough capacity or a new array containing the available values + * @return the array {@code a} if it had enough capacity or a new array containing the available values + * @since 1.2 */ - @Beta public T[] getValues(T[] a) { return state.buffer.toArray(a); } - - /** An empty array to trigger getValues() to return a new array. */ - private static final Object[] EMPTY_ARRAY = new Object[0]; - + /** * Returns a snapshot of the currently buffered non-terminal events. - *

      The operation is threadsafe. + *

      The operation is thread-safe. * * @return a snapshot of the currently buffered non-terminal events. - * @since (If this graduates from being an Experimental class method, replace this parenthetical with the release number) + * @since 1.2 */ @SuppressWarnings("unchecked") - @Beta public Object[] getValues() { T[] r = getValues((T[])EMPTY_ARRAY); if (r == EMPTY_ARRAY) { @@ -340,12 +341,15 @@ public Object[] getValues() { } return r; } - - @Beta + + /** + * @return the latest value available + * @since 1.2 + */ public T getValue() { return state.buffer.last(); } - + /** * Holds onto the array of Subscriber-wrapping ReplayProducers and * the buffer that holds values to be replayed; it manages @@ -353,32 +357,32 @@ public T getValue() { * * @param the value type */ - static final class ReplayState + static final class ReplayState extends AtomicReference[]> implements OnSubscribe, Observer { /** */ private static final long serialVersionUID = 5952362471246910544L; - + final ReplayBuffer buffer; - + @SuppressWarnings("rawtypes") static final ReplayProducer[] EMPTY = new ReplayProducer[0]; @SuppressWarnings("rawtypes") static final ReplayProducer[] TERMINATED = new ReplayProducer[0]; - + @SuppressWarnings("unchecked") public ReplayState(ReplayBuffer buffer) { this.buffer = buffer; lazySet(EMPTY); } - + @Override public void call(Subscriber t) { ReplayProducer rp = new ReplayProducer(t, this); t.add(rp); t.setProducer(rp); - + if (add(rp)) { if (rp.isUnsubscribed()) { remove(rp); @@ -387,27 +391,27 @@ public void call(Subscriber t) { } buffer.drain(rp); } - + boolean add(ReplayProducer rp) { for (;;) { ReplayProducer[] a = get(); if (a == TERMINATED) { return false; } - + int n = a.length; - + @SuppressWarnings("unchecked") ReplayProducer[] b = new ReplayProducer[n + 1]; System.arraycopy(a, 0, b, 0, n); b[n] = rp; - + if (compareAndSet(a, b)) { return true; } } } - + @SuppressWarnings("unchecked") void remove(ReplayProducer rp) { for (;;) { @@ -415,9 +419,9 @@ void remove(ReplayProducer rp) { if (a == TERMINATED || a == EMPTY) { return; } - + int n = a.length; - + int j = -1; for (int i = 0; i < n; i++) { if (a[i] == rp) { @@ -425,11 +429,11 @@ void remove(ReplayProducer rp) { break; } } - + if (j < 0) { return; } - + ReplayProducer[] b; if (n == 1) { b = EMPTY; @@ -447,7 +451,7 @@ void remove(ReplayProducer rp) { @Override public void onNext(T t) { ReplayBuffer b = buffer; - + b.next(t); for (ReplayProducer rp : get()) { b.drain(rp); @@ -458,7 +462,7 @@ public void onNext(T t) { @Override public void onError(Throwable e) { ReplayBuffer b = buffer; - + b.error(e); List errors = null; for (ReplayProducer rp : getAndSet(TERMINATED)) { @@ -471,7 +475,7 @@ public void onError(Throwable e) { errors.add(ex); } } - + Exceptions.throwIfAny(errors); } @@ -479,19 +483,19 @@ public void onError(Throwable e) { @Override public void onCompleted() { ReplayBuffer b = buffer; - + b.complete(); for (ReplayProducer rp : getAndSet(TERMINATED)) { b.drain(rp); } } - - + + boolean isTerminated() { return get() == TERMINATED; } } - + /** * The base interface for buffering signals to be replayed to individual * Subscribers. @@ -499,28 +503,28 @@ boolean isTerminated() { * @param the value type */ interface ReplayBuffer { - + void next(T t); - + void error(Throwable e); - + void complete(); - + void drain(ReplayProducer rp); - + boolean isComplete(); - + Throwable error(); - + T last(); - + int size(); - + boolean isEmpty(); - + T[] toArray(T[] a); } - + /** * An unbounded ReplayBuffer implementation that uses linked-arrays * to avoid copy-on-grow situation with ArrayList. @@ -529,18 +533,18 @@ interface ReplayBuffer { */ static final class ReplayUnboundedBuffer implements ReplayBuffer { final int capacity; - + volatile int size; - + final Object[] head; - + Object[] tail; - + int tailIndex; - + volatile boolean done; Throwable error; - + public ReplayUnboundedBuffer(int capacity) { this.capacity = capacity; this.tail = this.head = new Object[capacity + 1]; @@ -564,13 +568,13 @@ public void next(T t) { tailIndex = i + 1; } size++; - + } @Override public void error(Throwable e) { if (done) { - RxJavaPluginUtils.handleException(e); + RxJavaHooks.onError(e); return; } error = e; @@ -587,30 +591,30 @@ public void drain(ReplayProducer rp) { if (rp.getAndIncrement() != 0) { return; } - + int missed = 1; - + final Subscriber a = rp.actual; final int n = capacity; - + for (;;) { - + long r = rp.requested.get(); long e = 0L; - + Object[] node = (Object[])rp.node; if (node == null) { node = head; } int tailIndex = rp.tailIndex; int index = rp.index; - + while (e != r) { if (a.isUnsubscribed()) { rp.node = null; return; } - + boolean d = done; boolean empty = index == size; @@ -624,32 +628,32 @@ public void drain(ReplayProducer rp) { } return; } - + if (empty) { break; } - + if (tailIndex == n) { node = (Object[])node[tailIndex]; tailIndex = 0; } - + @SuppressWarnings("unchecked") T v = (T)node[tailIndex]; - + a.onNext(v); - + e++; tailIndex++; index++; } - + if (e == r) { if (a.isUnsubscribed()) { rp.node = null; return; } - + boolean d = done; boolean empty = index == size; @@ -664,17 +668,17 @@ public void drain(ReplayProducer rp) { return; } } - + if (e != 0L) { if (r != Long.MAX_VALUE) { BackpressureUtils.produced(rp.requested, e); } } - + rp.index = index; rp.tailIndex = tailIndex; rp.node = node; - + missed = rp.addAndGet(-missed); if (missed == 0) { return; @@ -705,12 +709,12 @@ public T last() { } Object[] h = head; int n = capacity; - + while (s >= n) { h = (Object[])h[n]; s -= n; } - + return (T)h[s - 1]; } @@ -742,29 +746,29 @@ public T[] toArray(T[] a) { j += n; h = (Object[])h[n]; } - + System.arraycopy(h, 0, a, j, s - j); - + if (a.length > s) { a[s] = null; } - + return a; } } - + static final class ReplaySizeBoundBuffer implements ReplayBuffer { final int limit; - + volatile Node head; - + Node tail; int size; volatile boolean done; Throwable error; - + public ReplaySizeBoundBuffer(int limit) { this.limit = limit; Node n = new Node(null); @@ -801,32 +805,32 @@ public void drain(ReplayProducer rp) { if (rp.getAndIncrement() != 0) { return; } - + final Subscriber a = rp.actual; - + int missed = 1; - + for (;;) { - + long r = rp.requested.get(); long e = 0L; - + @SuppressWarnings("unchecked") Node node = (Node)rp.node; if (node == null) { node = head; } - + while (e != r) { if (a.isUnsubscribed()) { rp.node = null; return; } - + boolean d = done; Node next = node.get(); boolean empty = next == null; - + if (d && empty) { rp.node = null; Throwable ex = error; @@ -837,26 +841,26 @@ public void drain(ReplayProducer rp) { } return; } - + if (empty) { break; } - + a.onNext(next.value); - + e++; node = next; } - + if (e == r) { if (a.isUnsubscribed()) { rp.node = null; return; } - + boolean d = done; boolean empty = node.get() == null; - + if (d && empty) { rp.node = null; Throwable ex = error; @@ -868,15 +872,15 @@ public void drain(ReplayProducer rp) { return; } } - + if (e != 0L) { if (r != Long.MAX_VALUE) { BackpressureUtils.produced(rp.requested, e); } } - + rp.node = node; - + missed = rp.addAndGet(-missed); if (missed == 0) { return; @@ -887,9 +891,9 @@ public void drain(ReplayProducer rp) { static final class Node extends AtomicReference> { /** */ private static final long serialVersionUID = 3713592843205853725L; - + final T value; - + public Node(T value) { this.value = value; } @@ -934,7 +938,7 @@ public boolean isEmpty() { @Override public T[] toArray(T[] a) { List list = new ArrayList(); - + Node n = head.get(); while (n != null) { list.add(n.value); @@ -947,20 +951,20 @@ public T[] toArray(T[] a) { static final class ReplaySizeAndTimeBoundBuffer implements ReplayBuffer { final int limit; - + final long maxAgeMillis; - + final Scheduler scheduler; - + volatile TimedNode head; - + TimedNode tail; int size; volatile boolean done; Throwable error; - + public ReplaySizeAndTimeBoundBuffer(int limit, long maxAgeMillis, Scheduler scheduler) { this.limit = limit; TimedNode n = new TimedNode(null, 0L); @@ -973,23 +977,23 @@ public ReplaySizeAndTimeBoundBuffer(int limit, long maxAgeMillis, Scheduler sche @Override public void next(T value) { long now = scheduler.now(); - + TimedNode n = new TimedNode(value, now); tail.set(n); tail = n; - + now -= maxAgeMillis; int s = size; TimedNode h0 = head; TimedNode h = h0; - + if (s == limit) { h = h.get(); } else { s++; } - + while ((n = h.get()) != null) { if (n.timestamp > now) { break; @@ -997,7 +1001,7 @@ public void next(T value) { h = n; s--; } - + size = s; if (h != h0) { head = h; @@ -1015,21 +1019,21 @@ public void complete() { evictFinal(); done = true; } - + void evictFinal() { long now = scheduler.now() - maxAgeMillis; - + TimedNode h0 = head; TimedNode h = h0; TimedNode n; - + while ((n = h.get()) != null) { if (n.timestamp > now) { break; } h = n; } - + if (h0 != h) { head = h; } @@ -1047,38 +1051,38 @@ TimedNode latestHead() { } return h; } - + @Override public void drain(ReplayProducer rp) { if (rp.getAndIncrement() != 0) { return; } - + final Subscriber a = rp.actual; - + int missed = 1; - + for (;;) { - + long r = rp.requested.get(); long e = 0L; - + @SuppressWarnings("unchecked") TimedNode node = (TimedNode)rp.node; if (node == null) { node = latestHead(); } - + while (e != r) { if (a.isUnsubscribed()) { rp.node = null; return; } - + boolean d = done; TimedNode next = node.get(); boolean empty = next == null; - + if (d && empty) { rp.node = null; Throwable ex = error; @@ -1089,26 +1093,26 @@ public void drain(ReplayProducer rp) { } return; } - + if (empty) { break; } - + a.onNext(next.value); - + e++; node = next; } - + if (e == r) { if (a.isUnsubscribed()) { rp.node = null; return; } - + boolean d = done; boolean empty = node.get() == null; - + if (d && empty) { rp.node = null; Throwable ex = error; @@ -1120,15 +1124,15 @@ public void drain(ReplayProducer rp) { return; } } - + if (e != 0L) { if (r != Long.MAX_VALUE) { BackpressureUtils.produced(rp.requested, e); } } - + rp.node = node; - + missed = rp.addAndGet(-missed); if (missed == 0) { return; @@ -1139,11 +1143,11 @@ public void drain(ReplayProducer rp) { static final class TimedNode extends AtomicReference> { /** */ private static final long serialVersionUID = 3713592843205853725L; - + final T value; - + final long timestamp; - + public TimedNode(T value, long timestamp) { this.value = value; this.timestamp = timestamp; @@ -1189,7 +1193,7 @@ public boolean isEmpty() { @Override public T[] toArray(T[] a) { List list = new ArrayList(); - + TimedNode n = latestHead().get(); while (n != null) { list.add(n.value); @@ -1206,10 +1210,10 @@ public T[] toArray(T[] a) { *

      * The this holds the current work-in-progress indicator used by serializing * replays. - * + * * @param the value type */ - static final class ReplayProducer + static final class ReplayProducer extends AtomicInteger implements Producer, Subscription { /** */ @@ -1217,36 +1221,36 @@ static final class ReplayProducer /** The wrapped Subscriber instance. */ final Subscriber actual; - + /** Holds the current requested amount. */ final AtomicLong requested; /** Holds the back-reference to the replay state object. */ final ReplayState state; - /** + /** * Unbounded buffer.drain() uses this field to remember the absolute index of * values replayed to this Subscriber. */ int index; - - /** + + /** * Unbounded buffer.drain() uses this index within its current node to indicate * how many items were replayed from that particular node so far. */ int tailIndex; - - /** + + /** * Stores the current replay node of the buffer to be used by buffer.drain(). */ Object node; - + public ReplayProducer(Subscriber actual, ReplayState state) { this.actual = actual; this.requested = new AtomicLong(); this.state = state; } - + @Override public void unsubscribe() { state.remove(this); diff --git a/src/main/java/rx/subjects/SerializedSubject.java b/src/main/java/rx/subjects/SerializedSubject.java index 02644d9c9b..9626df896a 100644 --- a/src/main/java/rx/subjects/SerializedSubject.java +++ b/src/main/java/rx/subjects/SerializedSubject.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -31,7 +31,7 @@ *

      {@code
        * mySafeSubject = new SerializedSubject( myUnsafeSubject );
        * }
      - * + * * @param the input value type * @param the output value type */ diff --git a/src/main/java/rx/subjects/Subject.java b/src/main/java/rx/subjects/Subject.java index f25a6d496b..8f080f2f5e 100644 --- a/src/main/java/rx/subjects/Subject.java +++ b/src/main/java/rx/subjects/Subject.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -33,12 +33,12 @@ protected Subject(OnSubscribe onSubscribe) { * @return true if there is at least one Observer subscribed to this Subject, false otherwise */ public abstract boolean hasObservers(); - + /** * Wraps a {@link Subject} so that it is safe to call its various {@code on} methods from different threads. *

      - * When you use an ordinary {@link Subject} as a {@link Subscriber}, you must take care not to call its - * {@link Subscriber#onNext} method (or its other {@code on} methods) from multiple threads, as this could + * When you use an ordinary {@link Subject} as a {@link Subscriber}, you must take care not to call its + * {@link Subscriber#onNext} method (or its other {@code 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. @@ -48,7 +48,7 @@ protected Subject(OnSubscribe onSubscribe) { *

      {@code
            * mySafeSubject = myUnsafeSubject.toSerialized();
            * }
      - * + * * @return SerializedSubject wrapping the current Subject */ public final SerializedSubject toSerialized() { diff --git a/src/main/java/rx/subjects/SubjectSubscriptionManager.java b/src/main/java/rx/subjects/SubjectSubscriptionManager.java index 215deb4936..3e486d2ce8 100644 --- a/src/main/java/rx/subjects/SubjectSubscriptionManager.java +++ b/src/main/java/rx/subjects/SubjectSubscriptionManager.java @@ -46,8 +46,6 @@ Action1> onAdded = Actions.empty(); /** Action called when the subscriber wants to subscribe to a terminal state. */ Action1> onTerminated = Actions.empty(); - /** The notification lite. */ - public final NotificationLite nl = NotificationLite.instance(); public SubjectSubscriptionManager() { super(State.EMPTY); @@ -72,7 +70,7 @@ public void call() { remove(bo); } })); - } + } /** Set the latest NotificationLite value. */ void setLatest(Object value) { latest = value; @@ -153,7 +151,7 @@ protected static final class State { static final SubjectObserver[] NO_OBSERVERS = new SubjectObserver[0]; static final State TERMINATED = new State(true, NO_OBSERVERS); static final State EMPTY = new State(false, NO_OBSERVERS); - + public State(boolean terminated, SubjectObserver[] observers) { this.terminated = terminated; this.observers = observers; @@ -197,7 +195,7 @@ public State remove(SubjectObserver o) { return new State(terminated, b); } } - + /** * Observer wrapping the actual Subscriber and providing various * emission facilities. @@ -214,7 +212,7 @@ protected static final class SubjectObserver implements Observer { List queue; /* volatile */boolean fastPath; /** Indicate that the observer has caught up. */ - protected volatile boolean caughtUp; + volatile boolean caughtUp; /** Indicate where the observer is at replaying. */ private volatile Object index; public SubjectObserver(Subscriber actual) { @@ -238,7 +236,7 @@ public void onCompleted() { * @param n the NotificationLite value * @param nl the type-appropriate notification lite object */ - protected void emitNext(Object n, final NotificationLite nl) { + void emitNext(Object n) { if (!fastPath) { synchronized (this) { first = false; @@ -252,15 +250,14 @@ protected void emitNext(Object n, final NotificationLite nl) { } fastPath = true; } - nl.accept(actual, n); + NotificationLite.accept(actual, n); } /** * Tries to emit a NotificationLite value as the first * value and drains the queue as long as possible. - * @param n the NotificationLite value * @param nl the type-appropriate notification lite object */ - protected void emitFirst(Object n, final NotificationLite nl) { + void emitFirst(Object n) { synchronized (this) { if (!first || emitting) { return; @@ -269,7 +266,7 @@ protected void emitFirst(Object n, final NotificationLite nl) { emitting = n != null; } if (n != null) { - emitLoop(null, n, nl); + emitLoop(null, n); } } /** @@ -278,19 +275,19 @@ protected void emitFirst(Object n, final NotificationLite nl) { * @param current the current content to emit * @param nl the type-appropriate notification lite object */ - protected void emitLoop(List localQueue, Object current, final NotificationLite nl) { + void emitLoop(List localQueue, Object current) { boolean once = true; boolean skipFinal = false; try { do { if (localQueue != null) { for (Object n : localQueue) { - accept(n, nl); + accept(n); } } if (once) { once = false; - accept(current, nl); + accept(current); } synchronized (this) { localQueue = queue; @@ -315,14 +312,14 @@ protected void emitLoop(List localQueue, Object current, final Notificat * @param n the value to dispatch * @param nl the type-appropriate notification lite object */ - protected void accept(Object n, final NotificationLite nl) { + void accept(Object n) { if (n != null) { - nl.accept(actual, n); + NotificationLite.accept(actual, n); } } - + /** @return the actual Observer. */ - protected Observer getActual() { + Observer getActual() { return actual; } /** diff --git a/src/main/java/rx/subjects/TestSubject.java b/src/main/java/rx/subjects/TestSubject.java index db57feedd4..be142ea0f3 100644 --- a/src/main/java/rx/subjects/TestSubject.java +++ b/src/main/java/rx/subjects/TestSubject.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -34,6 +34,9 @@ * the type of item observed by and emitted by the subject */ public final class TestSubject extends Subject { + private final SubjectSubscriptionManager state; + private final Scheduler.Worker innerScheduler; + /** * Creates and returns a new {@code TestSubject}. @@ -49,18 +52,15 @@ public static TestSubject create(TestScheduler scheduler) { @Override public void call(SubjectObserver o) { - o.emitFirst(state.getLatest(), state.nl); + o.emitFirst(state.getLatest()); } - + }; state.onTerminated = state.onAdded; return new TestSubject(state, state, scheduler); } - private final SubjectSubscriptionManager state; - private final Scheduler.Worker innerScheduler; - protected TestSubject(OnSubscribe onSubscribe, SubjectSubscriptionManager state, TestScheduler scheduler) { super(onSubscribe); this.state = state; @@ -75,9 +75,9 @@ public void onCompleted() { onCompleted(0); } - void _onCompleted() { + void internalOnCompleted() { if (state.active) { - for (SubjectObserver bo : state.terminate(NotificationLite.instance().completed())) { + for (SubjectObserver bo : state.terminate(NotificationLite.completed())) { bo.onCompleted(); } } @@ -94,7 +94,7 @@ public void onCompleted(long delayTime) { @Override public void call() { - _onCompleted(); + internalOnCompleted(); } }, delayTime, TimeUnit.MILLISECONDS); @@ -108,9 +108,9 @@ public void onError(final Throwable e) { onError(e, 0); } - void _onError(final Throwable e) { + void internalOnError(final Throwable e) { if (state.active) { - for (SubjectObserver bo : state.terminate(NotificationLite.instance().error(e))) { + for (SubjectObserver bo : state.terminate(NotificationLite.error(e))) { bo.onError(e); } } @@ -129,7 +129,7 @@ public void onError(final Throwable e, long delayTime) { @Override public void call() { - _onError(e); + internalOnError(e); } }, delayTime, TimeUnit.MILLISECONDS); @@ -143,7 +143,7 @@ public void onNext(T v) { onNext(v, 0); } - void _onNext(T v) { + void internalOnNext(T v) { for (Observer o : state.observers()) { o.onNext(v); } @@ -162,7 +162,7 @@ public void onNext(final T v, long delayTime) { @Override public void call() { - _onNext(v); + internalOnNext(v); } }, delayTime, TimeUnit.MILLISECONDS); diff --git a/src/main/java/rx/subjects/UnicastSubject.java b/src/main/java/rx/subjects/UnicastSubject.java index f401c364f1..e7addc5cd8 100644 --- a/src/main/java/rx/subjects/UnicastSubject.java +++ b/src/main/java/rx/subjects/UnicastSubject.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -19,7 +19,6 @@ import java.util.concurrent.atomic.*; import rx.*; -import rx.annotations.Experimental; import rx.exceptions.*; import rx.functions.Action0; import rx.internal.operators.*; @@ -32,21 +31,25 @@ * amount. In this case, the buffered values are no longer retained. If the Subscriber * requests a limited amount, queueing is involved and only those values are retained which * weren't requested by the Subscriber at that time. - * + *

      + * * @param the input and output value type + * @since 1.3 */ -@Experimental public final class UnicastSubject extends Subject { + final State state; + /** * Constructs an empty UnicastSubject instance with the default capacity hint of 16 elements. - * + * * @param the input and output value type * @return the created UnicastSubject instance */ public static UnicastSubject create() { return create(16); } + /** * Constructs an empty UnicastSubject instance with a capacity hint. *

      The capacity hint determines the internal queue's island size: the larger @@ -57,7 +60,19 @@ public static UnicastSubject create() { * @return the created BufferUntilSubscriber instance */ public static UnicastSubject create(int capacityHint) { - State state = new State(capacityHint, null); + State state = new State(capacityHint, false, null); + return new UnicastSubject(state); + } + + /** + * Constructs an empty UnicastSubject instance with the default capacity hint of 16 elements. + * + * @param delayError deliver pending next events before error. + * @param the input and output value type + * @return the created UnicastSubject instance + */ + public static UnicastSubject create(boolean delayError) { + State state = new State(16, delayError, null); return new UnicastSubject(state); } @@ -76,37 +91,56 @@ public static UnicastSubject create(int capacityHint) { * @return the created BufferUntilSubscriber instance */ public static UnicastSubject create(int capacityHint, Action0 onTerminated) { - State state = new State(capacityHint, onTerminated); + State state = new State(capacityHint, false, onTerminated); return new UnicastSubject(state); } - final State state; + /** + * Constructs an empty UnicastSubject instance with a capacity hint, delay error + * flag and Action0 instance to call if the subject reaches its terminal state + * or the single Subscriber unsubscribes mid-sequence. + *

      The capacity hint determines the internal queue's island size: the larger + * it is the less frequent allocation will happen if there is no subscriber + * or the subscriber hasn't caught up. + * @param the input and output value type + * @param capacityHint the capacity hint for the internal queue + * @param onTerminated the optional callback to call when subject reaches its terminal state + * or the single Subscriber unsubscribes mid-sequence. It will be called + * at most once. + * @param delayError flag indicating whether to deliver pending next events before error. + * @return the created BufferUntilSubscriber instance + */ + public static UnicastSubject create(int capacityHint, + Action0 onTerminated, boolean delayError) { + State state = new State(capacityHint, delayError, onTerminated); + return new UnicastSubject(state); + } private UnicastSubject(State state) { super(state); this.state = state; } - + @Override public void onNext(T t) { state.onNext(t); } - + @Override public void onError(Throwable e) { state.onError(e); } - + @Override public void onCompleted() { state.onCompleted(); } - + @Override public boolean hasObservers() { return state.subscriber.get() != null; } - + /** * The single-consumption replaying state. * @@ -119,8 +153,8 @@ static final class State extends AtomicLong implements Producer, Observer, final AtomicReference> subscriber; /** The queue holding values until the subscriber arrives and catches up. */ final Queue queue; - /** JCTools queues don't accept nulls. */ - final NotificationLite nl; + /** Deliver pending next events before error. */ + final boolean delayError; /** Atomically set to true on terminal condition. */ final AtomicReference terminateOnce; /** In case the source emitted an error. */ @@ -139,12 +173,13 @@ static final class State extends AtomicLong implements Producer, Observer, * reduce allocation frequency * @param onTerminated the action to call when the subject reaches its terminal state or * the single subscriber unsubscribes. + * @param delayError deliver pending next events before error. */ - public State(int capacityHint, Action0 onTerminated) { - this.nl = NotificationLite.instance(); + public State(int capacityHint, boolean delayError, Action0 onTerminated) { this.subscriber = new AtomicReference>(); this.terminateOnce = onTerminated != null ? new AtomicReference(onTerminated) : null; - + this.delayError = delayError; + Queue q; if (capacityHint > 1) { q = UnsafeAccess.isUnsafeAvailable() @@ -157,7 +192,7 @@ public State(int capacityHint, Action0 onTerminated) { } this.queue = q; } - + @Override public void onNext(T t) { if (!done) { @@ -171,7 +206,7 @@ public void onNext(T t) { */ synchronized (this) { if (!caughtUp) { - queue.offer(nl.next(t)); + queue.offer(NotificationLite.next(t)); stillReplay = true; } } @@ -191,13 +226,13 @@ public void onNext(T t) { @Override public void onError(Throwable e) { if (!done) { - + doTerminate(); - + error = e; done = true; if (!caughtUp) { - boolean stillReplay = false; + boolean stillReplay; synchronized (this) { stillReplay = !caughtUp; } @@ -217,7 +252,7 @@ public void onCompleted() { done = true; if (!caughtUp) { - boolean stillReplay = false; + boolean stillReplay; synchronized (this) { stillReplay = !caughtUp; } @@ -229,7 +264,7 @@ public void onCompleted() { subscriber.get().onCompleted(); } } - + @Override public void request(long n) { if (n < 0L) { @@ -246,7 +281,7 @@ public void request(long n) { /** * Tries to set the given subscriber if not already set, sending an * IllegalStateException to the subscriber otherwise. - * @param subscriber + * @param subscriber the incoming Subscriber instance, not null */ @Override public void call(Subscriber subscriber) { @@ -269,31 +304,31 @@ void replay() { emitting = true; } Queue q = queue; + boolean delayError = this.delayError; for (;;) { Subscriber s = subscriber.get(); boolean unlimited = false; if (s != null) { boolean d = done; boolean empty = q.isEmpty(); - - if (checkTerminated(d, empty, s)) { + if (checkTerminated(d, empty, delayError, s)) { return; } long r = get(); unlimited = r == Long.MAX_VALUE; long e = 0L; - + while (r != 0) { d = done; Object v = q.poll(); empty = v == null; - if (checkTerminated(d, empty, s)) { + if (checkTerminated(d, empty, delayError, s)) { return; } if (empty) { break; } - T value = nl.getValue(v); + T value = NotificationLite.getValue(v); try { s.onNext(value); } catch (Throwable ex) { @@ -309,7 +344,7 @@ void replay() { addAndGet(-e); } } - + synchronized (this) { if (!missed) { if (unlimited && q.isEmpty()) { @@ -340,40 +375,45 @@ public void unsubscribe() { } queue.clear(); } - + @Override public boolean isUnsubscribed() { return done; } - + /** * Checks if one of the terminal conditions have been met: child unsubscribed, * an error happened or the source terminated and the queue is empty - * @param done - * @param empty - * @param s - * @return + * @param done indicates the source has called onCompleted + * @param empty indicates if there are no more source values in the queue + * @param delayError indicates whether to deliver pending next events before error + * @param s the target Subscriber to emit events to + * @return true if this Subject reached a terminal state and the drain loop should quit */ - boolean checkTerminated(boolean done, boolean empty, Subscriber s) { + boolean checkTerminated(boolean done, boolean empty, boolean delayError, Subscriber s) { if (s.isUnsubscribed()) { queue.clear(); return true; } if (done) { Throwable e = error; - if (e != null) { + if (e != null && !delayError) { queue.clear(); s.onError(e); return true; - } else + } if (empty) { - s.onCompleted(); + if (e != null) { + s.onError(e); + } else { + s.onCompleted(); + } return true; } } return false; } - + /** * Call the optional termination action at most once. */ diff --git a/src/main/java/rx/subscriptions/BooleanSubscription.java b/src/main/java/rx/subscriptions/BooleanSubscription.java index 9ba4100a66..11a06fda69 100644 --- a/src/main/java/rx/subscriptions/BooleanSubscription.java +++ b/src/main/java/rx/subscriptions/BooleanSubscription.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -63,7 +63,7 @@ public boolean isUnsubscribed() { } @Override - public final void unsubscribe() { + public void unsubscribe() { Action0 action = actionRef.get(); if (action != EMPTY_ACTION) { action = actionRef.getAndSet(EMPTY_ACTION); @@ -76,7 +76,7 @@ public final void unsubscribe() { static final Action0 EMPTY_ACTION = new Action0() { @Override public void call() { - + // deliberately no-op } }; diff --git a/src/main/java/rx/subscriptions/CompositeSubscription.java b/src/main/java/rx/subscriptions/CompositeSubscription.java index 876a04a56b..c6d8aa35aa 100644 --- a/src/main/java/rx/subscriptions/CompositeSubscription.java +++ b/src/main/java/rx/subscriptions/CompositeSubscription.java @@ -35,7 +35,11 @@ public final class CompositeSubscription implements Subscription { private Set subscriptions; private volatile boolean unsubscribed; + /** + * Constructs an empty Composite subscription. + */ public CompositeSubscription() { + // start empty } public CompositeSubscription(final Subscription... subscriptions) { @@ -116,7 +120,7 @@ public void addAll(final Subscription... subscriptions) { */ public void remove(final Subscription s) { if (!unsubscribed) { - boolean unsubscribe = false; + boolean unsubscribe; synchronized (this) { if (unsubscribed || subscriptions == null) { return; @@ -137,7 +141,7 @@ public void remove(final Subscription s) { */ public void clear() { if (!unsubscribed) { - Collection unsubscribe = null; + Collection unsubscribe; synchronized (this) { if (unsubscribed || subscriptions == null) { return; @@ -158,7 +162,7 @@ public void clear() { @Override public void unsubscribe() { if (!unsubscribed) { - Collection unsubscribe = null; + Collection unsubscribe; synchronized (this) { if (unsubscribed) { return; diff --git a/src/main/java/rx/subscriptions/MultipleAssignmentSubscription.java b/src/main/java/rx/subscriptions/MultipleAssignmentSubscription.java index ec0ea7c6df..8b74d9517a 100644 --- a/src/main/java/rx/subscriptions/MultipleAssignmentSubscription.java +++ b/src/main/java/rx/subscriptions/MultipleAssignmentSubscription.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -15,10 +15,8 @@ */ package rx.subscriptions; -import java.util.concurrent.atomic.AtomicReference; - -import rx.Observable; -import rx.Subscription; +import rx.*; +import rx.internal.subscriptions.SequentialSubscription; /** * Subscription that can be checked for status such as in a loop inside an {@link Observable} to exit the loop @@ -26,45 +24,16 @@ */ public final class MultipleAssignmentSubscription implements Subscription { - final AtomicReference state = new AtomicReference(new State(false, Subscriptions.empty())); - - private static final class State { - final boolean isUnsubscribed; - final Subscription subscription; - - State(boolean u, Subscription s) { - this.isUnsubscribed = u; - this.subscription = s; - } - - State unsubscribe() { - return new State(true, subscription); - } + final SequentialSubscription state = new SequentialSubscription(); - State set(Subscription s) { - return new State(isUnsubscribed, s); - } - - } @Override public boolean isUnsubscribed() { - return state.get().isUnsubscribed; + return state.isUnsubscribed(); } @Override public void unsubscribe() { - State oldState; - State newState; - final AtomicReference localState = this.state; - do { - oldState = localState.get(); - if (oldState.isUnsubscribed) { - return; - } else { - newState = oldState.unsubscribe(); - } - } while (!localState.compareAndSet(oldState, newState)); - oldState.subscription.unsubscribe(); + state.unsubscribe(); } /** @@ -78,18 +47,7 @@ public void set(Subscription s) { if (s == null) { throw new IllegalArgumentException("Subscription can not be null"); } - State oldState; - State newState; - final AtomicReference localState = this.state; - do { - oldState = localState.get(); - if (oldState.isUnsubscribed) { - s.unsubscribe(); - return; - } else { - newState = oldState.set(s); - } - } while (!localState.compareAndSet(oldState, newState)); + state.replace(s); } /** @@ -98,7 +56,6 @@ public void set(Subscription s) { * @return the {@link Subscription} that underlies the {@code MultipleAssignmentSubscription} */ public Subscription get() { - return state.get().subscription; + return state.current(); } - } diff --git a/src/main/java/rx/subscriptions/RefCountSubscription.java b/src/main/java/rx/subscriptions/RefCountSubscription.java index 083b860f04..c615cabbd2 100644 --- a/src/main/java/rx/subscriptions/RefCountSubscription.java +++ b/src/main/java/rx/subscriptions/RefCountSubscription.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -29,7 +29,7 @@ public final class RefCountSubscription implements Subscription { static final State EMPTY_STATE = new State(false, 0); final AtomicReference state = new AtomicReference(EMPTY_STATE); - private static final class State { + static final class State { final boolean isUnsubscribed; final int children; @@ -54,7 +54,7 @@ State unsubscribe() { /** * Creates a {@code RefCountSubscription} by wrapping the given non-null {@code Subscription}. - * + * * @param s * the {@link Subscription} to wrap * @throws IllegalArgumentException @@ -125,10 +125,10 @@ void unsubscribeAChild() { } /** The individual sub-subscriptions. */ - private static final class InnerSubscription extends AtomicInteger implements Subscription { + static final class InnerSubscription extends AtomicInteger implements Subscription { /** */ private static final long serialVersionUID = 7005765588239987643L; - + final RefCountSubscription parent; public InnerSubscription(RefCountSubscription parent) { this.parent = parent; diff --git a/src/main/java/rx/subscriptions/SerialSubscription.java b/src/main/java/rx/subscriptions/SerialSubscription.java index f8aff9b67e..b86de81152 100644 --- a/src/main/java/rx/subscriptions/SerialSubscription.java +++ b/src/main/java/rx/subscriptions/SerialSubscription.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -15,91 +15,48 @@ */ package rx.subscriptions; -import java.util.concurrent.atomic.AtomicReference; - import rx.Subscription; +import rx.internal.subscriptions.SequentialSubscription; /** * Represents a subscription whose underlying subscription can be swapped for another subscription which causes * the previous underlying subscription to be unsubscribed. */ public final class SerialSubscription implements Subscription { - final AtomicReference state = new AtomicReference(new State(false, Subscriptions.empty())); - - private static final class State { - final boolean isUnsubscribed; - final Subscription subscription; - - State(boolean u, Subscription s) { - this.isUnsubscribed = u; - this.subscription = s; - } - - State unsubscribe() { - return new State(true, subscription); - } - State set(Subscription s) { - return new State(isUnsubscribed, s); - } - - } + final SequentialSubscription state = new SequentialSubscription(); @Override public boolean isUnsubscribed() { - return state.get().isUnsubscribed; + return state.isUnsubscribed(); } @Override public void unsubscribe() { - State oldState; - State newState; - final AtomicReference localState = this.state; - do { - oldState = localState.get(); - if (oldState.isUnsubscribed) { - return; - } else { - newState = oldState.unsubscribe(); - } - } while (!localState.compareAndSet(oldState, newState)); - oldState.subscription.unsubscribe(); + state.unsubscribe(); } /** - * Swaps out the old {@link Subscription} for the specified {@code Subscription}. + * Sets the underlying subscription. If the {@code MultipleAssignmentSubscription} is already unsubscribed, + * setting a new subscription causes the new subscription to also be immediately unsubscribed. * - * @param s - * the new {@code Subscription} to swap in - * @throws IllegalArgumentException - * if {@code s} is {@code null} + * @param s the {@link Subscription} to set + * @throws IllegalArgumentException if {@code s} is {@code null} */ public void set(Subscription s) { if (s == null) { throw new IllegalArgumentException("Subscription can not be null"); } - State oldState; - State newState; - final AtomicReference localState = this.state; - do { - oldState = localState.get(); - if (oldState.isUnsubscribed) { - s.unsubscribe(); - return; - } else { - newState = oldState.set(s); - } - } while (!localState.compareAndSet(oldState, newState)); - oldState.subscription.unsubscribe(); + state.update(s); } /** - * Retrieves the current {@link Subscription} that is being represented by this {@code SerialSubscription}. - * - * @return the current {@link Subscription} that is being represented by this {@code SerialSubscription} + * Gets the underlying subscription. + * + * @return the {@link Subscription} that underlies the {@code MultipleAssignmentSubscription} */ public Subscription get() { - return state.get().subscription; + return state.current(); } } diff --git a/src/main/java/rx/subscriptions/Subscriptions.java b/src/main/java/rx/subscriptions/Subscriptions.java index 64d941f13d..d9baf671d1 100644 --- a/src/main/java/rx/subscriptions/Subscriptions.java +++ b/src/main/java/rx/subscriptions/Subscriptions.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -24,6 +24,11 @@ * Helper methods and utilities for creating and working with {@link Subscription} objects */ public final class Subscriptions { + /** + * A {@link Subscription} that does nothing when its unsubscribe method is called. + */ + private static final Unsubscribed UNSUBSCRIBED = new Unsubscribed(); + private Subscriptions() { throw new IllegalStateException("No instances!"); } @@ -64,7 +69,7 @@ public static Subscription unsubscribed() { /** * Creates and returns a {@link Subscription} that invokes the given {@link Action0} when unsubscribed. - * + * * @param unsubscribe * Action to invoke on unsubscribe. * @return {@link Subscription} @@ -75,7 +80,7 @@ public static Subscription create(final Action0 unsubscribe) { /** * Converts a {@link Future} into a {@link Subscription} and cancels it when unsubscribed. - * + * * @param f * the {@link Future} to convert * @return a {@link Subscription} that wraps {@code f} @@ -85,7 +90,7 @@ public static Subscription from(final Future f) { } /** Naming classes helps with debugging. */ - private static final class FutureSubscription implements Subscription { + static final class FutureSubscription implements Subscription { final Future f; public FutureSubscription(Future f) { @@ -105,7 +110,7 @@ public boolean isUnsubscribed() { /** * Converts a set of {@link Subscription}s into a {@link CompositeSubscription} that groups the multiple * Subscriptions together and unsubscribes from all of them together. - * + * * @param subscriptions * the Subscriptions to group together * @return a {@link CompositeSubscription} representing the {@code subscriptions} set @@ -115,14 +120,11 @@ public static CompositeSubscription from(Subscription... subscriptions) { return new CompositeSubscription(subscriptions); } - /** - * A {@link Subscription} that does nothing when its unsubscribe method is called. - */ - private static final Unsubscribed UNSUBSCRIBED = new Unsubscribed(); /** Naming classes helps with debugging. */ static final class Unsubscribed implements Subscription { @Override public void unsubscribe() { + // deliberately ignored } @Override diff --git a/src/perf/java/rx/DeferredScalarPerf.java b/src/perf/java/rx/DeferredScalarPerf.java new file mode 100644 index 0000000000..2419c03efd --- /dev/null +++ b/src/perf/java/rx/DeferredScalarPerf.java @@ -0,0 +1,106 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * 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 rx; + +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +import org.openjdk.jmh.annotations.*; +import org.openjdk.jmh.infra.Blackhole; + +import rx.functions.*; +import rx.jmh.LatchedObserver; + +/** + * Benchmark operators that consume their sources completely and signal a single value. + *

      + * gradlew benchmarks "-Pjmh=-f 1 -tu s -bm thrpt -wi 5 -i 5 -r 1 .*DeferredScalarPerf.*" + *

      + * gradlew benchmarks "-Pjmh=-f 1 -tu ns -bm avgt -wi 5 -i 5 -r 1 .*DeferredScalarPerf.*" + */ +@BenchmarkMode(Mode.Throughput) +@OutputTimeUnit(TimeUnit.SECONDS) +@State(Scope.Thread) +public class DeferredScalarPerf { + + @Param({"1", "10", "100", "1000", "10000", "100000", "1000000"}) + public int count; + + Observable last; + + Observable reduce; + + Observable reduceSeed; + + Observable collect; + + @Setup + public void setup() { + Integer[] array = new Integer[count]; + Arrays.fill(array, 777); + + Observable source = Observable.from(array); + + reduce = source.reduce(new Func2() { + @Override + public Integer call(Integer a, Integer b) { + return b; + } + }); + reduceSeed = source.reduce(0, new Func2() { + @Override + public Integer call(Integer a, Integer b) { + return b; + } + }); + + last = source.takeLast(1); + + collect = source.collect(new Func0() { + @Override + public int[] call() { + return new int[1]; + } + }, new Action2() { + @Override + public void call(int[] a, Integer b) { + a[0] = b.intValue(); + } + } ); + } + + @Benchmark + public void reduce(Blackhole bh) { + reduce.subscribe(new LatchedObserver(bh)); + } + + @Benchmark + public void reduceSeed(Blackhole bh) { + reduceSeed.subscribe(new LatchedObserver(bh)); + } + + @Benchmark + public void last(Blackhole bh) { + last.subscribe(new LatchedObserver(bh)); + } + + @Benchmark + public void collect(Blackhole bh) { + collect.subscribe(new LatchedObserver(bh)); + } + +} diff --git a/src/perf/java/rx/ObservablePerfBaseline.java b/src/perf/java/rx/ObservablePerfBaseline.java index 061caf6264..bbf049ada9 100644 --- a/src/perf/java/rx/ObservablePerfBaseline.java +++ b/src/perf/java/rx/ObservablePerfBaseline.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -54,7 +54,7 @@ public void observableConsumption(Input input) throws InterruptedException { public void observableViaRange(Input input) throws InterruptedException { input.observable.subscribe(input.observer); } - + @Benchmark public void observableConsumptionUnsafe(Input input) throws InterruptedException { input.firehose.unsafeSubscribe(input.newSubscriber()); diff --git a/src/perf/java/rx/OneItemPerf.java b/src/perf/java/rx/OneItemPerf.java index c864164db8..7927268c69 100644 --- a/src/perf/java/rx/OneItemPerf.java +++ b/src/perf/java/rx/OneItemPerf.java @@ -1,12 +1,12 @@ /** * Copyright 2016 Netflix, Inc. - * + * * 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. @@ -37,7 +37,7 @@ @OutputTimeUnit(TimeUnit.SECONDS) @State(Scope.Thread) public class OneItemPerf { - + Observable scalar; Observable scalarHidden; Observable one; @@ -57,7 +57,7 @@ public class OneItemPerf { Observable scalarSwitch; Observable scalarHiddenSwitch; Observable oneSwitch; - + Single hide(final Single single) { return Single.create(new Single.OnSubscribe() { @Override @@ -66,11 +66,11 @@ public void call(SingleSubscriber t) { } }); } - + @Setup public void setup() { scalar = Observable.just(1); - one = Observable.create(new OnSubscribe() { + one = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber t) { t.setProducer(new SingleProducer(t, 1)); @@ -79,9 +79,9 @@ public void call(Subscriber t) { single = Single.just(1); singleHidden = hide(single); scalarHidden = scalar.asObservable(); - + // ---------------------------------------------------------------------------- - + scalarConcat = scalar.concatMap(new Func1>() { @Override public Observable call(Integer v) { @@ -94,7 +94,7 @@ public Observable call(Integer v) { return scalar; } }); - + oneConcat = one.concatMap(new Func1>() { @Override public Observable call(Integer v) { @@ -116,7 +116,7 @@ public Observable call(Integer v) { return scalar; } }); - + oneMerge = one.flatMap(new Func1>() { @Override public Observable call(Integer v) { @@ -135,9 +135,9 @@ public Single call(Integer v) { return single; } }); - + // ---------------------------------------------------------------------------- - + scalarSwitch = scalar.switchMap(new Func1>() { @Override public Observable call(Integer v) { @@ -150,7 +150,7 @@ public Observable call(Integer v) { return scalar; } }); - + oneSwitch = one.switchMap(new Func1>() { @Override public Observable call(Integer v) { @@ -158,7 +158,7 @@ public Observable call(Integer v) { } }); } - + @Benchmark public void scalar(Blackhole bh) { scalar.subscribe(new LatchedObserver(bh)); diff --git a/src/perf/java/rx/ScalarJustPerf.java b/src/perf/java/rx/ScalarJustPerf.java index 24543852ff..c50f945672 100644 --- a/src/perf/java/rx/ScalarJustPerf.java +++ b/src/perf/java/rx/ScalarJustPerf.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -42,26 +42,26 @@ static final class PlainSubscriber extends Subscriber { public PlainSubscriber(Blackhole bh) { this.bh = bh; } - + @Override public void onNext(Integer t) { bh.consume(t); } - + @Override public void onError(Throwable e) { bh.consume(e); } - + @Override public void onCompleted() { bh.consume(false); } } - + /** This is a simple just. */ Observable simple; - /** + /** * This is a simple just observed on the computation scheduler. * The current computation scheduler supports direct scheduling and should have * lower overhead than a regular createWorker-use-unsubscribe. @@ -69,8 +69,8 @@ public void onCompleted() { Observable observeOn; /** This is a simple just observed on the IO thread. */ Observable observeOnIO; - - /** + + /** * This is a simple just subscribed to on the computation scheduler. * In theory, for non-backpressured just(), this should be the * same as observeOn. @@ -81,29 +81,29 @@ public void onCompleted() { /** This is a just mapped to itself which should skip the operator flatMap completely. */ Observable justFlatMapJust; - /** + /** * This is a just mapped to a range of 2 elements; it tests the case where the inner * Observable isn't a just(). */ Observable justFlatMapRange; - + @Setup public void setup() { simple = Observable.just(1); - + observeOn = simple.observeOn(Schedulers.computation()); observeOnIO = simple.observeOn(Schedulers.io()); - + subscribeOn = simple.subscribeOn(Schedulers.computation()); subscribeOnIO = simple.subscribeOn(Schedulers.io()); - + justFlatMapJust = simple.flatMap(new Func1>() { @Override public Observable call(Integer v) { return Observable.just(v); } }); - + justFlatMapRange = simple.flatMap(new Func1>() { @Override public Observable call(Integer v) { @@ -123,12 +123,12 @@ public Observable call(Integer v) { */ void runAsync(Blackhole bh, Observable source) { LatchedObserver lo = new LatchedObserver(bh); - + source.subscribe(lo); - - while (lo.latch.getCount() != 0L); + + while (lo.latch.getCount() != 0L) { } } - + @Benchmark public void simple(Blackhole bh) { PlainSubscriber s = new PlainSubscriber(bh); @@ -148,12 +148,12 @@ public Object simpleEscapeAll(Blackhole bh) { bh.consume(s); return simple.subscribe(s); } - + @Benchmark public void observeOn(Blackhole bh) { runAsync(bh, observeOn); } - + @Benchmark public void observeOnIO(Blackhole bh) { runAsync(bh, observeOnIO); @@ -163,18 +163,18 @@ public void observeOnIO(Blackhole bh) { public void subscribeOn(Blackhole bh) { runAsync(bh, subscribeOn); } - + @Benchmark public void subscribeOnIO(Blackhole bh) { runAsync(bh, subscribeOnIO); } - + @Benchmark public void justFlatMapJust(Blackhole bh) { PlainSubscriber s = new PlainSubscriber(bh); justFlatMapJust.subscribe(s); } - + @Benchmark public void justFlatMapJustEscape(Blackhole bh) { PlainSubscriber s = new PlainSubscriber(bh); @@ -187,7 +187,7 @@ public void justFlatMapRange(Blackhole bh) { PlainSubscriber s = new PlainSubscriber(bh); justFlatMapRange.subscribe(s); } - + @Benchmark public void justFlatMapRangeEscape(Blackhole bh) { PlainSubscriber s = new PlainSubscriber(bh); diff --git a/src/perf/java/rx/SinglePerfBaseline.java b/src/perf/java/rx/SinglePerfBaseline.java index e1a646cef0..954cc354be 100644 --- a/src/perf/java/rx/SinglePerfBaseline.java +++ b/src/perf/java/rx/SinglePerfBaseline.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -38,12 +38,12 @@ public class SinglePerfBaseline { public void singleConsumption(Input input) throws InterruptedException { input.single.subscribe(input.newSubscriber()); } - + @Benchmark public void singleConsumptionUnsafe(Input input) throws InterruptedException { input.single.unsafeSubscribe(input.newSubscriber()); } - + @Benchmark public void newSingleAndSubscriberEachTime(Input input) throws InterruptedException { input.newSingle().subscribe(input.newSubscriber()); @@ -63,7 +63,7 @@ public void setup(final Blackhole bh) { public LatchedObserver newLatchedObserver() { return new LatchedObserver(bh); } - + public Single newSingle() { return Single.create(new OnSubscribe() { @@ -71,7 +71,7 @@ public Single newSingle() { public void call(SingleSubscriber t) { t.onSuccess(1); } - + }); } diff --git a/src/perf/java/rx/SingleSourcePerf.java b/src/perf/java/rx/SingleSourcePerf.java index fff9006ea6..743d8669fb 100644 --- a/src/perf/java/rx/SingleSourcePerf.java +++ b/src/perf/java/rx/SingleSourcePerf.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -35,7 +35,7 @@ @OutputTimeUnit(TimeUnit.SECONDS) @State(Scope.Thread) public class SingleSourcePerf { - + Single source; Single flatmapped; @@ -59,74 +59,74 @@ public class SingleSourcePerf { // Single sourceSubscribeOnFJ; ScheduledExecutorService scheduledExecutor; - + ExecutorService executor; @Setup public void setup() { source = Single.just(1); - + flatmapped = source.flatMap(new Func1>() { @Override public Single call(Integer t) { return Single.just(t); } }); - + flatmapped = source.flatMap(new Func1>() { @Override public Single call(Integer t) { return source; } }); - + sourceObserveOn = source.observeOn(Schedulers.computation()); sourceSubscribeOn = source.subscribeOn(Schedulers.computation()); - + // ---------- - + scheduledExecutor = Executors.newScheduledThreadPool(1); - + Scheduler s = Schedulers.from(scheduledExecutor); - + sourceObserveOnScheduledExecutor = source.observeOn(s); sourceSubscribeOnScheduledExecutor = source.subscribeOn(s); - + // ---------- - + executor = Executors.newSingleThreadExecutor(); - + Scheduler se = Schedulers.from(executor); - + sourceObserveOnExecutor = source.observeOn(se); sourceSubscribeOnExecutor = source.subscribeOn(se); - + // -------- - + // Scheduler fj = Schedulers.from(ForkJoinPool.commonPool()); - + // sourceObserveOnFJ = source.observeOn(fj); // sourceSubscribeOnFJ = source.subscribeOn(fj); } - + @TearDown public void teardown() { scheduledExecutor.shutdownNow(); - + executor.shutdownNow(); } - + static final class PlainSingleSubscriber extends SingleSubscriber { final Blackhole bh; - + public PlainSingleSubscriber(Blackhole bh) { this.bh = bh; } - + @Override public void onSuccess(Object value) { bh.consume(value); @@ -140,14 +140,14 @@ public void onError(Throwable error) { static final class LatchedSingleSubscriber extends SingleSubscriber { final Blackhole bh; - + final CountDownLatch cdl; - + public LatchedSingleSubscriber(Blackhole bh) { this.bh = bh; this.cdl = new CountDownLatch(1); } - + @Override public void onSuccess(Object value) { bh.consume(value); @@ -159,7 +159,7 @@ public void onError(Throwable error) { bh.consume(error); cdl.countDown(); } - + public void await() { try { cdl.await(); @@ -167,9 +167,9 @@ public void await() { throw new RuntimeException(ex); } } - + public void awaitSpin() { - while (cdl.getCount() != 0L) ; + while (cdl.getCount() != 0L) { } } } @@ -182,7 +182,7 @@ public void direct(Blackhole bh) { public void flatmap(Blackhole bh) { flatmapped.subscribe(new PlainSingleSubscriber(bh)); } - + @Benchmark public void flatmapConst(Blackhole bh) { flatmapped.subscribe(new PlainSingleSubscriber(bh)); @@ -191,72 +191,72 @@ public void flatmapConst(Blackhole bh) { @Benchmark public void observeOn(Blackhole bh) { LatchedSingleSubscriber o = new LatchedSingleSubscriber(bh); - + sourceObserveOn.subscribe(o); - + o.awaitSpin(); } @Benchmark public void observeOnExec(Blackhole bh) { LatchedSingleSubscriber o = new LatchedSingleSubscriber(bh); - + sourceObserveOnExecutor.subscribe(o); - + o.awaitSpin(); } @Benchmark public void subscribeOn(Blackhole bh) { LatchedSingleSubscriber o = new LatchedSingleSubscriber(bh); - + sourceSubscribeOn.subscribe(o); - + o.awaitSpin(); } @Benchmark public void subscribeOnExec(Blackhole bh) { LatchedSingleSubscriber o = new LatchedSingleSubscriber(bh); - + sourceSubscribeOnExecutor.subscribe(o); - + o.awaitSpin(); } @Benchmark public void subscribeOnSchExec(Blackhole bh) { LatchedSingleSubscriber o = new LatchedSingleSubscriber(bh); - + sourceSubscribeOnScheduledExecutor.subscribe(o); - + o.awaitSpin(); } // @Benchmark // public void subscribeOnFJ(Blackhole bh) { // LatchedSingleSubscriber o = new LatchedSingleSubscriber(bh); -// +// // sourceSubscribeOnFJ.subscribe(o); -// +// // o.awaitSpin(); // } @Benchmark public void observeOnSchExec(Blackhole bh) { LatchedSingleSubscriber o = new LatchedSingleSubscriber(bh); - + sourceObserveOnScheduledExecutor.subscribe(o); - + o.awaitSpin(); } // @Benchmark // public void observeOnFJ(Blackhole bh) { // LatchedSingleSubscriber o = new LatchedSingleSubscriber(bh); -// +// // sourceObserveOnFJ.subscribe(o); -// +// // o.awaitSpin(); // } diff --git a/src/perf/java/rx/SubscribingPerf.java b/src/perf/java/rx/SubscribingPerf.java index f172f00852..4f89cab1eb 100644 --- a/src/perf/java/rx/SubscribingPerf.java +++ b/src/perf/java/rx/SubscribingPerf.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -34,10 +34,10 @@ @OutputTimeUnit(TimeUnit.SECONDS) @State(Scope.Thread) public class SubscribingPerf { - + Observable just = Observable.just(1); Observable range = Observable.range(1, 2); - + @Benchmark public void justDirect(Blackhole bh) { DirectSubscriber subscriber = new DirectSubscriber(Long.MAX_VALUE, bh); @@ -126,16 +126,16 @@ public void rangeUsualUnsafe(Blackhole bh) { public static class Chain { @Param({"10", "1000", "1000000"}) public int times; - + @Param({"1", "2", "3", "4", "5"}) public int maps; - + Observable source; - + @Setup public void setup() { Observable o = Observable.range(1, times); - + for (int i = 0; i < maps; i++) { o = o.map(new Func1() { @Override @@ -144,10 +144,10 @@ public Integer call(Integer v) { } }); } - + source = o; } - + @Benchmark public void mapped(Chain c, Blackhole bh) { DirectSubscriber subscriber = new DirectSubscriber(Long.MAX_VALUE, bh); @@ -155,7 +155,7 @@ public void mapped(Chain c, Blackhole bh) { c.source.subscribe(subscriber); } } - + static final class DirectSubscriber extends Subscriber { final long r; final Blackhole bh; @@ -167,16 +167,16 @@ public DirectSubscriber(long r, Blackhole bh) { public void onNext(T t) { bh.consume(t); } - + @Override public void onError(Throwable e) { e.printStackTrace(); } - + @Override public void onCompleted() { } - + @Override public void setProducer(Producer p) { p.request(r); @@ -190,25 +190,25 @@ public StartedSubscriber(long r, Blackhole bh) { this.r = r; this.bh = bh; } - + @Override public void onStart() { request(r); } - + @Override public void onNext(T t) { bh.consume(t); } - + @Override public void onError(Throwable e) { e.printStackTrace(); } - + @Override public void onCompleted() { - + } } @@ -222,20 +222,20 @@ public UsualSubscriber(long r, Blackhole bh) { this.bh = bh; request(r); } - + @Override public void onNext(T t) { bh.consume(t); } - + @Override public void onError(Throwable e) { e.printStackTrace(); } - + @Override public void onCompleted() { - + } } } diff --git a/src/perf/java/rx/internal/AtomicPerf.java b/src/perf/java/rx/internal/AtomicPerf.java index 3d845056b4..605bf31258 100644 --- a/src/perf/java/rx/internal/AtomicPerf.java +++ b/src/perf/java/rx/internal/AtomicPerf.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -74,9 +74,9 @@ public static class AtomicIntState { public static class AtomicLongState { public final AtomicLong value = new AtomicLong(); } - + // ----------------------------------------------------------------------------------- - + @Benchmark public void volatileIntRead(VolatileIntState state, Times repeat, Blackhole bh) { for (int i = 0; i < repeat.times; i++) { @@ -126,7 +126,7 @@ public void atomicLongGetAndIncrement(AtomicLongState state, Times repeat, Black bh.consume(state.value.getAndIncrement()); } } - + @Benchmark public void atomicIntLazySet(AtomicIntState state, Times repeat, Blackhole bh) { for (int i = 0; i < repeat.times; i++) { @@ -225,7 +225,7 @@ public void atomicLongFieldGetAndIncrement(VolatileLongFieldState state, Times r bh.consume(VolatileLongFieldState.UPDATER.getAndIncrement(state)); } } - + @Benchmark public void atomicIntFieldLazySet(VolatileIntFieldState state, Times repeat, Blackhole bh) { for (int i = 0; i < repeat.times; i++) { diff --git a/src/perf/java/rx/internal/IndexedRingBufferPerf.java b/src/perf/java/rx/internal/IndexedRingBufferPerf.java index e523e337a6..890be86430 100644 --- a/src/perf/java/rx/internal/IndexedRingBufferPerf.java +++ b/src/perf/java/rx/internal/IndexedRingBufferPerf.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. diff --git a/src/perf/java/rx/jmh/InputWithIncrementingInteger.java b/src/perf/java/rx/jmh/InputWithIncrementingInteger.java index 6760202024..24468bb97b 100644 --- a/src/perf/java/rx/jmh/InputWithIncrementingInteger.java +++ b/src/perf/java/rx/jmh/InputWithIncrementingInteger.java @@ -44,7 +44,7 @@ public void setup(final Blackhole bh) { final int size = getSize(); observable = Observable.range(0, size); - firehose = Observable.create(new OnSubscribe() { + firehose = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber s) { @@ -59,22 +59,22 @@ public void call(Subscriber s) { @Override public Iterator iterator() { return new Iterator() { - int i = 0; - + int i; + @Override public boolean hasNext() { return i < size; } - + @Override public Integer next() { Blackhole.consumeCPU(10); return i++; } - + @Override public void remove() { - + } }; } diff --git a/src/perf/java/rx/jmh/PerfAsyncSingleSubscriber.java b/src/perf/java/rx/jmh/PerfAsyncSingleSubscriber.java index dba6f563df..0a579c3087 100644 --- a/src/perf/java/rx/jmh/PerfAsyncSingleSubscriber.java +++ b/src/perf/java/rx/jmh/PerfAsyncSingleSubscriber.java @@ -29,26 +29,26 @@ */ public final class PerfAsyncSingleSubscriber extends SingleSubscriber { final Blackhole bh; - + final CountDownLatch cdl; - + public PerfAsyncSingleSubscriber(Blackhole bh) { this.bh = bh; this.cdl = new CountDownLatch(1); } - + @Override public void onSuccess(Object value) { bh.consume(value); cdl.countDown(); } - + @Override public void onError(Throwable error) { bh.consume(error); cdl.countDown(); } - + /** * Sleeps until the subscriber receives an event. */ @@ -59,11 +59,11 @@ public void sleepAwait() { throw new RuntimeException(ex); } } - + /** * Spins until the subscriber receives an events. */ public void spinAwait() { - while (cdl.getCount() != 0) ; + while (cdl.getCount() != 0) { } } } diff --git a/src/perf/java/rx/jmh/PerfSingleSubscriber.java b/src/perf/java/rx/jmh/PerfSingleSubscriber.java index d46f6a99bf..57458d1000 100644 --- a/src/perf/java/rx/jmh/PerfSingleSubscriber.java +++ b/src/perf/java/rx/jmh/PerfSingleSubscriber.java @@ -25,16 +25,16 @@ */ public final class PerfSingleSubscriber extends SingleSubscriber { final Blackhole bh; - + public PerfSingleSubscriber(Blackhole bh) { this.bh = bh; } - + @Override public void onSuccess(Object value) { bh.consume(value); } - + @Override public void onError(Throwable error) { bh.consume(error); diff --git a/src/perf/java/rx/observables/MultiInput.java b/src/perf/java/rx/observables/MultiInput.java index e607249d07..2b41337532 100644 --- a/src/perf/java/rx/observables/MultiInput.java +++ b/src/perf/java/rx/observables/MultiInput.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. diff --git a/src/perf/java/rx/observables/SingleInput.java b/src/perf/java/rx/observables/SingleInput.java index 7949efcfa5..5bda399ceb 100644 --- a/src/perf/java/rx/observables/SingleInput.java +++ b/src/perf/java/rx/observables/SingleInput.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. diff --git a/src/perf/java/rx/observables/SyncOnSubscribePerf.java b/src/perf/java/rx/observables/SyncOnSubscribePerf.java index 91882df8d0..23c36e3d6b 100644 --- a/src/perf/java/rx/observables/SyncOnSubscribePerf.java +++ b/src/perf/java/rx/observables/SyncOnSubscribePerf.java @@ -42,14 +42,14 @@ public static void main(String[] args) { SyncOnSubscribePerf perf = new SyncOnSubscribePerf(); perf.benchSyncOnSubscribe(singleInput); } - private static class generated { + static class generated { private static Blackhole _jmh_tryInit_() { return new Blackhole(); } } - + private static OnSubscribe createSyncOnSubscribe(final Iterator iterator) { - return new SyncOnSubscribe(){ + return new SyncOnSubscribe() { @Override protected Void generateState() { @@ -60,14 +60,14 @@ protected Void generateState() { protected Void next(Void state, Observer observer) { if (iterator.hasNext()) { observer.onNext(iterator.next()); - } - else + } else { observer.onCompleted(); - return null; } - }; + return null; + } + }; } - + // @Benchmark // @Group("single") public void benchSyncOnSubscribe(final SingleInput input) { @@ -79,13 +79,13 @@ public void benchSyncOnSubscribe(final SingleInput input) { public void benchFromIterable(final SingleInput input) { new OnSubscribeFromIterable(input.iterable).call(input.newSubscriber()); } - + @Benchmark // @Group("multi") public void benchSyncOnSubscribe2(final MultiInput input) { createSyncOnSubscribe(input.iterable.iterator()).call(input.newSubscriber()); } - + @Benchmark // @Group("multi") public void benchFromIterable2(final MultiInput input) { diff --git a/src/perf/java/rx/operators/ConcatMapInterablePerf.java b/src/perf/java/rx/operators/ConcatMapInterablePerf.java index 40ea778acb..2fd278d25c 100644 --- a/src/perf/java/rx/operators/ConcatMapInterablePerf.java +++ b/src/perf/java/rx/operators/ConcatMapInterablePerf.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -47,21 +47,21 @@ public class ConcatMapInterablePerf { @Param({"1", "10", "100", "1000", "10000", "100000", "1000000"}) public int count; - + Observable justPlain; - + Observable justIterable; Observable rangePlain; - + Observable rangeIterable; Observable xrangePlain; - + Observable xrangeIterable; Observable chainPlain; - + Observable chainIterable; @Setup @@ -70,13 +70,13 @@ public void setup() { for (int i = 0; i < count; i++) { values[i] = i; } - + int c = 1000000 / count; Integer[] xvalues = new Integer[c]; for (int i = 0; i < c; i++) { xvalues[i] = i; } - + Observable source = Observable.from(values); justPlain = source.concatMap(new Func1>() { @@ -94,7 +94,7 @@ public Iterable call(Integer v) { final Observable range = Observable.range(1, 2); final List xrange = Arrays.asList(1, 2); - + rangePlain = source.concatMap(new Func1>() { @Override public Observable call(Integer v) { @@ -107,10 +107,10 @@ public Iterable call(Integer v) { return xrange; } }); - + final Observable xsource = Observable.from(xvalues); final List xvaluesList = Arrays.asList(xvalues); - + xrangePlain = source.concatMap(new Func1>() { @Override public Observable call(Integer v) { @@ -137,7 +137,7 @@ public Iterable call(Integer v) { } }); } - + @Benchmark public void justPlain(Blackhole bh) { justPlain.subscribe(new LatchedObserver(bh)); diff --git a/src/perf/java/rx/operators/ConcatPerf.java b/src/perf/java/rx/operators/ConcatPerf.java index c9c5e8e18f..cab3ce7881 100644 --- a/src/perf/java/rx/operators/ConcatPerf.java +++ b/src/perf/java/rx/operators/ConcatPerf.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -44,25 +44,25 @@ public class ConcatPerf { Observable source; - + Observable baseline; - + @Param({"1", "1000", "1000000"}) int count; - + @Setup public void setup() { Integer[] array = new Integer[count]; - + for (int i = 0; i < count; i++) { array[i] = 777; } - + baseline = Observable.from(array); - + source = Observable.concat(baseline, Observable.empty()); } - + @Benchmark public void normal(Blackhole bh) { source.subscribe(new LatchedObserver(bh)); diff --git a/src/perf/java/rx/operators/FlatMapAsFilterPerf.java b/src/perf/java/rx/operators/FlatMapAsFilterPerf.java index e8ca109fdf..df90cd48be 100644 --- a/src/perf/java/rx/operators/FlatMapAsFilterPerf.java +++ b/src/perf/java/rx/operators/FlatMapAsFilterPerf.java @@ -1,12 +1,12 @@ /** * Copyright 2016 Netflix, Inc. - * + * * 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. @@ -39,16 +39,16 @@ public class FlatMapAsFilterPerf { @Param({"1", "1000", "1000000"}) public int count; - + @Param({"0", "1", "3", "7"}) public int mask; - + public Observable justEmptyFlatMap; - + public Observable rangeEmptyFlatMap; public Observable justEmptyConcatMap; - + public Observable rangeEmptyConcatMap; @Setup @@ -61,20 +61,20 @@ public void setup() { values[i] = i; } final Observable just = Observable.just(1); - + final Observable range = Observable.range(1, 2); - + final Observable empty = Observable.empty(); - + final int m = mask; - + justEmptyFlatMap = Observable.from(values).flatMap(new Func1>() { @Override public Observable call(Integer v) { return (v & m) == 0 ? empty : just; } }); - + rangeEmptyFlatMap = Observable.from(values).flatMap(new Func1>() { @Override public Observable call(Integer v) { @@ -88,7 +88,7 @@ public Observable call(Integer v) { return (v & m) == 0 ? empty : just; } }); - + rangeEmptyConcatMap = Observable.from(values).concatMap(new Func1>() { @Override public Observable call(Integer v) { @@ -101,7 +101,7 @@ public Observable call(Integer v) { public void justEmptyFlatMap(Blackhole bh) { justEmptyFlatMap.subscribe(new LatchedObserver(bh)); } - + @Benchmark public void rangeEmptyFlatMap(Blackhole bh) { rangeEmptyFlatMap.subscribe(new LatchedObserver(bh)); @@ -111,7 +111,7 @@ public void rangeEmptyFlatMap(Blackhole bh) { public void justEmptyConcatMap(Blackhole bh) { justEmptyConcatMap.subscribe(new LatchedObserver(bh)); } - + @Benchmark public void rangeEmptyConcatMap(Blackhole bh) { rangeEmptyConcatMap.subscribe(new LatchedObserver(bh)); diff --git a/src/perf/java/rx/operators/FlatMapPerf.java b/src/perf/java/rx/operators/FlatMapPerf.java index f8dafd467d..970a213935 100644 --- a/src/perf/java/rx/operators/FlatMapPerf.java +++ b/src/perf/java/rx/operators/FlatMapPerf.java @@ -42,7 +42,7 @@ public class FlatMapPerf { Observable rxSource; Observable rxSource2; - + @Setup public void setup() { Observable rxRange = Observable.range(0, times); @@ -59,7 +59,7 @@ public Observable call(Integer v) { } }); } - + @Benchmark public Object rxFlatMap() { return rxSource.subscribe(); diff --git a/src/perf/java/rx/operators/FlatMapRangePerf.java b/src/perf/java/rx/operators/FlatMapRangePerf.java index e8d58795b7..93e04d0345 100644 --- a/src/perf/java/rx/operators/FlatMapRangePerf.java +++ b/src/perf/java/rx/operators/FlatMapRangePerf.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -38,14 +38,14 @@ public class FlatMapRangePerf { @Param({ "1", "10", "1000", "1000000" }) public int times; - + Observable rangeFlatMapJust; Observable rangeFlatMapRange; - + @Setup public void setup() { Observable range = Observable.range(1, times); - + rangeFlatMapJust = range.flatMap(new Func1>() { @Override public Observable call(Integer v) { @@ -59,7 +59,7 @@ public Observable call(Integer v) { } }); } - + @Benchmark public void rangeFlatMapJust(Blackhole bh) { rangeFlatMapJust.subscribe(new LatchedObserver(bh)); diff --git a/src/perf/java/rx/operators/FromComparison.java b/src/perf/java/rx/operators/FromComparison.java index 7a7a12545d..763a938cae 100644 --- a/src/perf/java/rx/operators/FromComparison.java +++ b/src/perf/java/rx/operators/FromComparison.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -38,21 +38,21 @@ public class FromComparison { @Param({ "1", "10", "100", "1000", "1000000" }) public int times; - + Observable iterableSource; - + Observable arraySource; - + @Setup public void setup() { Integer[] array = new Integer[times]; - + Arrays.fill(array, 1); - - iterableSource = Observable.create(new OnSubscribeFromIterable(Arrays.asList(array))); - arraySource = Observable.create(new OnSubscribeFromArray(array)); + + iterableSource = Observable.unsafeCreate(new OnSubscribeFromIterable(Arrays.asList(array))); + arraySource = Observable.unsafeCreate(new OnSubscribeFromArray(array)); } - + @Benchmark public void fastpathIterable(Blackhole bh) { iterableSource.subscribe(new RequestingSubscriber(bh, Long.MAX_VALUE)); @@ -83,18 +83,18 @@ public void slowpathArray2(Blackhole bh) { arraySource.subscribe(new RequestingSubscriber(bh, 128)); } - + static final class RequestingSubscriber extends Subscriber { final Blackhole bh; final long limit; long received; Producer p; - + public RequestingSubscriber(Blackhole bh, long limit) { this.bh = bh; this.limit = limit; } - + @Override public void onNext(T t) { bh.consume(t); @@ -103,17 +103,17 @@ public void onNext(T t) { p.request(limit); } } - + @Override public void onError(Throwable e) { e.printStackTrace(); } - + @Override public void onCompleted() { - + } - + @Override public void setProducer(Producer p) { this.p = p; diff --git a/src/perf/java/rx/operators/FromIterablePerf.java b/src/perf/java/rx/operators/FromIterablePerf.java index 1368fcbf30..0e412999d8 100644 --- a/src/perf/java/rx/operators/FromIterablePerf.java +++ b/src/perf/java/rx/operators/FromIterablePerf.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -41,7 +41,7 @@ public class FromIterablePerf { OnSubscribeFromIterable direct; @Param({"1", "1000", "1000000"}) public int size; - + @Setup public void setup() { Integer[] array = new Integer[size]; @@ -51,7 +51,7 @@ public void setup() { from = Observable.from(Arrays.asList(array)); direct = new OnSubscribeFromIterable(Arrays.asList(array)); } - + @Benchmark public void from(Blackhole bh) { from.subscribe(new LatchedObserver(bh)); @@ -60,12 +60,12 @@ public void from(Blackhole bh) { public void fromUnsafe(final Blackhole bh) { from.unsafeSubscribe(createSubscriber(bh)); } - + @Benchmark public void direct(final Blackhole bh) { direct.call(createSubscriber(bh)); } - + Subscriber createSubscriber(final Blackhole bh) { return new Subscriber() { @Override @@ -78,7 +78,7 @@ public void onError(Throwable e) { } @Override public void onCompleted() { - + } }; } diff --git a/src/perf/java/rx/operators/OperatorMapPerf.java b/src/perf/java/rx/operators/OperatorMapPerf.java index 124aee65e4..81ee0ae702 100644 --- a/src/perf/java/rx/operators/OperatorMapPerf.java +++ b/src/perf/java/rx/operators/OperatorMapPerf.java @@ -17,17 +17,9 @@ import java.util.concurrent.TimeUnit; -import org.openjdk.jmh.annotations.BenchmarkMode; -import org.openjdk.jmh.annotations.Benchmark; -import org.openjdk.jmh.annotations.Mode; -import org.openjdk.jmh.annotations.OutputTimeUnit; -import org.openjdk.jmh.annotations.Param; -import org.openjdk.jmh.annotations.Scope; -import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.*; -import rx.Observable.Operator; import rx.functions.Func1; -import rx.internal.operators.OperatorMap; import rx.jmh.InputWithIncrementingInteger; @BenchmarkMode(Mode.Throughput) @@ -49,7 +41,7 @@ public int getSize() { @Benchmark public void mapPassThruViaLift(Input input) throws InterruptedException { - input.observable.lift(MAP_OPERATOR).subscribe(input.observer); + input.observable.map(IDENTITY_FUNCTION).subscribe(input.observer); } @Benchmark @@ -63,7 +55,4 @@ public Integer call(Integer value) { return value; } }; - - private static final Operator MAP_OPERATOR = new OperatorMap(IDENTITY_FUNCTION); - } diff --git a/src/perf/java/rx/operators/OperatorObserveOnPerf.java b/src/perf/java/rx/operators/OperatorObserveOnPerf.java index a105f09548..5a7f7cfb94 100644 --- a/src/perf/java/rx/operators/OperatorObserveOnPerf.java +++ b/src/perf/java/rx/operators/OperatorObserveOnPerf.java @@ -45,7 +45,7 @@ public int getSize() { } } - + @Benchmark public void observeOnComputation(Input input) throws InterruptedException { LatchedObserver o = input.newLatchedObserver(); @@ -66,7 +66,7 @@ public void observeOnImmediate(Input input) throws InterruptedException { input.observable.observeOn(Schedulers.immediate()).subscribe(o); o.latch.await(); } - + @Benchmark public void observeOnComputationSubscribedOnComputation(Input input) throws InterruptedException { LatchedObserver o = input.newLatchedObserver(); diff --git a/src/perf/java/rx/operators/OperatorPublishPerf.java b/src/perf/java/rx/operators/OperatorPublishPerf.java index 1658917c29..e2fae22b0c 100644 --- a/src/perf/java/rx/operators/OperatorPublishPerf.java +++ b/src/perf/java/rx/operators/OperatorPublishPerf.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -72,8 +72,8 @@ public void onCompleted() { cdl.countDown(); } } - - + + /** How long the range should be. */ @Param({"1", "1000", "1000000"}) private int size; @@ -86,7 +86,7 @@ public void onCompleted() { /** How often the child subscribers should re-request. */ @Param({"1", "2", "4", "8", "16", "32", "64"}) private int batchFrequency; - + private ConnectableObservable source; private Observable observable; @Setup @@ -99,13 +99,13 @@ public void setup() { source = src.publish(); observable = async ? source.observeOn(Schedulers.computation()) : source; } - + @Benchmark - public void benchmark(Blackhole bh) throws InterruptedException, + public void benchmark(Blackhole bh) throws InterruptedException, TimeoutException, BrokenBarrierException { CountDownLatch completion = null; int cc = childCount; - + if (cc > 0) { completion = new CountDownLatch(cc); Observable o = observable; @@ -113,9 +113,9 @@ public void benchmark(Blackhole bh) throws InterruptedException, o.subscribe(new SharedLatchObserver(completion, batchFrequency, bh)); } } - + Subscription s = source.connect(); - + if (completion != null && !completion.await(2, TimeUnit.SECONDS)) { throw new RuntimeException("Source hung!"); } diff --git a/src/perf/java/rx/operators/OperatorRangePerf.java b/src/perf/java/rx/operators/OperatorRangePerf.java index 52fd0af7ff..78711e3d58 100644 --- a/src/perf/java/rx/operators/OperatorRangePerf.java +++ b/src/perf/java/rx/operators/OperatorRangePerf.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -43,7 +43,7 @@ public static class InputUsingRequest { @Setup public void setup(final Blackhole bh) { - observable = Observable.create(new OnSubscribeRange(0, size)); + observable = Observable.unsafeCreate(new OnSubscribeRange(0, size)); this.bh = bh; } @@ -54,7 +54,7 @@ public Subscriber newSubscriber() { public void onStart() { request(size); } - + @Override public void onCompleted() { @@ -91,7 +91,7 @@ public static class InputWithoutRequest { @Setup public void setup(final Blackhole bh) { - observable = Observable.create(new OnSubscribeRange(0, size)); + observable = Observable.unsafeCreate(new OnSubscribeRange(0, size)); this.bh = bh; } diff --git a/src/perf/java/rx/operators/OperatorSerializePerf.java b/src/perf/java/rx/operators/OperatorSerializePerf.java index cae310b72c..70b61421ec 100644 --- a/src/perf/java/rx/operators/OperatorSerializePerf.java +++ b/src/perf/java/rx/operators/OperatorSerializePerf.java @@ -59,7 +59,7 @@ public void serializedSingleStream(Input input) throws InterruptedException { @Benchmark public void serializedTwoStreamsHighlyContended(final Input input) throws InterruptedException { LatchedObserver o = input.newLatchedObserver(); - Observable.create(new OnSubscribe() { + Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber s) { @@ -101,7 +101,7 @@ public Integer call(Long t1) { @Benchmark public void serializedTwoStreamsSlightlyContended(final InputWithInterval input) throws InterruptedException { LatchedObserver o = input.newLatchedObserver(); - Observable.create(new OnSubscribe() { + Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber s) { @@ -118,7 +118,7 @@ public void call(Subscriber s) { @Benchmark public void serializedTwoStreamsOneFastOneSlow(final InputWithInterval input) throws InterruptedException { LatchedObserver o = input.newLatchedObserver(); - Observable.create(new OnSubscribe() { + Observable.unsafeCreate(new OnSubscribe() { @Override public void call(final Subscriber s) { diff --git a/src/perf/java/rx/operators/OperatorTakeLastOnePerf.java b/src/perf/java/rx/operators/OperatorTakeLastOnePerf.java index c04c73fa9b..c96f9a2c66 100644 --- a/src/perf/java/rx/operators/OperatorTakeLastOnePerf.java +++ b/src/perf/java/rx/operators/OperatorTakeLastOnePerf.java @@ -16,13 +16,10 @@ package rx.operators; -import org.openjdk.jmh.annotations.Benchmark; -import org.openjdk.jmh.annotations.Param; -import org.openjdk.jmh.annotations.Scope; -import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.*; -import rx.internal.operators.OperatorTakeLast; -import rx.internal.operators.OperatorTakeLastOne; +import rx.Observable; +import rx.internal.operators.*; import rx.jmh.InputWithIncrementingInteger; public class OperatorTakeLastOnePerf { @@ -41,15 +38,15 @@ public int getSize() { } } - + @Benchmark public void takeLastOneUsingTakeLast(Input input) { input.observable.lift(TAKE_LAST).subscribe(input.observer); } - + @Benchmark public void takeLastOneUsingTakeLastOne(Input input) { - input.observable.lift(OperatorTakeLastOne.instance()).subscribe(input.observer); + Observable.unsafeCreate(new OnSubscribeTakeLastOne(input.observable)).subscribe(input.observer); } - + } diff --git a/src/perf/java/rx/operators/RedoPerf.java b/src/perf/java/rx/operators/RedoPerf.java index 2c5dc7f491..6878790762 100644 --- a/src/perf/java/rx/operators/RedoPerf.java +++ b/src/perf/java/rx/operators/RedoPerf.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -47,68 +47,68 @@ public class RedoPerf { @Param({"1,1", "1,1000", "1,1000000", "1000,1", "1000,1000", "1000000,1"}) public String params; - + public int len; public int repeat; - + Observable sourceRepeating; - + Observable sourceRetrying; - + Observable redoRepeating; - + Observable redoRetrying; Observable baseline; - + @SuppressWarnings({ "rawtypes", "unchecked" }) @Setup public void setup() { String[] ps = params.split(","); len = Integer.parseInt(ps[0]); - repeat = Integer.parseInt(ps[1]); - + repeat = Integer.parseInt(ps[1]); + Integer[] values = new Integer[len]; Arrays.fill(values, 777); - + Observable source = Observable.from(values); - + Observable error = source.concatWith(Observable.error(new RuntimeException())); - + Integer[] values2 = new Integer[len * repeat]; Arrays.fill(values2, 777); - - baseline = Observable.from(values2); - + + baseline = Observable.from(values2); + sourceRepeating = source.repeat(repeat); - + sourceRetrying = error.retry(repeat); - + redoRepeating = source.repeatWhen((Func1)UtilityFunctions.identity()).take(len * repeat); redoRetrying = error.retryWhen((Func1)UtilityFunctions.identity()).take(len * repeat); } - + @Benchmark public void baseline(Blackhole bh) { baseline.subscribe(new LatchedObserver(bh)); } - + @Benchmark public void repeatCounted(Blackhole bh) { sourceRepeating.subscribe(new LatchedObserver(bh)); } - + @Benchmark public void retryCounted(Blackhole bh) { sourceRetrying.subscribe(new LatchedObserver(bh)); } - + @Benchmark public void repeatWhen(Blackhole bh) { redoRepeating.subscribe(new LatchedObserver(bh)); } - + @Benchmark public void retryWhen(Blackhole bh) { redoRetrying.subscribe(new LatchedObserver(bh)); diff --git a/src/perf/java/rx/operators/ZipPerf.java b/src/perf/java/rx/operators/ZipPerf.java index 9ded231790..1591c8e8d6 100644 --- a/src/perf/java/rx/operators/ZipPerf.java +++ b/src/perf/java/rx/operators/ZipPerf.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -45,41 +45,41 @@ @OutputTimeUnit(TimeUnit.SECONDS) @State(Scope.Thread) public class ZipPerf { - + @Param({"1", "1000", "1000000"}) public int firstLen; @Param({"1", "1000", "1000000"}) public int secondLen; - + Observable baseline; - + Observable bothSync; Observable firstSync; Observable secondSync; Observable bothAsync; - + boolean small; - + @Setup public void setup() { Integer[] array1 = new Integer[firstLen]; Arrays.fill(array1, 777); Integer[] array2 = new Integer[secondLen]; Arrays.fill(array2, 777); - - baseline = Observable.from(firstLen < secondLen? array2 : array1); - + + baseline = Observable.from(firstLen < secondLen ? array2 : array1); + Observable o1 = Observable.from(array1); - + Observable o2 = Observable.from(array2); - + Func2 plus = new Func2() { @Override public Integer call(Integer a, Integer b) { return a + b; } }; - + bothSync = Observable.zip(o1, o2, plus); firstSync = Observable.zip(o1, o2.subscribeOn(Schedulers.computation()), plus); @@ -87,10 +87,10 @@ public Integer call(Integer a, Integer b) { secondSync = Observable.zip(o1.subscribeOn(Schedulers.computation()), o2, plus); bothAsync = Observable.zip(o1.subscribeOn(Schedulers.computation()), o2.subscribeOn(Schedulers.computation()), plus); - + small = Math.min(firstLen, secondLen) < 100; } - + @Benchmark public void baseline(Blackhole bh) { baseline.subscribe(new LatchedObserver(bh)); @@ -105,9 +105,9 @@ public void syncSync(Blackhole bh) { public void syncAsync(Blackhole bh) throws Exception { LatchedObserver o = new LatchedObserver(bh); firstSync.subscribe(o); - + if (small) { - while (o.latch.getCount() != 0); + while (o.latch.getCount() != 0) { } } else { o.latch.await(); } @@ -117,9 +117,9 @@ public void syncAsync(Blackhole bh) throws Exception { public void asyncSync(Blackhole bh) throws Exception { LatchedObserver o = new LatchedObserver(bh); secondSync.subscribe(o); - + if (small) { - while (o.latch.getCount() != 0); + while (o.latch.getCount() != 0) { } } else { o.latch.await(); } @@ -129,9 +129,9 @@ public void asyncSync(Blackhole bh) throws Exception { public void asyncAsync(Blackhole bh) throws Exception { LatchedObserver o = new LatchedObserver(bh); bothAsync.subscribe(o); - + if (small) { - while (o.latch.getCount() != 0); + while (o.latch.getCount() != 0) { } } else { o.latch.await(); } diff --git a/src/perf/java/rx/subjects/PublishSubjectPerf.java b/src/perf/java/rx/subjects/PublishSubjectPerf.java new file mode 100644 index 0000000000..b5dd15c7a1 --- /dev/null +++ b/src/perf/java/rx/subjects/PublishSubjectPerf.java @@ -0,0 +1,62 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * 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 rx.subjects; + +import java.util.concurrent.TimeUnit; + +import org.openjdk.jmh.annotations.*; +import org.openjdk.jmh.infra.Blackhole; + +import rx.jmh.LatchedObserver; + +/** + * Benchmark PublishSubject. + *

      + * gradlew benchmarks "-Pjmh=-f 1 -tu s -bm thrpt -wi 5 -i 5 -r 1 .*PublishSubjectPerf.*" + *

      + * gradlew benchmarks "-Pjmh=-f 1 -tu ns -bm avgt -wi 5 -i 5 -r 1 .*PublishSubjectPerf.*" + */ +@BenchmarkMode(Mode.Throughput) +@OutputTimeUnit(TimeUnit.SECONDS) +@State(Scope.Thread) +public class PublishSubjectPerf { + + @Param({ "1", "2", "4", "8"}) + public int subscribers; + + @Param({ "1", "1000", "1000000"}) + public int count; + + @Benchmark + public Object benchmark(Blackhole bh) { + PublishSubject ps = PublishSubject.create(); + + int s = subscribers; + for (int i = 0; i < s; i++) { + ps.subscribe(new LatchedObserver(bh)); + } + + int c = count; + for (int i = 0; i < c; i++) { + ps.onNext(777); + } + + ps.onCompleted(); + + return ps; + } +} diff --git a/src/perf/java/rx/subjects/ReplaySubjectPerf.java b/src/perf/java/rx/subjects/ReplaySubjectPerf.java index 5d463efb53..6bd042dbd6 100644 --- a/src/perf/java/rx/subjects/ReplaySubjectPerf.java +++ b/src/perf/java/rx/subjects/ReplaySubjectPerf.java @@ -70,11 +70,11 @@ public void onNext(Object o) { sum.incrementAndGet(); } }); - + for (int i = 0; i < input.nextRuns; i++) { subject.onNext("Response"); } - + subject.onCompleted(); latch.await(); bh.consume(sum); @@ -97,7 +97,7 @@ private void subscribeAfterEvents(ReplaySubject subject, final Input inp for (int i = 0; i < input.nextRuns; i++) { subject.onNext("Response"); } - + subject.onCompleted(); subject.subscribe(new Observer() { diff --git a/src/perf/java/rx/subscriptions/CompositeSubscriptionConcurrentPerf.java b/src/perf/java/rx/subscriptions/CompositeSubscriptionConcurrentPerf.java index 0c2eee5796..fce4625e21 100644 --- a/src/perf/java/rx/subscriptions/CompositeSubscriptionConcurrentPerf.java +++ b/src/perf/java/rx/subscriptions/CompositeSubscriptionConcurrentPerf.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -46,11 +46,11 @@ public class CompositeSubscriptionConcurrentPerf { @Param({ "1", "1000", "100000" }) public int loop; - + public final CompositeSubscription csub = new CompositeSubscription(); @Param({ "1", "5", "10", "20" }) public int count; - + public Subscription[] values; @Setup public void setup() { @@ -63,7 +63,7 @@ public boolean isUnsubscribed() { } @Override public void unsubscribe() { - + } }; } @@ -75,7 +75,7 @@ public void unsubscribe() { public void addRemoveT1() { CompositeSubscription csub = this.csub; Subscription[] values = this.values; - + for (int i = loop; i > 0; i--) { for (int j = values.length - 1; j >= 0; j--) { csub.add(values[j]); @@ -91,7 +91,7 @@ public void addRemoveT1() { public void addRemoveT2() { CompositeSubscription csub = this.csub; Subscription[] values = this.values; - + for (int i = loop; i > 0; i--) { for (int j = values.length - 1; j >= 0; j--) { csub.add(values[j]); @@ -108,7 +108,7 @@ public void addRemoveHalfT1() { CompositeSubscription csub = this.csub; Subscription[] values = this.values; int n = values.length; - + for (int i = loop; i > 0; i--) { for (int j = n / 2 - 1; j >= 0; j--) { csub.add(values[j]); @@ -125,7 +125,7 @@ public void addRemoveHalfT2() { CompositeSubscription csub = this.csub; Subscription[] values = this.values; int n = values.length; - + for (int i = loop; i > 0; i--) { for (int j = n - 1; j >= n / 2; j--) { csub.add(values[j]); @@ -141,7 +141,7 @@ public void addRemoveHalfT2() { public void addClearT1() { CompositeSubscription csub = this.csub; Subscription[] values = this.values; - + for (int i = loop; i > 0; i--) { for (int j = values.length - 1; j >= 0; j--) { csub.add(values[j]); @@ -155,7 +155,7 @@ public void addClearT1() { public void addClearT2() { CompositeSubscription csub = this.csub; Subscription[] values = this.values; - + for (int i = loop; i > 0; i--) { for (int j = values.length - 1; j >= 0; j--) { csub.add(values[j]); @@ -172,7 +172,7 @@ public void addClearHalfT1() { CompositeSubscription csub = this.csub; Subscription[] values = this.values; int n = values.length; - + for (int i = loop; i > 0; i--) { for (int j = n / 2 - 1; j >= 0; j--) { csub.add(values[j]); @@ -187,7 +187,7 @@ public void addClearHalfT2() { CompositeSubscription csub = this.csub; Subscription[] values = this.values; int n = values.length; - + for (int i = loop; i > 0; i--) { for (int j = n - 1; j >= n / 2; j--) { csub.add(values[j]); diff --git a/src/perf/java/rx/subscriptions/CompositeSubscriptionPerf.java b/src/perf/java/rx/subscriptions/CompositeSubscriptionPerf.java index bbbd8f1b63..d49bfe1d0b 100644 --- a/src/perf/java/rx/subscriptions/CompositeSubscriptionPerf.java +++ b/src/perf/java/rx/subscriptions/CompositeSubscriptionPerf.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -39,9 +39,9 @@ public static class TheState { public int loop; @Param({ "1", "5", "10", "100" }) public int count; - + public final CompositeSubscription csub = new CompositeSubscription(); - + public Subscription[] values; @Setup public void setup() { @@ -54,7 +54,7 @@ public boolean isUnsubscribed() { } @Override public void unsubscribe() { - + } }; } @@ -64,7 +64,7 @@ public void unsubscribe() { public void addRemove(TheState state) { CompositeSubscription csub = state.csub; Subscription[] values = state.values; - + for (int i = state.loop; i > 0; i--) { for (int j = values.length - 1; j >= 0; j--) { csub.add(values[j]); @@ -78,7 +78,7 @@ public void addRemove(TheState state) { public void addRemoveLocal(TheState state, Blackhole bh) { CompositeSubscription csub = new CompositeSubscription(); Subscription[] values = state.values; - + for (int i = state.loop; i > 0; i--) { for (int j = values.length - 1; j >= 0; j--) { csub.add(values[j]); @@ -87,14 +87,14 @@ public void addRemoveLocal(TheState state, Blackhole bh) { csub.remove(values[j]); } } - + bh.consume(csub); } @Benchmark public void addClear(TheState state) { CompositeSubscription csub = state.csub; Subscription[] values = state.values; - + for (int i = state.loop; i > 0; i--) { for (int j = values.length - 1; j >= 0; j--) { csub.add(values[j]); @@ -106,7 +106,7 @@ public void addClear(TheState state) { public void addClearLocal(TheState state, Blackhole bh) { CompositeSubscription csub = new CompositeSubscription(); Subscription[] values = state.values; - + for (int i = state.loop; i > 0; i--) { for (int j = values.length - 1; j >= 0; j--) { csub.add(values[j]); diff --git a/src/perf/java/rx/subscriptions/MultipleAssignmentSubscriptionPerf.java b/src/perf/java/rx/subscriptions/MultipleAssignmentSubscriptionPerf.java index b45a1c4c83..da48bc1429 100644 --- a/src/perf/java/rx/subscriptions/MultipleAssignmentSubscriptionPerf.java +++ b/src/perf/java/rx/subscriptions/MultipleAssignmentSubscriptionPerf.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -39,9 +39,9 @@ public static class TheState { public int loop; @Param({ "1", "5", "10", "100" }) public int count; - + public final MultipleAssignmentSubscription csub = new MultipleAssignmentSubscription(); - + public Subscription[] values; @Setup public void setup() { @@ -54,7 +54,7 @@ public boolean isUnsubscribed() { } @Override public void unsubscribe() { - + } }; } @@ -64,7 +64,7 @@ public void unsubscribe() { public void add(TheState state) { MultipleAssignmentSubscription csub = state.csub; Subscription[] values = state.values; - + for (int i = state.loop; i > 0; i--) { for (int j = values.length - 1; j >= 0; j--) { csub.set(values[j]); @@ -75,7 +75,7 @@ public void add(TheState state) { public void addLocal(TheState state, Blackhole bh) { MultipleAssignmentSubscription csub = new MultipleAssignmentSubscription(); Subscription[] values = state.values; - + for (int i = state.loop; i > 0; i--) { for (int j = values.length - 1; j >= 0; j--) { csub.set(values[j]); diff --git a/src/perf/java/rx/subscriptions/SerialSubscriptionPerf.java b/src/perf/java/rx/subscriptions/SerialSubscriptionPerf.java index 5f6a275b00..32a820f931 100644 --- a/src/perf/java/rx/subscriptions/SerialSubscriptionPerf.java +++ b/src/perf/java/rx/subscriptions/SerialSubscriptionPerf.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -39,9 +39,9 @@ public static class TheState { public int loop; @Param({ "1", "5", "10", "100" }) public int count; - + public final SerialSubscription csub = new SerialSubscription(); - + public Subscription[] values; @Setup public void setup() { @@ -54,7 +54,7 @@ public boolean isUnsubscribed() { } @Override public void unsubscribe() { - + } }; } @@ -64,7 +64,7 @@ public void unsubscribe() { public void add(TheState state) { SerialSubscription csub = state.csub; Subscription[] values = state.values; - + for (int i = state.loop; i > 0; i--) { for (int j = values.length - 1; j >= 0; j--) { csub.set(values[j]); @@ -75,7 +75,7 @@ public void add(TheState state) { public void addLocal(TheState state, Blackhole bh) { SerialSubscription csub = new SerialSubscription(); Subscription[] values = state.values; - + for (int i = state.loop; i > 0; i--) { for (int j = values.length - 1; j >= 0; j--) { csub.set(values[j]); diff --git a/src/perf/java/rx/subscriptions/SubscriptionListConcurrentPerf.java b/src/perf/java/rx/subscriptions/SubscriptionListConcurrentPerf.java index 93c7438f2c..b43d6222d3 100644 --- a/src/perf/java/rx/subscriptions/SubscriptionListConcurrentPerf.java +++ b/src/perf/java/rx/subscriptions/SubscriptionListConcurrentPerf.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -47,11 +47,11 @@ public class SubscriptionListConcurrentPerf { @Param({ "1", "1000", "100000" }) public int loop; - + public final SubscriptionList csub = new SubscriptionList(); @Param({ "1", "5", "10", "20" }) public int count; - + public Subscription[] values; @Setup public void setup() { @@ -64,7 +64,7 @@ public boolean isUnsubscribed() { } @Override public void unsubscribe() { - + } }; } @@ -76,7 +76,7 @@ public void unsubscribe() { public void addClearT1() { SubscriptionList csub = this.csub; Subscription[] values = this.values; - + for (int i = loop; i > 0; i--) { for (int j = values.length - 1; j >= 0; j--) { csub.add(values[j]); @@ -90,7 +90,7 @@ public void addClearT1() { public void addClearT2() { SubscriptionList csub = this.csub; Subscription[] values = this.values; - + for (int i = loop; i > 0; i--) { for (int j = values.length - 1; j >= 0; j--) { csub.add(values[j]); @@ -105,7 +105,7 @@ public void addClearHalfT1() { SubscriptionList csub = this.csub; Subscription[] values = this.values; int n = values.length; - + for (int i = loop; i > 0; i--) { for (int j = n / 2 - 1; j >= 0; j--) { csub.add(values[j]); @@ -120,7 +120,7 @@ public void addRemoveHalfT2() { SubscriptionList csub = this.csub; Subscription[] values = this.values; int n = values.length; - + for (int i = loop; i > 0; i--) { for (int j = n - 1; j >= n / 2; j--) { csub.add(values[j]); diff --git a/src/perf/java/rx/subscriptions/SubscriptionListPerf.java b/src/perf/java/rx/subscriptions/SubscriptionListPerf.java index ca2240275e..9947457d73 100644 --- a/src/perf/java/rx/subscriptions/SubscriptionListPerf.java +++ b/src/perf/java/rx/subscriptions/SubscriptionListPerf.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -40,9 +40,9 @@ public static class TheState { public int loop; @Param({ "1", "5", "10", "100" }) public int count; - + public final SubscriptionList csub = new SubscriptionList(); - + public Subscription[] values; @Setup public void setup() { @@ -55,7 +55,7 @@ public boolean isUnsubscribed() { } @Override public void unsubscribe() { - + } }; } @@ -65,7 +65,7 @@ public void unsubscribe() { public void addClear(TheState state) { SubscriptionList csub = state.csub; Subscription[] values = state.values; - + for (int i = state.loop; i > 0; i--) { for (int j = values.length - 1; j >= 0; j--) { csub.add(values[j]); @@ -77,7 +77,7 @@ public void addClear(TheState state) { public void addClearLocal(TheState state, Blackhole bh) { SubscriptionList csub = new SubscriptionList(); Subscription[] values = state.values; - + for (int i = state.loop; i > 0; i--) { for (int j = values.length - 1; j >= 0; j--) { csub.add(values[j]); diff --git a/src/test/java/rx/BackpressureTests.java b/src/test/java/rx/BackpressureTests.java index 393c8155cf..16a1565b0a 100644 --- a/src/test/java/rx/BackpressureTests.java +++ b/src/test/java/rx/BackpressureTests.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -41,7 +41,7 @@ public class BackpressureTests { public void doAfterTest() { TestObstructionDetection.checkObstruction(); } - + @Test public void testObserveOn() { int NUM = (int) (RxRingBuffer.SIZE * 2.1); @@ -133,7 +133,7 @@ public void testMergeAsyncThenObserveOnLoop() { int NUM = (int) (RxRingBuffer.SIZE * 4.1); AtomicInteger c1 = new AtomicInteger(); AtomicInteger c2 = new AtomicInteger(); - + TestSubscriber ts = new TestSubscriber(); Observable merged = Observable.merge( incrementingIntegers(c1).subscribeOn(Schedulers.computation()), @@ -146,7 +146,7 @@ public void testMergeAsyncThenObserveOnLoop() { assertEquals(NUM, ts.getOnNextEvents().size()); } } - + @Test public void testMergeAsyncThenObserveOn() { int NUM = (int) (RxRingBuffer.SIZE * 4.1); @@ -192,7 +192,7 @@ public Observable call(Integer i) { } @Test - @Ignore // the test is non-deterministic and can't be made deterministic + @Ignore("The test is non-deterministic and can't be made deterministic") public void testFlatMapAsync() { int NUM = (int) (RxRingBuffer.SIZE * 2.1); AtomicInteger c = new AtomicInteger(); @@ -445,7 +445,7 @@ public void testFirehoseFailsAsExpected() { public void testOnBackpressureDrop() { long t = System.currentTimeMillis(); for (int i = 0; i < 100; i++) { - // stop the test if we are getting close to the timeout because slow machines + // stop the test if we are getting close to the timeout because slow machines // may not get through 100 iterations if (System.currentTimeMillis() - t > TimeUnit.SECONDS.toMillis(9)) { break; @@ -494,7 +494,7 @@ public void call(Integer integer) { .map(SLOW_PASS_THRU).take(NUM).subscribe(ts); ts.awaitTerminalEvent(); ts.assertNoErrors(); - + List onNextEvents = ts.getOnNextEvents(); 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); @@ -584,7 +584,7 @@ public Boolean call(Integer t1) { /** * A synchronous Observable that will emit incrementing integers as requested. - * + * * @param counter * @return */ @@ -593,14 +593,14 @@ private static Observable incrementingIntegers(final AtomicInteger coun } private static Observable incrementingIntegers(final AtomicInteger counter, final ConcurrentLinkedQueue threadsSeen) { - return Observable.create(new OnSubscribe() { + return Observable.unsafeCreate(new OnSubscribe() { final AtomicLong requested = new AtomicLong(); @Override public void call(final Subscriber s) { s.setProducer(new Producer() { - int i = 0; + int i; @Override public void request(long n) { @@ -632,14 +632,14 @@ public void request(long n) { /** * Incrementing int without backpressure. - * + * * @param counter * @return */ private static Observable firehose(final AtomicInteger counter) { - return Observable.create(new OnSubscribe() { + return Observable.unsafeCreate(new OnSubscribe() { - int i = 0; + int i; @Override public void call(final Subscriber s) { diff --git a/src/test/java/rx/CapturingUncaughtExceptionHandler.java b/src/test/java/rx/CapturingUncaughtExceptionHandler.java index a44c6df080..27709943b8 100644 --- a/src/test/java/rx/CapturingUncaughtExceptionHandler.java +++ b/src/test/java/rx/CapturingUncaughtExceptionHandler.java @@ -19,7 +19,7 @@ import java.util.concurrent.CountDownLatch; public final class CapturingUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler { - public int count = 0; + public int count; public Throwable caught; public CountDownLatch completed = new CountDownLatch(1); diff --git a/src/test/java/rx/CombineLatestTests.java b/src/test/java/rx/CombineLatestTests.java index 1e032c2686..bd71831986 100644 --- a/src/test/java/rx/CombineLatestTests.java +++ b/src/test/java/rx/CombineLatestTests.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. diff --git a/src/test/java/rx/CompletableTest.java b/src/test/java/rx/CompletableTest.java index 0d495cec67..3f34a983ca 100644 --- a/src/test/java/rx/CompletableTest.java +++ b/src/test/java/rx/CompletableTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -27,7 +27,6 @@ import org.junit.*; import rx.Completable.*; -import rx.Observable.OnSubscribe; import rx.exceptions.*; import rx.functions.*; import rx.observers.TestSubscriber; @@ -51,12 +50,12 @@ public Iterator iterator() { public boolean hasNext() { return true; } - + @Override public Completable next() { throw new TestException(); } - + @Override public void remove() { throw new UnsupportedOperationException(); @@ -76,12 +75,12 @@ public Iterator iterator() { public boolean hasNext() { throw new TestException(); } - + @Override public Completable next() { return null; } - + @Override public void remove() { @@ -94,11 +93,11 @@ public void remove() { /** * A class containing a completable instance and counts the number of subscribers. */ - static final class NormalCompletable extends AtomicInteger { + public static final class NormalCompletable extends AtomicInteger { /** */ private static final long serialVersionUID = 7192337844700923752L; - - public final Completable completable = Completable.create(new CompletableOnSubscribe() { + + public final Completable completable = Completable.create(new OnSubscribe() { @Override public void call(CompletableSubscriber s) { getAndIncrement(); @@ -106,7 +105,7 @@ public void call(CompletableSubscriber s) { s.onCompleted(); } }); - + /** * Asserts the given number of subscriptions happened. * @param n the expected number of subscriptions @@ -120,11 +119,11 @@ public void assertSubscriptions(int n) { * A class containing a completable instance that emits a TestException and counts * the number of subscribers. */ - static final class ErrorCompletable extends AtomicInteger { + public static final class ErrorCompletable extends AtomicInteger { /** */ private static final long serialVersionUID = 7192337844700923752L; - - public final Completable completable = Completable.create(new CompletableOnSubscribe() { + + public final Completable completable = Completable.create(new OnSubscribe() { @Override public void call(CompletableSubscriber s) { getAndIncrement(); @@ -132,7 +131,7 @@ public void call(CompletableSubscriber s) { s.onError(new TestException()); } }); - + /** * Asserts the given number of subscriptions happened. * @param n the expected number of subscriptions @@ -151,73 +150,73 @@ public void assertSubscriptions(int n) { @Test(timeout = 5000) public void complete() { Completable c = Completable.complete(); - + c.await(); } - + @Test(expected = NullPointerException.class) public void concatNull() { Completable.concat((Completable[])null); } - + @Test(timeout = 5000) public void concatEmpty() { Completable c = Completable.concat(); - + c.await(); } - + @Test(timeout = 5000) public void concatSingleSource() { Completable c = Completable.concat(normal.completable); - + c.await(); - + normal.assertSubscriptions(1); } - + @Test(timeout = 5000, expected = TestException.class) public void concatSingleSourceThrows() { Completable c = Completable.concat(error.completable); - + c.await(); } - + @Test(timeout = 5000) public void concatMultipleSources() { Completable c = Completable.concat(normal.completable, normal.completable, normal.completable); - + c.await(); - + normal.assertSubscriptions(3); } - + @Test(timeout = 5000, expected = TestException.class) public void concatMultipleOneThrows() { Completable c = Completable.concat(normal.completable, error.completable, normal.completable); - + c.await(); } - + @Test(timeout = 5000, expected = NullPointerException.class) public void concatMultipleOneIsNull() { Completable c = Completable.concat(normal.completable, null); - + c.await(); } - + @Test(timeout = 5000) public void concatIterableEmpty() { Completable c = Completable.concat(Collections.emptyList()); - + c.await(); } - + @Test(expected = NullPointerException.class) public void concatIterableNull() { Completable.concat((Iterable)null); } - + @Test(timeout = 5000, expected = NullPointerException.class) public void concatIterableIteratorNull() { Completable c = Completable.concat(new Iterable() { @@ -226,49 +225,49 @@ public Iterator iterator() { return null; } }); - + c.await(); } - + @Test(timeout = 5000, expected = NullPointerException.class) public void concatIterableWithNull() { Completable c = Completable.concat(Arrays.asList(normal.completable, (Completable)null)); - + c.await(); } - + @Test(timeout = 5000) public void concatIterableSingle() { Completable c = Completable.concat(Collections.singleton(normal.completable)); - + c.await(); - + normal.assertSubscriptions(1); } - + @Test(timeout = 5000) public void concatIterableMany() { Completable c = Completable.concat(Arrays.asList(normal.completable, normal.completable, normal.completable)); - + c.await(); - + normal.assertSubscriptions(3); } - + @Test(timeout = 5000, expected = TestException.class) public void concatIterableOneThrows() { Completable c = Completable.concat(Collections.singleton(error.completable)); - + c.await(); } - + @Test(timeout = 5000, expected = TestException.class) public void concatIterableManyOneThrows() { Completable c = Completable.concat(Arrays.asList(normal.completable, error.completable)); - + c.await(); } - + @Test(expected = TestException.class) public void concatIterableIterableThrows() { Completable c = Completable.concat(new Iterable() { @@ -277,70 +276,70 @@ public Iterator iterator() { throw new TestException(); } }); - + c.await(); } - + @Test(expected = TestException.class) public void concatIterableIteratorHasNextThrows() { Completable c = Completable.concat(new IterableIteratorHasNextThrows()); - + c.await(); } - + @Test(expected = TestException.class) public void concatIterableIteratorNextThrows() { Completable c = Completable.concat(new IterableIteratorNextThrows()); - + c.await(); } - + @Test(timeout = 5000) public void concatObservableEmpty() { Completable c = Completable.concat(Observable.empty()); - + c.await(); } - + @Test(timeout = 5000, expected = TestException.class) public void concatObservableError() { Completable c = Completable.concat(Observable.error(new TestException())); - + c.await(); } - + @Test(timeout = 5000) public void concatObservableSingle() { Completable c = Completable.concat(Observable.just(normal.completable)); - + c.await(); - + normal.assertSubscriptions(1); } - + @Test(timeout = 5000, expected = TestException.class) public void concatObservableSingleThrows() { Completable c = Completable.concat(Observable.just(error.completable)); - + c.await(); } - + @Test(timeout = 5000) public void concatObservableMany() { Completable c = Completable.concat(Observable.just(normal.completable).repeat(3)); - + c.await(); - + normal.assertSubscriptions(3); } - + @Test(timeout = 5000, expected = TestException.class) public void concatObservableManyOneThrows() { Completable c = Completable.concat(Observable.just(normal.completable, error.completable)); - + c.await(); } - + @Test(timeout = 5000) public void concatObservablePrefetch() { final List requested = new ArrayList(); @@ -353,15 +352,15 @@ public void call(Long v) { requested.add(v); } }); - + Completable c = Completable.concat(cs, 5); - + c.await(); - + // FIXME this request pattern looks odd because all 10 completions trigger 1 requests Assert.assertEquals(Arrays.asList(5L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L), requested); } - + @Test public void andThen() { TestSubscriber ts = new TestSubscriber(0); @@ -371,7 +370,7 @@ public void andThen() { ts.assertCompleted(); ts.assertNoErrors(); } - + @Test public void andThenNever() { TestSubscriber ts = new TestSubscriber(0); @@ -380,19 +379,19 @@ public void andThenNever() { ts.assertNoValues(); ts.assertNoTerminalEvent(); } - + @Test public void andThenError() { TestSubscriber ts = new TestSubscriber(0); final AtomicBoolean hasRun = new AtomicBoolean(false); final Exception e = new Exception(); - Completable.create(new CompletableOnSubscribe() { + Completable.create(new OnSubscribe() { @Override public void call(CompletableSubscriber cs) { cs.onError(e); } }) - .andThen(Observable.create(new OnSubscribe() { + .andThen(Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber s) { hasRun.set(true); @@ -405,7 +404,7 @@ public void call(Subscriber s) { ts.assertError(e); Assert.assertFalse("Should not have subscribed to observable when completable errors", hasRun.get()); } - + @Test public void andThenSubscribeOn() { TestSubscriber ts = new TestSubscriber(0); @@ -474,34 +473,34 @@ public void andThenSingleSubscribeOn() { ts.assertNoErrors(); ts.assertUnsubscribed(); } - + @Test(expected = NullPointerException.class) public void createNull() { - Completable.create(null); + Completable.create((Completable.OnSubscribe)null); } - + @Test(timeout = 5000, expected = NullPointerException.class) public void createOnSubscribeThrowsNPE() { - Completable c = Completable.create(new CompletableOnSubscribe() { + Completable c = Completable.create(new OnSubscribe() { @Override public void call(CompletableSubscriber s) { throw new NullPointerException(); } }); - + c.await(); } - + @Test(timeout = 5000) public void createOnSubscribeThrowsRuntimeException() { try { - Completable c = Completable.create(new CompletableOnSubscribe() { + Completable c = Completable.create(new OnSubscribe() { @Override public void call(CompletableSubscriber s) { throw new TestException(); } }); - + c.await(); - + Assert.fail("Did not throw exception"); } catch (NullPointerException ex) { if (!(ex.getCause() instanceof TestException)) { @@ -510,7 +509,7 @@ public void call(CompletableSubscriber s) { } } } - + @Test(timeout = 5000) public void defer() { Completable c = Completable.defer(new Func0() { @@ -519,19 +518,19 @@ public Completable call() { return normal.completable; } }); - + normal.assertSubscriptions(0); - + c.await(); - + normal.assertSubscriptions(1); } - + @Test(expected = NullPointerException.class) public void deferNull() { Completable.defer(null); } - + @Test(timeout = 5000, expected = NullPointerException.class) public void deferReturnsNull() { Completable c = Completable.defer(new Func0() { @@ -540,20 +539,20 @@ public Completable call() { return null; } }); - + c.await(); } - + @Test(timeout = 5000, expected = TestException.class) public void deferFunctionThrows() { Completable c = Completable.defer(new Func0() { @Override public Completable call() { throw new TestException(); } }); - + c.await(); } - + @Test(timeout = 5000, expected = TestException.class) public void deferErrorSource() { Completable c = Completable.defer(new Func0() { @@ -562,62 +561,62 @@ public Completable call() { return error.completable; } }); - + c.await(); } - + @Test(expected = NullPointerException.class) public void errorNull() { Completable.error((Throwable)null); } - + @Test(timeout = 5000, expected = TestException.class) public void errorNormal() { Completable c = Completable.error(new TestException()); - + c.await(); } - + @Test(expected = NullPointerException.class) public void fromCallableNull() { Completable.fromCallable(null); } - + @Test(timeout = 5000) public void fromCallableNormal() { final AtomicInteger calls = new AtomicInteger(); - + Completable c = Completable.fromCallable(new Callable() { @Override public Object call() throws Exception { return calls.getAndIncrement(); } }); - + c.await(); - + Assert.assertEquals(1, calls.get()); } - + @Test(timeout = 5000, expected = TestException.class) public void fromCallableThrows() { Completable c = Completable.fromCallable(new Callable() { @Override public Object call() throws Exception { throw new TestException(); } }); - + c.await(); } - + @Test(expected = NullPointerException.class) public void fromObservableNull() { Completable.fromObservable(null); } - + @Test(timeout = 5000) public void fromObservableEmpty() { Completable c = Completable.fromObservable(Observable.empty()); - + c.await(); } @@ -625,52 +624,52 @@ public void fromObservableEmpty() { public void fromObservableSome() { for (int n = 1; n < 10000; n *= 10) { Completable c = Completable.fromObservable(Observable.range(1, n)); - + c.await(); } } - + @Test(timeout = 5000, expected = TestException.class) public void fromObservableError() { Completable c = Completable.fromObservable(Observable.error(new TestException())); - + c.await(); } - + @Test(expected = NullPointerException.class) public void fromFutureNull() { Completable.fromFuture(null); } - + @Test(timeout = 5000) public void fromFutureNormal() { ExecutorService exec = Executors.newSingleThreadExecutor(); - + try { Completable c = Completable.fromFuture(exec.submit(new Runnable() { @Override - public void run() { + public void run() { // no action } })); - + c.await(); } finally { exec.shutdown(); } } - + @Test(timeout = 5000) public void fromFutureThrows() { ExecutorService exec = Executors.newSingleThreadExecutor(); - + Completable c = Completable.fromFuture(exec.submit(new Runnable() { @Override - public void run() { + public void run() { throw new TestException(); } })); - + try { c.await(); Assert.fail("Failed to throw Exception"); @@ -683,120 +682,120 @@ public void run() { exec.shutdown(); } } - + @Test(expected = NullPointerException.class) public void fromActionNull() { Completable.fromAction(null); } - + @Test(timeout = 5000) public void fromActionNormal() { final AtomicInteger calls = new AtomicInteger(); - + Completable c = Completable.fromAction(new Action0() { @Override public void call() { calls.getAndIncrement(); } }); - + c.await(); - + Assert.assertEquals(1, calls.get()); } - + @Test(timeout = 5000, expected = TestException.class) public void fromActionThrows() { Completable c = Completable.fromAction(new Action0() { @Override public void call() { throw new TestException(); } }); - + c.await(); } - + @Test(expected = NullPointerException.class) public void fromSingleNull() { Completable.fromSingle(null); } - + @Test(timeout = 5000) public void fromSingleNormal() { Completable c = Completable.fromSingle(Single.just(1)); - + c.await(); } - + @Test(timeout = 5000, expected = TestException.class) public void fromSingleThrows() { Completable c = Completable.fromSingle(Single.error(new TestException())); - + c.await(); } - + @Test(expected = NullPointerException.class) public void mergeNull() { Completable.merge((Completable[])null); } - + @Test(timeout = 5000) public void mergeEmpty() { Completable c = Completable.merge(); - + c.await(); } - + @Test(timeout = 5000) public void mergeSingleSource() { Completable c = Completable.merge(normal.completable); - + c.await(); - + normal.assertSubscriptions(1); } - + @Test(timeout = 5000, expected = TestException.class) public void mergeSingleSourceThrows() { Completable c = Completable.merge(error.completable); - + c.await(); } - + @Test(timeout = 5000) public void mergeMultipleSources() { Completable c = Completable.merge(normal.completable, normal.completable, normal.completable); - + c.await(); - + normal.assertSubscriptions(3); } - + @Test(timeout = 5000, expected = TestException.class) public void mergeMultipleOneThrows() { Completable c = Completable.merge(normal.completable, error.completable, normal.completable); - + c.await(); } - + @Test(timeout = 5000, expected = NullPointerException.class) public void mergeMultipleOneIsNull() { Completable c = Completable.merge(normal.completable, null); - + c.await(); } - + @Test(timeout = 5000) public void mergeIterableEmpty() { Completable c = Completable.merge(Collections.emptyList()); - + c.await(); } - + @Test(expected = NullPointerException.class) public void mergeIterableNull() { Completable.merge((Iterable)null); } - + @Test(timeout = 5000, expected = NullPointerException.class) public void mergeIterableIteratorNull() { Completable c = Completable.merge(new Iterable() { @@ -805,49 +804,49 @@ public Iterator iterator() { return null; } }); - + c.await(); } - + @Test(timeout = 5000, expected = NullPointerException.class) public void mergeIterableWithNull() { Completable c = Completable.merge(Arrays.asList(normal.completable, (Completable)null)); - + c.await(); } - + @Test(timeout = 5000) public void mergeIterableSingle() { Completable c = Completable.merge(Collections.singleton(normal.completable)); - + c.await(); - + normal.assertSubscriptions(1); } - + @Test(timeout = 5000) public void mergeIterableMany() { Completable c = Completable.merge(Arrays.asList(normal.completable, normal.completable, normal.completable)); - + c.await(); - + normal.assertSubscriptions(3); } - + @Test(timeout = 5000, expected = TestException.class) public void mergeIterableOneThrows() { Completable c = Completable.merge(Collections.singleton(error.completable)); - + c.await(); } - + @Test(timeout = 5000, expected = TestException.class) public void mergeIterableManyOneThrows() { Completable c = Completable.merge(Arrays.asList(normal.completable, error.completable)); - + c.await(); } - + @Test(expected = TestException.class) public void mergeIterableIterableThrows() { Completable c = Completable.merge(new Iterable() { @@ -856,70 +855,70 @@ public Iterator iterator() { throw new TestException(); } }); - + c.await(); } - + @Test(expected = TestException.class) public void mergeIterableIteratorHasNextThrows() { Completable c = Completable.merge(new IterableIteratorHasNextThrows()); - + c.await(); } - + @Test(expected = TestException.class) public void mergeIterableIteratorNextThrows() { Completable c = Completable.merge(new IterableIteratorNextThrows()); - + c.await(); } - + @Test(timeout = 5000) public void mergeObservableEmpty() { Completable c = Completable.merge(Observable.empty()); - + c.await(); } - + @Test(timeout = 5000, expected = TestException.class) public void mergeObservableError() { Completable c = Completable.merge(Observable.error(new TestException())); - + c.await(); } - + @Test(timeout = 5000) public void mergeObservableSingle() { Completable c = Completable.merge(Observable.just(normal.completable)); - + c.await(); - + normal.assertSubscriptions(1); } - + @Test(timeout = 5000, expected = TestException.class) public void mergeObservableSingleThrows() { Completable c = Completable.merge(Observable.just(error.completable)); - + c.await(); } - + @Test(timeout = 5000) public void mergeObservableMany() { Completable c = Completable.merge(Observable.just(normal.completable).repeat(3)); - + c.await(); - + normal.assertSubscriptions(3); } - + @Test(timeout = 5000, expected = TestException.class) public void mergeObservableManyOneThrows() { Completable c = Completable.merge(Observable.just(normal.completable, error.completable)); - + c.await(); } - + @Test(timeout = 5000) public void mergeObservableMaxConcurrent() { final List requested = new ArrayList(); @@ -932,11 +931,11 @@ public void call(Long v) { requested.add(v); } }); - + Completable c = Completable.merge(cs, 5); - + c.await(); - + // FIXME this request pattern looks odd because all 10 completions trigger 1 requests Assert.assertEquals(Arrays.asList(5L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L), requested); } @@ -945,69 +944,69 @@ public void call(Long v) { public void mergeDelayErrorNull() { Completable.mergeDelayError((Completable[])null); } - + @Test(timeout = 5000) public void mergeDelayErrorEmpty() { Completable c = Completable.mergeDelayError(); - + c.await(); } - + @Test(timeout = 5000) public void mergeDelayErrorSingleSource() { Completable c = Completable.mergeDelayError(normal.completable); - + c.await(); - + normal.assertSubscriptions(1); } - + @Test(timeout = 5000, expected = TestException.class) public void mergeDelayErrorSingleSourceThrows() { Completable c = Completable.mergeDelayError(error.completable); - + c.await(); } - + @Test(timeout = 5000) public void mergeDelayErrorMultipleSources() { Completable c = Completable.mergeDelayError(normal.completable, normal.completable, normal.completable); - + c.await(); - + normal.assertSubscriptions(3); } - + @Test(timeout = 5000) public void mergeDelayErrorMultipleOneThrows() { Completable c = Completable.mergeDelayError(normal.completable, error.completable, normal.completable); - + try { c.await(); } catch (TestException ex) { normal.assertSubscriptions(2); } } - + @Test(timeout = 5000, expected = NullPointerException.class) public void mergeDelayErrorMultipleOneIsNull() { Completable c = Completable.mergeDelayError(normal.completable, null); - + c.await(); } - + @Test(timeout = 5000) public void mergeDelayErrorIterableEmpty() { Completable c = Completable.mergeDelayError(Collections.emptyList()); - + c.await(); } - + @Test(expected = NullPointerException.class) public void mergeDelayErrorIterableNull() { Completable.mergeDelayError((Iterable)null); } - + @Test(timeout = 5000, expected = NullPointerException.class) public void mergeDelayErrorIterableIteratorNull() { Completable c = Completable.mergeDelayError(new Iterable() { @@ -1016,53 +1015,53 @@ public Iterator iterator() { return null; } }); - + c.await(); } - + @Test(timeout = 5000, expected = NullPointerException.class) public void mergeDelayErrorIterableWithNull() { Completable c = Completable.mergeDelayError(Arrays.asList(normal.completable, (Completable)null)); - + c.await(); } - + @Test(timeout = 5000) public void mergeDelayErrorIterableSingle() { Completable c = Completable.mergeDelayError(Collections.singleton(normal.completable)); - + c.await(); - + normal.assertSubscriptions(1); } - + @Test(timeout = 5000) public void mergeDelayErrorIterableMany() { Completable c = Completable.mergeDelayError(Arrays.asList(normal.completable, normal.completable, normal.completable)); - + c.await(); - + normal.assertSubscriptions(3); } - + @Test(timeout = 5000, expected = TestException.class) public void mergeDelayErrorIterableOneThrows() { Completable c = Completable.mergeDelayError(Collections.singleton(error.completable)); - + c.await(); } - + @Test(timeout = 5000) public void mergeDelayErrorIterableManyOneThrows() { Completable c = Completable.mergeDelayError(Arrays.asList(normal.completable, error.completable, normal.completable)); - + try { c.await(); } catch (TestException ex) { normal.assertSubscriptions(2); } } - + @Test(expected = TestException.class) public void mergeDelayErrorIterableIterableThrows() { Completable c = Completable.mergeDelayError(new Iterable() { @@ -1071,70 +1070,70 @@ public Iterator iterator() { throw new TestException(); } }); - + c.await(); } - + @Test(expected = TestException.class) public void mergeDelayErrorIterableIteratorHasNextThrows() { Completable c = Completable.mergeDelayError(new IterableIteratorHasNextThrows()); - + c.await(); } - + @Test(expected = TestException.class) public void mergeDelayErrorIterableIteratorNextThrows() { Completable c = Completable.mergeDelayError(new IterableIteratorNextThrows()); - + c.await(); } - + @Test(timeout = 5000) public void mergeDelayErrorObservableEmpty() { Completable c = Completable.mergeDelayError(Observable.empty()); - + c.await(); } - + @Test(timeout = 5000, expected = TestException.class) public void mergeDelayErrorObservableError() { Completable c = Completable.mergeDelayError(Observable.error(new TestException())); - + c.await(); } - + @Test(timeout = 5000) public void mergeDelayErrorObservableSingle() { Completable c = Completable.mergeDelayError(Observable.just(normal.completable)); - + c.await(); - + normal.assertSubscriptions(1); } - + @Test(timeout = 5000, expected = TestException.class) public void mergeDelayErrorObservableSingleThrows() { Completable c = Completable.mergeDelayError(Observable.just(error.completable)); - + c.await(); } - + @Test(timeout = 5000) public void mergeDelayErrorObservableMany() { Completable c = Completable.mergeDelayError(Observable.just(normal.completable).repeat(3)); - + c.await(); - + normal.assertSubscriptions(3); } - + @Test(timeout = 5000, expected = TestException.class) public void mergeDelayErrorObservableManyOneThrows() { Completable c = Completable.mergeDelayError(Observable.just(normal.completable, error.completable)); - + c.await(); } - + @Test(timeout = 5000) public void mergeDelayErrorObservableMaxConcurrent() { final List requested = new ArrayList(); @@ -1147,11 +1146,11 @@ public void call(Long v) { requested.add(v); } }); - + Completable c = Completable.mergeDelayError(cs, 5); - + c.await(); - + // FIXME this request pattern looks odd because all 10 completions trigger 1 requests Assert.assertEquals(Arrays.asList(5L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L), requested); } @@ -1165,55 +1164,55 @@ public void never() { public void onSubscribe(Subscription d) { onSubscribeCalled.set(true); } - + @Override public void onError(Throwable e) { calls.getAndIncrement(); } - + @Override public void onCompleted() { calls.getAndIncrement(); } }); - + Assert.assertTrue("onSubscribe not called", onSubscribeCalled.get()); Assert.assertEquals("There were calls to onXXX methods", 0, calls.get()); } - + @Test(timeout = 1500) public void timer() { Completable c = Completable.timer(500, TimeUnit.MILLISECONDS); - + c.await(); } - + @Test(timeout = 1500) public void timerNewThread() { Completable c = Completable.timer(500, TimeUnit.MILLISECONDS, Schedulers.newThread()); - + c.await(); } - + @Test(timeout = 5000) public void timerTestScheduler() { TestScheduler scheduler = Schedulers.test(); - + Completable c = Completable.timer(250, TimeUnit.MILLISECONDS, scheduler); - + final AtomicInteger calls = new AtomicInteger(); - + c.unsafeSubscribe(new CompletableSubscriber() { @Override public void onSubscribe(Subscription d) { - + } - + @Override public void onCompleted() { calls.getAndIncrement(); } - + @Override public void onError(Throwable e) { RxJavaHooks.onError(e); @@ -1221,61 +1220,61 @@ public void onError(Throwable e) { }); scheduler.advanceTimeBy(100, TimeUnit.MILLISECONDS); - + Assert.assertEquals(0, calls.get()); - + scheduler.advanceTimeBy(200, TimeUnit.MILLISECONDS); - + Assert.assertEquals(1, calls.get()); } - + @Test(timeout = 2000) public void timerCancel() throws InterruptedException { Completable c = Completable.timer(250, TimeUnit.MILLISECONDS); - + final MultipleAssignmentSubscription mad = new MultipleAssignmentSubscription(); final AtomicInteger calls = new AtomicInteger(); - + c.unsafeSubscribe(new CompletableSubscriber() { @Override public void onSubscribe(Subscription d) { mad.set(d); } - + @Override public void onError(Throwable e) { calls.getAndIncrement(); } - + @Override public void onCompleted() { calls.getAndIncrement(); } }); - + Thread.sleep(100); - + mad.unsubscribe(); - + Thread.sleep(200); - + Assert.assertEquals(0, calls.get()); } - + @Test(expected = NullPointerException.class) public void timerUnitNull() { Completable.timer(1, null); } - + @Test(expected = NullPointerException.class) public void timerSchedulerNull() { Completable.timer(1, TimeUnit.SECONDS, null); } - + @Test(timeout = 5000) public void usingNormalEager() { final AtomicInteger unsubscribe = new AtomicInteger(); - + Completable c = Completable.using(new Func0() { @Override public Integer call() { @@ -1292,36 +1291,36 @@ public void call(Integer d) { unsubscribe.set(d); } }); - + final AtomicBoolean unsubscribedFirst = new AtomicBoolean(); final AtomicReference error = new AtomicReference(); - + c.unsafeSubscribe(new CompletableSubscriber() { @Override public void onSubscribe(Subscription d) { - + } - + @Override public void onError(Throwable e) { error.lazySet(e); } - + @Override public void onCompleted() { unsubscribedFirst.set(unsubscribe.get() != 0); } }); - + Assert.assertEquals(1, unsubscribe.get()); Assert.assertTrue("Not unsubscribed first", unsubscribedFirst.get()); Assert.assertNull(error.get()); } - + @Test(timeout = 5000) public void usingNormalLazy() { final AtomicInteger unsubscribe = new AtomicInteger(); - + Completable c = Completable.using(new Func0() { @Override public Integer call() { @@ -1338,36 +1337,36 @@ public void call(Integer d) { unsubscribe.set(d); } }, false); - + final AtomicBoolean unsubscribedFirst = new AtomicBoolean(); final AtomicReference error = new AtomicReference(); - + c.unsafeSubscribe(new CompletableSubscriber() { @Override public void onSubscribe(Subscription d) { - + } - + @Override public void onError(Throwable e) { error.lazySet(e); } - + @Override public void onCompleted() { unsubscribedFirst.set(unsubscribe.get() != 0); } }); - + Assert.assertEquals(1, unsubscribe.get()); Assert.assertFalse("Disposed first", unsubscribedFirst.get()); Assert.assertNull(error.get()); } - + @Test(timeout = 5000) public void usingErrorEager() { final AtomicInteger unsubscribe = new AtomicInteger(); - + Completable c = Completable.using(new Func0() { @Override public Integer call() { @@ -1384,36 +1383,36 @@ public void call(Integer d) { unsubscribe.set(d); } }); - + final AtomicBoolean unsubscribedFirst = new AtomicBoolean(); final AtomicBoolean complete = new AtomicBoolean(); - + c.unsafeSubscribe(new CompletableSubscriber() { @Override public void onSubscribe(Subscription d) { - + } - + @Override public void onError(Throwable e) { unsubscribedFirst.set(unsubscribe.get() != 0); } - + @Override public void onCompleted() { complete.set(true); } }); - + Assert.assertEquals(1, unsubscribe.get()); Assert.assertTrue("Not unsubscribed first", unsubscribedFirst.get()); Assert.assertFalse(complete.get()); } - + @Test(timeout = 5000) public void usingErrorLazy() { final AtomicInteger unsubscribe = new AtomicInteger(); - + Completable c = Completable.using(new Func0() { @Override public Integer call() { @@ -1430,32 +1429,32 @@ public void call(Integer d) { unsubscribe.set(d); } }, false); - + final AtomicBoolean unsubscribedFirst = new AtomicBoolean(); final AtomicBoolean complete = new AtomicBoolean(); - + c.unsafeSubscribe(new CompletableSubscriber() { @Override public void onSubscribe(Subscription d) { - + } - + @Override public void onError(Throwable e) { unsubscribedFirst.set(unsubscribe.get() != 0); } - + @Override public void onCompleted() { complete.set(true); } }); - + Assert.assertEquals(1, unsubscribe.get()); Assert.assertFalse("Disposed first", unsubscribedFirst.get()); Assert.assertFalse(complete.get()); } - + @Test(expected = NullPointerException.class) public void usingResourceSupplierNull() { Completable.using(null, new Func1() { @@ -1498,10 +1497,10 @@ public Completable call(Object v) { @Override public void call(Object v) { } }); - + c.await(); } - + @Test(expected = NullPointerException.class) public void usingDisposeNull() { Completable.using(new Func0() { @@ -1516,13 +1515,13 @@ public Completable call(Object v) { } }, null); } - + @Test(expected = TestException.class) public void usingResourceThrows() { Completable c = Completable.using(new Func0() { @Override public Object call() { throw new TestException(); } - }, + }, new Func1() { @Override public Completable call(Object v) { @@ -1532,10 +1531,10 @@ public Completable call(Object v) { @Override public void call(Object v) { } }); - + c.await(); } - + @Test(expected = TestException.class) public void usingMapperThrows() { Completable c = Completable.using(new Func0() { @@ -1543,7 +1542,7 @@ public void usingMapperThrows() { public Object call() { return 1; } - }, + }, new Func1() { @Override public Completable call(Object v) { throw new TestException(); } @@ -1551,10 +1550,10 @@ public Object call() { @Override public void call(Object v) { } }); - + c.await(); } - + @Test(expected = TestException.class) public void usingDisposerThrows() { Completable c = Completable.using(new Func0() { @@ -1562,7 +1561,7 @@ public void usingDisposerThrows() { public Object call() { return 1; } - }, + }, new Func1() { @Override public Completable call(Object v) { @@ -1572,40 +1571,40 @@ public Completable call(Object v) { @Override public void call(Object v) { throw new TestException(); } }); - + c.await(); } - + @Test(timeout = 5000) public void composeNormal() { - Completable c = error.completable.compose(new CompletableTransformer() { + Completable c = error.completable.compose(new Transformer() { @Override public Completable call(Completable n) { return n.onErrorComplete(); } }); - + c.await(); } - + @Test(expected = NullPointerException.class) public void composeNull() { error.completable.compose(null); } - + @Test(timeout = 5000) public void concatWithNormal() { Completable c = normal.completable.concatWith(normal.completable); - + c.await(); - + normal.assertSubscriptions(2); } @Test(timeout = 5000, expected = TestException.class) public void concatWithError() { Completable c = normal.completable.concatWith(error.completable); - + c.await(); } @@ -1613,7 +1612,7 @@ public void concatWithError() { public void concatWithNull() { normal.completable.concatWith(null); } - + @Test(expected = NullPointerException.class) public void delayUnitNull() { normal.completable.delay(1, null); @@ -1623,60 +1622,60 @@ public void delayUnitNull() { public void delaySchedulerNull() { normal.completable.delay(1, TimeUnit.SECONDS, null); } - + @Test(timeout = 5000) public void delayNormal() throws InterruptedException { Completable c = normal.completable.delay(250, TimeUnit.MILLISECONDS); - + final AtomicBoolean done = new AtomicBoolean(); final AtomicReference error = new AtomicReference(); - + c.unsafeSubscribe(new CompletableSubscriber() { @Override public void onSubscribe(Subscription d) { - + } - + @Override public void onError(Throwable e) { error.set(e); } - + @Override public void onCompleted() { done.set(true); } }); - + Thread.sleep(100); - + Assert.assertFalse("Already done", done.get()); - + Thread.sleep(200); - + Assert.assertTrue("Not done", done.get()); - + Assert.assertNull(error.get()); } - + @Test(timeout = 5000) public void delayErrorImmediately() throws InterruptedException { Completable c = error.completable.delay(250, TimeUnit.MILLISECONDS); - + final AtomicBoolean done = new AtomicBoolean(); final AtomicReference error = new AtomicReference(); - + c.unsafeSubscribe(new CompletableSubscriber() { @Override public void onSubscribe(Subscription d) { - + } - + @Override public void onError(Throwable e) { error.set(e); } - + @Override public void onCompleted() { done.set(true); @@ -1687,126 +1686,126 @@ public void onCompleted() { Assert.assertFalse("Already done", done.get()); Thread.sleep(100); - + Assert.assertFalse("Already done", done.get()); - + Thread.sleep(200); } - + @Test(timeout = 5000) public void delayErrorToo() throws InterruptedException { Completable c = error.completable.delay(250, TimeUnit.MILLISECONDS, Schedulers.computation(), true); - + final AtomicBoolean done = new AtomicBoolean(); final AtomicReference error = new AtomicReference(); - + c.unsafeSubscribe(new CompletableSubscriber() { @Override public void onSubscribe(Subscription d) { - + } - + @Override public void onError(Throwable e) { error.set(e); } - + @Override public void onCompleted() { done.set(true); } }); - + Thread.sleep(100); - + Assert.assertFalse("Already done", done.get()); Assert.assertNull(error.get()); - + Thread.sleep(200); - + Assert.assertFalse("Already done", done.get()); Assert.assertTrue(error.get() instanceof TestException); } - + @Test(timeout = 5000) public void doOnCompletedNormal() { final AtomicInteger calls = new AtomicInteger(); - + Completable c = normal.completable.doOnCompleted(new Action0() { @Override public void call() { calls.getAndIncrement(); } }); - + c.await(); - + Assert.assertEquals(1, calls.get()); } - + @Test(timeout = 5000) public void doOnCompletedError() { final AtomicInteger calls = new AtomicInteger(); - + Completable c = error.completable.doOnCompleted(new Action0() { @Override public void call() { calls.getAndIncrement(); } }); - + try { c.await(); Assert.fail("Failed to throw TestException"); } catch (TestException ex) { // expected } - + Assert.assertEquals(0, calls.get()); } - + @Test(expected = NullPointerException.class) public void doOnCompletedNull() { normal.completable.doOnCompleted(null); } - + @Test(timeout = 5000, expected = TestException.class) public void doOnCompletedThrows() { Completable c = normal.completable.doOnCompleted(new Action0() { @Override public void call() { throw new TestException(); } }); - + c.await(); } - + @Test(timeout = 5000) public void doOnDisposeNormalDoesntCall() { final AtomicInteger calls = new AtomicInteger(); - + Completable c = normal.completable.doOnUnsubscribe(new Action0() { @Override public void call() { calls.getAndIncrement(); } }); - + c.await(); - + Assert.assertEquals(0, calls.get()); } - + @Test(timeout = 5000) public void doOnDisposeErrorDoesntCall() { final AtomicInteger calls = new AtomicInteger(); - + Completable c = error.completable.doOnUnsubscribe(new Action0() { @Override public void call() { calls.getAndIncrement(); } }); - + try { c.await(); Assert.fail("No exception thrown"); @@ -1815,117 +1814,117 @@ public void call() { } Assert.assertEquals(0, calls.get()); } - + @Test(timeout = 5000) public void doOnDisposeChildCancels() { final AtomicInteger calls = new AtomicInteger(); - + Completable c = normal.completable.doOnUnsubscribe(new Action0() { @Override public void call() { calls.getAndIncrement(); } }); - + c.unsafeSubscribe(new CompletableSubscriber() { @Override public void onSubscribe(Subscription d) { d.unsubscribe(); } - + @Override public void onError(Throwable e) { // ignored } - + @Override public void onCompleted() { // ignored } }); - + Assert.assertEquals(1, calls.get()); } - + @Test(expected = NullPointerException.class) public void doOnDisposeNull() { normal.completable.doOnUnsubscribe(null); } - + @Test(timeout = 5000) public void doOnDisposeThrows() { Completable c = normal.completable.doOnUnsubscribe(new Action0() { @Override public void call() { throw new TestException(); } }); - + c.unsafeSubscribe(new CompletableSubscriber() { @Override public void onSubscribe(Subscription d) { d.unsubscribe(); } - + @Override public void onError(Throwable e) { // ignored } - + @Override public void onCompleted() { // ignored } }); } - + @Test(timeout = 5000) public void doOnErrorNoError() { final AtomicReference error = new AtomicReference(); - + Completable c = normal.completable.doOnError(new Action1() { @Override public void call(Throwable e) { error.set(e); } }); - + c.await(); - + Assert.assertNull(error.get()); } - + @Test(timeout = 5000) public void doOnErrorHasError() { final AtomicReference err = new AtomicReference(); - + Completable c = error.completable.doOnError(new Action1() { @Override public void call(Throwable e) { err.set(e); } }); - + try { c.await(); Assert.fail("Did not throw exception"); } catch (Throwable e) { // expected } - + Assert.assertTrue(err.get() instanceof TestException); } - + @Test(expected = NullPointerException.class) public void doOnErrorNull() { normal.completable.doOnError(null); } - + @Test(timeout = 5000) public void doOnErrorThrows() { Completable c = error.completable.doOnError(new Action1() { @Override public void call(Throwable e) { throw new IllegalStateException(); } }); - + try { c.await(); } catch (CompositeException ex) { @@ -1935,148 +1934,148 @@ public void doOnErrorThrows() { Assert.assertTrue(a.get(1) instanceof IllegalStateException); } } - + @Test(timeout = 5000) public void doOnSubscribeNormal() { final AtomicInteger calls = new AtomicInteger(); - + Completable c = normal.completable.doOnSubscribe(new Action1() { @Override public void call(Subscription s) { calls.getAndIncrement(); } }); - + for (int i = 0; i < 10; i++) { c.await(); } - + Assert.assertEquals(10, calls.get()); } - + @Test(expected = NullPointerException.class) public void doOnSubscribeNull() { normal.completable.doOnSubscribe(null); } - + @Test(expected = TestException.class) public void doOnSubscribeThrows() { Completable c = normal.completable.doOnSubscribe(new Action1() { @Override public void call(Subscription d) { throw new TestException(); } }); - + c.await(); } - + @Test(timeout = 5000) public void doOnTerminateNormal() { final AtomicInteger calls = new AtomicInteger(); - + Completable c = normal.completable.doOnTerminate(new Action0() { @Override public void call() { calls.getAndIncrement(); } }); - + c.await(); - + Assert.assertEquals(1, calls.get()); } - + @Test(timeout = 5000) public void doOnTerminateError() { final AtomicInteger calls = new AtomicInteger(); - + Completable c = error.completable.doOnTerminate(new Action0() { @Override public void call() { calls.getAndIncrement(); } }); - + try { c.await(); Assert.fail("Did dot throw exception"); } catch (TestException ex) { // expected } - + Assert.assertEquals(1, calls.get()); } - + @Test(timeout = 5000) public void doAfterTerminateNormal() { final AtomicBoolean doneAfter = new AtomicBoolean(); final AtomicBoolean complete = new AtomicBoolean(); - + Completable c = normal.completable.doAfterTerminate(new Action0() { @Override public void call() { doneAfter.set(complete.get()); } }); - + c.unsafeSubscribe(new CompletableSubscriber() { @Override public void onSubscribe(Subscription d) { - + } @Override public void onError(Throwable e) { - + } - + @Override public void onCompleted() { complete.set(true); } }); - + c.await(); - + Assert.assertTrue("Not completed", complete.get()); Assert.assertTrue("Closure called before onComplete", doneAfter.get()); } - + @Test(timeout = 5000) public void doAfterTerminateWithError() { final AtomicBoolean doneAfter = new AtomicBoolean(); - + Completable c = error.completable.doAfterTerminate(new Action0() { @Override public void call() { doneAfter.set(true); } }); - + try { c.await(); Assert.fail("Did not throw TestException"); } catch (TestException ex) { // expected } - - Assert.assertFalse("Closure called", doneAfter.get()); + + Assert.assertTrue("Closure called", doneAfter.get()); } - + @Test(expected = NullPointerException.class) public void doAfterTerminateNull() { normal.completable.doAfterTerminate(null); } - + @Test(timeout = 5000) public void getNormal() { Assert.assertNull(normal.completable.get()); } - + @Test(timeout = 5000) public void getError() { Assert.assertTrue(error.completable.get() instanceof TestException); } - + @Test(timeout = 5000) public void getTimeout() { try { @@ -2087,30 +2086,30 @@ public void getTimeout() { } } } - + @Test(expected = NullPointerException.class) public void getNullUnit() { normal.completable.get(1, null); } - + @Test(expected = NullPointerException.class) public void liftNull() { - normal.completable.lift(null); + normal.completable.lift((Completable.Operator)null); } - + @Test(timeout = 5000, expected = NullPointerException.class) public void liftReturnsNull() { - Completable c = normal.completable.lift(new CompletableOperator() { + Completable c = normal.completable.lift(new Operator() { @Override public CompletableSubscriber call(CompletableSubscriber v) { return null; } }); - + c.await(); } - final static class CompletableOperatorSwap implements CompletableOperator { + final static class CompletableOperatorSwap implements Operator { @Override public CompletableSubscriber call(final CompletableSubscriber v) { return new CompletableSubscriber() { @@ -2129,96 +2128,96 @@ public void onError(Throwable e) { public void onSubscribe(Subscription d) { v.onSubscribe(d); } - + }; } } @Test(timeout = 5000, expected = TestException.class) public void liftOnCompleteError() { Completable c = normal.completable.lift(new CompletableOperatorSwap()); - + c.await(); } - + @Test(timeout = 5000) public void liftOnErrorComplete() { Completable c = error.completable.lift(new CompletableOperatorSwap()); - + c.await(); } - + @Test(expected = NullPointerException.class) public void mergeWithNull() { normal.completable.mergeWith(null); } - + @Test(timeout = 5000) public void mergeWithNormal() { Completable c = normal.completable.mergeWith(normal.completable); - + c.await(); - + normal.assertSubscriptions(2); } - + @Test(expected = NullPointerException.class) public void observeOnNull() { normal.completable.observeOn(null); } - + @Test(timeout = 5000) public void observeOnNormal() throws InterruptedException { final AtomicReference name = new AtomicReference(); final AtomicReference err = new AtomicReference(); final CountDownLatch cdl = new CountDownLatch(1); - + Completable c = normal.completable.observeOn(Schedulers.computation()); - + c.unsafeSubscribe(new CompletableSubscriber() { @Override public void onSubscribe(Subscription d) { - + } - + @Override public void onCompleted() { name.set(Thread.currentThread().getName()); cdl.countDown(); } - + @Override public void onError(Throwable e) { err.set(e); cdl.countDown(); } }); - + cdl.await(); - + Assert.assertNull(err.get()); Assert.assertTrue(name.get().startsWith("RxComputation")); } - + @Test(timeout = 5000) public void observeOnError() throws InterruptedException { final AtomicReference name = new AtomicReference(); final AtomicReference err = new AtomicReference(); final CountDownLatch cdl = new CountDownLatch(1); - + Completable c = error.completable.observeOn(Schedulers.computation()); - + c.unsafeSubscribe(new CompletableSubscriber() { @Override public void onSubscribe(Subscription d) { - + } - + @Override public void onCompleted() { name.set(Thread.currentThread().getName()); cdl.countDown(); } - + @Override public void onError(Throwable e) { name.set(Thread.currentThread().getName()); @@ -2226,20 +2225,20 @@ public void onError(Throwable e) { cdl.countDown(); } }); - + cdl.await(); - + Assert.assertTrue(err.get() instanceof TestException); Assert.assertTrue(name.get().startsWith("RxComputation")); } - + @Test(timeout = 5000) public void onErrorComplete() { Completable c = error.completable.onErrorComplete(); - + c.await(); } - + @Test(timeout = 5000, expected = TestException.class) public void onErrorCompleteFalse() { Completable c = error.completable.onErrorComplete(new Func1() { @@ -2248,20 +2247,20 @@ public Boolean call(Throwable e) { return e instanceof IllegalStateException; } }); - + c.await(); } - + @Test(expected = NullPointerException.class) public void onErrorCompleteNull() { error.completable.onErrorComplete(null); } - + @Test(expected = NullPointerException.class) public void onErrorResumeNextNull() { error.completable.onErrorResumeNext(null); } - + @Test(timeout = 5000) public void onErrorResumeNextFunctionReturnsNull() { Completable c = error.completable.onErrorResumeNext(new Func1() { @@ -2270,7 +2269,7 @@ public Completable call(Throwable e) { return null; } }); - + try { c.await(); Assert.fail("Did not throw an exception"); @@ -2281,14 +2280,14 @@ public Completable call(Throwable e) { Assert.assertTrue(a.get(1) instanceof NullPointerException); } } - + @Test(timeout = 5000) public void onErrorResumeNextFunctionThrows() { Completable c = error.completable.onErrorResumeNext(new Func1() { @Override public Completable call(Throwable e) { throw new TestException(); } }); - + try { c.await(); Assert.fail("Did not throw an exception"); @@ -2299,7 +2298,7 @@ public void onErrorResumeNextFunctionThrows() { Assert.assertTrue(a.get(1) instanceof TestException); } } - + @Test(timeout = 5000) public void onErrorResumeNextNormal() { Completable c = error.completable.onErrorResumeNext(new Func1() { @@ -2308,10 +2307,10 @@ public Completable call(Throwable v) { return normal.completable; } }); - + c.await(); } - + @Test(timeout = 5000, expected = TestException.class) public void onErrorResumeNextError() { Completable c = error.completable.onErrorResumeNext(new Func1() { @@ -2320,15 +2319,15 @@ public Completable call(Throwable v) { return error.completable; } }); - + c.await(); } - + @Test(timeout = 2000) public void repeatNormal() { final AtomicReference err = new AtomicReference(); final AtomicInteger calls = new AtomicInteger(); - + Completable c = Completable.fromCallable(new Callable() { @Override public Object call() throws Exception { @@ -2337,7 +2336,7 @@ public Object call() throws Exception { return null; } }).repeat(); - + c.unsafeSubscribe(new CompletableSubscriber() { @Override public void onSubscribe(final Subscription d) { @@ -2353,33 +2352,33 @@ public void call() { } }, 550, TimeUnit.MILLISECONDS); } - + @Override public void onError(Throwable e) { err.set(e); } - + @Override public void onCompleted() { - + } }); - + Assert.assertEquals(6, calls.get()); Assert.assertNull(err.get()); } - + @Test(timeout = 5000, expected = TestException.class) public void repeatError() { Completable c = error.completable.repeat(); - + c.await(); } - + @Test(timeout = 5000) public void repeat5Times() { final AtomicInteger calls = new AtomicInteger(); - + Completable c = Completable.fromCallable(new Callable() { @Override public Object call() throws Exception { @@ -2387,16 +2386,16 @@ public Object call() throws Exception { return null; } }).repeat(5); - + c.await(); - + Assert.assertEquals(5, calls.get()); } - + @Test(timeout = 5000) public void repeat1Time() { final AtomicInteger calls = new AtomicInteger(); - + Completable c = Completable.fromCallable(new Callable() { @Override public Object call() throws Exception { @@ -2404,16 +2403,16 @@ public Object call() throws Exception { return null; } }).repeat(1); - + c.await(); - + Assert.assertEquals(1, calls.get()); } - + @Test(timeout = 5000) public void repeat0Time() { final AtomicInteger calls = new AtomicInteger(); - + Completable c = Completable.fromCallable(new Callable() { @Override public Object call() throws Exception { @@ -2421,30 +2420,30 @@ public Object call() throws Exception { return null; } }).repeat(0); - + c.await(); - + Assert.assertEquals(0, calls.get()); } - + @Test(expected = NullPointerException.class) public void repeatWhenNull() { normal.completable.repeatWhen(null); } - + @Test(timeout = 5000) public void retryNormal() { Completable c = normal.completable.retry(); - + c.await(); - + normal.assertSubscriptions(1); } - + @Test(timeout = 5000) public void retry5Times() { final AtomicInteger calls = new AtomicInteger(5); - + Completable c = Completable.fromAction(new Action0() { @Override public void call() { @@ -2453,10 +2452,10 @@ public void call() { } } }).retry(); - + c.await(); } - + @Test(timeout = 5000, expected = TestException.class) public void retryBiPredicate5Times() { Completable c = error.completable.retry(new Func2() { @@ -2465,17 +2464,17 @@ public Boolean call(Integer n, Throwable e) { return n < 5; } }); - + c.await(); } - + @Test(timeout = 5000, expected = TestException.class) public void retryTimes5Error() { Completable c = error.completable.retry(5); - + c.await(); } - + @Test(timeout = 5000) public void retryTimes5Normal() { final AtomicInteger calls = new AtomicInteger(5); @@ -2488,15 +2487,15 @@ public void call() { } } }).retry(5); - + c.await(); } - + @Test(expected = IllegalArgumentException.class) public void retryNegativeTimes() { normal.completable.retry(-1); } - + @Test(timeout = 5000) public void retryWhen5Times() { final AtomicInteger calls = new AtomicInteger(5); @@ -2515,14 +2514,14 @@ public Observable call(Observable o) { return (Observable)o; } }); - + c.await(); } - + @Test(timeout = 5000) public void subscribe() throws InterruptedException { final AtomicBoolean complete = new AtomicBoolean(); - + Completable c = normal.completable .delay(100, TimeUnit.MILLISECONDS) .doOnCompleted(new Action0() { @@ -2531,18 +2530,18 @@ public void call() { complete.set(true); } }); - + c.subscribe(); - + Thread.sleep(150); - + Assert.assertTrue("Not completed", complete.get()); } - + @Test(timeout = 5000) public void subscribeDispose() throws InterruptedException { final AtomicBoolean complete = new AtomicBoolean(); - + Completable c = normal.completable .delay(200, TimeUnit.MILLISECONDS) .doOnCompleted(new Action0() { @@ -2551,139 +2550,139 @@ public void call() { complete.set(true); } }); - + Subscription d = c.subscribe(); - + Thread.sleep(100); - + d.unsubscribe(); - + Thread.sleep(150); - + Assert.assertFalse("Completed", complete.get()); } - + @Test(timeout = 5000) public void subscribeTwoCallbacksNormal() { final AtomicReference err = new AtomicReference(); final AtomicBoolean complete = new AtomicBoolean(); - normal.completable.subscribe(new Action1() { - @Override - public void call(Throwable e) { - err.set(e); - } - }, new Action0() { + normal.completable.subscribe(new Action0() { @Override public void call() { complete.set(true); } - }); - - Assert.assertNull(err.get()); + }, new Action1() { + @Override + public void call(Throwable e) { + err.set(e); + } + }); + + Assert.assertNull(err.get()); Assert.assertTrue("Not completed", complete.get()); } - + @Test(timeout = 5000) public void subscribeTwoCallbacksError() { final AtomicReference err = new AtomicReference(); final AtomicBoolean complete = new AtomicBoolean(); - error.completable.subscribe(new Action1() { - @Override - public void call(Throwable e) { - err.set(e); - } - }, new Action0() { + error.completable.subscribe(new Action0() { @Override public void call() { complete.set(true); } + }, new Action1() { + @Override + public void call(Throwable e) { + err.set(e); + } }); - + Assert.assertTrue(err.get() instanceof TestException); Assert.assertFalse("Not completed", complete.get()); } - + @Test(expected = NullPointerException.class) public void subscribeTwoCallbacksFirstNull() { - normal.completable.subscribe(null, new Action0() { + normal.completable.subscribe(null, new Action1() { @Override - public void call() { } + public void call(Throwable throwable) { } }); } - + @Test(expected = NullPointerException.class) public void subscribeTwoCallbacksSecondNull() { - normal.completable.subscribe(null, new Action0() { + normal.completable.subscribe(new Action0() { @Override public void call() { } - }); + }, null); } - + @Test(timeout = 5000) public void subscribeTwoCallbacksCompleteThrows() { final AtomicReference err = new AtomicReference(); - normal.completable.subscribe(new Action1() { + normal.completable.subscribe(new Action0() { + @Override + public void call() { throw new TestException(); } + }, new Action1() { @Override public void call(Throwable e) { err.set(e); } - }, new Action0() { - @Override - public void call() { throw new TestException(); } }); - + Assert.assertTrue(String.valueOf(err.get()), err.get() instanceof TestException); } - + @Test(timeout = 5000) public void subscribeTwoCallbacksOnErrorThrows() { - error.completable.subscribe(new Action1() { - @Override - public void call(Throwable e) { throw new TestException(); } - }, new Action0() { + error.completable.subscribe(new Action0() { @Override public void call() { } + }, new Action1() { + @Override + public void call(Throwable e) { throw new TestException(); } }); } - + @Test(timeout = 5000) public void subscribeActionNormal() { final AtomicBoolean run = new AtomicBoolean(); - + normal.completable.subscribe(new Action0() { @Override public void call() { run.set(true); } }); - + Assert.assertTrue("Not completed", run.get()); } @Test(timeout = 5000) public void subscribeActionError() { final AtomicBoolean run = new AtomicBoolean(); - + error.completable.subscribe(new Action0() { @Override public void call() { run.set(true); } }); - + Assert.assertFalse("Completed", run.get()); } - + @Test(expected = NullPointerException.class) public void subscribeActionNull() { normal.completable.subscribe((Action0)null); } - + @Test(expected = NullPointerException.class) public void subscribeSubscriberNull() { normal.completable.unsafeSubscribe((Subscriber)null); } - + @Test(expected = NullPointerException.class) public void subscribeCompletableSubscriberNull() { normal.completable.unsafeSubscribe((CompletableSubscriber)null); @@ -2692,9 +2691,9 @@ public void subscribeCompletableSubscriberNull() { @Test(timeout = 5000) public void subscribeSubscriberNormal() { TestSubscriber ts = new TestSubscriber(); - + normal.completable.unsafeSubscribe(ts); - + ts.assertCompleted(); ts.assertNoValues(); ts.assertNoErrors(); @@ -2703,57 +2702,57 @@ public void subscribeSubscriberNormal() { @Test(timeout = 5000) public void subscribeSubscriberError() { TestSubscriber ts = new TestSubscriber(); - + error.completable.unsafeSubscribe(ts); - + ts.assertNotCompleted(); ts.assertNoValues(); ts.assertError(TestException.class); } - + @Test(expected = NullPointerException.class) public void subscribeOnNull() { normal.completable.subscribeOn(null); } - + @Test(timeout = 5000) public void subscribeOnNormal() { final AtomicReference name = new AtomicReference(); - - Completable c = Completable.create(new CompletableOnSubscribe() { + + Completable c = Completable.create(new OnSubscribe() { @Override - public void call(CompletableSubscriber s) { + public void call(CompletableSubscriber s) { name.set(Thread.currentThread().getName()); s.onSubscribe(Subscriptions.unsubscribed()); s.onCompleted(); } }).subscribeOn(Schedulers.computation()); - + c.await(); - + Assert.assertTrue(name.get().startsWith("RxComputation")); } - + @Test(timeout = 5000) public void subscribeOnError() { final AtomicReference name = new AtomicReference(); - - Completable c = Completable.create(new CompletableOnSubscribe() { + + Completable c = Completable.create(new OnSubscribe() { @Override - public void call(CompletableSubscriber s) { + public void call(CompletableSubscriber s) { name.set(Thread.currentThread().getName()); s.onSubscribe(Subscriptions.unsubscribed()); s.onError(new TestException()); } }).subscribeOn(Schedulers.computation()); - + try { c.await(); Assert.fail("No exception thrown"); } catch (TestException ex) { // expected } - + Assert.assertTrue(name.get().startsWith("RxComputation")); } @@ -2800,14 +2799,14 @@ public void subscribeTwoActionsThrowFromOnError() { expectUncaughtTestException(new Action0() { @Override public void call() { - error.completable.subscribe(new Action1() { + error.completable.subscribe(new Action0() { @Override - public void call(Throwable throwable) { - throw new TestException(); + public void call() { } - }, new Action0() { + }, new Action1() { @Override - public void call() { + public void call(Throwable throwable) { + throw new TestException(); } }); } @@ -2841,19 +2840,19 @@ public void call(Integer integer) { @Test(timeout = 5000) public void timeoutEmitError() { Throwable e = Completable.never().timeout(100, TimeUnit.MILLISECONDS).get(); - + Assert.assertTrue(e instanceof TimeoutException); } - + @Test(timeout = 5000) public void timeoutSwitchNormal() { Completable c = Completable.never().timeout(100, TimeUnit.MILLISECONDS, normal.completable); - + c.await(); - + normal.assertSubscriptions(1); } - + @Test(timeout = 5000) public void timeoutTimerCancelled() throws InterruptedException { Completable c = Completable.fromCallable(new Callable() { @@ -2863,14 +2862,14 @@ public Object call() throws Exception { return null; } }).timeout(100, TimeUnit.MILLISECONDS, normal.completable); - + c.await(); - + Thread.sleep(100); - + normal.assertSubscriptions(0); } - + @Test(expected = NullPointerException.class) public void timeoutUnitNull() { normal.completable.timeout(1, null); @@ -2880,12 +2879,12 @@ public void timeoutUnitNull() { public void timeoutSchedulerNull() { normal.completable.timeout(1, TimeUnit.SECONDS, (Scheduler)null); } - + @Test(expected = NullPointerException.class) public void timeoutOtherNull() { normal.completable.timeout(1, TimeUnit.SECONDS, (Completable)null); } - + @Test(timeout = 5000) public void toNormal() { Observable flow = normal.completable.to(new Func1>() { @@ -2894,18 +2893,18 @@ public Observable call(Completable c) { return c.toObservable(); } }); - - flow.toBlocking().forEach(new Action1(){ - @Override - public void call(Object e){ } + + flow.toBlocking().forEach(new Action1() { + @Override + public void call(Object e) { } }); } - + @Test(expected = NullPointerException.class) public void toNull() { normal.completable.to(null); } - + @Test(timeout = 5000) public void toObservableNormal() { normal.completable.toObservable().toBlocking().forEach(new Action1() { @@ -2913,7 +2912,7 @@ public void toObservableNormal() { public void call(Object e) { } }); } - + @Test(timeout = 5000, expected = TestException.class) public void toObservableError() { error.completable.toObservable().toBlocking().forEach(new Action1() { @@ -2924,10 +2923,10 @@ public void call(Object e) { } static T get(Single single) { final CountDownLatch cdl = new CountDownLatch(1); - + final AtomicReference v = new AtomicReference(); final AtomicReference e = new AtomicReference(); - + single.subscribe(new SingleSubscriber() { @Override @@ -2942,19 +2941,19 @@ public void onError(Throwable error) { cdl.countDown(); } }); - + try { cdl.await(); } catch (InterruptedException ex) { Exceptions.propagate(ex); } - + if (e.get() != null) { Exceptions.propagate(e.get()); } return v.get(); } - + @Test(timeout = 5000) public void toSingleSupplierNormal() { int v = get(normal.completable.toSingle(new Func0() { @@ -2963,7 +2962,7 @@ public Integer call() { return 1; } })); - + Assert.assertEquals(1, v); } @@ -2981,7 +2980,7 @@ public Object call() { public void toSingleSupplierNull() { normal.completable.toSingle(null); } - + @Test(expected = NullPointerException.class) public void toSingleSupplierReturnsNull() { get(normal.completable.toSingle(new Func0() { @@ -3004,22 +3003,22 @@ public void toSingleSupplierThrows() { public void toSingleDefaultError() { get(error.completable.toSingleDefault(1)); } - + @Test(timeout = 5000) public void toSingleDefaultNormal() { Assert.assertEquals((Integer)1, get(normal.completable.toSingleDefault(1))); } - + @Test(expected = NullPointerException.class) public void toSingleDefaultNull() { normal.completable.toSingleDefault(null); } - + @Test(timeout = 5000) public void unsubscribeOnNormal() throws InterruptedException { final AtomicReference name = new AtomicReference(); final CountDownLatch cdl = new CountDownLatch(1); - + normal.completable.delay(1, TimeUnit.SECONDS) .doOnUnsubscribe(new Action0() { @Override @@ -3033,7 +3032,7 @@ public void call() { @Override public void onSubscribe(final Subscription d) { final Scheduler.Worker w = Schedulers.io().createWorker(); - + w.schedule(new Action0() { @Override public void call() { @@ -3045,23 +3044,23 @@ public void call() { } }, 100, TimeUnit.MILLISECONDS); } - + @Override public void onError(Throwable e) { - + } - + @Override public void onCompleted() { - + } }); - + cdl.await(); - + Assert.assertTrue(name.get().startsWith("RxComputation")); } - + @Test(expected = NullPointerException.class) public void ambArrayNull() { Completable.amb((Completable[])null); @@ -3070,52 +3069,52 @@ public void ambArrayNull() { @Test(timeout = 5000) public void ambArrayEmpty() { Completable c = Completable.amb(); - + c.await(); } @Test(timeout = 5000) public void ambArraySingleNormal() { Completable c = Completable.amb(normal.completable); - + c.await(); } @Test(timeout = 5000, expected = TestException.class) public void ambArraySingleError() { Completable c = Completable.amb(error.completable); - + c.await(); } - + @Test(timeout = 5000) public void ambArrayOneFires() { PublishSubject ps1 = PublishSubject.create(); PublishSubject ps2 = PublishSubject.create(); - + Completable c1 = Completable.fromObservable(ps1); Completable c2 = Completable.fromObservable(ps2); - + Completable c = Completable.amb(c1, c2); - + final AtomicBoolean complete = new AtomicBoolean(); - + c.subscribe(new Action0() { @Override public void call() { complete.set(true); } }); - + Assert.assertTrue("First subject no subscribers", ps1.hasObservers()); Assert.assertTrue("Second subject no subscribers", ps2.hasObservers()); - + ps1.onCompleted(); - + Assert.assertFalse("First subject has subscribers", ps1.hasObservers()); Assert.assertFalse("Second subject has subscribers", ps2.hasObservers()); - + Assert.assertTrue("Not completed", complete.get()); } @@ -3123,64 +3122,64 @@ public void call() { public void ambArrayOneFiresError() { PublishSubject ps1 = PublishSubject.create(); PublishSubject ps2 = PublishSubject.create(); - + Completable c1 = Completable.fromObservable(ps1); Completable c2 = Completable.fromObservable(ps2); - + Completable c = Completable.amb(c1, c2); - + final AtomicReference complete = new AtomicReference(); - - c.subscribe(new Action1() { + + c.subscribe(new Action0() { + @Override + public void call() { } + }, new Action1() { @Override public void call(Throwable e) { complete.set(e); } - }, new Action0() { - @Override - public void call() { } }); - + Assert.assertTrue("First subject no subscribers", ps1.hasObservers()); Assert.assertTrue("Second subject no subscribers", ps2.hasObservers()); - + ps1.onError(new TestException()); - + Assert.assertFalse("First subject has subscribers", ps1.hasObservers()); Assert.assertFalse("Second subject has subscribers", ps2.hasObservers()); - + Assert.assertTrue("Not completed", complete.get() instanceof TestException); } - + @Test(timeout = 5000) public void ambArraySecondFires() { PublishSubject ps1 = PublishSubject.create(); PublishSubject ps2 = PublishSubject.create(); - + Completable c1 = Completable.fromObservable(ps1); Completable c2 = Completable.fromObservable(ps2); - + Completable c = Completable.amb(c1, c2); - + final AtomicBoolean complete = new AtomicBoolean(); - + c.subscribe(new Action0() { @Override public void call() { complete.set(true); } }); - + Assert.assertTrue("First subject no subscribers", ps1.hasObservers()); Assert.assertTrue("Second subject no subscribers", ps2.hasObservers()); - + ps2.onCompleted(); - + Assert.assertFalse("First subject has subscribers", ps1.hasObservers()); Assert.assertFalse("Second subject has subscribers", ps2.hasObservers()); - + Assert.assertTrue("Not completed", complete.get()); } @@ -3188,55 +3187,55 @@ public void call() { public void ambArraySecondFiresError() { PublishSubject ps1 = PublishSubject.create(); PublishSubject ps2 = PublishSubject.create(); - + Completable c1 = Completable.fromObservable(ps1); Completable c2 = Completable.fromObservable(ps2); - + Completable c = Completable.amb(c1, c2); - + final AtomicReference complete = new AtomicReference(); - - c.subscribe(new Action1() { + + c.subscribe(new Action0() { + @Override + public void call() { } + }, new Action1() { @Override public void call(Throwable e) { complete.set(e); } - }, new Action0() { - @Override - public void call() { } }); - + Assert.assertTrue("First subject no subscribers", ps1.hasObservers()); Assert.assertTrue("Second subject no subscribers", ps2.hasObservers()); - + ps2.onError(new TestException()); - + Assert.assertFalse("First subject has subscribers", ps1.hasObservers()); Assert.assertFalse("Second subject has subscribers", ps2.hasObservers()); - + Assert.assertTrue("Not completed", complete.get() instanceof TestException); } - + @Test(timeout = 5000, expected = NullPointerException.class) public void ambMultipleOneIsNull() { Completable c = Completable.amb(null, normal.completable); - + c.await(); } - + @Test(timeout = 5000) public void ambIterableEmpty() { Completable c = Completable.amb(Collections.emptyList()); - + c.await(); } - + @Test(expected = NullPointerException.class) public void ambIterableNull() { Completable.amb((Iterable)null); } - + @Test(timeout = 5000, expected = NullPointerException.class) public void ambIterableIteratorNull() { Completable c = Completable.amb(new Iterable() { @@ -3245,49 +3244,49 @@ public Iterator iterator() { return null; } }); - + c.await(); } - + @Test(timeout = 5000, expected = NullPointerException.class) public void ambIterableWithNull() { Completable c = Completable.amb(Arrays.asList(null, normal.completable)); - + c.await(); } - + @Test(timeout = 5000) public void ambIterableSingle() { Completable c = Completable.amb(Collections.singleton(normal.completable)); - + c.await(); - + normal.assertSubscriptions(1); } - + @Test(timeout = 5000) public void ambIterableMany() { Completable c = Completable.amb(Arrays.asList(normal.completable, normal.completable, normal.completable)); - + c.await(); - + normal.assertSubscriptions(1); } - + @Test(timeout = 5000, expected = TestException.class) public void ambIterableOneThrows() { Completable c = Completable.amb(Collections.singleton(error.completable)); - + c.await(); } - + @Test(timeout = 5000, expected = TestException.class) public void ambIterableManyOneThrows() { Completable c = Completable.amb(Arrays.asList(error.completable, normal.completable)); - + c.await(); } - + @Test(expected = TestException.class) public void ambIterableIterableThrows() { Completable c = Completable.amb(new Iterable() { @@ -3296,57 +3295,57 @@ public Iterator iterator() { throw new TestException(); } }); - + c.await(); } - + @Test(expected = TestException.class) public void ambIterableIteratorHasNextThrows() { Completable c = Completable.amb(new IterableIteratorHasNextThrows()); - + c.await(); } - + @Test(expected = TestException.class) public void ambIterableIteratorNextThrows() { Completable c = Completable.amb(new IterableIteratorNextThrows()); - + c.await(); } - + @Test(expected = NullPointerException.class) public void ambWithNull() { normal.completable.ambWith(null); } - + @Test(timeout = 5000) public void ambWithArrayOneFires() { PublishSubject ps1 = PublishSubject.create(); PublishSubject ps2 = PublishSubject.create(); - + Completable c1 = Completable.fromObservable(ps1); Completable c2 = Completable.fromObservable(ps2); - + Completable c = c1.ambWith(c2); - + final AtomicBoolean complete = new AtomicBoolean(); - + c.subscribe(new Action0() { @Override public void call() { complete.set(true); } }); - + Assert.assertTrue("First subject no subscribers", ps1.hasObservers()); Assert.assertTrue("Second subject no subscribers", ps2.hasObservers()); - + ps1.onCompleted(); - + Assert.assertFalse("First subject has subscribers", ps1.hasObservers()); Assert.assertFalse("Second subject has subscribers", ps2.hasObservers()); - + Assert.assertTrue("Not completed", complete.get()); } @@ -3354,64 +3353,64 @@ public void call() { public void ambWithArrayOneFiresError() { PublishSubject ps1 = PublishSubject.create(); PublishSubject ps2 = PublishSubject.create(); - + Completable c1 = Completable.fromObservable(ps1); Completable c2 = Completable.fromObservable(ps2); - + Completable c = c1.ambWith(c2); - + final AtomicReference complete = new AtomicReference(); - - c.subscribe(new Action1() { + + c.subscribe(new Action0() { + @Override + public void call() { } + }, new Action1() { @Override public void call(Throwable e) { complete.set(e); } - }, new Action0() { - @Override - public void call() { } }); - + Assert.assertTrue("First subject no subscribers", ps1.hasObservers()); Assert.assertTrue("Second subject no subscribers", ps2.hasObservers()); - + ps1.onError(new TestException()); - + Assert.assertFalse("First subject has subscribers", ps1.hasObservers()); Assert.assertFalse("Second subject has subscribers", ps2.hasObservers()); - + Assert.assertTrue("Not completed", complete.get() instanceof TestException); } - + @Test(timeout = 5000) public void ambWithArraySecondFires() { PublishSubject ps1 = PublishSubject.create(); PublishSubject ps2 = PublishSubject.create(); - + Completable c1 = Completable.fromObservable(ps1); Completable c2 = Completable.fromObservable(ps2); - + Completable c = c1.ambWith(c2); - + final AtomicBoolean complete = new AtomicBoolean(); - + c.subscribe(new Action0() { @Override public void call() { complete.set(true); } }); - + Assert.assertTrue("First subject no subscribers", ps1.hasObservers()); Assert.assertTrue("Second subject no subscribers", ps2.hasObservers()); - + ps2.onCompleted(); - + Assert.assertFalse("First subject has subscribers", ps1.hasObservers()); Assert.assertFalse("Second subject has subscribers", ps2.hasObservers()); - + Assert.assertTrue("Not completed", complete.get()); } @@ -3419,36 +3418,36 @@ public void call() { public void ambWithArraySecondFiresError() { PublishSubject ps1 = PublishSubject.create(); PublishSubject ps2 = PublishSubject.create(); - + Completable c1 = Completable.fromObservable(ps1); Completable c2 = Completable.fromObservable(ps2); - + Completable c = c1.ambWith(c2); - + final AtomicReference complete = new AtomicReference(); - - c.subscribe(new Action1() { + + c.subscribe(new Action0() { + @Override + public void call() { } + }, new Action1() { @Override public void call(Throwable e) { complete.set(e); } - }, new Action0() { - @Override - public void call() { } }); - + Assert.assertTrue("First subject no subscribers", ps1.hasObservers()); Assert.assertTrue("Second subject no subscribers", ps2.hasObservers()); - + ps2.onError(new TestException()); - + Assert.assertFalse("First subject has subscribers", ps1.hasObservers()); Assert.assertFalse("Second subject has subscribers", ps2.hasObservers()); - + Assert.assertTrue("Not completed", complete.get() instanceof TestException); } - + @Test(timeout = 5000) public void startWithCompletableNormal() { final AtomicBoolean run = new AtomicBoolean(); @@ -3460,17 +3459,17 @@ public Object call() throws Exception { return null; } })); - + c.await(); - + Assert.assertTrue("Did not start with other", run.get()); normal.assertSubscriptions(1); } - + @Test(timeout = 5000) public void startWithCompletableError() { Completable c = normal.completable.startWith(error.completable); - + try { c.await(); Assert.fail("Did not throw TestException"); @@ -3479,7 +3478,7 @@ public void startWithCompletableError() { error.assertSubscriptions(1); } } - + @Test(timeout = 5000) public void startWithFlowableNormal() { final AtomicBoolean run = new AtomicBoolean(); @@ -3491,35 +3490,35 @@ public Object call() throws Exception { return 1; } })); - + TestSubscriber ts = new TestSubscriber(); - + c.subscribe(ts); - + Assert.assertTrue("Did not start with other", run.get()); normal.assertSubscriptions(1); - + ts.assertValue(1); ts.assertCompleted(); ts.assertNoErrors(); } - + @Test(timeout = 5000) public void startWithFlowableError() { Observable c = normal.completable .startWith(Observable.error(new TestException())); - + TestSubscriber ts = new TestSubscriber(); - + c.subscribe(ts); - + normal.assertSubscriptions(0); - + ts.assertNoValues(); ts.assertError(TestException.class); ts.assertNotCompleted(); } - + @Test(expected = NullPointerException.class) public void startWithCompletableNull() { normal.completable.startWith((Completable)null); @@ -3551,17 +3550,17 @@ public Object call() throws Exception { return null; } })); - + c.await(); - + Assert.assertFalse("Start with other", run.get()); normal.assertSubscriptions(1); } - + @Test(timeout = 5000) public void andThenCompletableError() { Completable c = normal.completable.andThen(error.completable); - + try { c.await(); Assert.fail("Did not throw TestException"); @@ -3570,7 +3569,7 @@ public void andThenCompletableError() { error.assertSubscriptions(1); } } - + @Test(timeout = 5000) public void andThenFlowableNormal() { final AtomicBoolean run = new AtomicBoolean(); @@ -3582,42 +3581,42 @@ public Object call() throws Exception { return 1; } })); - + TestSubscriber ts = new TestSubscriber(); - + c.subscribe(ts); - + Assert.assertFalse("Start with other", run.get()); normal.assertSubscriptions(1); - + ts.assertValue(1); ts.assertCompleted(); ts.assertNoErrors(); } - + @Test(timeout = 5000) public void andThenFlowableError() { Observable c = normal.completable .andThen(Observable.error(new TestException())); - + TestSubscriber ts = new TestSubscriber(); - + c.subscribe(ts); - + normal.assertSubscriptions(1); - + ts.assertNoValues(); ts.assertError(TestException.class); ts.assertNotCompleted(); } - + @Test public void usingFactoryThrows() { @SuppressWarnings("unchecked") Action1 onDispose = mock(Action1.class); - + TestSubscriber ts = TestSubscriber.create(); - + Completable.using(new Func0() { @Override public Integer call() { @@ -3630,9 +3629,9 @@ public Completable call(Integer t) { throw new TestException(); } }, onDispose).unsafeSubscribe(ts); - + verify(onDispose).call(1); - + ts.assertNoValues(); ts.assertNotCompleted(); ts.assertError(TestException.class); @@ -3646,9 +3645,9 @@ public void call(Integer t) { throw new TestException(); } }; - + TestSubscriber ts = TestSubscriber.create(); - + Completable.using(new Func0() { @Override public Integer call() { @@ -3661,17 +3660,17 @@ public Completable call(Integer t) { throw new TestException(); } }, onDispose).unsafeSubscribe(ts); - + ts.assertNoValues(); ts.assertNotCompleted(); ts.assertError(CompositeException.class); - + CompositeException ex = (CompositeException)ts.getOnErrorEvents().get(0); - + List listEx = ex.getExceptions(); - + assertEquals(2, listEx.size()); - + assertTrue(listEx.get(0).toString(), listEx.get(0) instanceof TestException); assertTrue(listEx.get(1).toString(), listEx.get(1) instanceof TestException); } @@ -3680,9 +3679,9 @@ public Completable call(Integer t) { public void usingFactoryReturnsNull() { @SuppressWarnings("unchecked") Action1 onDispose = mock(Action1.class); - + TestSubscriber ts = TestSubscriber.create(); - + Completable.using(new Func0() { @Override public Integer call() { @@ -3695,9 +3694,9 @@ public Completable call(Integer t) { return null; } }, onDispose).unsafeSubscribe(ts); - + verify(onDispose).call(1); - + ts.assertNoValues(); ts.assertNotCompleted(); ts.assertError(NullPointerException.class); @@ -3711,9 +3710,9 @@ public void call(Integer t) { throw new TestException(); } }; - + TestSubscriber ts = TestSubscriber.create(); - + Completable.using(new Func0() { @Override public Integer call() { @@ -3726,17 +3725,17 @@ public Completable call(Integer t) { return null; } }, onDispose).unsafeSubscribe(ts); - + ts.assertNoValues(); ts.assertNotCompleted(); ts.assertError(CompositeException.class); - + CompositeException ex = (CompositeException)ts.getOnErrorEvents().get(0); - + List listEx = ex.getExceptions(); - + assertEquals(2, listEx.size()); - + assertTrue(listEx.get(0).toString(), listEx.get(0) instanceof NullPointerException); assertTrue(listEx.get(1).toString(), listEx.get(1) instanceof TestException); } @@ -3745,11 +3744,11 @@ public Completable call(Integer t) { public void subscribeReportsUnsubscribed() { PublishSubject stringSubject = PublishSubject.create(); Completable completable = stringSubject.toCompletable(); - + Subscription completableSubscription = completable.subscribe(); - + stringSubject.onCompleted(); - + assertTrue("Not unsubscribed?", completableSubscription.isUnsubscribed()); } @@ -3757,11 +3756,11 @@ public void subscribeReportsUnsubscribed() { public void subscribeReportsUnsubscribedOnError() { PublishSubject stringSubject = PublishSubject.create(); Completable completable = stringSubject.toCompletable(); - + Subscription completableSubscription = completable.subscribe(); - + stringSubject.onError(new TestException()); - + assertTrue("Not unsubscribed?", completableSubscription.isUnsubscribed()); } @@ -3769,11 +3768,11 @@ public void subscribeReportsUnsubscribedOnError() { public void subscribeActionReportsUnsubscribed() { PublishSubject stringSubject = PublishSubject.create(); Completable completable = stringSubject.toCompletable(); - + Subscription completableSubscription = completable.subscribe(Actions.empty()); - + stringSubject.onCompleted(); - + assertTrue("Not unsubscribed?", completableSubscription.isUnsubscribed()); } @@ -3781,7 +3780,7 @@ public void subscribeActionReportsUnsubscribed() { public void subscribeActionReportsUnsubscribedAfter() { PublishSubject stringSubject = PublishSubject.create(); Completable completable = stringSubject.toCompletable(); - + final AtomicReference subscriptionRef = new AtomicReference(); Subscription completableSubscription = completable.subscribe(new Action0() { @Override @@ -3792,9 +3791,9 @@ public void call() { } }); subscriptionRef.set(completableSubscription); - + stringSubject.onCompleted(); - + assertTrue("Not unsubscribed?", completableSubscription.isUnsubscribed()); assertNotNull("Unsubscribed before the call to onCompleted", subscriptionRef.get()); } @@ -3803,11 +3802,11 @@ public void call() { public void subscribeActionReportsUnsubscribedOnError() { PublishSubject stringSubject = PublishSubject.create(); Completable completable = stringSubject.toCompletable(); - + Subscription completableSubscription = completable.subscribe(Actions.empty()); - + stringSubject.onError(new TestException()); - + assertTrue("Not unsubscribed?", completableSubscription.isUnsubscribed()); } @@ -3815,11 +3814,11 @@ public void subscribeActionReportsUnsubscribedOnError() { public void subscribeAction2ReportsUnsubscribed() { PublishSubject stringSubject = PublishSubject.create(); Completable completable = stringSubject.toCompletable(); - + Subscription completableSubscription = completable.subscribe(Actions.empty(), Actions.empty()); - + stringSubject.onCompleted(); - + assertTrue("Not unsubscribed?", completableSubscription.isUnsubscribed()); } @@ -3827,11 +3826,11 @@ public void subscribeAction2ReportsUnsubscribed() { public void subscribeAction2ReportsUnsubscribedOnError() { PublishSubject stringSubject = PublishSubject.create(); Completable completable = stringSubject.toCompletable(); - + Subscription completableSubscription = completable.subscribe(Actions.empty(), Actions.empty()); - + stringSubject.onError(new TestException()); - + assertTrue("Not unsubscribed?", completableSubscription.isUnsubscribed()); } @@ -3839,20 +3838,20 @@ public void subscribeAction2ReportsUnsubscribedOnError() { public void subscribeAction2ReportsUnsubscribedAfter() { PublishSubject stringSubject = PublishSubject.create(); Completable completable = stringSubject.toCompletable(); - + final AtomicReference subscriptionRef = new AtomicReference(); - Subscription completableSubscription = completable.subscribe(Actions.empty(), new Action0() { + Subscription completableSubscription = completable.subscribe(new Action0() { @Override public void call() { if (subscriptionRef.get().isUnsubscribed()) { subscriptionRef.set(null); } } - }); + }, Actions.empty()); subscriptionRef.set(completableSubscription); - + stringSubject.onCompleted(); - + assertTrue("Not unsubscribed?", completableSubscription.isUnsubscribed()); assertNotNull("Unsubscribed before the call to onCompleted", subscriptionRef.get()); } @@ -3861,20 +3860,20 @@ public void call() { public void subscribeAction2ReportsUnsubscribedOnErrorAfter() { PublishSubject stringSubject = PublishSubject.create(); Completable completable = stringSubject.toCompletable(); - + final AtomicReference subscriptionRef = new AtomicReference(); - Subscription completableSubscription = completable.subscribe(new Action1() { + Subscription completableSubscription = completable.subscribe(Actions.empty(), new Action1() { @Override public void call(Throwable e) { if (subscriptionRef.get().isUnsubscribed()) { subscriptionRef.set(null); } } - }, Actions.empty()); + }); subscriptionRef.set(completableSubscription); - + stringSubject.onError(new TestException()); - + assertTrue("Not unsubscribed?", completableSubscription.isUnsubscribed()); assertNotNull("Unsubscribed before the call to onError", subscriptionRef.get()); } @@ -3888,8 +3887,12 @@ private static void expectUncaughtTestException(Action0 action) { assertEquals("Should have received exactly 1 exception", 1, handler.count); Throwable caught = handler.caught; while (caught != null) { - if (caught instanceof TestException) break; - if (caught == caught.getCause()) break; + if (caught instanceof TestException) { + break; + } + if (caught == caught.getCause()) { + break; + } caught = caught.getCause(); } assertTrue("A TestException should have been delivered to the handler", @@ -3903,28 +3906,28 @@ private static void expectUncaughtTestException(Action0 action) { public void safeOnCompleteThrows() { try { normal.completable.subscribe(new CompletableSubscriber() { - + @Override public void onCompleted() { throw new TestException("Forced failure"); } - + @Override public void onError(Throwable e) { - + } - + @Override public void onSubscribe(Subscription d) { - + } - + }); Assert.fail("Did not propagate exception!"); } catch (OnCompletedFailedException ex) { Throwable c = ex.getCause(); Assert.assertNotNull(c); - + Assert.assertEquals("Forced failure", c.getMessage()); } } @@ -3933,27 +3936,27 @@ public void onSubscribe(Subscription d) { public void safeOnCompleteThrowsRegularSubscriber() { try { normal.completable.subscribe(new Subscriber() { - + @Override public void onCompleted() { throw new TestException("Forced failure"); } - + @Override public void onError(Throwable e) { - + } - + @Override public void onNext(Object t) { - + } }); Assert.fail("Did not propagate exception!"); } catch (OnCompletedFailedException ex) { Throwable c = ex.getCause(); Assert.assertNotNull(c); - + Assert.assertEquals("Forced failure", c.getMessage()); } } @@ -3962,31 +3965,31 @@ public void onNext(Object t) { public void safeOnErrorThrows() { try { error.completable.subscribe(new CompletableSubscriber() { - + @Override public void onCompleted() { } - + @Override public void onError(Throwable e) { throw new TestException("Forced failure"); } - + @Override public void onSubscribe(Subscription d) { - + } - + }); Assert.fail("Did not propagate exception!"); } catch (OnErrorFailedException ex) { Throwable c = ex.getCause(); Assert.assertTrue("" + c, c instanceof CompositeException); - + CompositeException ce = (CompositeException)c; - + List list = ce.getExceptions(); - + Assert.assertEquals(2, list.size()); Assert.assertTrue("" + list.get(0), list.get(0) instanceof TestException); @@ -4001,31 +4004,31 @@ public void onSubscribe(Subscription d) { public void safeOnErrorThrowsRegularSubscriber() { try { error.completable.subscribe(new Subscriber() { - + @Override public void onCompleted() { } - + @Override public void onError(Throwable e) { throw new TestException("Forced failure"); } - + @Override public void onNext(Object t) { - + } }); Assert.fail("Did not propagate exception!"); } catch (OnErrorFailedException ex) { Throwable c = ex.getCause(); Assert.assertTrue("" + c, c instanceof CompositeException); - + CompositeException ce = (CompositeException)c; - + List list = ce.getExceptions(); - + Assert.assertEquals(2, list.size()); Assert.assertTrue("" + list.get(0), list.get(0) instanceof TestException); @@ -4036,31 +4039,31 @@ public void onNext(Object t) { } } - private Func1 onCreate; - - private Func2 onStart; + private Func1 onCreate; + + private Func2 onStart; @Before public void setUp() throws Exception { - onCreate = spy(new Func1() { + onCreate = spy(new Func1() { @Override - public CompletableOnSubscribe call(CompletableOnSubscribe t) { + public OnSubscribe call(OnSubscribe t) { return t; } }); - + RxJavaHooks.setOnCompletableCreate(onCreate); - - onStart = spy(new Func2() { + + onStart = spy(new Func2() { @Override - public CompletableOnSubscribe call(Completable t1, CompletableOnSubscribe t2) { + public OnSubscribe call(Completable t1, OnSubscribe t2) { return t2; } }); - + RxJavaHooks.setOnCompletableStart(onStart); } - + @After public void after() { RxJavaHooks.reset(); @@ -4068,7 +4071,7 @@ public void after() { @Test public void testHookCreate() { - CompletableOnSubscribe subscriber = mock(CompletableOnSubscribe.class); + OnSubscribe subscriber = mock(OnSubscribe.class); Completable.create(subscriber); verify(onCreate, times(1)).call(subscriber); @@ -4078,27 +4081,27 @@ public void testHookCreate() { public void testHookSubscribeStart() { TestSubscriber ts = new TestSubscriber(); - Completable completable = Completable.create(new CompletableOnSubscribe() { + Completable completable = Completable.create(new OnSubscribe() { @Override public void call(CompletableSubscriber s) { s.onCompleted(); } }); completable.subscribe(ts); - verify(onStart, times(1)).call(eq(completable), any(Completable.CompletableOnSubscribe.class)); + verify(onStart, times(1)).call(eq(completable), any(OnSubscribe.class)); } @Test public void testHookUnsafeSubscribeStart() { TestSubscriber ts = new TestSubscriber(); - Completable completable = Completable.create(new CompletableOnSubscribe() { + Completable completable = Completable.create(new OnSubscribe() { @Override public void call(CompletableSubscriber s) { s.onCompleted(); } }); completable.unsafeSubscribe(ts); - verify(onStart, times(1)).call(eq(completable), any(Completable.CompletableOnSubscribe.class)); + verify(onStart, times(1)).call(eq(completable), any(OnSubscribe.class)); } @Test @@ -4109,9 +4112,9 @@ public void onStart() { onNext(1); } }; - + normal.completable.subscribe(ts); - + ts.assertValue(1); ts.assertNoErrors(); ts.assertCompleted(); @@ -4125,9 +4128,9 @@ public void onStart() { onNext(1); } }; - + normal.completable.unsafeSubscribe(ts); - + ts.assertValue(1); ts.assertNoErrors(); ts.assertCompleted(); @@ -4136,7 +4139,7 @@ public void onStart() { @Test public void onErrorCompleteFunctionThrows() { TestSubscriber ts = new TestSubscriber(); - + error.completable.onErrorComplete(new Func1() { @Override public Boolean call(Throwable t) { @@ -4147,15 +4150,67 @@ public Boolean call(Throwable t) { ts.assertNoValues(); ts.assertNotCompleted(); ts.assertError(CompositeException.class); - + CompositeException composite = (CompositeException)ts.getOnErrorEvents().get(0); - + List errors = composite.getExceptions(); 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.assertTrue(errors.get(1).toString(), errors.get(1) instanceof TestException); Assert.assertEquals(errors.get(1).toString(), "Forced inner failure", errors.get(1).getMessage()); } -} \ No newline at end of file + + @Test public void toFunctionReceivesObservableReturnsResult() { + Completable c = Completable.error(new RuntimeException()); + + final Object expectedResult = new Object(); + final AtomicReference completableRef = new AtomicReference(); + Object actualResult = c.to(new Func1() { + @Override + public Object call(Completable completable) { + completableRef.set(completable); + return expectedResult; + } + }); + + assertSame(expectedResult, actualResult); + assertSame(c, completableRef.get()); + } + + @Test(expected = IllegalArgumentException.class) + public void doOnEachNullAction() { + Completable.complete().doOnEach(null); + } + + @Test + public void doOnEachCompleted() { + final AtomicInteger atomicInteger = new AtomicInteger(0); + Completable.complete().doOnEach(new Action1>() { + @Override + public void call(final Notification notification) { + if (notification.isOnCompleted()) { + atomicInteger.incrementAndGet(); + } + } + }).subscribe(); + + assertEquals(1, atomicInteger.get()); + } + + @Test + public void doOnEachError() { + final AtomicInteger atomicInteger = new AtomicInteger(0); + Completable.error(new RuntimeException("What?")).doOnEach(new Action1>() { + @Override + public void call(final Notification notification) { + if (notification.isOnError()) { + atomicInteger.incrementAndGet(); + } + } + }).subscribe(); + + assertEquals(1, atomicInteger.get()); + } +} diff --git a/src/test/java/rx/ConcatTests.java b/src/test/java/rx/ConcatTests.java index c231b59109..d00d812c52 100644 --- a/src/test/java/rx/ConcatTests.java +++ b/src/test/java/rx/ConcatTests.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -69,14 +69,14 @@ public void testConcatWithIterableOfObservable() { @SuppressWarnings("unchecked") Iterable> is = Arrays.asList(o1, o2, o3); - List values = Observable.concat(Observable.from(is)).toList().toBlocking().single(); + List values = Observable.concat(is).toList().toBlocking().single(); assertEquals("one", values.get(0)); assertEquals("two", values.get(1)); assertEquals("three", values.get(2)); assertEquals("four", values.get(3)); assertEquals("five", values.get(4)); - assertEquals("six", values.get(5)); + assertEquals("six", values.get(5)); } @Test @@ -85,14 +85,14 @@ public void testConcatCovariance() { Movie movie = new Movie(); Media media = new Media(); HorrorMovie horrorMovie2 = new HorrorMovie(); - + Observable o1 = Observable. just(horrorMovie1, movie); Observable o2 = Observable.just(media, horrorMovie2); Observable> os = Observable.just(o1, o2); List values = Observable.concat(os).toList().toBlocking().single(); - + assertEquals(horrorMovie1, values.get(0)); assertEquals(movie, values.get(1)); assertEquals(media, values.get(2)); @@ -107,7 +107,7 @@ public void testConcatCovariance2() { Media media1 = new Media(); Media media2 = new Media(); HorrorMovie horrorMovie2 = new HorrorMovie(); - + Observable o1 = Observable.just(horrorMovie1, movie, media1); Observable o2 = Observable.just(media2, horrorMovie2); @@ -129,7 +129,7 @@ public void testConcatCovariance3() { Movie movie = new Movie(); Media media = new Media(); HorrorMovie horrorMovie2 = new HorrorMovie(); - + Observable o1 = Observable.just(horrorMovie1, movie); Observable o2 = Observable.just(media, horrorMovie2); @@ -148,8 +148,8 @@ public void testConcatCovariance4() { final Movie movie = new Movie(); Media media = new Media(); HorrorMovie horrorMovie2 = new HorrorMovie(); - - Observable o1 = Observable.create(new OnSubscribe() { + + Observable o1 = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber o) { diff --git a/src/test/java/rx/CovarianceTest.java b/src/test/java/rx/CovarianceTest.java index cca05ae36c..3d19821967 100644 --- a/src/test/java/rx/CovarianceTest.java +++ b/src/test/java/rx/CovarianceTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -33,7 +33,7 @@ /** * Test super/extends of generics. - * + * * See https://github.com/Netflix/RxJava/pull/331 */ public class CovarianceTest { @@ -116,10 +116,10 @@ public void testCovarianceOfCompose() { public Observable call(Observable t1) { return Observable.just(new Movie()); } - + }); } - + @SuppressWarnings("unused") @Test public void testCovarianceOfCompose2() { @@ -167,7 +167,7 @@ public HorrorMovie call(HorrorMovie horrorMovie) { } }); } - + @Test public void testComposeWithDeltaLogic() { List list1 = Arrays.asList(new Movie(), new HorrorMovie(), new ActionMovie()); diff --git a/src/test/java/rx/ErrorHandlingTests.java b/src/test/java/rx/ErrorHandlingTests.java index 2da7aea120..3f52dad66b 100644 --- a/src/test/java/rx/ErrorHandlingTests.java +++ b/src/test/java/rx/ErrorHandlingTests.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. diff --git a/src/test/java/rx/EventStream.java b/src/test/java/rx/EventStream.java index dc8d1e03d7..91a6482a5c 100644 --- a/src/test/java/rx/EventStream.java +++ b/src/test/java/rx/EventStream.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -32,7 +32,7 @@ private EventStream() { throw new IllegalStateException("No instances!"); } public static Observable getEventStream(final String type, final int numInstances) { - return Observable.create(new OnSubscribe() { + return Observable.unsafeCreate(new OnSubscribe() { @Override public void call(final Subscriber subscriber) { diff --git a/src/test/java/rx/EventStreamTest.java b/src/test/java/rx/EventStreamTest.java new file mode 100644 index 0000000000..6599459bb7 --- /dev/null +++ b/src/test/java/rx/EventStreamTest.java @@ -0,0 +1,25 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * 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 rx; + +import org.junit.Test; + +public class EventStreamTest { + @Test + public void constructorShouldBePrivate() { + TestUtil.checkUtilityClass(EventStream.class); + } +} diff --git a/src/test/java/rx/GroupByTests.java b/src/test/java/rx/GroupByTests.java index a4527777ef..03a2ee75e9 100644 --- a/src/test/java/rx/GroupByTests.java +++ b/src/test/java/rx/GroupByTests.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -91,11 +91,11 @@ public void call(String v) { System.out.println("**** finished"); } - + @Test public void groupsCompleteAsSoonAsMainCompletes() { TestSubscriber ts = TestSubscriber.create(); - + Observable.range(0, 20) .groupBy(new Func1() { @Override @@ -109,7 +109,7 @@ public Observable call(GroupedObservable v) { return v; } }).subscribe(ts); - + ts.assertValues(0, 5, 10, 15, 1, 6, 11, 16, 2, 7, 12, 17, 3, 8, 13, 18, 4, 9, 14, 19); ts.assertCompleted(); ts.assertNoErrors(); diff --git a/src/test/java/rx/IntervalDemo.java b/src/test/java/rx/IntervalDemo.java index 31c4cd679b..a0fb028e10 100644 --- a/src/test/java/rx/IntervalDemo.java +++ b/src/test/java/rx/IntervalDemo.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -25,8 +25,7 @@ import rx.functions.Action0; import rx.functions.Action1; -@Ignore -// since this doesn't do any automatic testing +@Ignore("Since this doesn't do any automatic testing") public class IntervalDemo { @Test diff --git a/src/test/java/rx/MergeTests.java b/src/test/java/rx/MergeTests.java index 51f99caaa7..c42579cc37 100644 --- a/src/test/java/rx/MergeTests.java +++ b/src/test/java/rx/MergeTests.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -48,7 +48,7 @@ public void testMergeCovariance() { Observable> os = Observable.just(o1, o2); List values = Observable.merge(os).toList().toBlocking().single(); - + assertEquals(4, values.size()); } @@ -80,7 +80,7 @@ public void testMergeCovariance3() { @Test public void testMergeCovariance4() { - Observable o1 = Observable.create(new OnSubscribe() { + Observable o1 = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber o) { diff --git a/src/test/java/rx/NotificationTest.java b/src/test/java/rx/NotificationTest.java index bd6e224ca9..c25e28d136 100644 --- a/src/test/java/rx/NotificationTest.java +++ b/src/test/java/rx/NotificationTest.java @@ -18,6 +18,7 @@ import static org.mockito.Matchers.any; import static org.mockito.Mockito.*; +import static org.junit.Assert.*; import java.util.*; @@ -28,68 +29,69 @@ public class NotificationTest { @Test - public void testOnNextIntegerNotificationDoesNotEqualNullNotification(){ + public void testOnNextIntegerNotificationDoesNotEqualNullNotification() { final Notification integerNotification = Notification.createOnNext(1); final Notification nullNotification = Notification.createOnNext(null); - Assert.assertFalse(integerNotification.equals(nullNotification)); + assertFalse(integerNotification.equals(nullNotification)); } @Test - public void testOnNextNullNotificationDoesNotEqualIntegerNotification(){ + public void testOnNextNullNotificationDoesNotEqualIntegerNotification() { final Notification integerNotification = Notification.createOnNext(1); final Notification nullNotification = Notification.createOnNext(null); - Assert.assertFalse(nullNotification.equals(integerNotification)); + assertFalse(nullNotification.equals(integerNotification)); } @Test - public void testOnNextIntegerNotificationsWhenEqual(){ + public void testOnNextIntegerNotificationsWhenEqual() { final Notification integerNotification = Notification.createOnNext(1); final Notification integerNotification2 = Notification.createOnNext(1); - Assert.assertTrue(integerNotification.equals(integerNotification2)); + assertTrue(integerNotification.equals(integerNotification2)); } @Test - public void testOnNextIntegerNotificationsWhenNotEqual(){ + public void testOnNextIntegerNotificationsWhenNotEqual() { final Notification integerNotification = Notification.createOnNext(1); final Notification integerNotification2 = Notification.createOnNext(2); - Assert.assertFalse(integerNotification.equals(integerNotification2)); + assertFalse(integerNotification.equals(integerNotification2)); } @Test - public void testOnErrorIntegerNotificationDoesNotEqualNullNotification(){ + public void testOnErrorIntegerNotificationDoesNotEqualNullNotification() { final Notification integerNotification = Notification.createOnError(new Exception()); final Notification nullNotification = Notification.createOnError(null); - Assert.assertFalse(integerNotification.equals(nullNotification)); + assertFalse(integerNotification.equals(nullNotification)); } @Test - public void testOnErrorNullNotificationDoesNotEqualIntegerNotification(){ + public void testOnErrorNullNotificationDoesNotEqualIntegerNotification() { final Notification integerNotification = Notification.createOnError(new Exception()); final Notification nullNotification = Notification.createOnError(null); - Assert.assertFalse(nullNotification.equals(integerNotification)); + assertFalse(nullNotification.equals(integerNotification)); } @Test - public void testOnErrorIntegerNotificationsWhenEqual(){ + public void testOnErrorIntegerNotificationsWhenEqual() { final Exception exception = new Exception(); final Notification onErrorNotification = Notification.createOnError(exception); final Notification onErrorNotification2 = Notification.createOnError(exception); - Assert.assertTrue(onErrorNotification.equals(onErrorNotification2)); + assertTrue(onErrorNotification.equals(onErrorNotification2)); } @Test - public void testOnErrorIntegerNotificationWhenNotEqual(){ + public void testOnErrorIntegerNotificationWhenNotEqual() { final Notification onErrorNotification = Notification.createOnError(new Exception()); final Notification onErrorNotification2 = Notification.createOnError(new Exception()); - Assert.assertFalse(onErrorNotification.equals(onErrorNotification2)); + assertFalse(onErrorNotification.equals(onErrorNotification2)); } @Test public void createWithClass() { + @SuppressWarnings("deprecation") Notification n = Notification.createOnCompleted(Integer.class); - Assert.assertTrue(n.isOnCompleted()); - Assert.assertFalse(n.hasThrowable()); - Assert.assertFalse(n.hasValue()); + assertTrue(n.isOnCompleted()); + assertFalse(n.hasThrowable()); + assertFalse(n.hasValue()); } @Test @@ -121,9 +123,9 @@ static String stripAt(String s) { @Test public void toStringVariants() { - Assert.assertEquals("[rx.Notification OnNext 1]", stripAt(Notification.createOnNext(1).toString())); - Assert.assertEquals("[rx.Notification OnError Forced failure]", stripAt(Notification.createOnError(new TestException("Forced failure")).toString())); - Assert.assertEquals("[rx.Notification OnCompleted]", stripAt(Notification.createOnCompleted().toString())); + assertEquals("[rx.Notification OnNext 1]", stripAt(Notification.createOnNext(1).toString())); + assertEquals("[rx.Notification OnError Forced failure]", stripAt(Notification.createOnError(new TestException("Forced failure")).toString())); + assertEquals("[rx.Notification OnCompleted]", stripAt(Notification.createOnCompleted().toString())); } @Test @@ -134,7 +136,7 @@ public void hashCodeWorks() { Notification e1 = Notification.createOnError(new TestException()); Notification c1 = Notification.createOnCompleted(); - Assert.assertEquals(n1.hashCode(), n1a.hashCode()); + assertEquals(n1.hashCode(), n1a.hashCode()); Set> set = new HashSet>(); @@ -143,11 +145,11 @@ public void hashCodeWorks() { set.add(e1); set.add(c1); - Assert.assertTrue(set.contains(n1)); - Assert.assertTrue(set.contains(n1a)); - Assert.assertTrue(set.contains(n2)); - Assert.assertTrue(set.contains(e1)); - Assert.assertTrue(set.contains(c1)); + assertTrue(set.contains(n1)); + assertTrue(set.contains(n1a)); + assertTrue(set.contains(n2)); + assertTrue(set.contains(e1)); + assertTrue(set.contains(c1)); } @Test @@ -155,7 +157,7 @@ public void equalsWorks() { Notification z1 = Notification.createOnNext(null); Notification z1a = Notification.createOnNext(null); - + Notification n1 = Notification.createOnNext(1); Notification n1a = Notification.createOnNext(new Integer(1)); // make unique reference Notification n2 = Notification.createOnNext(2); @@ -164,29 +166,29 @@ public void equalsWorks() { Notification c1 = Notification.createOnCompleted(); Notification c2 = Notification.createOnCompleted(); - Assert.assertEquals(n1, n1a); - Assert.assertNotEquals(n1, n2); - Assert.assertNotEquals(n2, n1); + assertEquals(n1, n1a); + assertNotEquals(n1, n2); + assertNotEquals(n2, n1); + + assertNotEquals(n1, e1); + assertNotEquals(e1, n1); + assertNotEquals(e1, c1); + assertNotEquals(n1, c1); + assertNotEquals(c1, n1); + assertNotEquals(c1, e1); - Assert.assertNotEquals(n1, e1); - Assert.assertNotEquals(e1, n1); - Assert.assertNotEquals(e1, c1); - Assert.assertNotEquals(n1, c1); - Assert.assertNotEquals(c1, n1); - Assert.assertNotEquals(c1, e1); + assertEquals(e1, e1); + assertNotEquals(e1, e2); - Assert.assertEquals(e1, e1); - Assert.assertEquals(e1, e2); + assertEquals(c1, c2); - Assert.assertEquals(c1, c2); + assertFalse(n1.equals(null)); + assertFalse(n1.equals(1)); - Assert.assertFalse(n1.equals(null)); - Assert.assertFalse(n1.equals(1)); - - Assert.assertEquals(z1a, z1); - Assert.assertEquals(z1, z1a); + assertEquals(z1a, z1); + assertEquals(z1, z1a); } - + @Test public void contentChecks() { Notification z1 = Notification.createOnNext(null); @@ -195,25 +197,69 @@ public void contentChecks() { Notification e2 = Notification.createOnError(null); Notification c1 = Notification.createOnCompleted(); - Assert.assertFalse(z1.hasValue()); - Assert.assertFalse(z1.hasThrowable()); - Assert.assertFalse(z1.isOnCompleted()); - - Assert.assertTrue(n1.hasValue()); - Assert.assertFalse(n1.hasThrowable()); - Assert.assertFalse(n1.isOnCompleted()); - - Assert.assertFalse(e1.hasValue()); - Assert.assertTrue(e1.hasThrowable()); - Assert.assertFalse(e1.isOnCompleted()); - - Assert.assertFalse(e2.hasValue()); - Assert.assertFalse(e2.hasThrowable()); - Assert.assertFalse(e2.isOnCompleted()); - - Assert.assertFalse(c1.hasValue()); - Assert.assertFalse(c1.hasThrowable()); - Assert.assertTrue(c1.isOnCompleted()); + assertFalse(z1.hasValue()); + assertFalse(z1.hasThrowable()); + assertFalse(z1.isOnCompleted()); + + assertTrue(n1.hasValue()); + assertFalse(n1.hasThrowable()); + assertFalse(n1.isOnCompleted()); + + assertFalse(e1.hasValue()); + assertTrue(e1.hasThrowable()); + assertFalse(e1.isOnCompleted()); + + assertFalse(e2.hasValue()); + assertFalse(e2.hasThrowable()); + assertFalse(e2.isOnCompleted()); + + assertFalse(c1.hasValue()); + assertFalse(c1.hasThrowable()); + assertTrue(c1.isOnCompleted()); + + } + + @Test + public void exceptionEquality() { + EqualException ex1 = new EqualException("1"); + EqualException ex2 = new EqualException("1"); + EqualException ex3 = new EqualException("3"); + + Notification e1 = Notification.createOnError(ex1); + Notification e2 = Notification.createOnError(ex2); + Notification e3 = Notification.createOnError(ex3); + + assertEquals(e1, e1); + assertEquals(e1, e2); + assertEquals(e2, e1); + assertEquals(e2, e2); + + assertNotEquals(e1, e3); + assertNotEquals(e2, e3); + assertNotEquals(e3, e1); + assertNotEquals(e3, e2); + } + + static final class EqualException extends RuntimeException { + + /** */ + private static final long serialVersionUID = 446310455393317050L; + + public EqualException(String message) { + super(message); + } + @Override + public boolean equals(Object o) { + if (o instanceof EqualException) { + return getMessage().equals(((EqualException)o).getMessage()); + } + return false; + } + + @Override + public int hashCode() { + return getMessage().hashCode(); + } } } diff --git a/src/test/java/rx/ObservableConversionTest.java b/src/test/java/rx/ObservableConversionTest.java deleted file mode 100644 index 56f3ee2cd5..0000000000 --- a/src/test/java/rx/ObservableConversionTest.java +++ /dev/null @@ -1,250 +0,0 @@ -/** - * Copyright 2016 Netflix, Inc. - * - * 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 rx; - -import java.util.Arrays; -import java.util.List; -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicReference; - -import static org.junit.Assert.*; - -import org.junit.Test; - -import rx.Observable.OnSubscribe; -import rx.Observable.Operator; -import rx.exceptions.OnErrorNotImplementedException; -import rx.functions.Func1; -import rx.functions.Func2; -import rx.internal.operators.OperatorFilter; -import rx.internal.operators.OperatorMap; -import rx.observers.TestSubscriber; -import rx.schedulers.Schedulers; - -public class ObservableConversionTest { - - public static class Cylon {} - - public static class Jail { - Object cylon; - - Jail(Object cylon) { - this.cylon = cylon; - } - } - - public static class CylonDetectorObservable { - protected OnSubscribe onSubscribe; - - public static CylonDetectorObservable create(OnSubscribe onSubscribe) { - return new CylonDetectorObservable(onSubscribe); - } - - protected CylonDetectorObservable(OnSubscribe onSubscribe) { - this.onSubscribe = onSubscribe; - } - - public void subscribe(Subscriber subscriber) { - onSubscribe.call(subscriber); - } - - public CylonDetectorObservable lift(Operator operator) { - return x(new RobotConversionFunc(operator)); - } - - public O x(Func1, O> operator) { - return operator.call(onSubscribe); - } - - public CylonDetectorObservable compose(Func1, CylonDetectorObservable> transformer) { - return transformer.call(this); - } - - public final CylonDetectorObservable beep(Func1 predicate) { - return lift(new OperatorFilter(predicate)); - } - - public final CylonDetectorObservable boop(Func1 func) { - return lift(new OperatorMap(func)); - } - - public CylonDetectorObservable DESTROY() { - return boop(new Func1() { - @Override - public String call(T t) { - Object cylon = ((Jail) t).cylon; - throwOutTheAirlock(cylon); - if (t instanceof Jail) { - String name = cylon.toString(); - return "Cylon '" + name + "' has been destroyed"; - } - else { - return "Cylon 'anonymous' has been destroyed"; - } - }}); - } - - private static void throwOutTheAirlock(Object cylon) { - // ... - } - } - - public static class RobotConversionFunc implements Func1, CylonDetectorObservable> { - private Operator operator; - - public RobotConversionFunc(Operator operator) { - this.operator = operator; - } - - @Override - public CylonDetectorObservable call(final OnSubscribe onSubscribe) { - return CylonDetectorObservable.create(new OnSubscribe() { - @Override - public void call(Subscriber o) { - try { - Subscriber st = operator.call(o); - try { - st.onStart(); - onSubscribe.call(st); - } catch (OnErrorNotImplementedException e) { - throw e; - } catch (Throwable e) { - st.onError(e); - } - } catch (OnErrorNotImplementedException e) { - throw e; - } catch (Throwable e) { - o.onError(e); - } - - }}); - } - } - - public static class ConvertToCylonDetector implements Func1, CylonDetectorObservable> { - @Override - public CylonDetectorObservable call(final OnSubscribe onSubscribe) { - return CylonDetectorObservable.create(onSubscribe); - } - } - - public static class ConvertToObservable implements Func1, Observable> { - @Override - public Observable call(final OnSubscribe onSubscribe) { - return Observable.create(onSubscribe); - } - } - - @Test - public void testConversionBetweenObservableClasses() { - final TestSubscriber subscriber = new TestSubscriber(new Subscriber(){ - - @Override - public void onCompleted() { - System.out.println("Complete"); - } - - @Override - public void onError(Throwable e) { - System.out.println("error: " + e.getMessage()); - e.printStackTrace(); - } - - @Override - public void onNext(String t) { - System.out.println(t); - }}); - List crewOfBattlestarGalactica = Arrays.asList(new Object[] {"William Adama", "Laura Roslin", "Lee Adama", new Cylon()}); - Observable.from(crewOfBattlestarGalactica) - .extend(new ConvertToCylonDetector()) - .beep(new Func1(){ - @Override - public Boolean call(Object t) { - return t instanceof Cylon; - }}) - .boop(new Func1() { - @Override - public Jail call(Object cylon) { - return new Jail(cylon); - }}) - .DESTROY() - .x(new ConvertToObservable()) - .reduce("Cylon Detector finished. Report:\n", new Func2() { - @Override - public String call(String a, String n) { - return a + n + "\n"; - }}) - .subscribe(subscriber); - subscriber.assertNoErrors(); - subscriber.assertCompleted(); - } - - @Test - public void testConvertToConcurrentQueue() { - final AtomicReference thrown = new AtomicReference(null); - final AtomicBoolean isFinished = new AtomicBoolean(false); - ConcurrentLinkedQueue queue = Observable.range(0,5) - .flatMap(new Func1>(){ - @Override - public Observable call(final Integer i) { - return Observable.range(0, 5) - .observeOn(Schedulers.io()) - .map(new Func1(){ - @Override - public Integer call(Integer k) { - try { - Thread.sleep(System.currentTimeMillis() % 100); - } catch (InterruptedException e) { - e.printStackTrace(); - } - return i + k; - }}); - }}) - .extend(new Func1, ConcurrentLinkedQueue>() { - @Override - public ConcurrentLinkedQueue call(OnSubscribe onSubscribe) { - final ConcurrentLinkedQueue q = new ConcurrentLinkedQueue(); - onSubscribe.call(new Subscriber(){ - @Override - public void onCompleted() { - isFinished.set(true); - } - - @Override - public void onError(Throwable e) { - thrown.set(e); - } - - @Override - public void onNext(Integer t) { - q.add(t); - }}); - return q; - }}); - - int x = 0; - while(!isFinished.get()) { - Integer i = queue.poll(); - if (i != null) { - x++; - System.out.println(x + " item: " + i); - } - } - assertEquals(null, thrown.get()); - } -} diff --git a/src/test/java/rx/ObservableDoOnTest.java b/src/test/java/rx/ObservableDoOnTest.java index 0f22e6ecbd..911426bb53 100644 --- a/src/test/java/rx/ObservableDoOnTest.java +++ b/src/test/java/rx/ObservableDoOnTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -27,6 +27,7 @@ import rx.functions.Action0; import rx.functions.Action1; +import rx.observers.TestSubscriber; public class ObservableDoOnTest { @@ -66,6 +67,21 @@ public void call(Throwable v) { assertEquals(t, r.get()); } + @Test + public void testDoOnErrorWithActionOfTypeObject() { + final AtomicReference r = new AtomicReference(); + TestSubscriber ts = TestSubscriber.create(); + Observable. error(new RuntimeException("an error")) + .doOnError(new Action1() { + + @Override + public void call(Object v) { + r.set(true); + } + }).subscribe(ts); + assertTrue(r.get()); + } + @Test public void testDoOnCompleted() { final AtomicBoolean r = new AtomicBoolean(); diff --git a/src/test/java/rx/ObservableTests.java b/src/test/java/rx/ObservableTests.java index 3dd215564a..1ad7daaa72 100644 --- a/src/test/java/rx/ObservableTests.java +++ b/src/test/java/rx/ObservableTests.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -28,10 +28,11 @@ import org.mockito.*; import rx.Observable.*; -import rx.exceptions.OnErrorNotImplementedException; +import rx.exceptions.*; import rx.functions.*; import rx.observables.ConnectableObservable; -import rx.observers.TestSubscriber; +import rx.observers.*; +import rx.plugins.RxJavaHooks; import rx.schedulers.*; import rx.subjects.*; import rx.subscriptions.BooleanSubscription; @@ -93,7 +94,7 @@ public void fromArityArgs1() { @Test public void testCreate() { - Observable observable = Observable.create(new OnSubscribe() { + Observable observable = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber Observer) { @@ -139,7 +140,7 @@ public void testCountZeroItems() { @Test public void testCountError() { - Observable o = Observable.create(new OnSubscribe() { + Observable o = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber obsv) { obsv.onError(new RuntimeException()); @@ -151,6 +152,7 @@ public void call(Subscriber obsv) { verify(w, times(1)).onError(any(RuntimeException.class)); } + @Test public void testTakeFirstWithPredicateOfSome() { Observable observable = Observable.just(1, 3, 5, 4, 6, 3); observable.takeFirst(IS_EVEN).subscribe(w); @@ -248,7 +250,7 @@ public void call(Integer t1) { /** * A reduce on an empty Observable and a seed should just pass the seed through. - * + * * This is confirmed at https://github.com/ReactiveX/RxJava/issues/423#issuecomment-27642456 */ @Test @@ -287,7 +289,7 @@ public void testOnSubscribeFails() { @SuppressWarnings("unchecked") Observer observer = mock(Observer.class); final RuntimeException re = new RuntimeException("bad impl"); - Observable o = Observable.create(new OnSubscribe() { + Observable o = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber t1) { @@ -317,9 +319,9 @@ public void testMaterializeDematerializeChaining() { /** * The error from the user provided Observer is not handled by the subscribe method try/catch. - * + * * It is handled by the AtomicObserver that wraps the provided Observer. - * + * * Result: Passes (if AtomicObserver functionality exists) * @throws InterruptedException on interrupt */ @@ -328,7 +330,7 @@ public void testCustomObservableWithErrorInObserverAsynchronous() throws Interru final CountDownLatch latch = new CountDownLatch(1); final AtomicInteger count = new AtomicInteger(); final AtomicReference error = new AtomicReference(); - Observable.create(new OnSubscribe() { + Observable.unsafeCreate(new OnSubscribe() { @Override public void call(final Subscriber observer) { @@ -386,14 +388,14 @@ public void onNext(String v) { /** * The error from the user provided Observer is handled by the subscribe try/catch because this is synchronous - * + * * Result: Passes */ @Test public void testCustomObservableWithErrorInObserverSynchronous() { final AtomicInteger count = new AtomicInteger(); final AtomicReference error = new AtomicReference(); - Observable.create(new OnSubscribe() { + Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber observer) { @@ -435,15 +437,15 @@ public void onNext(String v) { /** * The error from the user provided Observable is handled by the subscribe try/catch because this is synchronous - * - * + * + * * Result: Passes */ @Test public void testCustomObservableWithErrorInObservableSynchronous() { final AtomicInteger count = new AtomicInteger(); final AtomicReference error = new AtomicReference(); - Observable.create(new OnSubscribe() { + Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber observer) { @@ -482,7 +484,7 @@ public void onNext(String v) { @Test public void testPublishLast() throws InterruptedException { final AtomicInteger count = new AtomicInteger(); - ConnectableObservable connectable = Observable.create(new OnSubscribe() { + ConnectableObservable connectable = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(final Subscriber observer) { count.incrementAndGet(); @@ -520,7 +522,7 @@ public void call(String value) { @Test public void testReplay() throws InterruptedException { final AtomicInteger counter = new AtomicInteger(); - ConnectableObservable o = Observable.create(new OnSubscribe() { + ConnectableObservable o = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(final Subscriber observer) { @@ -575,7 +577,7 @@ public void call(String v) { @Test public void testCache() throws InterruptedException { final AtomicInteger counter = new AtomicInteger(); - Observable o = Observable.create(new OnSubscribe() { + Observable o = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(final Subscriber observer) { @@ -623,7 +625,7 @@ public void call(String v) { @Test public void testCacheWithCapacity() throws InterruptedException { final AtomicInteger counter = new AtomicInteger(); - Observable o = Observable.create(new OnSubscribe() { + Observable o = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(final Subscriber observer) { @@ -670,9 +672,9 @@ public void call(String v) { /** * https://github.com/ReactiveX/RxJava/issues/198 - * + * * Rx Design Guidelines 5.2 - * + * * "when calling the Subscribe method that only has an onNext argument, the OnError behavior will be * to rethrow the exception on the thread that the message comes out from the Observable. * The OnCompleted behavior in this case is to do nothing." @@ -696,20 +698,20 @@ public void call(Object t1) { /** * https://github.com/ReactiveX/RxJava/issues/198 - * + * * Rx Design Guidelines 5.2 - * + * * "when calling the Subscribe method that only has an onNext argument, the OnError behavior will be * to rethrow the exception on the thread that the message comes out from the Observable. * The OnCompleted behavior in this case is to do nothing." - * + * * @throws InterruptedException */ @Test public void testErrorThrownWithoutErrorHandlerAsynchronous() throws InterruptedException { final CountDownLatch latch = new CountDownLatch(1); final AtomicReference exception = new AtomicReference(); - Observable.create(new OnSubscribe() { + Observable.unsafeCreate(new OnSubscribe() { @Override public void call(final Subscriber observer) { @@ -884,13 +886,13 @@ public void testIgnoreElements() { public void testJustWithScheduler() { TestScheduler scheduler = new TestScheduler(); Observable observable = Observable.from(Arrays.asList(1, 2)).subscribeOn(scheduler); - + @SuppressWarnings("unchecked") Observer observer = mock(Observer.class); observable.subscribe(observer); - + scheduler.advanceTimeBy(1, TimeUnit.MILLISECONDS); - + InOrder inOrder = inOrder(observer); inOrder.verify(observer, times(1)).onNext(1); inOrder.verify(observer, times(1)).onNext(2); @@ -938,76 +940,21 @@ public void testRangeWithScheduler() { inOrder.verifyNoMoreInteractions(); } - @Test - public void testCollectToList() { - Observable> o = Observable.just(1, 2, 3).collect(new Func0>() { - - @Override - public List call() { - return new ArrayList(); - } - - }, new Action2, Integer>() { - - @Override - public void call(List list, Integer v) { - list.add(v); - } - }); - - List list = o.toBlocking().last(); - - assertEquals(3, list.size()); - assertEquals(1, list.get(0).intValue()); - assertEquals(2, list.get(1).intValue()); - assertEquals(3, list.get(2).intValue()); - - // test multiple subscribe - List list2 = o.toBlocking().last(); - - assertEquals(3, list2.size()); - assertEquals(1, list2.get(0).intValue()); - assertEquals(2, list2.get(1).intValue()); - assertEquals(3, list2.get(2).intValue()); - } - @Test - public void testCollectToString() { - String value = Observable.just(1, 2, 3).collect(new Func0() { - - @Override - public StringBuilder call() { - return new StringBuilder(); - } - - }, new Action2() { - - @Override - public void call(StringBuilder sb, Integer v) { - if (sb.length() > 0) { - sb.append("-"); - } - sb.append(v); - } - }).toBlocking().last().toString(); - - assertEquals("1-2-3", value); - } - @Test public void testMergeWith() { TestSubscriber ts = new TestSubscriber(); Observable.just(1).mergeWith(Observable.just(2)).subscribe(ts); ts.assertReceivedOnNext(Arrays.asList(1, 2)); } - + @Test public void testConcatWith() { TestSubscriber ts = new TestSubscriber(); Observable.just(1).concatWith(Observable.just(2)).subscribe(ts); ts.assertReceivedOnNext(Arrays.asList(1, 2)); } - + @Test public void testAmbWith() { TestSubscriber ts = new TestSubscriber(); @@ -1050,7 +997,7 @@ public void call(List booleans) { } assertEquals(expectedCount, count.get()); } - + @Test public void testCompose() { TestSubscriber ts = new TestSubscriber(); @@ -1059,31 +1006,31 @@ public void testCompose() { @Override public Observable call(Observable t1) { return t1.map(new Func1() { - + @Override public String call(Integer t1) { return String.valueOf(t1); } - + }); } - + }).subscribe(ts); ts.assertTerminalEvent(); ts.assertNoErrors(); ts.assertReceivedOnNext(Arrays.asList("1", "2", "3")); } - + @Test public void testErrorThrownIssue1685() throws Exception { Subject subject = ReplaySubject.create(); ExecutorService exec = Executors.newSingleThreadExecutor(); - + try { - + final AtomicReference err = new AtomicReference(); - + Scheduler s = Schedulers.from(exec); exec.submit(new Runnable() { @Override @@ -1096,7 +1043,7 @@ public void uncaughtException(Thread t, Throwable e) { }); } }).get(); - + subject.subscribe(); Observable.error(new RuntimeException("oops")) @@ -1104,15 +1051,15 @@ public void uncaughtException(Thread t, Throwable e) { .delay(1, TimeUnit.SECONDS, s) .dematerialize() .subscribe(subject); - + subject.materialize().toBlocking().first(); for (int i = 0; i < 50 && err.get() == null; i++) { Thread.sleep(100); // the uncaught exception comes after the terminal event reaches toBlocking } - + assertNotNull("UncaughtExceptionHandler didn't get anything.", err.get()); - + System.out.println("Done"); } finally { exec.shutdownNow(); @@ -1123,16 +1070,16 @@ public void uncaughtException(Thread t, Throwable e) { public void testEmptyIdentity() { assertEquals(Observable.empty(), Observable.empty()); } - + @Test public void testEmptyIsEmpty() { Observable.empty().subscribe(w); - + verify(w).onCompleted(); verify(w, never()).onNext(any(Integer.class)); verify(w, never()).onError(any(Throwable.class)); } - + @Test // cf. https://github.com/ReactiveX/RxJava/issues/2599 public void testSubscribingSubscriberAsObserverMaintainsSubscriptionChain() { TestSubscriber subscriber = new TestSubscriber(); @@ -1142,7 +1089,7 @@ public void testSubscribingSubscriberAsObserverMaintainsSubscriptionChain() { subscriber.assertUnsubscribed(); } - @Test(expected=OnErrorNotImplementedException.class) + @Test(expected = OnErrorNotImplementedException.class) public void testForEachWithError() { Observable.error(new Exception("boo")) // @@ -1152,26 +1099,330 @@ public void call(Object t) { //do nothing }}); } - - @Test(expected=IllegalArgumentException.class) + + @Test(expected = IllegalArgumentException.class) public void testForEachWithNull() { Observable.error(new Exception("boo")) // .forEach(null); } - + @Test - public void testExtend() { - final TestSubscriber subscriber = new TestSubscriber(); - final Object value = new Object(); - Observable.just(value).extend(new Func1,Object>(){ + public void nullOnSubscribe() { + Observable source = Observable.unsafeCreate((OnSubscribe)null); + + try { + source.subscribe(); + fail("Should have thrown IllegalStateException"); + } catch (IllegalStateException ex) { + // expected + } + } + + @Test + public void nullObserver() { + Observable source = Observable.just(1); + + try { + source.subscribe((Observer)null); + fail("Should have thrown IllegalStateException"); + } catch (NullPointerException ex) { + // expected + } + } + + @Test + public void nullSubscriber() { + Observable source = Observable.just(1); + + try { + source.subscribe((Subscriber)null); + fail("Should have thrown IllegalStateException"); + } catch (IllegalArgumentException ex) { + // expected + } + } + + @SuppressWarnings("deprecation") + @Test + public void testCacheHint() throws InterruptedException { + final AtomicInteger counter = new AtomicInteger(); + Observable o = Observable.unsafeCreate(new OnSubscribe() { + @Override - public Object call(OnSubscribe onSubscribe) { - onSubscribe.call(subscriber); - subscriber.assertNoErrors(); - subscriber.assertCompleted(); - subscriber.assertValue(value); - return subscriber.getOnNextEvents().get(0); - }}); + public void call(final Subscriber observer) { + new Thread(new Runnable() { + + @Override + public void run() { + counter.incrementAndGet(); + observer.onNext("one"); + observer.onCompleted(); + } + }).start(); + } + }).cache(1); + + // we then expect the following 2 subscriptions to get that same value + final CountDownLatch latch = new CountDownLatch(2); + + // subscribe once + o.subscribe(new Action1() { + + @Override + public void call(String v) { + assertEquals("one", v); + latch.countDown(); + } + }); + + // subscribe again + o.subscribe(new Action1() { + + @Override + public void call(String v) { + assertEquals("one", v); + latch.countDown(); + } + }); + + assertTrue("subscriptions did not receive values", latch.await(1000, TimeUnit.MILLISECONDS)); + assertEquals(1, counter.get()); + } + + @Test + public void subscribeWithNull() { + Action1 onNext = Actions.empty(); + Action1 onError = Actions.empty(); + Action0 onCompleted = Actions.empty(); + try { + Observable.just(1).subscribe((Action1)null); + fail("Should have thrown IllegalArgumentException"); + } catch (IllegalArgumentException ex) { + assertEquals("onNext can not be null", ex.getMessage()); + } + + try { + Observable.just(1).subscribe(null, onError); + fail("Should have thrown IllegalArgumentException"); + } catch (IllegalArgumentException ex) { + assertEquals("onNext can not be null", ex.getMessage()); + } + + try { + Observable.just(1).subscribe(null, onError, onCompleted); + fail("Should have thrown IllegalArgumentException"); + } catch (IllegalArgumentException ex) { + assertEquals("onNext can not be null", ex.getMessage()); + } + + try { + Observable.just(1).subscribe(onNext, null); + fail("Should have thrown IllegalArgumentException"); + } catch (IllegalArgumentException ex) { + assertEquals("onError can not be null", ex.getMessage()); + } + + try { + Observable.just(1).subscribe(onNext, null, onCompleted); + fail("Should have thrown IllegalArgumentException"); + } catch (IllegalArgumentException ex) { + assertEquals("onError can not be null", ex.getMessage()); + } + + try { + Observable.just(1).subscribe(onNext, onError, null); + fail("Should have thrown IllegalArgumentException"); + } catch (IllegalArgumentException ex) { + assertEquals("onComplete can not be null", ex.getMessage()); + } + } + + @Test + public void forEachWithNull() { + Action1 onNext = Actions.empty(); + Action1 onError = Actions.empty(); + Action0 onCompleted = Actions.empty(); + try { + Observable.just(1).forEach((Action1)null); + fail("Should have thrown IllegalArgumentException"); + } catch (IllegalArgumentException ex) { + assertEquals("onNext can not be null", ex.getMessage()); + } + + try { + Observable.just(1).forEach(null, onError); + fail("Should have thrown IllegalArgumentException"); + } catch (IllegalArgumentException ex) { + assertEquals("onNext can not be null", ex.getMessage()); + } + + try { + Observable.just(1).forEach(null, onError, onCompleted); + fail("Should have thrown IllegalArgumentException"); + } catch (IllegalArgumentException ex) { + assertEquals("onNext can not be null", ex.getMessage()); + } + + try { + Observable.just(1).forEach(onNext, null); + fail("Should have thrown IllegalArgumentException"); + } catch (IllegalArgumentException ex) { + assertEquals("onError can not be null", ex.getMessage()); + } + + try { + Observable.just(1).forEach(onNext, null, onCompleted); + fail("Should have thrown IllegalArgumentException"); + } catch (IllegalArgumentException ex) { + assertEquals("onError can not be null", ex.getMessage()); + } + + try { + Observable.just(1).forEach(onNext, onError, null); + fail("Should have thrown IllegalArgumentException"); + } catch (IllegalArgumentException ex) { + assertEquals("onComplete can not be null", ex.getMessage()); + } + } + + @Test + public void observableThrowsWhileSubscriberIsUnsubscribed() { + TestSubscriber ts = TestSubscriber.create(); + ts.unsubscribe(); + + final List list = new ArrayList(); + + RxJavaHooks.setOnError(new Action1() { + @Override + public void call(Throwable t) { + list.add(t); + } + }); + + try { + new FailingObservable().subscribe(ts); + + assertEquals(1, list.size()); + + assertEquals("Forced failure", list.get(0).getMessage()); + } finally { + RxJavaHooks.reset(); + } + } + + @Test + public void observableThrowsWhileOnErrorFails() { + Subscriber ts = new SafeSubscriber(new TestSubscriber()) { + @Override + public void onError(Throwable e) { + throw new TestException("Forced failure"); + } + }; + + try { + new FailingObservable().subscribe(ts); + fail("Should have thrown OnErrorFailedException"); + } catch (OnErrorFailedException ex) { + // expected + assertTrue(ex.getCause().toString(), ex.getCause() instanceof TestException); + assertEquals("Forced failure", ex.getCause().getMessage()); + } + } + + @Test + public void observableThrowsWhileOnErrorFailsUnsafe() { + Subscriber ts = new TestSubscriber() { + @Override + public void onError(Throwable e) { + throw new TestException("Forced failure"); + } + }; + + try { + new FailingObservable().unsafeSubscribe(ts); + fail("Should have thrown OnErrorFailedException"); + } catch (OnErrorFailedException ex) { + // expected + assertTrue(ex.getCause().toString(), ex.getCause() instanceof TestException); + assertEquals("Forced failure", ex.getCause().getMessage()); + } + } + + static final class FailingObservable extends Observable { + + protected FailingObservable() { + super(new OnSubscribe() { + @Override + public void call(Subscriber t) { + throw new TestException("Forced failure"); + } + }); + } + + } + + @Test + public void forEachWithError() { + Action1 onError = Actions.empty(); + + final List list = new ArrayList(); + Observable.just(1).forEach(new Action1() { + @Override + public void call(Integer t) { + list.add(t); + } + }, onError); + + Observable.error(new TestException()).forEach(new Action1() { + @Override + public void call(Integer t) { + list.add(t); + } + }, new Action1() { + @Override + public void call(Throwable t) { + list.add(t); + } + }); + + Observable.empty().forEach(new Action1() { + @Override + public void call(Integer t) { + list.add(t); + } + }, new Action1() { + @Override + public void call(Throwable t) { + list.add(t); + } + }, new Action0() { + @Override + public void call() { + list.add(100); + } + }); + + assertEquals(3, list.size()); + assertEquals(1, list.get(0)); + assertTrue(list.get(1).toString(), list.get(1) instanceof TestException); + assertEquals(100, list.get(2)); + } + + @Test public void toFunctionReceivesObservableReturnsResult() { + Observable o = Observable.just("Hi"); + + final Object expectedResult = new Object(); + final AtomicReference> observableRef = new AtomicReference>(); + Object actualResult = o.to(new Func1, Object>() { + @Override + public Object call(Observable observable) { + observableRef.set(observable); + return expectedResult; + } + }); + + assertSame(expectedResult, actualResult); + assertSame(o, observableRef.get()); } } diff --git a/src/test/java/rx/ObservableWindowTests.java b/src/test/java/rx/ObservableWindowTests.java index 3833b934a9..a512186416 100644 --- a/src/test/java/rx/ObservableWindowTests.java +++ b/src/test/java/rx/ObservableWindowTests.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. diff --git a/src/test/java/rx/ReduceTests.java b/src/test/java/rx/ReduceTests.java index f7445e4aea..0e0e4735d0 100644 --- a/src/test/java/rx/ReduceTests.java +++ b/src/test/java/rx/ReduceTests.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -59,7 +59,7 @@ public Movie call(Movie t1, Movie t2) { /** * Reduce consumes and produces T so can't do covariance. - * + * * https://github.com/ReactiveX/RxJava/issues/360#issuecomment-24203016 */ @SuppressWarnings("unused") @@ -80,7 +80,7 @@ public Movie call(Movie t1, Movie t2) { /** * Reduce consumes and produces T so can't do covariance. - * + * * https://github.com/ReactiveX/RxJava/issues/360#issuecomment-24203016 */ @Test diff --git a/src/test/java/rx/ScanTests.java b/src/test/java/rx/ScanTests.java index 36cb429255..06e557b50d 100644 --- a/src/test/java/rx/ScanTests.java +++ b/src/test/java/rx/ScanTests.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. diff --git a/src/test/java/rx/SchedulerWorkerTest.java b/src/test/java/rx/SchedulerWorkerTest.java index 8bb1094b46..159d3d34a7 100644 --- a/src/test/java/rx/SchedulerWorkerTest.java +++ b/src/test/java/rx/SchedulerWorkerTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -23,10 +23,11 @@ import org.junit.Test; import rx.functions.Action0; +import rx.internal.schedulers.SchedulePeriodicHelper; import rx.schedulers.Schedulers; public class SchedulerWorkerTest { - + static final class CustomDriftScheduler extends Scheduler { public volatile long drift; @Override @@ -53,29 +54,29 @@ public Subscription schedule(Action0 action) { public Subscription schedule(Action0 action, long delayTime, TimeUnit unit) { return w.schedule(action, delayTime, unit); } - + @Override public long now() { return super.now() + drift; } }; } - + @Override public long now() { return super.now() + drift; } } - + @Test public void testCurrentTimeDriftBackwards() throws Exception { CustomDriftScheduler s = new CustomDriftScheduler(); - + Scheduler.Worker w = s.createWorker(); - + try { final List times = new ArrayList(); - + Subscription d = w.schedulePeriodically(new Action0() { @Override public void call() { @@ -84,17 +85,17 @@ public void call() { }, 100, 100, TimeUnit.MILLISECONDS); Thread.sleep(150); - - s.drift = -1000 - TimeUnit.NANOSECONDS.toMillis(Scheduler.CLOCK_DRIFT_TOLERANCE_NANOS); - + + s.drift = -1000 - TimeUnit.NANOSECONDS.toMillis(SchedulePeriodicHelper.CLOCK_DRIFT_TOLERANCE_NANOS); + Thread.sleep(400); - + d.unsubscribe(); - + Thread.sleep(150); - + System.out.println("Runs: " + times.size()); - + for (int i = 0; i < times.size() - 1 ; i++) { long diff = times.get(i + 1) - times.get(i); System.out.println("Diff #" + i + ": " + diff); @@ -102,22 +103,22 @@ public void call() { } assertTrue("Too few invocations: " + times.size(), times.size() > 2); - + } finally { w.unsubscribe(); } - + } - + @Test public void testCurrentTimeDriftForwards() throws Exception { CustomDriftScheduler s = new CustomDriftScheduler(); - + Scheduler.Worker w = s.createWorker(); - + try { final List times = new ArrayList(); - + Subscription d = w.schedulePeriodically(new Action0() { @Override public void call() { @@ -126,28 +127,28 @@ public void call() { }, 100, 100, TimeUnit.MILLISECONDS); Thread.sleep(150); - - s.drift = 1000 + TimeUnit.NANOSECONDS.toMillis(Scheduler.CLOCK_DRIFT_TOLERANCE_NANOS); - + + s.drift = 1000 + TimeUnit.NANOSECONDS.toMillis(SchedulePeriodicHelper.CLOCK_DRIFT_TOLERANCE_NANOS); + Thread.sleep(400); - + d.unsubscribe(); - + Thread.sleep(150); - + System.out.println("Runs: " + times.size()); - + assertTrue(times.size() > 2); - + for (int i = 0; i < times.size() - 1 ; i++) { long diff = times.get(i + 1) - times.get(i); System.out.println("Diff #" + i + ": " + diff); assertTrue("Diff out of range: " + diff, diff < 250 && diff > 50); } - + } finally { w.unsubscribe(); } - + } } diff --git a/src/test/java/rx/SingleTest.java b/src/test/java/rx/SingleTest.java index 7f45192044..9ae7fa0f90 100644 --- a/src/test/java/rx/SingleTest.java +++ b/src/test/java/rx/SingleTest.java @@ -39,9 +39,9 @@ public class SingleTest { @SuppressWarnings("rawtypes") private Func1 onCreate; - + @SuppressWarnings("rawtypes") - private Func2 onStart; + private Func2 onStart; private Func1 onReturn; @@ -54,28 +54,28 @@ public Single.OnSubscribe call(Single.OnSubscribe t) { return t; } }); - + RxJavaHooks.setOnSingleCreate(onCreate); - - onStart = spy(new Func2() { + + onStart = spy(new Func2() { @Override - public Observable.OnSubscribe call(Single t1, Observable.OnSubscribe t2) { + public Single.OnSubscribe call(Single t1, Single.OnSubscribe t2) { return t2; } }); - + RxJavaHooks.setOnSingleStart(onStart); - + onReturn = spy(new Func1() { @Override public Subscription call(Subscription t) { return t; } }); - + RxJavaHooks.setOnSingleReturn(onReturn); } - + @After public void after() { RxJavaHooks.reset(); @@ -445,7 +445,7 @@ public void testHookSubscribeStart() { }); single.subscribe(ts); - verify(onStart, times(1)).call(eq(single), any(Observable.OnSubscribe.class)); + verify(onStart, times(1)).call(eq(single), any(Single.OnSubscribe.class)); } @Test @@ -458,7 +458,7 @@ public void testHookUnsafeSubscribeStart() { }); single.unsafeSubscribe(ts); - verify(onStart, times(1)).call(eq(single), any(Observable.OnSubscribe.class)); + verify(onStart, times(1)).call(eq(single), any(Single.OnSubscribe.class)); } @Test @@ -503,6 +503,53 @@ public void testReturnUnsubscribedWhenHookThrowsError() { assertTrue(subscription.isUnsubscribed()); } + @Test + public void testCache() throws InterruptedException { + final AtomicInteger counter = new AtomicInteger(); + Single s = Single.create(new OnSubscribe() { + + @Override + public void call(final SingleSubscriber observer) { + new Thread(new Runnable() { + + @Override + public void run() { + counter.incrementAndGet(); + observer.onSuccess("one"); + } + }).start(); + } + }).cache(); + + // we then expect the following 2 subscriptions to get that same value + final CountDownLatch latch = new CountDownLatch(2); + + // subscribe once + s.subscribe(new Action1() { + + @Override + public void call(String v) { + assertEquals("one", v); + latch.countDown(); + } + }); + + // subscribe again + s.subscribe(new Action1() { + + @Override + public void call(String v) { + assertEquals("one", v); + latch.countDown(); + } + }); + + if (!latch.await(1000, TimeUnit.MILLISECONDS)) { + fail("subscriptions did not receive values"); + } + assertEquals(1, counter.get()); + } + @Test public void testCreateSuccess() { TestSubscriber ts = new TestSubscriber(); @@ -869,6 +916,11 @@ public void toCompletableError() { testSubscriber.assertNotCompleted(); } + @Test(expected = IllegalArgumentException.class) + public void doOnErrorNull() { + Single.just(1).doOnError(null); + } + @Test public void doOnErrorShouldNotCallActionIfNoErrorHasOccurred() { @SuppressWarnings("unchecked") @@ -974,6 +1026,11 @@ public void shouldPassErrorFromCallable() throws Exception { verify(callable).call(); } + @Test(expected = IllegalArgumentException.class) + public void doOnSuccessNull() { + Single.just(1).doOnSuccess(null); + } + @Test public void doOnSuccessShouldInvokeAction() { @SuppressWarnings("unchecked") @@ -1969,7 +2026,7 @@ public void subscribeWithNullObserver() { @Test public void unsubscribeComposesThrough() { PublishSubject ps = PublishSubject.create(); - + Subscription s = ps.toSingle() .flatMap(new Func1>() { @Override @@ -1978,16 +2035,16 @@ public Single call(Integer v) { } }) .subscribe(); - + s.unsubscribe(); - + assertFalse("Observers present?!", ps.hasObservers()); } @Test(timeout = 1000) public void unsubscribeComposesThroughAsync() { PublishSubject ps = PublishSubject.create(); - + Subscription s = ps.toSingle() .subscribeOn(Schedulers.io()) .flatMap(new Func1>() { @@ -1997,12 +2054,241 @@ public Single call(Integer v) { } }) .subscribe(); - - while (!ps.hasObservers() && !Thread.currentThread().isInterrupted()) ; - + + while (!ps.hasObservers() && !Thread.currentThread().isInterrupted()) { } + s.unsubscribe(); - + assertFalse("Observers present?!", ps.hasObservers()); } + @Test + public void flatMapCompletableComplete() { + final AtomicInteger atomicInteger = new AtomicInteger(); + TestSubscriber testSubscriber = TestSubscriber.create(); + + Single.just(1).flatMapCompletable(new Func1() { + @Override + public Completable call(final Integer integer) { + return Completable.fromAction(new Action0() { + @Override + public void call() { + atomicInteger.set(5); + } + }); + } + }).subscribe(testSubscriber); + + testSubscriber.assertCompleted(); + + assertEquals(5, atomicInteger.get()); + } + + @Test + public void flatMapCompletableError() { + final RuntimeException error = new RuntimeException("some error"); + TestSubscriber testSubscriber = TestSubscriber.create(); + + Single.just(1).flatMapCompletable(new Func1() { + @Override + public Completable call(final Integer integer) { + return Completable.error(error); + } + }).subscribe(testSubscriber); + + testSubscriber.assertError(error); + } + + @Test + public void flatMapCompletableNullCompletable() { + TestSubscriber testSubscriber = TestSubscriber.create(); + + Single.just(1).flatMapCompletable(new Func1() { + @Override + public Completable call(final Integer integer) { + return null; + } + }).subscribe(testSubscriber); + + testSubscriber.assertError(NullPointerException.class); + } + + @Test + public void flatMapCompletableException() { + TestSubscriber testSubscriber = TestSubscriber.create(); + + Single.just(1).flatMapCompletable(new Func1() { + @Override + public Completable call(final Integer integer) { + throw new UnsupportedOperationException(); + } + }).subscribe(testSubscriber); + + testSubscriber.assertError(UnsupportedOperationException.class); + } + + @Test public void toFunctionReceivesObservableReturnsResult() { + Single s = Single.just("Hi"); + + final Object expectedResult = new Object(); + final AtomicReference> singleRef = new AtomicReference>(); + Object actualResult = s.to(new Func1, Object>() { + @Override + public Object call(Single single) { + singleRef.set(single); + return expectedResult; + } + }); + + assertSame(expectedResult, actualResult); + assertSame(s, singleRef.get()); + } + + @Test(expected = IllegalArgumentException.class) + public void doOnEachNull() { + Single.just(1).doOnEach(null); + } + + @Test + public void doOnEachError() { + final AtomicInteger atomicInteger = new AtomicInteger(0); + Single.error(new RuntimeException()).doOnEach(new Action1>() { + @Override + public void call(final Notification notification) { + if (notification.isOnError()) { + atomicInteger.incrementAndGet(); + } + } + }).subscribe(Actions.empty(), new Action1() { + @Override + public void call(final Throwable throwable) { + // Do nothing this is expected. + } + }); + + assertEquals(1, atomicInteger.get()); + } + + @Test + public void doOnEachSuccess() { + final AtomicInteger atomicInteger = new AtomicInteger(0); + Single.just(1).doOnEach(new Action1>() { + @Override + public void call(final Notification notification) { + if (notification.isOnNext()) { + atomicInteger.getAndAdd(notification.getValue()); + } + } + }).subscribe(); + + assertEquals(1, atomicInteger.get()); + } + + @Test + public void isUnsubscribedAfterSuccess() { + PublishSubject ps = PublishSubject.create(); + + final int[] calls = { 0 }; + + Subscription s = ps.toSingle().subscribe(new Action1() { + @Override + public void call(Integer t) { + calls[0]++; + } + }); + + assertFalse(s.isUnsubscribed()); + + ps.onNext(1); + ps.onCompleted(); + + assertTrue(s.isUnsubscribed()); + + assertEquals(1, calls[0]); + } + + @Test + public void isUnsubscribedAfterError() { + PublishSubject ps = PublishSubject.create(); + + final int[] calls = { 0 }; + + Action1 a = Actions.empty(); + + Subscription s = ps.toSingle().subscribe(a, new Action1() { + @Override + public void call(Throwable t) { + calls[0]++; + } + }); + + assertFalse(s.isUnsubscribed()); + + ps.onError(new TestException()); + + assertTrue(s.isUnsubscribed()); + + assertEquals(1, calls[0]); + } + + @Test + public void unsubscribeOnSuccess() throws InterruptedException { + final AtomicReference name = new AtomicReference(); + + final CountDownLatch cdl = new CountDownLatch(1); + + TestSubscriber ts = TestSubscriber.create(); + + Single.fromCallable(new Callable() { + @Override + public Integer call() throws Exception { + return 1; + } + }) + .doOnUnsubscribe(new Action0() { + @Override + public void call() { + name.set(Thread.currentThread().getName()); + cdl.countDown(); + } + }) + .subscribeOn(Schedulers.io()) + .unsubscribeOn(Schedulers.computation()) + .subscribe(ts); + + cdl.await(); + + ts.awaitTerminalEvent(); + ts.assertReceivedOnNext(Arrays.asList(1)); + + assertTrue(name.get().startsWith("RxComputation")); + } + + @Test + public void unsubscribeOnError() throws InterruptedException { + final AtomicReference name = new AtomicReference(); + + final CountDownLatch cdl = new CountDownLatch(1); + + TestSubscriber ts = TestSubscriber.create(); + + Single.error(new RuntimeException()) + .doOnUnsubscribe(new Action0() { + @Override + public void call() { + name.set(Thread.currentThread().getName()); + cdl.countDown(); + } + }) + .subscribeOn(Schedulers.io()) + .unsubscribeOn(Schedulers.computation()) + .subscribe(ts); + + cdl.await(); + + ts.awaitTerminalEvent(); + ts.assertError(RuntimeException.class); + + assertTrue(name.get().startsWith("RxComputation")); + } } diff --git a/src/test/java/rx/StartWithTests.java b/src/test/java/rx/StartWithTests.java index 4153ea325b..bf628384d6 100644 --- a/src/test/java/rx/StartWithTests.java +++ b/src/test/java/rx/StartWithTests.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. diff --git a/src/test/java/rx/SubscriberTest.java b/src/test/java/rx/SubscriberTest.java index 95d489e2c6..a57f2b0229 100644 --- a/src/test/java/rx/SubscriberTest.java +++ b/src/test/java/rx/SubscriberTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -229,7 +229,7 @@ public void testRequestToObservable() { TestSubscriber ts = new TestSubscriber(); ts.request(3); final AtomicLong requested = new AtomicLong(); - Observable.create(new OnSubscribe() { + Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber s) { @@ -252,7 +252,7 @@ public void testRequestThroughMap() { TestSubscriber ts = new TestSubscriber(); ts.request(3); final AtomicLong requested = new AtomicLong(); - Observable.create(new OnSubscribe() { + Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber s) { @@ -282,7 +282,7 @@ public void testRequestThroughTakeThatReducesRequest() { TestSubscriber ts = new TestSubscriber(); ts.request(3); final AtomicLong requested = new AtomicLong(); - Observable.create(new OnSubscribe() { + Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber s) { @@ -305,7 +305,7 @@ public void testRequestThroughTakeWhereRequestIsSmallerThanTake() { TestSubscriber ts = new TestSubscriber(); ts.request(3); final AtomicLong requested = new AtomicLong(); - Observable.create(new OnSubscribe() { + Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber s) { @@ -423,7 +423,7 @@ public void onNext(Integer t) { assertEquals(1, c.get()); } - + @Test public void testNegativeRequestThrowsIllegalArgumentException() throws InterruptedException { final CountDownLatch latch = new CountDownLatch(1); @@ -434,10 +434,10 @@ public void testNegativeRequestThrowsIllegalArgumentException() throws Interrupt public void onStart() { request(1); } - + @Override public void onCompleted() { - + } @Override @@ -454,7 +454,7 @@ public void onNext(Integer t) { assertTrue(latch.await(10, TimeUnit.SECONDS)); assertTrue(exception.get() instanceof IllegalArgumentException); } - + @Test public void testOnStartRequestsAreAdditive() { final List list = new ArrayList(); @@ -464,15 +464,15 @@ public void onStart() { request(3); request(2); } - + @Override public void onCompleted() { - + } @Override public void onError(Throwable e) { - + } @Override @@ -481,7 +481,7 @@ public void onNext(Integer t) { }}); assertEquals(Arrays.asList(1,2,3,4,5), list); } - + @Test public void testOnStartRequestsAreAdditiveAndOverflowBecomesMaxValue() { final List list = new ArrayList(); @@ -489,17 +489,17 @@ public void testOnStartRequestsAreAdditiveAndOverflowBecomesMaxValue() { @Override public void onStart() { request(2); - request(Long.MAX_VALUE-1); + request(Long.MAX_VALUE - 1); } - + @Override public void onCompleted() { - + } @Override public void onError(Throwable e) { - + } @Override diff --git a/src/test/java/rx/TestUtil.java b/src/test/java/rx/TestUtil.java new file mode 100644 index 0000000000..15414b510e --- /dev/null +++ b/src/test/java/rx/TestUtil.java @@ -0,0 +1,116 @@ +/** + * Copyright 2014 Netflix, Inc. + * + * 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 rx; + +import java.util.*; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicInteger; + +import com.pushtorefresh.private_constructor_checker.PrivateConstructorChecker; + +import rx.Scheduler.Worker; +import rx.exceptions.Exceptions; +import rx.functions.Action0; +import rx.schedulers.Schedulers; + +/** + * Common test utility methods. + */ +public enum TestUtil { + ; + + /** + * Verifies that the given class has a private constructor that + * throws IllegalStateException("No instances!") upon instantiation. + * @param clazz the target class to check + */ + public static void checkUtilityClass(Class clazz) { + PrivateConstructorChecker + .forClass(clazz) + .expectedTypeOfException(IllegalStateException.class) + .expectedExceptionMessage("No instances!") + .check(); + } + + /** + * Runs two actions concurrently, one in the current thread and the other on + * the IO scheduler, synchronizing their execution as much as possible; rethrowing + * any exceptions they produce. + *

      This helper waits until both actions have run or times out in 5 seconds. + * @param r1 the first action + * @param r2 the second action + */ + public static void race(Action0 r1, final Action0 r2) { + final AtomicInteger counter = new AtomicInteger(2); + final Throwable[] errors = { null, null }; + final CountDownLatch cdl = new CountDownLatch(1); + + Worker w = Schedulers.io().createWorker(); + + try { + + w.schedule(new Action0() { + @Override + public void call() { + if (counter.decrementAndGet() != 0) { + while (counter.get() != 0) { } + } + + try { + r2.call(); + } catch (Throwable ex) { + errors[1] = ex; + } + + cdl.countDown(); + } + }); + + if (counter.decrementAndGet() != 0) { + while (counter.get() != 0) { } + } + + try { + r1.call(); + } catch (Throwable ex) { + errors[0] = ex; + } + + List errorList = new ArrayList(); + + try { + if (!cdl.await(5, TimeUnit.SECONDS)) { + errorList.add(new TimeoutException()); + } + } catch (InterruptedException ex) { + errorList.add(ex); + } + + if (errors[0] != null) { + errorList.add(errors[0]); + } + + if (errors[1] != null) { + errorList.add(errors[1]); + } + + Exceptions.throwIfAny(errorList); + } finally { + w.unsubscribe(); + } + } +} diff --git a/src/test/java/rx/ThrottleLastTests.java b/src/test/java/rx/ThrottleLastTests.java index 73cfe27485..3ce8d5600b 100644 --- a/src/test/java/rx/ThrottleLastTests.java +++ b/src/test/java/rx/ThrottleLastTests.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. diff --git a/src/test/java/rx/ThrottleWithTimeoutTests.java b/src/test/java/rx/ThrottleWithTimeoutTests.java index 0782b4ca6b..166d2a9b56 100644 --- a/src/test/java/rx/ThrottleWithTimeoutTests.java +++ b/src/test/java/rx/ThrottleWithTimeoutTests.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -23,6 +23,7 @@ import org.junit.Test; import org.mockito.InOrder; +import rx.observers.TestSubscriber; import rx.schedulers.TestScheduler; import rx.subjects.PublishSubject; @@ -59,4 +60,17 @@ public void testThrottle() { inOrder.verify(observer).onCompleted(); inOrder.verifyNoMoreInteractions(); } + + @Test + public void timed() { + TestSubscriber ts = TestSubscriber.create(); + + Observable.range(1, 2).throttleWithTimeout(1, TimeUnit.SECONDS).subscribe(ts); + + ts.awaitTerminalEventAndUnsubscribeOnTimeout(5, TimeUnit.SECONDS); + ts.assertValue(2); + ts.assertNoErrors(); + ts.assertCompleted(); + + } } diff --git a/src/test/java/rx/ZipTests.java b/src/test/java/rx/ZipTests.java index 5d35ea94bf..465319a565 100644 --- a/src/test/java/rx/ZipTests.java +++ b/src/test/java/rx/ZipTests.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -101,8 +101,8 @@ public void testCovarianceOfZip() { /** * Occasionally zip may be invoked with 0 observables. Test that we don't block indefinitely instead * of immediately invoking zip with 0 argument. - * - * We now expect an NoSuchElementException since last() requires at least one value and nothing will be emitted. + * + * We now expect a NoSuchElementException since last() requires at least one value and nothing will be emitted. */ @Test(expected = NoSuchElementException.class) public void nonBlockingObservable() { diff --git a/src/test/java/rx/exceptions/CompositeExceptionTest.java b/src/test/java/rx/exceptions/CompositeExceptionTest.java index 9cfc7fa6dd..e7f33251c5 100644 --- a/src/test/java/rx/exceptions/CompositeExceptionTest.java +++ b/src/test/java/rx/exceptions/CompositeExceptionTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -15,18 +15,12 @@ */ package rx.exceptions; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.*; -import java.io.ByteArrayOutputStream; -import java.io.PrintStream; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; +import java.io.*; +import java.util.*; -import org.junit.Test; +import org.junit.*; import rx.exceptions.CompositeException.CompositeExceptionCausalChain; @@ -58,7 +52,7 @@ public void testMultipleWithSameCause() { System.err.println("----------------------------- print composite stacktrace"); ce.printStackTrace(); assertEquals(3, ce.getExceptions().size()); - + assertNoCircularReferences(ce); assertNotNull(getRootCause(ce)); System.err.println("----------------------------- print cause stacktrace"); @@ -68,14 +62,14 @@ public void testMultipleWithSameCause() { @Test(timeout = 1000) public void testCompositeExceptionFromParentThenChild() { CompositeException cex = new CompositeException(Arrays.asList(ex1, ex2)); - + System.err.println("----------------------------- print composite stacktrace"); cex.printStackTrace(); assertEquals(2, cex.getExceptions().size()); - + assertNoCircularReferences(cex); assertNotNull(getRootCause(cex)); - + System.err.println("----------------------------- print cause stacktrace"); cex.getCause().printStackTrace(); } @@ -83,14 +77,14 @@ public void testCompositeExceptionFromParentThenChild() { @Test(timeout = 1000) public void testCompositeExceptionFromChildThenParent() { CompositeException cex = new CompositeException(Arrays.asList(ex2, ex1)); - + System.err.println("----------------------------- print composite stacktrace"); cex.printStackTrace(); assertEquals(2, cex.getExceptions().size()); - + assertNoCircularReferences(cex); assertNotNull(getRootCause(cex)); - + System.err.println("----------------------------- print cause stacktrace"); cex.getCause().printStackTrace(); } @@ -98,11 +92,11 @@ public void testCompositeExceptionFromChildThenParent() { @Test(timeout = 1000) public void testCompositeExceptionFromChildAndComposite() { CompositeException cex = new CompositeException(Arrays.asList(ex1, getNewCompositeExceptionWithEx123())); - + System.err.println("----------------------------- print composite stacktrace"); cex.printStackTrace(); assertEquals(3, cex.getExceptions().size()); - + assertNoCircularReferences(cex); assertNotNull(getRootCause(cex)); @@ -113,11 +107,11 @@ public void testCompositeExceptionFromChildAndComposite() { @Test(timeout = 1000) public void testCompositeExceptionFromCompositeAndChild() { CompositeException cex = new CompositeException(Arrays.asList(getNewCompositeExceptionWithEx123(), ex1)); - + System.err.println("----------------------------- print composite stacktrace"); cex.printStackTrace(); assertEquals(3, cex.getExceptions().size()); - + assertNoCircularReferences(cex); assertNotNull(getRootCause(cex)); @@ -131,11 +125,11 @@ public void testCompositeExceptionFromTwoDuplicateComposites() { exs.add(getNewCompositeExceptionWithEx123()); exs.add(getNewCompositeExceptionWithEx123()); CompositeException cex = new CompositeException(exs); - + System.err.println("----------------------------- print composite stacktrace"); cex.printStackTrace(); assertEquals(3, cex.getExceptions().size()); - + assertNoCircularReferences(cex); assertNotNull(getRootCause(cex)); @@ -159,7 +153,7 @@ private static Throwable getRootCause(Throwable ex) { if (root == null) { return null; } else { - while(true) { + while (true) { if (root.getCause() == null) { return root; } else { @@ -168,7 +162,7 @@ private static Throwable getRootCause(Throwable ex) { } } } - + @Test public void testNullCollection() { CompositeException composite = new CompositeException((List)null); @@ -257,7 +251,7 @@ public void complexCauses() { e5.initCause(e6); CompositeException compositeException = new CompositeException(e1, e3, e5); - assert(compositeException.getCause() instanceof CompositeExceptionCausalChain); + Assert.assertTrue(compositeException.getCause() instanceof CompositeExceptionCausalChain); List causeChain = new ArrayList(); Throwable cause = compositeException.getCause().getCause(); diff --git a/src/test/java/rx/exceptions/ExceptionsNullTest.java b/src/test/java/rx/exceptions/ExceptionsNullTest.java index e704d7cf7c..5a42eca52f 100644 --- a/src/test/java/rx/exceptions/ExceptionsNullTest.java +++ b/src/test/java/rx/exceptions/ExceptionsNullTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -25,65 +25,65 @@ public class ExceptionsNullTest { @Test public void testOnCompleteFailedExceptionNull() { Throwable t = new OnCompletedFailedException(null); - + Assert.assertTrue(t.getCause() instanceof NullPointerException); } - + @Test public void testOnCompleteFailedExceptionMessageAndNull() { Throwable t = new OnCompletedFailedException("Message", null); - + Assert.assertTrue(t.getCause() instanceof NullPointerException); } @Test public void testOnErrorFailedExceptionNull() { Throwable t = new OnErrorFailedException(null); - + Assert.assertTrue(t.getCause() instanceof NullPointerException); } - + @Test public void testOnErrorFailedExceptionMessageAndNull() { Throwable t = new OnErrorFailedException("Message", null); - + Assert.assertTrue(t.getCause() instanceof NullPointerException); } - + @Test public void testUnsubscribeFailedExceptionNull() { Throwable t = new UnsubscribeFailedException(null); - + Assert.assertTrue(t.getCause() instanceof NullPointerException); } - + @Test public void testUnsubscribeFailedExceptionMessageAndNull() { Throwable t = new UnsubscribeFailedException("Message", null); - + Assert.assertTrue(t.getCause() instanceof NullPointerException); } @Test public void testOnErrorNotImplementedExceptionNull() { Throwable t = new OnErrorNotImplementedException(null); - + Assert.assertTrue(t.getCause() instanceof NullPointerException); } - + @Test public void testOnErrorNotImplementedExceptionMessageAndNull() { Throwable t = new OnErrorNotImplementedException("Message", null); - + Assert.assertTrue(t.getCause() instanceof NullPointerException); } - + @Test public void testOnErrorThrowableFrom() { Throwable t = OnErrorThrowable.from(null); Assert.assertTrue(t.getCause() instanceof NullPointerException); } - + @Test public void testOnErrorThrowableAddValueAsLastCause() { Throwable t = OnErrorThrowable.addValueAsLastCause(null, "value"); diff --git a/src/test/java/rx/exceptions/ExceptionsTest.java b/src/test/java/rx/exceptions/ExceptionsTest.java index c325ab81fc..07bcb644a8 100644 --- a/src/test/java/rx/exceptions/ExceptionsTest.java +++ b/src/test/java/rx/exceptions/ExceptionsTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -15,24 +15,22 @@ */ package rx.exceptions; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import static org.junit.Assert.*; import java.util.concurrent.atomic.AtomicInteger; import org.junit.Test; -import rx.Single; -import rx.SingleSubscriber; -import rx.Subscriber; -import rx.Observable; -import rx.Observer; -import rx.functions.Action1; -import rx.functions.Func1; +import rx.*; +import rx.functions.*; import rx.observables.GroupedObservable; import rx.subjects.PublishSubject; public class ExceptionsTest { + @Test + public void constructorShouldBePrivate() { + TestUtil.checkUtilityClass(Exceptions.class); + } @Test(expected = OnErrorNotImplementedException.class) public void testOnErrorNotImplementedIsThrown() { @@ -74,7 +72,7 @@ public void testStackOverflowWouldOccur() { final PublishSubject b = PublishSubject.create(); final int MAX_STACK_DEPTH = 800; final AtomicInteger depth = new AtomicInteger(); - + a.subscribe(new Observer() { @Override @@ -106,7 +104,7 @@ public void onError(Throwable e) { @Override public void onNext(Integer n) { - if (depth.get() < MAX_STACK_DEPTH) { + if (depth.get() < MAX_STACK_DEPTH) { depth.set(Thread.currentThread().getStackTrace().length); a.onNext(n + 1); } @@ -115,7 +113,7 @@ public void onNext(Integer n) { a.onNext(1); assertTrue(depth.get() > MAX_STACK_DEPTH); } - + @Test(expected = StackOverflowError.class) public void testStackOverflowErrorIsThrown() { Observable.just(1).subscribe(new Observer() { @@ -256,10 +254,10 @@ public void onNext(Integer integer) { @Test(expected = OnErrorFailedException.class) public void testOnErrorExceptionIsThrownFromSubscribe() { - Observable.create(new Observable.OnSubscribe() { + Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber s1) { - Observable.create(new Observable.OnSubscribe() { + Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber s2) { throw new IllegalArgumentException("original exception"); @@ -272,10 +270,10 @@ public void call(Subscriber s2) { @Test(expected = OnErrorFailedException.class) public void testOnErrorExceptionIsThrownFromUnsafeSubscribe() { - Observable.create(new Observable.OnSubscribe() { + Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber s1) { - Observable.create(new Observable.OnSubscribe() { + Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber s2) { throw new IllegalArgumentException("original exception"); diff --git a/src/test/java/rx/exceptions/OnNextValueTest.java b/src/test/java/rx/exceptions/OnNextValueTest.java index 9342afac60..31a37692f8 100644 --- a/src/test/java/rx/exceptions/OnNextValueTest.java +++ b/src/test/java/rx/exceptions/OnNextValueTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -15,20 +15,16 @@ */ package rx.exceptions; +import static org.junit.Assert.*; + +import java.io.*; + import org.junit.Test; -import rx.Observable; -import rx.Observer; +import rx.*; import rx.exceptions.OnErrorThrowable.OnNextValue; import rx.functions.Func1; -import java.io.PrintWriter; -import java.io.StringWriter; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - /** * ```java * public OnNextValue(Object value) { @@ -121,49 +117,77 @@ public BadToString call(BadToString badToString) { }).subscribe(observer); } - + @Test public void testRenderInteger() { assertEquals("123", OnNextValue.renderValue(123)); } - + @Test public void testRenderByte() { assertEquals("10", OnNextValue.renderValue((byte) 10)); } - + @Test public void testRenderBoolean() { assertEquals("true", OnNextValue.renderValue(true)); } - + @Test public void testRenderShort() { assertEquals("10", OnNextValue.renderValue((short) 10)); } - + @Test public void testRenderLong() { assertEquals("10", OnNextValue.renderValue(10L)); } - + @Test public void testRenderCharacter() { assertEquals("10", OnNextValue.renderValue(10L)); } - + @Test public void testRenderFloat() { assertEquals("10.0", OnNextValue.renderValue(10.0f)); } - + @Test public void testRenderDouble() { assertEquals("10.0", OnNextValue.renderValue(10.0)); } - + @Test public void testRenderVoid() { assertEquals("null", OnNextValue.renderValue((Void) null)); } + + static class Value { + @Override + public String toString() { + return "Value"; + } + } + + @Test + public void nonSerializableValue() throws Exception { + Throwable e = OnErrorThrowable.addValueAsLastCause(new RuntimeException(), new Value()); + + ByteArrayOutputStream bout = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(bout); + oos.writeObject(e); + oos.close(); + + ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray()); + ObjectInputStream ois = new ObjectInputStream(bin); + + Throwable f = (Throwable)ois.readObject(); + + ois.close(); + + Object v = ((OnNextValue)f.getCause()).getValue(); + + assertEquals("Value", v); + } } diff --git a/src/test/java/rx/exceptions/TestException.java b/src/test/java/rx/exceptions/TestException.java index 34bcd8d68b..b513b3c54e 100644 --- a/src/test/java/rx/exceptions/TestException.java +++ b/src/test/java/rx/exceptions/TestException.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -33,14 +33,4 @@ public TestException() { public TestException(String message) { super(message); } - - @Override - public boolean equals(Object o) { - if (o == null || getClass() != o.getClass()) { - return false; - } - String thisMessage = getMessage(); - String otherMessage = ((TestException)o).getMessage(); - return thisMessage == otherMessage || (thisMessage != null && thisMessage.equals(otherMessage)); - } } diff --git a/src/test/java/rx/functions/ActionsTest.java b/src/test/java/rx/functions/ActionsTest.java index 8ffecb97ca..aaddbf3fbf 100644 --- a/src/test/java/rx/functions/ActionsTest.java +++ b/src/test/java/rx/functions/ActionsTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -17,22 +17,23 @@ import static org.junit.Assert.*; -import java.lang.reflect.*; import java.util.Arrays; import java.util.concurrent.atomic.AtomicLong; import org.junit.Test; +import rx.TestUtil; + public class ActionsTest { @Test public void testEmptyArities() { Action0 a0 = Actions.empty(); a0.call(); - + Action1 a1 = Actions.empty(); a1.call(1); - + Action2 a2 = Actions.empty(); a2.call(1, 2); @@ -41,7 +42,7 @@ public void testEmptyArities() { Action4 a4 = Actions.empty(); a4.call(1, 2, 3, 4); - + Action5 a5 = Actions.empty(); a5.call(1, 2, 3, 4, 5); @@ -67,10 +68,10 @@ public void testEmptyArities() { ann.call(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); ActionN annn = Actions.empty(); - annn.call(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, + annn.call(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20); } - + @Test public void testToFunc0() { final AtomicLong value = new AtomicLong(-1L); @@ -80,14 +81,14 @@ public void call() { value.set(0); } }; - + assertNull(Actions.toFunc(action).call()); assertEquals(0, value.get()); value.set(-1L); assertEquals((Integer)0, Actions.toFunc(action, 0).call()); assertEquals(0, value.get()); } - + @Test public void testToFunc1() { final AtomicLong value = new AtomicLong(-1L); @@ -97,14 +98,14 @@ public void call(Integer t1) { value.set(t1); } }; - + assertNull(Actions.toFunc(action).call(1)); assertEquals(1, value.get()); value.set(-1L); assertEquals((Integer)0, Actions.toFunc(action, 0).call(1)); assertEquals(1, value.get()); } - + @Test public void testToFunc2() { final AtomicLong value = new AtomicLong(-1L); @@ -114,7 +115,7 @@ public void call(Integer t1, Integer t2) { value.set(t1 | t2); } }; - + assertNull(Actions.toFunc(action).call(1, 2)); assertNull(Actions.toFunc(action).call(1, 2)); assertEquals(3, value.get()); @@ -122,7 +123,7 @@ public void call(Integer t1, Integer t2) { assertEquals((Integer)0, Actions.toFunc(action, 0).call(1, 2)); assertEquals(3, value.get()); } - + @Test public void testToFunc3() { final AtomicLong value = new AtomicLong(-1L); @@ -132,14 +133,14 @@ public void call(Integer t1, Integer t2, Integer t3) { value.set(t1 | t2 | t3); } }; - + assertNull(Actions.toFunc(action).call(1, 2, 4)); assertEquals(7, value.get()); value.set(-1L); assertEquals((Integer)0, Actions.toFunc(action, 0).call(1, 2, 4)); assertEquals(7, value.get()); } - + @Test public void testToFunc4() { final AtomicLong value = new AtomicLong(-1L); @@ -149,61 +150,61 @@ public void call(Integer t1, Integer t2, Integer t3, Integer t4) { value.set(t1 | t2 | t3 | t4); } }; - + assertNull(Actions.toFunc(action).call(1, 2, 4, 8)); assertEquals(15, value.get()); value.set(-1L); assertEquals((Integer)0, Actions.toFunc(action, 0).call(1, 2, 4, 8)); assertEquals(15, value.get()); } - + @Test public void testToFunc5() { final AtomicLong value = new AtomicLong(-1L); - final Action5 action = + final Action5 action = new Action5() { @Override public void call(Integer t1, Integer t2, Integer t3, Integer t4, Integer t5) { value.set(t1 | t2 | t3 | t4 | t5); } }; - + assertNull(Actions.toFunc(action).call(1, 2, 4, 8, 16)); assertEquals(31, value.get()); value.set(-1L); assertEquals((Integer)0, Actions.toFunc(action, 0).call(1, 2, 4, 8, 16)); assertEquals(31, value.get()); } - + @Test public void testToFunc6() { final AtomicLong value = new AtomicLong(-1L); - final Action6 action = + final Action6 action = new Action6() { @Override public void call(Integer t1, Integer t2, Integer t3, Integer t4, Integer t5, Integer t6) { value.set(t1 | t2 | t3 | t4 | t5 | t6); } }; - + assertNull(Actions.toFunc(action).call(1, 2, 4, 8, 16, 32)); assertEquals(63, value.get()); value.set(-1L); assertEquals((Integer)0, Actions.toFunc(action, 0).call(1, 2, 4, 8, 16, 32)); assertEquals(63, value.get()); } - + @Test public void testToFunc7() { final AtomicLong value = new AtomicLong(-1L); - final Action7 action = + final Action7 action = new Action7() { @Override public void call(Integer t1, Integer t2, Integer t3, Integer t4, Integer t5, Integer t6, Integer t7) { value.set(t1 | t2 | t3 | t4 | t5 | t6 | t7); } }; - + assertNull(Actions.toFunc(action).call(1, 2, 4, 8, 16, 32, 64)); assertEquals(127, value.get()); value.set(-1L); @@ -213,14 +214,14 @@ public void call(Integer t1, Integer t2, Integer t3, Integer t4, Integer t5, Int @Test public void testToFunc8() { final AtomicLong value = new AtomicLong(-1L); - final Action8 action = + final Action8 action = new Action8() { @Override public void call(Integer t1, Integer t2, Integer t3, Integer t4, Integer t5, Integer t6, Integer t7, Integer t8) { value.set(t1 | t2 | t3 | t4 | t5 | t6 | t7 | t8); } }; - + assertNull(Actions.toFunc(action).call(1, 2, 4, 8, 16, 32, 64, 128)); assertEquals(255, value.get()); value.set(-1L); @@ -230,21 +231,21 @@ public void call(Integer t1, Integer t2, Integer t3, Integer t4, Integer t5, Int @Test public void testToFunc9() { final AtomicLong value = new AtomicLong(-1L); - final Action9 action = + final Action9 action = new Action9() { @Override public void call(Integer t1, Integer t2, Integer t3, Integer t4, Integer t5, Integer t6, Integer t7, Integer t8, Integer t9) { value.set(t1 | t2 | t3 | t4 | t5 | t6 | t7 | t8 | t9); } }; - + assertNull(Actions.toFunc(action).call(1, 2, 4, 8, 16, 32, 64, 128, 256)); assertEquals(511, value.get()); value.set(-1L); assertEquals((Integer)0, Actions.toFunc(action, 0).call(1, 2, 4, 8, 16, 32, 64, 128, 256)); assertEquals(511, value.get()); } - + @Test public void testToFuncN() { for (int i = 0; i < 100; i++) { @@ -261,7 +262,7 @@ public void call(Object... args) { }; Object[] arr = new Object[i]; Arrays.fill(arr, 1); - + assertNull(Actions.toFunc(action).call(arr)); assertEquals(i, value.get()); value.set(-1L); @@ -269,22 +270,9 @@ public void call(Object... args) { assertEquals(i, value.get()); } } - + @Test - public void testNotInstantiable() { - try { - Constructor c = Actions.class.getDeclaredConstructor(); - c.setAccessible(true); - Object instance = c.newInstance(); - fail("Could instantiate Actions! " + instance); - } catch (NoSuchMethodException ex) { - ex.printStackTrace(); - } catch (InvocationTargetException ex) { - ex.printStackTrace(); - } catch (InstantiationException ex) { - ex.printStackTrace(); - } catch (IllegalAccessException ex) { - ex.printStackTrace(); - } + public void constructorShouldBePrivate() { + TestUtil.checkUtilityClass(Actions.class); } } diff --git a/src/test/java/rx/functions/FunctionsTest.java b/src/test/java/rx/functions/FunctionsTest.java index 1d2cc95374..61e61934de 100644 --- a/src/test/java/rx/functions/FunctionsTest.java +++ b/src/test/java/rx/functions/FunctionsTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -15,33 +15,21 @@ */ package rx.functions; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; -import java.lang.reflect.*; import java.util.Arrays; import java.util.concurrent.atomic.AtomicLong; import org.junit.Test; +import rx.TestUtil; + public class FunctionsTest { @Test - public void testNotInstantiable() { - try { - Constructor c = Functions.class.getDeclaredConstructor(); - c.setAccessible(true); - Object instance = c.newInstance(); - fail("Could instantiate Actions! " + instance); - } catch (NoSuchMethodException ex) { - ex.printStackTrace(); - } catch (InvocationTargetException ex) { - ex.printStackTrace(); - } catch (InstantiationException ex) { - ex.printStackTrace(); - } catch (IllegalAccessException ex) { - ex.printStackTrace(); - } + public void constructorShouldBePrivate() { + TestUtil.checkUtilityClass(Functions.class); } - + @Test(expected = RuntimeException.class) public void testFromFunc0() { Func0 func = new Func0() { @@ -50,13 +38,13 @@ public Integer call() { return 0; } }; - + Object[] params = new Object[0]; assertEquals((Integer)0, Functions.fromFunc(func).call(params)); - + Functions.fromFunc(func).call(Arrays.copyOf(params, params.length + 1)); } - + @Test(expected = RuntimeException.class) public void testFromFunc1() { Func1 func = new Func1() { @@ -65,13 +53,13 @@ public Integer call(Integer t1) { return t1; } }; - + Object[] params = new Object[] { 1 }; assertEquals((Integer)1, Functions.fromFunc(func).call(params)); - + Functions.fromFunc(func).call(Arrays.copyOf(params, params.length + 1)); } - + @Test(expected = RuntimeException.class) public void testFromFunc2() { Func2 func = new Func2() { @@ -80,13 +68,13 @@ public Integer call(Integer t1, Integer t2) { return t1 | t2; } }; - + Object[] params = new Object[] { 1, 2 }; assertEquals((Integer)3, Functions.fromFunc(func).call(params)); - + Functions.fromFunc(func).call(Arrays.copyOf(params, params.length + 1)); } - + @Test(expected = RuntimeException.class) public void testFromFunc3() { Func3 func = new Func3() { @@ -95,109 +83,109 @@ public Integer call(Integer t1, Integer t2, Integer t3) { return t1 | t2 | t3; } }; - + Object[] params = new Object[] { 1, 2, 4 }; assertEquals((Integer)7, Functions.fromFunc(func).call(params)); - + Functions.fromFunc(func).call(Arrays.copyOf(params, params.length + 1)); } - + @Test(expected = RuntimeException.class) public void testFromFunc4() { - Func4 func = + Func4 func = new Func4() { @Override public Integer call(Integer t1, Integer t2, Integer t3, Integer t4) { return t1 | t2 | t3 | t4; } }; - + Object[] params = new Object[] { 1, 2, 4, 8 }; assertEquals((Integer)15, Functions.fromFunc(func).call(params)); - + Functions.fromFunc(func).call(Arrays.copyOf(params, params.length + 1)); } - + @Test(expected = RuntimeException.class) public void testFromFunc5() { - Func5 func = + Func5 func = new Func5() { @Override public Integer call(Integer t1, Integer t2, Integer t3, Integer t4, Integer t5) { return t1 | t2 | t3 | t4 | t5; } }; - + Object[] params = new Object[] { 1, 2, 4, 8, 16 }; assertEquals((Integer)31, Functions.fromFunc(func).call(params)); - + Functions.fromFunc(func).call(Arrays.copyOf(params, params.length + 1)); } - + @Test(expected = RuntimeException.class) public void testFromFunc6() { - Func6 func = + Func6 func = new Func6() { @Override public Integer call(Integer t1, Integer t2, Integer t3, Integer t4, Integer t5, Integer t6) { return t1 | t2 | t3 | t4 | t5 | t6; } }; - + Object[] params = new Object[] { 1, 2, 4, 8, 16, 32 }; assertEquals((Integer)63, Functions.fromFunc(func).call(params)); - + Functions.fromFunc(func).call(Arrays.copyOf(params, params.length + 1)); } - + @Test(expected = RuntimeException.class) public void testFromFunc7() { - Func7 func = + Func7 func = new Func7() { @Override public Integer call(Integer t1, Integer t2, Integer t3, Integer t4, Integer t5, Integer t6, Integer t7) { return t1 | t2 | t3 | t4 | t5 | t6 | t7; } }; - + Object[] params = new Object[] { 1, 2, 4, 8, 16, 32, 64 }; assertEquals((Integer)127, Functions.fromFunc(func).call(params)); - + Functions.fromFunc(func).call(Arrays.copyOf(params, params.length + 1)); } - + @Test(expected = RuntimeException.class) public void testFromFunc8() { - Func8 func = + Func8 func = new Func8() { @Override public Integer call(Integer t1, Integer t2, Integer t3, Integer t4, Integer t5, Integer t6, Integer t7, Integer t8) { return t1 | t2 | t3 | t4 | t5 | t6 | t7 | t8; } }; - + Object[] params = new Object[] { 1, 2, 4, 8, 16, 32, 64, 128 }; assertEquals((Integer)255, Functions.fromFunc(func).call(params)); - + Functions.fromFunc(func).call(Arrays.copyOf(params, params.length + 1)); } - + @Test(expected = RuntimeException.class) public void testFromFunc9() { - Func9 func = + Func9 func = new Func9() { @Override public Integer call(Integer t1, Integer t2, Integer t3, Integer t4, Integer t5, Integer t6, Integer t7, Integer t8, Integer t9) { return t1 | t2 | t3 | t4 | t5 | t6 | t7 | t8 | t9; } }; - + Object[] params = new Object[] { 1, 2, 4, 8, 16, 32, 64, 128, 256 }; assertEquals((Integer)511, Functions.fromFunc(func).call(params)); - + Functions.fromFunc(func).call(Arrays.copyOf(params, params.length + 1)); } - + @Test(expected = RuntimeException.class) public void testFromAction0() { final AtomicLong value = new AtomicLong(); @@ -207,14 +195,14 @@ public void call() { value.set(0); } }; - + Object[] params = new Object[] { }; Functions.fromAction(action).call(params); assertEquals(0, value.get()); - + Functions.fromAction(action).call(Arrays.copyOf(params, params.length + 1)); } - + @Test(expected = RuntimeException.class) public void testFromAction1() { final AtomicLong value = new AtomicLong(); @@ -224,14 +212,14 @@ public void call(Integer t1) { value.set(t1); } }; - + Object[] params = new Object[] { 1 }; Functions.fromAction(action).call(params); assertEquals(1, value.get()); - + Functions.fromAction(action).call(Arrays.copyOf(params, params.length + 1)); } - + @Test(expected = RuntimeException.class) public void testFromAction2() { final AtomicLong value = new AtomicLong(); @@ -241,14 +229,14 @@ public void call(Integer t1, Integer t2) { value.set(t1 | t2); } }; - + Object[] params = new Object[] { 1, 2 }; Functions.fromAction(action).call(params); assertEquals(3, value.get()); - + Functions.fromAction(action).call(Arrays.copyOf(params, params.length + 1)); } - + @Test(expected = RuntimeException.class) public void testFromAction3() { final AtomicLong value = new AtomicLong(); @@ -258,11 +246,11 @@ public void call(Integer t1, Integer t2, Integer t3) { value.set(t1 | t2 | t3); } }; - + Object[] params = new Object[] { 1, 2, 4 }; Functions.fromAction(action).call(params); assertEquals(7, value.get()); - + Functions.fromAction(action).call(Arrays.copyOf(params, params.length + 1)); } } diff --git a/src/test/java/rx/internal/operators/BackpressureUtilsTest.java b/src/test/java/rx/internal/operators/BackpressureUtilsTest.java index 0bc7f542bf..f158fc8192 100644 --- a/src/test/java/rx/internal/operators/BackpressureUtilsTest.java +++ b/src/test/java/rx/internal/operators/BackpressureUtilsTest.java @@ -15,10 +15,18 @@ */ package rx.internal.operators; +import static org.junit.Assert.assertEquals; + import org.junit.Test; -import static org.junit.Assert.*; + +import rx.TestUtil; public class BackpressureUtilsTest { + @Test + public void constructorShouldBePrivate() { + TestUtil.checkUtilityClass(BackpressureUtils.class); + } + @Test public void testAddCap() { assertEquals(2L, BackpressureUtils.addCap(1, 1)); @@ -27,7 +35,7 @@ public void testAddCap() { assertEquals(Long.MAX_VALUE, BackpressureUtils.addCap(Long.MAX_VALUE - 1, Long.MAX_VALUE - 1)); assertEquals(Long.MAX_VALUE, BackpressureUtils.addCap(Long.MAX_VALUE, Long.MAX_VALUE)); } - + @Test public void testMultiplyCap() { assertEquals(6, BackpressureUtils.multiplyCap(2, 3)); diff --git a/src/test/java/rx/internal/operators/BlockingOperatorLatestTest.java b/src/test/java/rx/internal/operators/BlockingOperatorLatestTest.java index 2c586019ed..8244bf9c48 100644 --- a/src/test/java/rx/internal/operators/BlockingOperatorLatestTest.java +++ b/src/test/java/rx/internal/operators/BlockingOperatorLatestTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -15,20 +15,23 @@ */ package rx.internal.operators; -import java.util.Iterator; -import java.util.NoSuchElementException; +import java.util.*; import java.util.concurrent.TimeUnit; -import org.junit.Assert; - -import org.junit.Test; +import org.junit.*; import rx.Observable; +import rx.TestUtil; import rx.observables.BlockingObservable; import rx.schedulers.TestScheduler; import rx.subjects.PublishSubject; public class BlockingOperatorLatestTest { + @Test + public void constructorShouldBePrivate() { + TestUtil.checkUtilityClass(BlockingOperatorLatest.class); + } + @Test(timeout = 1000) public void testSimple() { TestScheduler scheduler = new TestScheduler(); diff --git a/src/test/java/rx/internal/operators/BlockingOperatorMostRecentTest.java b/src/test/java/rx/internal/operators/BlockingOperatorMostRecentTest.java index 3f377cffb1..b814c50973 100644 --- a/src/test/java/rx/internal/operators/BlockingOperatorMostRecentTest.java +++ b/src/test/java/rx/internal/operators/BlockingOperatorMostRecentTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -15,25 +15,26 @@ */ package rx.internal.operators; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.*; import static rx.internal.operators.BlockingOperatorMostRecent.mostRecent; import java.util.Iterator; import java.util.concurrent.TimeUnit; -import org.junit.Assert; -import org.junit.Test; +import org.junit.*; -import rx.Observable; +import rx.*; import rx.exceptions.TestException; import rx.observables.BlockingObservable; import rx.schedulers.TestScheduler; -import rx.subjects.PublishSubject; -import rx.subjects.Subject; +import rx.subjects.*; public class BlockingOperatorMostRecentTest { + @Test + public void constructorShouldBePrivate() { + TestUtil.checkUtilityClass(BlockingOperatorMostRecent.class); + } + @Test public void testMostRecentNull() { assertEquals(null, Observable.never().toBlocking().mostRecent(null).iterator().next()); diff --git a/src/test/java/rx/internal/operators/BlockingOperatorNextTest.java b/src/test/java/rx/internal/operators/BlockingOperatorNextTest.java index 4b44f5d739..69541f712b 100644 --- a/src/test/java/rx/internal/operators/BlockingOperatorNextTest.java +++ b/src/test/java/rx/internal/operators/BlockingOperatorNextTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -15,30 +15,21 @@ */ package rx.internal.operators; -import org.junit.Assert; -import org.junit.Test; +import static org.junit.Assert.*; +import static rx.internal.operators.BlockingOperatorNext.next; + +import java.util.*; +import java.util.concurrent.*; +import java.util.concurrent.atomic.*; -import java.util.Iterator; -import java.util.NoSuchElementException; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; +import org.junit.*; +import rx.*; import rx.Observable; -import rx.Subscriber; import rx.exceptions.TestException; import rx.observables.BlockingObservable; import rx.schedulers.Schedulers; -import rx.subjects.BehaviorSubject; -import rx.subjects.PublishSubject; -import rx.subjects.Subject; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; -import static rx.internal.operators.BlockingOperatorNext.next; +import rx.subjects.*; public class BlockingOperatorNextTest { @@ -70,6 +61,11 @@ public void run() { }.start(); } + @Test + public void constructorShouldBePrivate() { + TestUtil.checkUtilityClass(BlockingOperatorNext.class); + } + @Test public void testNext() { Subject obs = PublishSubject.create(); @@ -117,7 +113,7 @@ public void testNextWithError() { fireOnErrorInNewThread(obs); try { it.hasNext(); - fail("Expected an TestException"); + fail("Expected a TestException"); } catch (TestException e) { } @@ -153,7 +149,7 @@ public void testOnError() throws Throwable { obs.onError(new TestException()); try { it.hasNext(); - fail("Expected an TestException"); + fail("Expected a TestException"); } catch (TestException e) { // successful } @@ -170,7 +166,7 @@ public void testOnErrorInNewThread() { try { it.hasNext(); - fail("Expected an TestException"); + fail("Expected a TestException"); } catch (TestException e) { // successful } @@ -233,74 +229,85 @@ public void testNextWithCallingHasNextMultipleTimes() { * Confirm that no buffering or blocking of the Observable onNext calls occurs and it just grabs the next emitted value. *

      * This results in output such as => a: 1 b: 2 c: 89 - * + * * @throws Throwable */ @Test public void testNoBufferingOrBlockingOfSequence() throws Throwable { - final CountDownLatch finished = new CountDownLatch(1); - final int COUNT = 30; - final CountDownLatch timeHasPassed = new CountDownLatch(COUNT); - final AtomicBoolean running = new AtomicBoolean(true); - final AtomicInteger count = new AtomicInteger(0); - final Observable obs = Observable.create(new Observable.OnSubscribe() { + int retries = 10; - @Override - public void call(final Subscriber o) { - new Thread(new Runnable() { + for (;;) { + try { + final CountDownLatch finished = new CountDownLatch(1); + final int COUNT = 30; + final CountDownLatch timeHasPassed = new CountDownLatch(COUNT); + final AtomicBoolean running = new AtomicBoolean(true); + final AtomicInteger count = new AtomicInteger(0); + final Observable obs = Observable.unsafeCreate(new Observable.OnSubscribe() { @Override - public void run() { - try { - while (running.get()) { - o.onNext(count.incrementAndGet()); - timeHasPassed.countDown(); + public void call(final Subscriber o) { + new Thread(new Runnable() { + + @Override + public void run() { + try { + while (running.get()) { + o.onNext(count.incrementAndGet()); + timeHasPassed.countDown(); + } + o.onCompleted(); + } catch (Throwable e) { + o.onError(e); + } finally { + finished.countDown(); + } } - o.onCompleted(); - } catch (Throwable e) { - o.onError(e); - } finally { - finished.countDown(); - } + }).start(); } - }).start(); - } - }); + }); - try { - Iterator it = next(obs).iterator(); - - assertTrue(it.hasNext()); - int a = it.next(); - assertTrue(it.hasNext()); - int b = it.next(); - // we should have a different value - assertTrue("a and b should be different", a != b); - - // wait for some time (if times out we are blocked somewhere so fail ... set very high for very slow, constrained machines) - timeHasPassed.await(8000, TimeUnit.MILLISECONDS); - - assertTrue(it.hasNext()); - int c = it.next(); - - assertTrue("c should not just be the next in sequence", c != (b + 1)); - assertTrue("expected that c [" + c + "] is higher than or equal to " + COUNT, c >= COUNT); - - assertTrue(it.hasNext()); - int d = it.next(); - assertTrue(d > c); - - // shut down the thread - running.set(false); - - finished.await(); - - assertFalse(it.hasNext()); - - System.out.println("a: " + a + " b: " + b + " c: " + c); - } finally { - running.set(false); // don't let the thread spin indefinitely + try { + Iterator it = next(obs).iterator(); + + assertTrue(it.hasNext()); + int a = it.next(); + assertTrue(it.hasNext()); + int b = it.next(); + // we should have a different value + assertTrue("a and b should be different", a != b); + + // wait for some time (if times out we are blocked somewhere so fail ... set very high for very slow, constrained machines) + timeHasPassed.await(8000, TimeUnit.MILLISECONDS); + + assertTrue(it.hasNext()); + int c = it.next(); + + assertTrue("c should not just be the next in sequence", c != (b + 1)); + assertTrue("expected that c [" + c + "] is higher than or equal to " + COUNT, c >= COUNT); + + assertTrue(it.hasNext()); + int d = it.next(); + assertTrue(d > c); + + // shut down the thread + running.set(false); + + finished.await(); + + assertFalse(it.hasNext()); + + System.out.println("a: " + a + " b: " + b + " c: " + c); + } finally { + running.set(false); // don't let the thread spin indefinitely + } + return; + } catch (AssertionError ex) { + if (retries-- == 0) { + throw ex; + } + } } } @@ -322,7 +329,7 @@ public void testSingleSourceManyIterators() throws InterruptedException { terminal.onNext(null); } } - + @Test public void testSynchronousNext() { assertEquals(1, BehaviorSubject.create(1).take(1).toBlocking().single().intValue()); diff --git a/src/test/java/rx/internal/operators/BlockingOperatorToFutureTest.java b/src/test/java/rx/internal/operators/BlockingOperatorToFutureTest.java index 96edb2ced8..81f54dcdca 100644 --- a/src/test/java/rx/internal/operators/BlockingOperatorToFutureTest.java +++ b/src/test/java/rx/internal/operators/BlockingOperatorToFutureTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -15,26 +15,24 @@ */ package rx.internal.operators; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import static org.junit.Assert.*; import static rx.internal.operators.BlockingOperatorToFuture.toFuture; -import java.util.List; -import java.util.NoSuchElementException; -import java.util.concurrent.CancellationException; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; +import java.util.*; +import java.util.concurrent.*; import org.junit.Test; +import rx.*; import rx.Observable; import rx.Observable.OnSubscribe; -import rx.Subscriber; import rx.exceptions.TestException; public class BlockingOperatorToFutureTest { + @Test + public void constructorShouldBePrivate() { + TestUtil.checkUtilityClass(BlockingOperatorToFuture.class); + } @Test public void testToFuture() throws InterruptedException, ExecutionException { @@ -60,14 +58,14 @@ public void testExceptionWithMoreThanOneElement() throws Throwable { // we expect an exception since there are more than 1 element f.get(); } - catch(ExecutionException e) { + catch (ExecutionException e) { throw e.getCause(); } } @Test public void testToFutureWithException() { - Observable obs = Observable.create(new OnSubscribe() { + Observable obs = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber observer) { @@ -85,18 +83,18 @@ public void call(Subscriber observer) { } } - @Test(expected=CancellationException.class) + @Test(expected = CancellationException.class) public void testGetAfterCancel() throws Exception { - Observable obs = Observable.create(new OperationNeverComplete()); + Observable obs = Observable.unsafeCreate(new OperationNeverComplete()); Future f = toFuture(obs); boolean cancelled = f.cancel(true); assertTrue(cancelled); // because OperationNeverComplete never does f.get(); // Future.get() docs require this to throw } - @Test(expected=CancellationException.class) + @Test(expected = CancellationException.class) public void testGetWithTimeoutAfterCancel() throws Exception { - Observable obs = Observable.create(new OperationNeverComplete()); + Observable obs = Observable.unsafeCreate(new OperationNeverComplete()); Future f = toFuture(obs); boolean cancelled = f.cancel(true); assertTrue(cancelled); // because OperationNeverComplete never does @@ -120,7 +118,7 @@ public void testGetWithEmptyObservable() throws Throwable { try { f.get(); } - catch(ExecutionException e) { + catch (ExecutionException e) { throw e.getCause(); } } diff --git a/src/test/java/rx/internal/operators/BlockingOperatorToIteratorTest.java b/src/test/java/rx/internal/operators/BlockingOperatorToIteratorTest.java index 4ed030660b..91902b2c77 100644 --- a/src/test/java/rx/internal/operators/BlockingOperatorToIteratorTest.java +++ b/src/test/java/rx/internal/operators/BlockingOperatorToIteratorTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -22,14 +22,17 @@ import org.junit.Test; -import rx.Observable; +import rx.*; import rx.Observable.OnSubscribe; -import rx.Subscriber; import rx.exceptions.TestException; import rx.internal.operators.BlockingOperatorToIterator.SubscriberIterator; import rx.internal.util.RxRingBuffer; public class BlockingOperatorToIteratorTest { + @Test + public void constructorShouldBePrivate() { + TestUtil.checkUtilityClass(BlockingOperatorToIterator.class); + } @Test public void testToIterator() { @@ -52,7 +55,7 @@ public void testToIterator() { @Test(expected = TestException.class) public void testToIteratorWithException() { - Observable obs = Observable.create(new OnSubscribe() { + Observable obs = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber observer) { @@ -72,7 +75,7 @@ public void call(Subscriber observer) { @Test(expected = TestException.class) public void testExceptionThrownFromOnSubscribe() { - Iterable strings = Observable.create(new Observable.OnSubscribe() { + Iterable strings = Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber subscriber) { throw new TestException("intentional"); diff --git a/src/test/java/rx/internal/operators/CachedObservableTest.java b/src/test/java/rx/internal/operators/CachedObservableTest.java index b1ef245485..40fe2e56de 100644 --- a/src/test/java/rx/internal/operators/CachedObservableTest.java +++ b/src/test/java/rx/internal/operators/CachedObservableTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -36,16 +36,16 @@ public class CachedObservableTest { @Test public void testColdReplayNoBackpressure() { CachedObservable source = CachedObservable.from(Observable.range(0, 1000)); - + assertFalse("Source is connected!", source.isConnected()); - + TestSubscriber ts = new TestSubscriber(); - + source.subscribe(ts); assertTrue("Source is not connected!", source.isConnected()); assertFalse("Subscribers retained!", source.hasObservers()); - + ts.assertNoErrors(); ts.assertTerminalEvent(); List onNextEvents = ts.getOnNextEvents(); @@ -58,17 +58,17 @@ public void testColdReplayNoBackpressure() { @Test public void testColdReplayBackpressure() { CachedObservable source = CachedObservable.from(Observable.range(0, 1000)); - + assertFalse("Source is connected!", source.isConnected()); - + TestSubscriber ts = new TestSubscriber(); ts.requestMore(10); - + source.subscribe(ts); assertTrue("Source is not connected!", source.isConnected()); assertTrue("Subscribers not retained!", source.hasObservers()); - + ts.assertNoErrors(); assertEquals(0, ts.getCompletions()); List onNextEvents = ts.getOnNextEvents(); @@ -77,15 +77,15 @@ public void testColdReplayBackpressure() { for (int i = 0; i < 10; i++) { assertEquals((Integer)i, onNextEvents.get(i)); } - + ts.unsubscribe(); assertFalse("Subscribers retained!", source.hasObservers()); } - + @Test public void testCache() throws InterruptedException { final AtomicInteger counter = new AtomicInteger(); - Observable o = Observable.create(new Observable.OnSubscribe() { + Observable o = Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(final Subscriber observer) { @@ -142,39 +142,39 @@ public void testUnsubscribeSource() { o.subscribe(); verify(unsubscribe, times(1)).call(); } - + @Test public void testTake() { TestSubscriber ts = new TestSubscriber(); CachedObservable cached = CachedObservable.from(Observable.range(1, 100)); cached.take(10).subscribe(ts); - + ts.assertNoErrors(); ts.assertTerminalEvent(); ts.assertReceivedOnNext(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)); ts.assertUnsubscribed(); assertFalse(cached.hasObservers()); } - + @Test public void testAsync() { Observable source = Observable.range(1, 10000); for (int i = 0; i < 100; i++) { TestSubscriber ts1 = new TestSubscriber(); - + CachedObservable cached = CachedObservable.from(source); - + cached.observeOn(Schedulers.computation()).subscribe(ts1); - + ts1.awaitTerminalEvent(2, TimeUnit.SECONDS); ts1.assertNoErrors(); ts1.assertTerminalEvent(); assertEquals(10000, ts1.getOnNextEvents().size()); - + TestSubscriber ts2 = new TestSubscriber(); cached.observeOn(Schedulers.computation()).subscribe(ts2); - + ts2.awaitTerminalEvent(2, TimeUnit.SECONDS); ts2.assertNoErrors(); ts2.assertTerminalEvent(); @@ -187,9 +187,9 @@ public void testAsyncComeAndGo() { .take(1000) .subscribeOn(Schedulers.io()); CachedObservable cached = CachedObservable.from(source); - + Observable output = cached.observeOn(Schedulers.computation()); - + List> list = new ArrayList>(100); for (int i = 0; i < 100; i++) { TestSubscriber ts = new TestSubscriber(); @@ -206,21 +206,21 @@ public void testAsyncComeAndGo() { ts.awaitTerminalEvent(3, TimeUnit.SECONDS); ts.assertNoErrors(); ts.assertTerminalEvent(); - + for (int i = j * 10; i < j * 10 + 10; i++) { expected.set(i - j * 10, (long)i); } - + ts.assertReceivedOnNext(expected); - + j++; } } - + @Test public void testNoMissingBackpressureException() { final int m = 4 * 1000 * 1000; - Observable firehose = Observable.create(new OnSubscribe() { + Observable firehose = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber t) { for (int i = 0; i < m; i++) { @@ -229,43 +229,43 @@ public void call(Subscriber t) { t.onCompleted(); } }); - + TestSubscriber ts = new TestSubscriber(); firehose.cache().observeOn(Schedulers.computation()).takeLast(100).subscribe(ts); - + ts.awaitTerminalEvent(3, TimeUnit.SECONDS); ts.assertNoErrors(); ts.assertTerminalEvent(); - + assertEquals(100, ts.getOnNextEvents().size()); } - + @Test public void testValuesAndThenError() { Observable source = Observable.range(1, 10) .concatWith(Observable.error(new TestException())) .cache(); - - + + TestSubscriber ts = new TestSubscriber(); source.subscribe(ts); - + ts.assertReceivedOnNext(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)); Assert.assertEquals(0, ts.getCompletions()); Assert.assertEquals(1, ts.getOnErrorEvents().size()); - + TestSubscriber ts2 = new TestSubscriber(); source.subscribe(ts2); - + ts2.assertReceivedOnNext(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)); Assert.assertEquals(0, ts2.getCompletions()); Assert.assertEquals(1, ts2.getOnErrorEvents().size()); } - + @Test public void unsafeChildThrows() { final AtomicInteger count = new AtomicInteger(); - + Observable source = Observable.range(1, 100) .doOnNext(new Action1() { @Override @@ -274,16 +274,16 @@ public void call(Integer t) { } }) .cache(); - + TestSubscriber ts = new TestSubscriber() { @Override public void onNext(Integer t) { throw new TestException(); } }; - + source.unsafeSubscribe(ts); - + Assert.assertEquals(100, count.get()); ts.assertNoValues(); diff --git a/src/test/java/rx/internal/operators/CompletableConcatTest.java b/src/test/java/rx/internal/operators/CompletableConcatTest.java new file mode 100644 index 0000000000..f55898193c --- /dev/null +++ b/src/test/java/rx/internal/operators/CompletableConcatTest.java @@ -0,0 +1,138 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * 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 rx.internal.operators; + +import static org.junit.Assert.assertFalse; + +import java.util.Arrays; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.junit.*; + +import rx.*; +import rx.functions.*; +import rx.schedulers.Schedulers; + +public class CompletableConcatTest { + + @Test + public void asyncObservables() { + + final int[] calls = { 0 }; + + Completable.concat(Observable.range(1, 5).map(new Func1() { + @Override + public Completable call(final Integer v) { + System.out.println("Mapping " + v); + return Completable.fromAction(new Action0() { + @Override + public void call() { + System.out.println("Processing " + (calls[0] + 1)); + calls[0]++; + } + }) + .subscribeOn(Schedulers.io()) + .doOnCompleted(new Action0() { + @Override + public void call() { + System.out.println("Inner complete " + v); + } + }) + .observeOn(Schedulers.computation()); + } + }) + ).test() + .awaitTerminalEventAndUnsubscribeOnTimeout(5, TimeUnit.SECONDS) + .assertResult(); + + Assert.assertEquals(5, calls[0]); + } + + @Test + public void andThenNoInterrupt() throws InterruptedException { + for (int k = 0; k < 100; k++) { + final int count = 10; + final CountDownLatch latch = new CountDownLatch(count); + final AtomicBoolean interrupted = new AtomicBoolean(); + + for (int i = 0; i < count; i++) { + Completable.complete() + .subscribeOn(Schedulers.io()) + .observeOn(Schedulers.io()) + .andThen(Completable.fromAction(new Action0() { + @Override + public void call() { + try { + Thread.sleep(30); + } catch (InterruptedException e) { + System.out.println("Interrupted! " + Thread.currentThread()); + interrupted.set(true); + } + } + })) + .subscribe(new Action0() { + @Override + public void call() { + latch.countDown(); + } + }); + } + + latch.await(); + assertFalse("The second Completable was interrupted!", interrupted.get()); + } + } + + @Test + public void noInterrupt() throws InterruptedException { + for (int k = 0; k < 100; k++) { + final int count = 10; + final CountDownLatch latch = new CountDownLatch(count); + final AtomicBoolean interrupted = new AtomicBoolean(); + + for (int i = 0; i < count; i++) { + Completable c0 = Completable.fromAction(new Action0() { + @Override + public void call() { + try { + Thread.sleep(30); + } catch (InterruptedException e) { + System.out.println("Interrupted! " + Thread.currentThread()); + interrupted.set(true); + } + } + }); + Completable.concat(Arrays.asList(Completable.complete() + .subscribeOn(Schedulers.io()) + .observeOn(Schedulers.io()), + c0) + ) + .subscribe(new Action0() { + @Override + public void call() { + latch.countDown(); + } + }); + } + + latch.await(); + assertFalse("The second Completable was interrupted!", interrupted.get()); + } + } + +} diff --git a/src/test/java/rx/internal/operators/CompletableFromEmitterTest.java b/src/test/java/rx/internal/operators/CompletableFromEmitterTest.java new file mode 100644 index 0000000000..32107591fc --- /dev/null +++ b/src/test/java/rx/internal/operators/CompletableFromEmitterTest.java @@ -0,0 +1,330 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * 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 rx.internal.operators; + +import static org.junit.Assert.*; + +import java.io.IOException; + +import org.junit.Test; +import org.mockito.Mockito; + +import rx.*; +import rx.exceptions.TestException; +import rx.functions.*; +import rx.observers.TestSubscriber; +import rx.subscriptions.BooleanSubscription; + +public class CompletableFromEmitterTest { + + @Test + public void normal() { + TestSubscriber ts = TestSubscriber.create(); + + Completable.fromEmitter(new Action1() { + @Override + public void call(CompletableEmitter e) { + e.onCompleted(); + } + }).subscribe(ts); + + ts.assertNoValues(); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @Test + public void error() { + TestSubscriber ts = TestSubscriber.create(); + + Completable.fromEmitter(new Action1() { + @Override + public void call(CompletableEmitter e) { + e.onError(new TestException()); + } + }).subscribe(ts); + + ts.assertNoValues(); + ts.assertError(TestException.class); + ts.assertNotCompleted(); + } + + @Test + public void ensureProtocol1() { + TestSubscriber ts = TestSubscriber.create(); + + Completable.fromEmitter(new Action1() { + @Override + public void call(CompletableEmitter e) { + e.onError(new TestException()); + e.onCompleted(); + e.onError(new IOException()); + } + }).subscribe(ts); + + ts.assertNoValues(); + ts.assertError(TestException.class); + ts.assertNotCompleted(); + } + + @Test + public void ensureProtocol2() { + TestSubscriber ts = TestSubscriber.create(); + + Completable.fromEmitter(new Action1() { + @Override + public void call(CompletableEmitter e) { + e.onCompleted(); + e.onError(new TestException()); + e.onCompleted(); + } + }).subscribe(ts); + + ts.assertNoValues(); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @Test + public void resourceCleanupNormal() { + TestSubscriber ts = TestSubscriber.create(); + + final BooleanSubscription bs = new BooleanSubscription(); + + assertFalse(bs.isUnsubscribed()); + + Completable.fromEmitter(new Action1() { + @Override + public void call(CompletableEmitter e) { + e.setSubscription(bs); + e.onCompleted(); + } + }).subscribe(ts); + + ts.assertNoValues(); + ts.assertNoErrors(); + ts.assertCompleted(); + + assertTrue(bs.isUnsubscribed()); + } + + @Test + public void resourceCleanupError() { + TestSubscriber ts = TestSubscriber.create(); + + final BooleanSubscription bs = new BooleanSubscription(); + + assertFalse(bs.isUnsubscribed()); + + Completable.fromEmitter(new Action1() { + @Override + public void call(CompletableEmitter e) { + e.setSubscription(bs); + e.onError(new TestException()); + } + }).subscribe(ts); + + ts.assertNoValues(); + ts.assertError(TestException.class); + ts.assertNotCompleted(); + + assertTrue(bs.isUnsubscribed()); + } + + @Test + public void resourceCleanupCancellable() throws Exception { + TestSubscriber ts = TestSubscriber.create(); + + final Cancellable c = Mockito.mock(Cancellable.class); + + Completable.fromEmitter(new Action1() { + @Override + public void call(CompletableEmitter e) { + e.setCancellation(c); + e.onCompleted(); + } + }).subscribe(ts); + + ts.assertNoValues(); + ts.assertNoErrors(); + ts.assertCompleted(); + + Mockito.verify(c).cancel(); + } + + @Test + public void resourceCleanupUnsubscirbe() throws Exception { + TestSubscriber ts = TestSubscriber.create(); + ts.unsubscribe(); + + final Cancellable c = Mockito.mock(Cancellable.class); + + Completable.fromEmitter(new Action1() { + @Override + public void call(CompletableEmitter e) { + e.setCancellation(c); + e.onCompleted(); + } + }).subscribe(ts); + + ts.assertNoValues(); + ts.assertNoErrors(); + ts.assertNotCompleted(); + + Mockito.verify(c).cancel(); + } + + @Test + public void resourceCleanupOnCompleteCrashes() throws Exception { + final Cancellable c = Mockito.mock(Cancellable.class); + + Completable.fromEmitter(new Action1() { + @Override + public void call(CompletableEmitter e) { + e.setCancellation(c); + e.onCompleted(); + } + }).unsafeSubscribe(new CompletableSubscriber() { + + @Override + public void onCompleted() { + throw new TestException(); + } + + @Override + public void onError(Throwable e) { + throw new TestException(); + } + + @Override + public void onSubscribe(Subscription d) { + + } + + }); + + + Mockito.verify(c).cancel(); + } + + @Test + public void resourceCleanupOnErrorCrashes() throws Exception { + final Cancellable c = Mockito.mock(Cancellable.class); + + Completable.fromEmitter(new Action1() { + @Override + public void call(CompletableEmitter e) { + e.setCancellation(c); + e.onError(new IOException()); + } + }).unsafeSubscribe(new CompletableSubscriber() { + + @Override + public void onCompleted() { + throw new TestException(); + } + + @Override + public void onError(Throwable e) { + throw new TestException(); + } + + @Override + public void onSubscribe(Subscription d) { + + } + + }); + + + Mockito.verify(c).cancel(); + } + @Test + public void producerCrashes() { + TestSubscriber ts = TestSubscriber.create(); + + Completable.fromEmitter(new Action1() { + @Override + public void call(CompletableEmitter e) { + throw new TestException(); + } + }).subscribe(ts); + + ts.assertNoValues(); + ts.assertError(TestException.class); + ts.assertNotCompleted(); + } + + @Test + public void producerCrashesAfterSignal() { + TestSubscriber ts = TestSubscriber.create(); + + Completable.fromEmitter(new Action1() { + @Override + public void call(CompletableEmitter e) { + e.onCompleted(); + throw new TestException(); + } + }).subscribe(ts); + + ts.assertNoValues(); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @Test + public void concurrentUse() { + for (int i = 0; i < 500; i++) { + TestSubscriber ts = TestSubscriber.create(); + + final CompletableEmitter[] emitter = { null }; + + Completable.fromEmitter(new Action1() { + @Override + public void call(CompletableEmitter e) { + emitter[0] = e; + } + }).subscribe(ts); + + final TestException ex = new TestException(); + final CompletableEmitter e = emitter[0]; + + TestUtil.race(new Action0() { + @Override + public void call() { + e.onCompleted(); + } + }, new Action0() { + @Override + public void call() { + e.onError(ex); + } + }); + + if (ts.getCompletions() != 0) { + ts.assertNoValues(); + ts.assertNoErrors(); + ts.assertCompleted(); + } + if (!ts.getOnErrorEvents().isEmpty()) { + ts.assertNoValues(); + ts.assertError(ex); + ts.assertNotCompleted(); + } + } + } +} diff --git a/src/test/java/rx/internal/operators/CompletableMergeTest.java b/src/test/java/rx/internal/operators/CompletableMergeTest.java new file mode 100644 index 0000000000..a16518ab36 --- /dev/null +++ b/src/test/java/rx/internal/operators/CompletableMergeTest.java @@ -0,0 +1,61 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * 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 rx.internal.operators; + +import java.util.concurrent.TimeUnit; + +import org.junit.*; + +import rx.*; +import rx.functions.*; +import rx.schedulers.Schedulers; + +public class CompletableMergeTest { + + @Test + public void asyncObservables() { + + final int[] calls = { 0 }; + + Completable.merge(Observable.range(1, 5).map(new Func1() { + @Override + public Completable call(final Integer v) { + System.out.println("Mapping " + v); + return Completable.fromAction(new Action0() { + @Override + public void call() { + System.out.println("Processing " + (calls[0] + 1)); + calls[0]++; + } + }) + .subscribeOn(Schedulers.io()) + .doOnCompleted(new Action0() { + @Override + public void call() { + System.out.println("Inner complete " + v); + } + }) + .observeOn(Schedulers.computation()); + } + }), 1 + ).test() + .awaitTerminalEventAndUnsubscribeOnTimeout(5, TimeUnit.SECONDS) + .assertResult(); + + Assert.assertEquals(5, calls[0]); + } +} diff --git a/src/test/java/rx/internal/operators/CompletableOnErrorXTest.java b/src/test/java/rx/internal/operators/CompletableOnErrorXTest.java new file mode 100644 index 0000000000..31cbff9df1 --- /dev/null +++ b/src/test/java/rx/internal/operators/CompletableOnErrorXTest.java @@ -0,0 +1,63 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * 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 rx.internal.operators; + +import static org.junit.Assert.*; +import org.junit.Test; + +import rx.Completable; +import rx.functions.Func1; +import rx.observers.AssertableSubscriber; +import rx.subjects.PublishSubject; + +public class CompletableOnErrorXTest { + + @Test + public void nextUnsubscribe() { + PublishSubject ps = PublishSubject.create(); + + AssertableSubscriber as = ps.toCompletable() + .onErrorResumeNext(new Func1() { + @Override + public Completable call(Throwable e) { + return Completable.complete(); + } + }) + .test(); + + assertTrue(ps.hasObservers()); + + as.unsubscribe(); + + assertFalse("Still subscribed!", ps.hasObservers()); + } + + @Test + public void completeUnsubscribe() { + PublishSubject ps = PublishSubject.create(); + + AssertableSubscriber as = ps.toCompletable() + .onErrorComplete() + .test(); + + assertTrue(ps.hasObservers()); + + as.unsubscribe(); + + assertFalse("Still subscribed!", ps.hasObservers()); + } +} diff --git a/src/test/java/rx/internal/operators/DeferredScalarSubscriberTest.java b/src/test/java/rx/internal/operators/DeferredScalarSubscriberTest.java new file mode 100644 index 0000000000..c3ad148e75 --- /dev/null +++ b/src/test/java/rx/internal/operators/DeferredScalarSubscriberTest.java @@ -0,0 +1,409 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * 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 rx.internal.operators; + +import static org.junit.Assert.*; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.Test; + +import rx.*; +import rx.Scheduler.Worker; +import rx.exceptions.TestException; +import rx.functions.Action0; +import rx.observers.TestSubscriber; +import rx.schedulers.Schedulers; +import rx.subjects.PublishSubject; + +public class DeferredScalarSubscriberTest { + + @Test + public void completeFirst() { + TestSubscriber ts = TestSubscriber.create(0L); + TestingDeferredScalarSubscriber ds = new TestingDeferredScalarSubscriber(ts); + ds.setupDownstream(); + + ds.onNext(1); + + ts.assertNoValues(); + + ds.onCompleted(); + + ts.assertNoValues(); + + ts.requestMore(1); + + ts.assertValues(1); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @Test + public void requestFirst() { + TestSubscriber ts = TestSubscriber.create(1); + TestingDeferredScalarSubscriber ds = new TestingDeferredScalarSubscriber(ts); + ds.setupDownstream(); + + ds.onNext(1); + + ts.assertNoValues(); + + ds.onCompleted(); + + ts.assertValues(1); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @Test + public void empty() { + TestSubscriber ts = TestSubscriber.create(0L); + TestingDeferredScalarSubscriber ds = new TestingDeferredScalarSubscriber(ts); + ds.setupDownstream(); + + ts.assertNoValues(); + + ds.onCompleted(); + + ts.assertNoValues(); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @Test + public void error() { + TestSubscriber ts = TestSubscriber.create(0L); + TestingDeferredScalarSubscriber ds = new TestingDeferredScalarSubscriber(ts); + ds.setupDownstream(); + + ts.assertNoValues(); + + ds.onError(new TestException()); + + ts.assertNoValues(); + ts.assertError(TestException.class); + ts.assertNotCompleted(); + } + + @Test + public void unsubscribeComposes() { + PublishSubject ps = PublishSubject.create(); + TestSubscriber ts = TestSubscriber.create(0L); + TestingDeferredScalarSubscriber ds = new TestingDeferredScalarSubscriber(ts); + + ds.subscribeTo(ps); + + assertTrue("No subscribers?", ps.hasObservers()); + + ts.unsubscribe(); + + ds.onNext(1); + ds.onCompleted(); + + ts.requestMore(1); + + ts.assertNoValues(); + ts.assertNoErrors(); + ts.assertNotCompleted(); + + assertFalse("Subscribers?", ps.hasObservers()); + assertTrue("Deferred not unsubscribed?", ds.isUnsubscribed()); + } + + @Test + public void emptySource() { + TestSubscriber ts = TestSubscriber.create(0L); + TestingDeferredScalarSubscriber ds = new TestingDeferredScalarSubscriber(ts); + ds.subscribeTo(Observable.just(1).ignoreElements()); // we need a producer from upstream + + ts.assertNoValues(); + + ts.assertNoValues(); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @Test + public void justSource() { + TestSubscriber ts = TestSubscriber.create(0L); + TestingDeferredScalarSubscriber ds = new TestingDeferredScalarSubscriber(ts); + ds.subscribeTo(Observable.just(1)); + + ts.assertNoValues(); + + ts.requestMore(1); + + ts.assertValue(1); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @Test + public void rangeSource() { + TestSubscriber ts = TestSubscriber.create(0); + TestingDeferredScalarSubscriber ds = new TestingDeferredScalarSubscriber(ts); + ds.subscribeTo(Observable.range(1, 10)); + + ts.assertNoValues(); + + ts.requestMore(1); + + ts.assertValue(10); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @Test + public void completeAfterNext() { + TestSubscriber ts = new TestSubscriber() { + @Override + public void onNext(Integer t) { + super.onNext(t); + unsubscribe(); + } + }; + TestingDeferredScalarSubscriber ds = new TestingDeferredScalarSubscriber(ts); + ds.setupDownstream(); + ds.onNext(1); + + ts.assertNoValues(); + + ds.onCompleted(); + + ts.assertValue(1); + ts.assertNoErrors(); + ts.assertNotCompleted(); + } + + @Test + public void completeAfterNextViaRequest() { + TestSubscriber ts = new TestSubscriber(0L) { + @Override + public void onNext(Integer t) { + super.onNext(t); + unsubscribe(); + } + }; + TestingDeferredScalarSubscriber ds = new TestingDeferredScalarSubscriber(ts); + ds.setupDownstream(); + ds.onNext(1); + ds.onCompleted(); + + ts.assertNoValues(); + + ts.requestMore(1); + + ts.assertValue(1); + ts.assertNoErrors(); + ts.assertNotCompleted(); + } + + @Test + public void doubleComplete() { + TestSubscriber ts = TestSubscriber.create(0); + TestingDeferredScalarSubscriber ds = new TestingDeferredScalarSubscriber(ts); + ds.setupDownstream(); + + ds.onNext(1); + + ts.requestMore(1); + + ds.onCompleted(); + ds.onCompleted(); + + + ts.assertValue(1); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @Test + public void doubleComplete2() { + TestSubscriber ts = TestSubscriber.create(0); + TestingDeferredScalarSubscriber ds = new TestingDeferredScalarSubscriber(ts); + ds.setupDownstream(); + + ds.onNext(1); + + ds.onCompleted(); + ds.onCompleted(); + + ts.requestMore(1); + + ts.assertValue(1); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @Test + public void doubleRequest() { + TestSubscriber ts = TestSubscriber.create(0); + TestingDeferredScalarSubscriber ds = new TestingDeferredScalarSubscriber(ts); + ds.setupDownstream(); + + ds.onNext(1); + + ts.requestMore(1); + ts.requestMore(1); + + ds.onCompleted(); + + ts.assertValue(1); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @Test + public void negativeRequest() { + TestSubscriber ts = TestSubscriber.create(0); + TestingDeferredScalarSubscriber ds = new TestingDeferredScalarSubscriber(ts); + ds.setupDownstream(); + + try { + ds.downstreamRequest(-99); + fail("Failed to throw IllegalArgumentException"); + } catch (IllegalArgumentException ex) { + assertEquals("n >= 0 required but it was -99", ex.getMessage()); + } + } + + @Test + public void callsAfterUnsubscribe() { + TestSubscriber ts = TestSubscriber.create(0); + TestingDeferredScalarSubscriber ds = new TestingDeferredScalarSubscriber(ts); + ds.setupDownstream(); + + ts.unsubscribe(); + + ds.downstreamRequest(1); + ds.onNext(1); + ds.onCompleted(); + ds.onCompleted(); + + ts.assertNoValues(); + ts.assertNoErrors(); + ts.assertNotCompleted(); + } + @Test + public void emissionRequestRace() { + Worker w = Schedulers.computation().createWorker(); + try { + for (int i = 0; i < 10000; i++) { + + final TestSubscriber ts = TestSubscriber.create(0L); + TestingDeferredScalarSubscriber ds = new TestingDeferredScalarSubscriber(ts); + ds.setupDownstream(); + ds.onNext(1); + + final AtomicInteger ready = new AtomicInteger(2); + + w.schedule(new Action0() { + @Override + public void call() { + ready.decrementAndGet(); + while (ready.get() != 0) { } + + ts.requestMore(1); + } + }); + + ready.decrementAndGet(); + while (ready.get() != 0) { } + + ds.onCompleted(); + + ts.awaitTerminalEventAndUnsubscribeOnTimeout(5, TimeUnit.SECONDS); + ts.assertValues(1); + ts.assertNoErrors(); + ts.assertCompleted(); + + } + } finally { + w.unsubscribe(); + } + } + + @Test + public void emissionRequestRace2() { + Worker w = Schedulers.io().createWorker(); + Worker w2 = Schedulers.io().createWorker(); + int m = 10000; + if (Runtime.getRuntime().availableProcessors() < 3) { + m = 1000; + } + try { + for (int i = 0; i < m; i++) { + + final TestSubscriber ts = TestSubscriber.create(0L); + TestingDeferredScalarSubscriber ds = new TestingDeferredScalarSubscriber(ts); + ds.setupDownstream(); + ds.onNext(1); + + final AtomicInteger ready = new AtomicInteger(3); + + w.schedule(new Action0() { + @Override + public void call() { + ready.decrementAndGet(); + while (ready.get() != 0) { } + + ts.requestMore(1); + } + }); + + w2.schedule(new Action0() { + @Override + public void call() { + ready.decrementAndGet(); + while (ready.get() != 0) { } + + ts.requestMore(1); + } + }); + + ready.decrementAndGet(); + while (ready.get() != 0) { } + + ds.onCompleted(); + + ts.awaitTerminalEventAndUnsubscribeOnTimeout(5, TimeUnit.SECONDS); + ts.assertValues(1); + ts.assertNoErrors(); + ts.assertCompleted(); + + } + } finally { + w.unsubscribe(); + w2.unsubscribe(); + } + } + + static final class TestingDeferredScalarSubscriber extends DeferredScalarSubscriber { + + public TestingDeferredScalarSubscriber(Subscriber actual) { + super(actual); + } + + @Override + public void onNext(Integer t) { + value = t; + hasValue = true; + } + } +} diff --git a/src/test/java/rx/internal/operators/NotificationLiteTest.java b/src/test/java/rx/internal/operators/NotificationLiteTest.java index 9b9f3f7661..74e6201baa 100644 --- a/src/test/java/rx/internal/operators/NotificationLiteTest.java +++ b/src/test/java/rx/internal/operators/NotificationLiteTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -26,30 +26,28 @@ public class NotificationLiteTest { @Test public void testComplete() { - NotificationLite on = NotificationLite.instance(); - Object n = on.next("Hello"); - Object c = on.completed(); - - assertTrue(on.isCompleted(c)); - assertFalse(on.isCompleted(n)); - - assertEquals("Hello", on.getValue(n)); + Object n = NotificationLite.next("Hello"); + Object c = NotificationLite.completed(); + + assertTrue(NotificationLite.isCompleted(c)); + assertFalse(NotificationLite.isCompleted(n)); + + assertEquals("Hello", NotificationLite.getValue(n)); } - + @Test public void testValueKind() { - NotificationLite on = NotificationLite.instance(); - - assertTrue(on.isNull(on.next(null))); - assertFalse(on.isNull(on.next(1))); - assertFalse(on.isNull(on.error(new TestException()))); - assertFalse(on.isNull(on.completed())); - assertFalse(on.isNull(null)); - - assertTrue(on.isNext(on.next(null))); - assertTrue(on.isNext(on.next(1))); - assertFalse(on.isNext(on.completed())); - assertFalse(on.isNext(null)); - assertFalse(on.isNext(on.error(new TestException()))); + + assertTrue(NotificationLite.isNull(NotificationLite.next(null))); + assertFalse(NotificationLite.isNull(NotificationLite.next(1))); + assertFalse(NotificationLite.isNull(NotificationLite.error(new TestException()))); + assertFalse(NotificationLite.isNull(NotificationLite.completed())); + assertFalse(NotificationLite.isNull(null)); + + assertTrue(NotificationLite.isNext(NotificationLite.next(null))); + assertTrue(NotificationLite.isNext(NotificationLite.next(1))); + assertFalse(NotificationLite.isNext(NotificationLite.completed())); + assertFalse(NotificationLite.isNext(null)); + assertFalse(NotificationLite.isNext(NotificationLite.error(new TestException()))); } } diff --git a/src/test/java/rx/internal/operators/OnSubscribeAmbTest.java b/src/test/java/rx/internal/operators/OnSubscribeAmbTest.java index 4173c56eb5..7f9bf0ff49 100644 --- a/src/test/java/rx/internal/operators/OnSubscribeAmbTest.java +++ b/src/test/java/rx/internal/operators/OnSubscribeAmbTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -15,33 +15,27 @@ */ package rx.internal.operators; -import static org.junit.Assert.assertEquals; -import static org.mockito.Mockito.inOrder; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; import static rx.internal.operators.OnSubscribeAmb.amb; import java.io.IOException; +import java.lang.reflect.Method; import java.util.Arrays; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; -import org.junit.Before; -import org.junit.Test; +import org.junit.*; import org.mockito.InOrder; -import rx.Observable; +import rx.*; import rx.Observable.OnSubscribe; -import rx.Observer; -import rx.Producer; -import rx.Scheduler; -import rx.Subscriber; -import rx.functions.Action0; -import rx.functions.Action1; +import rx.exceptions.TestException; +import rx.functions.*; import rx.internal.util.RxRingBuffer; import rx.observers.TestSubscriber; -import rx.schedulers.Schedulers; -import rx.schedulers.TestScheduler; +import rx.schedulers.*; +import rx.subjects.PublishSubject; import rx.subscriptions.CompositeSubscription; public class OnSubscribeAmbTest { @@ -57,7 +51,7 @@ public void setUp() { private Observable createObservable(final String[] values, final long interval, final Throwable e) { - return Observable.create(new OnSubscribe() { + return Observable.unsafeCreate(new OnSubscribe() { @Override public void call(final Subscriber subscriber) { @@ -96,7 +90,7 @@ public void testAmb() { Observable observable3 = createObservable(new String[] { "3", "33", "333", "3333" }, 3000, null); - Observable o = Observable.create(amb(observable1, + Observable o = Observable.unsafeCreate(amb(observable1, observable2, observable3)); @SuppressWarnings("unchecked") @@ -125,7 +119,7 @@ public void testAmb2() { Observable observable3 = createObservable(new String[] {}, 3000, new IOException("fake exception")); - Observable o = Observable.create(amb(observable1, + Observable o = Observable.unsafeCreate(amb(observable1, observable2, observable3)); @SuppressWarnings("unchecked") @@ -152,7 +146,7 @@ public void testAmb3() { Observable observable3 = createObservable(new String[] { "3" }, 3000, null); - Observable o = Observable.create(amb(observable1, + Observable o = Observable.unsafeCreate(amb(observable1, observable2, observable3)); @SuppressWarnings("unchecked") @@ -171,7 +165,7 @@ public void testProducerRequestThroughAmb() { ts.requestMore(3); final AtomicLong requested1 = new AtomicLong(); final AtomicLong requested2 = new AtomicLong(); - Observable o1 = Observable.create(new OnSubscribe() { + Observable o1 = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber s) { @@ -187,7 +181,7 @@ public void request(long n) { } }); - Observable o2 = Observable.create(new OnSubscribe() { + Observable o2 = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber s) { @@ -221,8 +215,8 @@ public void testBackpressure() { ts.assertNoErrors(); assertEquals(RxRingBuffer.SIZE * 2, ts.getOnNextEvents().size()); } - - + + @Test public void testSubscriptionOnlyHappensOnce() throws InterruptedException { final AtomicLong count = new AtomicLong(); @@ -245,7 +239,7 @@ public void call() { ts.assertNoErrors(); assertEquals(2, count.get()); } - + @Test public void testSecondaryRequestsPropagatedToChildren() throws InterruptedException { //this aync stream should emit first @@ -295,13 +289,13 @@ public void testMultipleUse() { TestSubscriber ts2 = new TestSubscriber(); Observable amb = Observable.timer(100, TimeUnit.MILLISECONDS).ambWith(Observable.timer(200, TimeUnit.MILLISECONDS)); - + amb.subscribe(ts1); amb.subscribe(ts2); - + ts1.awaitTerminalEvent(); ts2.awaitTerminalEvent(); - + ts1.assertValue(0L); ts1.assertCompleted(); ts1.assertNoErrors(); @@ -310,4 +304,173 @@ public void testMultipleUse() { ts2.assertCompleted(); ts2.assertNoErrors(); } + + @SuppressWarnings("unchecked") + @Test + public void ambIterable() { + PublishSubject ps1 = PublishSubject.create(); + PublishSubject ps2 = PublishSubject.create(); + + TestSubscriber ts = TestSubscriber.create(); + + Observable.amb(Arrays.asList(ps1, ps2)).subscribe(ts); + + ts.assertNoValues(); + + ps1.onNext(1); + ps1.onCompleted(); + + assertFalse(ps1.hasObservers()); + assertFalse(ps2.hasObservers()); + + ts.assertValue(1); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @SuppressWarnings("unchecked") + @Test + public void ambIterable2() { + PublishSubject ps1 = PublishSubject.create(); + PublishSubject ps2 = PublishSubject.create(); + + TestSubscriber ts = TestSubscriber.create(); + + Observable.amb(Arrays.asList(ps1, ps2)).subscribe(ts); + + ts.assertNoValues(); + + ps2.onNext(2); + ps2.onCompleted(); + + assertFalse(ps1.hasObservers()); + assertFalse(ps2.hasObservers()); + + ts.assertValue(2); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @SuppressWarnings("unchecked") + @Test + public void ambMany() throws Exception { + for (int i = 2; i < 10; i++) { + Class[] clazz = new Class[i]; + Arrays.fill(clazz, Observable.class); + + PublishSubject[] ps = new PublishSubject[i]; + + for (int j = 0; j < i; j++) { + + for (int k = 0; k < i; k++) { + ps[k] = PublishSubject.create(); + } + + Method m = Observable.class.getMethod("amb", clazz); + + Observable obs = (Observable)m.invoke(null, (Object[])ps); + + TestSubscriber ts = TestSubscriber.create(); + + obs.subscribe(ts); + + for (int k = 0; k < i; k++) { + assertTrue("@" + i + "/" + k + " has no observers?", ps[k].hasObservers()); + } + + ps[j].onNext(j); + ps[j].onCompleted(); + + for (int k = 0; k < i; k++) { + assertFalse("@" + i + "/" + k + " has observers?", ps[k].hasObservers()); + } + + ts.assertValue(j); + ts.assertNoErrors(); + ts.assertCompleted(); + } + } + } + + @SuppressWarnings("unchecked") + @Test + public void ambManyError() throws Exception { + for (int i = 2; i < 10; i++) { + Class[] clazz = new Class[i]; + Arrays.fill(clazz, Observable.class); + + PublishSubject[] ps = new PublishSubject[i]; + + for (int j = 0; j < i; j++) { + + for (int k = 0; k < i; k++) { + ps[k] = PublishSubject.create(); + } + + Method m = Observable.class.getMethod("amb", clazz); + + Observable obs = (Observable)m.invoke(null, (Object[])ps); + + TestSubscriber ts = TestSubscriber.create(); + + obs.subscribe(ts); + + for (int k = 0; k < i; k++) { + assertTrue("@" + i + "/" + k + " has no observers?", ps[k].hasObservers()); + } + + ps[j].onError(new TestException(Integer.toString(j))); + + for (int k = 0; k < i; k++) { + assertFalse("@" + i + "/" + k + " has observers?", ps[k].hasObservers()); + } + + ts.assertNoValues(); + ts.assertError(TestException.class); + ts.assertNotCompleted(); + + assertEquals(Integer.toString(j), ts.getOnErrorEvents().get(0).getMessage()); + } + } + } + + @SuppressWarnings("unchecked") + @Test + public void ambManyComplete() throws Exception { + for (int i = 2; i < 10; i++) { + Class[] clazz = new Class[i]; + Arrays.fill(clazz, Observable.class); + + PublishSubject[] ps = new PublishSubject[i]; + + for (int j = 0; j < i; j++) { + + for (int k = 0; k < i; k++) { + ps[k] = PublishSubject.create(); + } + + Method m = Observable.class.getMethod("amb", clazz); + + Observable obs = (Observable)m.invoke(null, (Object[])ps); + + TestSubscriber ts = TestSubscriber.create(); + + obs.subscribe(ts); + + for (int k = 0; k < i; k++) { + assertTrue("@" + i + "/" + k + " has no observers?", ps[k].hasObservers()); + } + + ps[j].onCompleted(); + + for (int k = 0; k < i; k++) { + assertFalse("@" + i + "/" + k + " has observers?", ps[k].hasObservers()); + } + + ts.assertNoValues(); + ts.assertNoErrors(); + ts.assertCompleted(); + } + } + } } diff --git a/src/test/java/rx/internal/operators/OnSubscribeCollectTest.java b/src/test/java/rx/internal/operators/OnSubscribeCollectTest.java new file mode 100644 index 0000000000..75d6b51954 --- /dev/null +++ b/src/test/java/rx/internal/operators/OnSubscribeCollectTest.java @@ -0,0 +1,252 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * 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 rx.internal.operators; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.junit.Test; + +import rx.Observable; +import rx.Producer; +import rx.Subscriber; +import rx.Observable.OnSubscribe; +import rx.functions.Action1; +import rx.functions.Action2; +import rx.functions.Func0; +import rx.observers.TestSubscriber; +import rx.plugins.RxJavaHooks; + +public class OnSubscribeCollectTest { + + @Test + public void testCollectToList() { + Observable> o = Observable.just(1, 2, 3).collect(new Func0>() { + + @Override + public List call() { + return new ArrayList(); + } + + }, new Action2, Integer>() { + + @Override + public void call(List list, Integer v) { + list.add(v); + } + }); + + List list = o.toBlocking().last(); + + assertEquals(3, list.size()); + assertEquals(1, list.get(0).intValue()); + assertEquals(2, list.get(1).intValue()); + assertEquals(3, list.get(2).intValue()); + + // test multiple subscribe + List list2 = o.toBlocking().last(); + + assertEquals(3, list2.size()); + assertEquals(1, list2.get(0).intValue()); + assertEquals(2, list2.get(1).intValue()); + assertEquals(3, list2.get(2).intValue()); + } + + @Test + public void testCollectToString() { + String value = Observable.just(1, 2, 3).collect(new Func0() { + + @Override + public StringBuilder call() { + return new StringBuilder(); + } + + }, new Action2() { + + @Override + public void call(StringBuilder sb, Integer v) { + if (sb.length() > 0) { + sb.append("-"); + } + sb.append(v); + } + }).toBlocking().last().toString(); + + assertEquals("1-2-3", value); + } + + @Test + public void testFactoryFailureResultsInErrorEmission() { + TestSubscriber ts = TestSubscriber.create(); + final RuntimeException e = new RuntimeException(); + Observable.just(1).collect(new Func0>() { + + @Override + public List call() { + throw e; + } + }, new Action2, Integer>() { + + @Override + public void call(List list, Integer t) { + list.add(t); + } + }).subscribe(ts); + ts.assertNoValues(); + ts.assertError(e); + ts.assertNotCompleted(); + } + + @Test + public void testCollectorFailureDoesNotResultInTwoErrorEmissions() { + try { + final List list = new CopyOnWriteArrayList(); + RxJavaHooks.setOnError(new Action1() { + + @Override + public void call(Throwable t) { + list.add(t); + } + }); + final RuntimeException e1 = new RuntimeException(); + final RuntimeException e2 = new RuntimeException(); + TestSubscriber> ts = TestSubscriber.create(); + Observable.unsafeCreate(new OnSubscribe() { + + @Override + public void call(final Subscriber sub) { + sub.setProducer(new Producer() { + + @Override + public void request(long n) { + if (n > 0) { + sub.onNext(1); + sub.onError(e2); + } + } + }); + } + }).collect(new Func0>() { + + @Override + public List call() { + return new ArrayList(); + } + }, // + new Action2, Integer>() { + + @Override + public void call(List t1, Integer t2) { + throw e1; + } + }).unsafeSubscribe(ts); + assertEquals(Arrays.asList(e1), ts.getOnErrorEvents()); + ts.assertNotCompleted(); + assertEquals(Arrays.asList(e2), list); + } finally { + RxJavaHooks.reset(); + } + } + + @Test + public void testCollectorFailureDoesNotResultInErrorAndCompletedEmissions() { + final RuntimeException e1 = new RuntimeException(); + TestSubscriber> ts = TestSubscriber.create(); + Observable.unsafeCreate(new OnSubscribe() { + + @Override + public void call(final Subscriber sub) { + sub.setProducer(new Producer() { + + @Override + public void request(long n) { + if (n > 0) { + sub.onNext(1); + sub.onCompleted(); + } + } + }); + } + }).collect(new Func0>() { + + @Override + public List call() { + return new ArrayList(); + } + }, // + new Action2, Integer>() { + + @Override + public void call(List t1, Integer t2) { + throw e1; + } + }).unsafeSubscribe(ts); + assertEquals(Arrays.asList(e1), ts.getOnErrorEvents()); + ts.assertNotCompleted(); + } + + @Test + public void testCollectorFailureDoesNotResultInErrorAndOnNextEmissions() { + final RuntimeException e1 = new RuntimeException(); + TestSubscriber> ts = TestSubscriber.create(); + final AtomicBoolean added = new AtomicBoolean(); + Observable.unsafeCreate(new OnSubscribe() { + + @Override + public void call(final Subscriber sub) { + sub.setProducer(new Producer() { + + @Override + public void request(long n) { + if (n > 0) { + sub.onNext(1); + sub.onNext(2); + } + } + }); + } + }).collect(new Func0>() { + + @Override + public List call() { + return new ArrayList(); + } + }, // + new Action2, Integer>() { + boolean once = true; + @Override + public void call(List list, Integer t) { + if (once) { + once = false; + throw e1; + } else { + added.set(true); + } + } + }).unsafeSubscribe(ts); + assertEquals(Arrays.asList(e1), ts.getOnErrorEvents()); + ts.assertNoValues(); + ts.assertNotCompleted(); + assertFalse(added.get()); + } + +} diff --git a/src/test/java/rx/internal/operators/OnSubscribeCombineLatestTest.java b/src/test/java/rx/internal/operators/OnSubscribeCombineLatestTest.java index 840077af59..b5ebd07e5e 100644 --- a/src/test/java/rx/internal/operators/OnSubscribeCombineLatestTest.java +++ b/src/test/java/rx/internal/operators/OnSubscribeCombineLatestTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -773,7 +773,7 @@ public void testBackpressureLoop() { testBackpressure(); } } - + @Test public void testBackpressure() { Func2 combineLatestFunction = getConcatStringIntegerCombineLatestFunction(); @@ -830,8 +830,8 @@ public Long call(Long t1, Integer t2) { assertEquals(SIZE, count.get()); } - - @Test(timeout=10000) + + @Test(timeout = 10000) public void testCombineLatestRequestOverflow() throws InterruptedException { @SuppressWarnings("unchecked") List> sources = Arrays.asList(Observable.from(Arrays.asList(1,2,3,4)), Observable.from(Arrays.asList(5,6,7,8))); @@ -843,7 +843,7 @@ public Integer call(Object... args) { //should get at least 4 final CountDownLatch latch = new CountDownLatch(4); o.subscribeOn(Schedulers.computation()).subscribe(new Subscriber() { - + @Override public void onStart() { request(2); @@ -862,7 +862,7 @@ public void onError(Throwable e) { @Override public void onNext(Integer t) { latch.countDown(); - request(Long.MAX_VALUE-1); + request(Long.MAX_VALUE - 1); }}); assertTrue(latch.await(10, TimeUnit.SECONDS)); } @@ -870,18 +870,18 @@ public void onNext(Integer t) { @Test public void testCombineMany() { int n = RxRingBuffer.SIZE * 3; - + List> sources = new ArrayList>(); - + StringBuilder expected = new StringBuilder(n * 2); - + for (int i = 0; i < n; i++) { sources.add(Observable.just(i)); expected.append(i); } - + TestSubscriber ts = TestSubscriber.create(); - + Observable.combineLatest(sources, new FuncN() { @Override public String call(Object... args) { @@ -892,26 +892,26 @@ public String call(Object... args) { return b.toString(); } }).subscribe(ts); - + ts.assertNoErrors(); ts.assertValue(expected.toString()); ts.assertCompleted(); } - + @Test public void testCombineManyNulls() { int n = RxRingBuffer.SIZE * 3; - + Observable source = Observable.just((Integer)null); - + List> sources = new ArrayList>(); - + for (int i = 0; i < n; i++) { sources.add(source); } - + TestSubscriber ts = TestSubscriber.create(); - + Observable.combineLatest(sources, new FuncN() { @Override public Integer call(Object... args) { @@ -924,7 +924,7 @@ public Integer call(Object... args) { return sum; } }).subscribe(ts); - + ts.assertValue(n); ts.assertNoErrors(); ts.assertCompleted(); @@ -948,7 +948,7 @@ public void call(Throwable t) { .subscribe(ts); assertFalse(errorOccurred.get()); } - + private static final FuncN THROW_NON_FATAL = new FuncN() { @Override public Integer call(Object... args) { @@ -956,12 +956,12 @@ public Integer call(Object... args) { } }; - + @SuppressWarnings("unchecked") @Test public void firstJustError() { TestSubscriber ts = TestSubscriber.create(); - + Observable.combineLatestDelayError( Arrays.asList(Observable.just(1), Observable.error(new TestException())), new FuncN() { @@ -971,7 +971,7 @@ public Integer call(Object... args) { } } ).subscribe(ts); - + ts.assertNoValues(); ts.assertError(TestException.class); ts.assertNotCompleted(); @@ -981,7 +981,7 @@ public Integer call(Object... args) { @Test public void secondJustError() { TestSubscriber ts = TestSubscriber.create(); - + Observable.combineLatestDelayError( Arrays.asList(Observable.error(new TestException()), Observable.just(1)), new FuncN() { @@ -991,7 +991,7 @@ public Integer call(Object... args) { } } ).subscribe(ts); - + ts.assertNoValues(); ts.assertError(TestException.class); ts.assertNotCompleted(); @@ -1001,7 +1001,7 @@ public Integer call(Object... args) { @Test public void oneErrors() { TestSubscriber ts = TestSubscriber.create(); - + Observable.combineLatestDelayError( Arrays.asList(Observable.just(10).concatWith(Observable.error(new TestException())), Observable.just(1)), new FuncN() { @@ -1011,7 +1011,7 @@ public Integer call(Object... args) { } } ).subscribe(ts); - + ts.assertValues(11); ts.assertError(TestException.class); ts.assertNotCompleted(); @@ -1021,7 +1021,7 @@ public Integer call(Object... args) { @Test public void twoErrors() { TestSubscriber ts = TestSubscriber.create(); - + Observable.combineLatestDelayError( Arrays.asList(Observable.just(1), Observable.just(10).concatWith(Observable.error(new TestException()))), new FuncN() { @@ -1031,7 +1031,7 @@ public Integer call(Object... args) { } } ).subscribe(ts); - + ts.assertValues(11); ts.assertError(TestException.class); ts.assertNotCompleted(); @@ -1041,9 +1041,9 @@ public Integer call(Object... args) { @Test public void bothError() { TestSubscriber ts = TestSubscriber.create(); - + Observable.combineLatestDelayError( - Arrays.asList(Observable.just(1).concatWith(Observable.error(new TestException())), + Arrays.asList(Observable.just(1).concatWith(Observable.error(new TestException())), Observable.just(10).concatWith(Observable.error(new TestException()))), new FuncN() { @Override @@ -1052,10 +1052,29 @@ public Integer call(Object... args) { } } ).subscribe(ts); - + ts.assertValues(11); ts.assertError(CompositeException.class); ts.assertNotCompleted(); } + @SuppressWarnings("unchecked") + @Test + public void combineLatestIterable() { + Observable source = Observable.just(1); + + TestSubscriber ts = TestSubscriber.create(); + + Observable.combineLatest((Iterable>)Arrays.asList(source, source), new FuncN() { + @Override + public Integer call(Object... args) { + return (Integer)args[0] + (Integer)args[1]; + } + }) + .subscribe(ts); + + ts.assertValue(2); + ts.assertNoErrors(); + ts.assertCompleted(); + } } diff --git a/src/test/java/rx/internal/operators/OnSubscribeConcatDelayErrorTest.java b/src/test/java/rx/internal/operators/OnSubscribeConcatDelayErrorTest.java index b61630f837..c51a7877cd 100644 --- a/src/test/java/rx/internal/operators/OnSubscribeConcatDelayErrorTest.java +++ b/src/test/java/rx/internal/operators/OnSubscribeConcatDelayErrorTest.java @@ -16,6 +16,10 @@ package rx.internal.operators; +import static org.junit.Assert.assertEquals; + +import java.util.Arrays; + import org.junit.Test; import rx.Observable; @@ -29,20 +33,20 @@ public class OnSubscribeConcatDelayErrorTest { @Test public void mainCompletes() { PublishSubject source = PublishSubject.create(); - + TestSubscriber ts = TestSubscriber.create(); - + source.concatMapDelayError(new Func1>() { @Override public Observable call(Integer v) { return Observable.range(v, 2); } }).subscribe(ts); - + source.onNext(1); source.onNext(2); source.onCompleted(); - + ts.assertValues(1, 2, 2, 3); ts.assertNoErrors(); ts.assertCompleted(); @@ -51,29 +55,29 @@ public Observable call(Integer v) { @Test public void mainErrors() { PublishSubject source = PublishSubject.create(); - + TestSubscriber ts = TestSubscriber.create(); - + source.concatMapDelayError(new Func1>() { @Override public Observable call(Integer v) { return Observable.range(v, 2); } }).subscribe(ts); - + source.onNext(1); source.onNext(2); source.onError(new TestException()); - + ts.assertValues(1, 2, 2, 3); ts.assertError(TestException.class); ts.assertNotCompleted(); } - + @Test public void innerErrors() { final Observable inner = Observable.range(1, 2).concatWith(Observable.error(new TestException())); - + TestSubscriber ts = TestSubscriber.create(); Observable.range(1, 3).concatMapDelayError(new Func1>() { @@ -82,7 +86,7 @@ public Observable call(Integer v) { return inner; } }).subscribe(ts); - + ts.assertValues(1, 2, 1, 2, 1, 2); ts.assertError(CompositeException.class); ts.assertNotCompleted(); @@ -91,7 +95,7 @@ public Observable call(Integer v) { @Test public void singleInnerErrors() { final Observable inner = Observable.range(1, 2).concatWith(Observable.error(new TestException())); - + TestSubscriber ts = TestSubscriber.create(); Observable.just(1) @@ -102,7 +106,7 @@ public Observable call(Integer v) { return inner; } }).subscribe(ts); - + ts.assertValues(1, 2); ts.assertError(TestException.class); ts.assertNotCompleted(); @@ -120,7 +124,7 @@ public Observable call(Integer v) { return null; } }).subscribe(ts); - + ts.assertNoValues(); ts.assertError(NullPointerException.class); ts.assertNotCompleted(); @@ -138,7 +142,7 @@ public Observable call(Integer v) { throw new TestException(); } }).subscribe(ts); - + ts.assertNoValues(); ts.assertError(TestException.class); ts.assertNotCompleted(); @@ -155,7 +159,7 @@ public Observable call(Integer v) { return v == 2 ? Observable.empty() : Observable.range(1, 2); } }).subscribe(ts); - + ts.assertValues(1, 2, 1, 2); ts.assertNoErrors(); ts.assertCompleted(); @@ -172,7 +176,7 @@ public Observable call(Integer v) { return v == 2 ? Observable.just(3) : Observable.range(1, 2); } }).subscribe(ts); - + ts.assertValues(1, 2, 3, 1, 2); ts.assertNoErrors(); ts.assertCompleted(); @@ -181,7 +185,7 @@ public Observable call(Integer v) { @Test public void backpressure() { TestSubscriber ts = TestSubscriber.create(0); - + Observable.range(1, 3).concatMapDelayError(new Func1>() { @Override public Observable call(Integer v) { @@ -192,7 +196,7 @@ public Observable call(Integer v) { ts.assertNoValues(); ts.assertNoErrors(); ts.assertNotCompleted(); - + ts.requestMore(1); ts.assertValues(1); ts.assertNoErrors(); @@ -204,10 +208,370 @@ public Observable call(Integer v) { ts.assertNotCompleted(); ts.requestMore(2); - + ts.assertValues(1, 2, 2, 3, 3, 4); ts.assertNoErrors(); ts.assertCompleted(); } + static Observable withError(Observable source) { + return source.concatWith(Observable.error(new TestException())); + } + + + @Test + public void concatDelayErrorObservable() { + TestSubscriber ts = TestSubscriber.create(); + + Observable.concatDelayError( + Observable.just(Observable.just(1), Observable.just(2))) + .subscribe(ts); + + ts.assertValues(1, 2); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @Test + public void concatDelayErrorObservableError() { + TestSubscriber ts = TestSubscriber.create(); + + Observable.concatDelayError( + withError(Observable.just(withError(Observable.just(1)), withError(Observable.just(2))))) + .subscribe(ts); + + ts.assertValues(1, 2); + ts.assertError(CompositeException.class); + ts.assertNotCompleted(); + + assertEquals(3, ((CompositeException)ts.getOnErrorEvents().get(0)).getExceptions().size()); + } + + @SuppressWarnings("unchecked") + @Test + public void concatDelayErrorIterable() { + TestSubscriber ts = TestSubscriber.create(); + + Observable.concatDelayError( + Arrays.asList(Observable.just(1), Observable.just(2))) + .subscribe(ts); + + ts.assertValues(1, 2); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @SuppressWarnings("unchecked") + @Test + public void concatDelayErrorIterableError() { + TestSubscriber ts = TestSubscriber.create(); + + Observable.concatDelayError( + Arrays.asList(withError(Observable.just(1)), withError(Observable.just(2)))) + .subscribe(ts); + + ts.assertValues(1, 2); + ts.assertError(CompositeException.class); + ts.assertNotCompleted(); + + assertEquals(2, ((CompositeException)ts.getOnErrorEvents().get(0)).getExceptions().size()); + } + + @Test + public void concatDelayError2() { + TestSubscriber ts = TestSubscriber.create(); + Observable o1 = Observable.just(1); + Observable o2 = Observable.just(2); + + Observable.concatDelayError(o1, o2) + .subscribe(ts); + + ts.assertValues(1, 2); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @Test + public void concatDelayError2Error() { + TestSubscriber ts = TestSubscriber.create(); + Observable withError1 = withError(Observable.just(1)); + Observable withError2 = withError(Observable.just(2)); + + Observable.concatDelayError(withError1, withError2) + .subscribe(ts); + + ts.assertValues(1, 2); + ts.assertError(CompositeException.class); + ts.assertNotCompleted(); + + assertEquals(2, ((CompositeException)ts.getOnErrorEvents().get(0)).getExceptions().size()); + } + + @Test + public void concatDelayError3() { + TestSubscriber ts = TestSubscriber.create(); + Observable o1 = Observable.just(1); + Observable o2 = Observable.just(2); + Observable o3 = Observable.just(3); + + Observable.concatDelayError(o1, o2, o3) + .subscribe(ts); + + ts.assertValues(1, 2, 3); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @Test + public void concatDelayError3Error() { + TestSubscriber ts = TestSubscriber.create(); + Observable withError1 = withError(Observable.just(1)); + Observable withError2 = withError(Observable.just(2)); + Observable withError3 = withError(Observable.just(3)); + + Observable.concatDelayError(withError1, withError2, withError3) + .subscribe(ts); + + ts.assertValues(1, 2, 3); + ts.assertError(CompositeException.class); + ts.assertNotCompleted(); + + assertEquals(3, ((CompositeException)ts.getOnErrorEvents().get(0)).getExceptions().size()); + } + + @Test + public void concatDelayError4() { + TestSubscriber ts = TestSubscriber.create(); + Observable o1 = Observable.just(1); + Observable o2 = Observable.just(2); + Observable o3 = Observable.just(3); + Observable o4 = Observable.just(4); + + Observable.concatDelayError(o1, o2, o3, o4) + .subscribe(ts); + + ts.assertValues(1, 2, 3, 4); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @Test + public void concatDelayError4Error() { + TestSubscriber ts = TestSubscriber.create(); + Observable withError1 = withError(Observable.just(1)); + Observable withError2 = withError(Observable.just(2)); + Observable withError3 = withError(Observable.just(3)); + Observable withError4 = withError(Observable.just(4)); + + Observable.concatDelayError(withError1, withError2, withError3, withError4) + .subscribe(ts); + + ts.assertValues(1, 2, 3, 4); + ts.assertError(CompositeException.class); + ts.assertNotCompleted(); + + assertEquals(4, ((CompositeException)ts.getOnErrorEvents().get(0)).getExceptions().size()); + } + + @Test + public void concatDelayError5() { + TestSubscriber ts = TestSubscriber.create(); + Observable o1 = Observable.just(1); + Observable o2 = Observable.just(2); + Observable o3 = Observable.just(3); + Observable o4 = Observable.just(4); + Observable o5 = Observable.just(5); + + Observable.concatDelayError(o1, o2, o3, o4, o5) + .subscribe(ts); + + ts.assertValues(1, 2, 3, 4, 5); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @Test + public void concatDelayError5Error() { + TestSubscriber ts = TestSubscriber.create(); + Observable withError1 = withError(Observable.just(1)); + Observable withError2 = withError(Observable.just(2)); + Observable withError3 = withError(Observable.just(3)); + Observable withError4 = withError(Observable.just(4)); + Observable withError5 = withError(Observable.just(5)); + + Observable.concatDelayError(withError1, withError2, withError3, withError4, withError5) + .subscribe(ts); + + ts.assertValues(1, 2, 3, 4, 5); + ts.assertError(CompositeException.class); + ts.assertNotCompleted(); + + assertEquals(5, ((CompositeException)ts.getOnErrorEvents().get(0)).getExceptions().size()); + } + + @Test + public void concatDelayError6() { + TestSubscriber ts = TestSubscriber.create(); + Observable o1 = Observable.just(1); + Observable o2 = Observable.just(2); + Observable o3 = Observable.just(3); + Observable o4 = Observable.just(4); + Observable o5 = Observable.just(5); + Observable o6 = Observable.just(6); + + Observable.concatDelayError(o1, o2, o3, o4, o5, o6) + .subscribe(ts); + + ts.assertValues(1, 2, 3, 4, 5, 6); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @Test + public void concatDelayError6Error() { + TestSubscriber ts = TestSubscriber.create(); + Observable withError1 = withError(Observable.just(1)); + Observable withError2 = withError(Observable.just(2)); + Observable withError3 = withError(Observable.just(3)); + Observable withError4 = withError(Observable.just(4)); + Observable withError5 = withError(Observable.just(5)); + Observable withError6 = withError(Observable.just(6)); + + Observable.concatDelayError(withError1, withError2, withError3, withError4, withError5, withError6) + .subscribe(ts); + + ts.assertValues(1, 2, 3, 4, 5, 6); + ts.assertError(CompositeException.class); + ts.assertNotCompleted(); + + assertEquals(6, ((CompositeException)ts.getOnErrorEvents().get(0)).getExceptions().size()); + } + + @Test + public void concatDelayError7() { + TestSubscriber ts = TestSubscriber.create(); + Observable o1 = Observable.just(1); + Observable o2 = Observable.just(2); + Observable o3 = Observable.just(3); + Observable o4 = Observable.just(4); + Observable o5 = Observable.just(5); + Observable o6 = Observable.just(6); + Observable o7 = Observable.just(7); + + Observable.concatDelayError(o1, o2, o3, o4, o5, o6, o7) + .subscribe(ts); + + ts.assertValues(1, 2, 3, 4, 5, 6, 7); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @Test + public void concatDelayError7Error() { + TestSubscriber ts = TestSubscriber.create(); + Observable withError1 = withError(Observable.just(1)); + Observable withError2 = withError(Observable.just(2)); + Observable withError3 = withError(Observable.just(3)); + Observable withError4 = withError(Observable.just(4)); + Observable withError5 = withError(Observable.just(5)); + Observable withError6 = withError(Observable.just(6)); + Observable withError7 = withError(Observable.just(7)); + + Observable.concatDelayError(withError1, withError2, withError3, withError4, withError5, withError6, withError7) + .subscribe(ts); + + ts.assertValues(1, 2, 3, 4, 5, 6, 7); + ts.assertError(CompositeException.class); + ts.assertNotCompleted(); + + assertEquals(7, ((CompositeException)ts.getOnErrorEvents().get(0)).getExceptions().size()); + } + + @Test + public void concatDelayError8() { + TestSubscriber ts = TestSubscriber.create(); + Observable o1 = Observable.just(1); + Observable o2 = Observable.just(2); + Observable o3 = Observable.just(3); + Observable o4 = Observable.just(4); + Observable o5 = Observable.just(5); + Observable o6 = Observable.just(6); + Observable o7 = Observable.just(7); + Observable o8 = Observable.just(8); + + + Observable.concatDelayError(o1, o2, o3, o4, o5, o6, o7, o8) + .subscribe(ts); + + ts.assertValues(1, 2, 3, 4, 5, 6, 7, 8); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @Test + public void concatDelayError8Error() { + TestSubscriber ts = TestSubscriber.create(); + Observable withError1 = withError(Observable.just(1)); + Observable withError2 = withError(Observable.just(2)); + Observable withError3 = withError(Observable.just(3)); + Observable withError4 = withError(Observable.just(4)); + Observable withError5 = withError(Observable.just(5)); + Observable withError6 = withError(Observable.just(6)); + Observable withError7 = withError(Observable.just(7)); + Observable withError8 = withError(Observable.just(8)); + + Observable.concatDelayError(withError1, withError2, withError3, withError4, withError5, withError6, withError7, withError8) + .subscribe(ts); + + ts.assertValues(1, 2, 3, 4, 5, 6, 7, 8); + ts.assertError(CompositeException.class); + ts.assertNotCompleted(); + + assertEquals(8, ((CompositeException)ts.getOnErrorEvents().get(0)).getExceptions().size()); + } + + @Test + public void concatDelayError9() { + TestSubscriber ts = TestSubscriber.create(); + Observable o1 = Observable.just(1); + Observable o2 = Observable.just(2); + Observable o3 = Observable.just(3); + Observable o4 = Observable.just(4); + Observable o5 = Observable.just(5); + Observable o6 = Observable.just(6); + Observable o7 = Observable.just(7); + Observable o8 = Observable.just(8); + Observable o9 = Observable.just(9); + + + Observable.concatDelayError(o1, o2, o3, o4, o5, o6, o7, o8, o9) + .subscribe(ts); + + ts.assertValues(1, 2, 3, 4, 5, 6, 7, 8, 9); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @Test + public void concatDelayError9Error() { + TestSubscriber ts = TestSubscriber.create(); + Observable withError1 = withError(Observable.just(1)); + Observable withError2 = withError(Observable.just(2)); + Observable withError3 = withError(Observable.just(3)); + Observable withError4 = withError(Observable.just(4)); + Observable withError5 = withError(Observable.just(5)); + Observable withError6 = withError(Observable.just(6)); + Observable withError7 = withError(Observable.just(7)); + Observable withError8 = withError(Observable.just(8)); + Observable withError9 = withError(Observable.just(9)); + + Observable.concatDelayError(withError1, withError2, withError3, withError4, withError5, withError6, withError7, withError8, withError9) + .subscribe(ts); + + ts.assertValues(1, 2, 3, 4, 5, 6, 7, 8, 9); + ts.assertError(CompositeException.class); + ts.assertNotCompleted(); + + assertEquals(9, ((CompositeException)ts.getOnErrorEvents().get(0)).getExceptions().size()); + } } diff --git a/src/test/java/rx/internal/operators/OnSubscribeCreateTest.java b/src/test/java/rx/internal/operators/OnSubscribeCreateTest.java new file mode 100644 index 0000000000..2d8e5f843c --- /dev/null +++ b/src/test/java/rx/internal/operators/OnSubscribeCreateTest.java @@ -0,0 +1,804 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * 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 rx.internal.operators; + +import static org.junit.Assert.assertEquals; + +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +import org.junit.*; + +import rx.*; +import rx.exceptions.*; +import rx.functions.Action1; +import rx.functions.Cancellable; +import rx.observers.TestSubscriber; +import rx.plugins.RxJavaHooks; +import rx.subjects.PublishSubject; + +public class OnSubscribeCreateTest { + + PublishEmitter source; + + PublishEmitterNoCancel sourceNoCancel; + + TestSubscriber ts; + + @Before + public void before() { + source = new PublishEmitter(); + sourceNoCancel = new PublishEmitterNoCancel(); + ts = TestSubscriber.create(0L); + } + + @Test + public void normalBuffered() { + Observable.create(source, Emitter.BackpressureMode.BUFFER).subscribe(ts); + + source.onNext(1); + source.onNext(2); + source.onCompleted(); + + ts.requestMore(1); + + ts.assertValue(1); + + Assert.assertEquals(0, source.requested()); + + ts.requestMore(1); + + ts.assertValues(1, 2); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @Test + public void normalDrop() { + Observable.create(source, Emitter.BackpressureMode.DROP).subscribe(ts); + + source.onNext(1); + + ts.requestMore(1); + + source.onNext(2); + source.onCompleted(); + + ts.assertValues(2); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @Test + public void normalLatest() { + Observable.create(source, Emitter.BackpressureMode.LATEST).subscribe(ts); + + source.onNext(1); + + source.onNext(2); + source.onCompleted(); + + ts.requestMore(1); + + ts.assertValues(2); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @Test + public void normalNone() { + Observable.create(source, Emitter.BackpressureMode.NONE).subscribe(ts); + + source.onNext(1); + source.onNext(2); + source.onCompleted(); + + ts.assertValues(1, 2); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @Test + public void normalNoneRequested() { + Observable.create(source, Emitter.BackpressureMode.NONE).subscribe(ts); + ts.requestMore(2); + + source.onNext(1); + source.onNext(2); + source.onCompleted(); + + ts.assertValues(1, 2); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + + @Test + public void normalError() { + Observable.create(source, Emitter.BackpressureMode.ERROR).subscribe(ts); + + source.onNext(1); + source.onNext(2); + source.onCompleted(); + + ts.assertNoValues(); + ts.assertError(MissingBackpressureException.class); + ts.assertNotCompleted(); + + Assert.assertEquals("create: could not emit value due to lack of requests", ts.getOnErrorEvents().get(0).getMessage()); + } + + @Test + public void overflowErrorIsNotFollowedByAnotherErrorDueToOnNextFromUpstream() { + Action1> source = new Action1>() { + + @Override + public void call(Emitter emitter) { + emitter.onNext(1); + //don't check for unsubscription + emitter.onNext(2); + }}; + Observable.create(source, Emitter.BackpressureMode.ERROR).unsafeSubscribe(ts); + + ts.assertNoValues(); + ts.assertError(MissingBackpressureException.class); + ts.assertNotCompleted(); + + Assert.assertEquals("create: could not emit value due to lack of requests", ts.getOnErrorEvents().get(0).getMessage()); + } + + @Test + public void overflowErrorIsNotFollowedByAnotherCompletedDueToCompletedFromUpstream() { + Action1> source = new Action1>() { + + @Override + public void call(Emitter emitter) { + emitter.onNext(1); + //don't check for unsubscription + emitter.onCompleted(); + }}; + Observable.create(source, Emitter.BackpressureMode.ERROR).unsafeSubscribe(ts); + + ts.assertNoValues(); + ts.assertError(MissingBackpressureException.class); + ts.assertNotCompleted(); + + Assert.assertEquals("create: could not emit value due to lack of requests", ts.getOnErrorEvents().get(0).getMessage()); + } + + @Test + public void overflowErrorIsNotFollowedByAnotherErrorDueToOnErrorFromUpstreamAndSecondErrorIsReportedToHook() { + try { + final List list = new CopyOnWriteArrayList(); + RxJavaHooks.setOnError(new Action1() { + @Override + public void call(Throwable t) { + list.add(t); + }}); + final RuntimeException e = new RuntimeException(); + Action1> source = new Action1>() { + + @Override + public void call(Emitter emitter) { + emitter.onNext(1); + //don't check for unsubscription + emitter.onError(e); + }}; + Observable.create(source, Emitter.BackpressureMode.ERROR).unsafeSubscribe(ts); + + ts.assertNoValues(); + ts.assertError(MissingBackpressureException.class); + ts.assertNotCompleted(); + + Assert.assertEquals("create: could not emit value due to lack of requests", ts.getOnErrorEvents().get(0).getMessage()); + assertEquals(Arrays.asList(e), list); + } finally { + RxJavaHooks.reset(); + } + } + + @Test + public void errorBuffered() { + Observable.create(source, Emitter.BackpressureMode.BUFFER).subscribe(ts); + + source.onNext(1); + source.onNext(2); + source.onError(new TestException()); + + ts.requestMore(1); + + ts.assertValue(1); + + ts.requestMore(1); + + ts.assertValues(1, 2); + ts.assertError(TestException.class); + ts.assertNotCompleted(); + } + + @Test + public void errorLatest() { + Observable.create(source, Emitter.BackpressureMode.LATEST).subscribe(ts); + + source.onNext(1); + source.onNext(2); + source.onError(new TestException()); + + ts.requestMore(1); + + ts.assertValues(2); + ts.assertError(TestException.class); + ts.assertNotCompleted(); + } + + @Test + public void errorNone() { + Observable.create(source, Emitter.BackpressureMode.NONE).subscribe(ts); + + source.onNext(1); + source.onNext(2); + source.onError(new TestException()); + + ts.requestMore(1); + + ts.assertValues(1, 2); + ts.assertError(TestException.class); + ts.assertNotCompleted(); + } + + @Test + public void unsubscribedBuffer() { + Observable.create(source, Emitter.BackpressureMode.BUFFER).subscribe(ts); + ts.unsubscribe(); + + source.onNext(1); + source.onNext(2); + source.onError(new TestException()); + + ts.requestMore(1); + + ts.assertNoValues(); + ts.assertNoErrors(); + ts.assertNotCompleted(); + } + + @Test + public void unsubscribedLatest() { + Observable.create(source, Emitter.BackpressureMode.LATEST).subscribe(ts); + ts.unsubscribe(); + + source.onNext(1); + source.onNext(2); + source.onError(new TestException()); + + ts.requestMore(1); + + ts.assertNoValues(); + ts.assertNoErrors(); + ts.assertNotCompleted(); + } + + @Test + public void unsubscribedError() { + Observable.create(source, Emitter.BackpressureMode.ERROR).subscribe(ts); + ts.unsubscribe(); + + source.onNext(1); + source.onNext(2); + source.onError(new TestException()); + + ts.requestMore(1); + + ts.assertNoValues(); + ts.assertNoErrors(); + ts.assertNotCompleted(); + } + + @Test + public void unsubscribedDrop() { + Observable.create(source, Emitter.BackpressureMode.DROP).subscribe(ts); + ts.unsubscribe(); + + source.onNext(1); + source.onNext(2); + source.onError(new TestException()); + + ts.requestMore(1); + + ts.assertNoValues(); + ts.assertNoErrors(); + ts.assertNotCompleted(); + } + + @Test + public void unsubscribedNone() { + Observable.create(source, Emitter.BackpressureMode.NONE).subscribe(ts); + ts.unsubscribe(); + + source.onNext(1); + source.onNext(2); + source.onError(new TestException()); + + ts.requestMore(1); + + ts.assertNoValues(); + ts.assertNoErrors(); + ts.assertNotCompleted(); + } + + @Test + public void unsubscribedNoCancelBuffer() { + Observable.create(sourceNoCancel, Emitter.BackpressureMode.BUFFER).subscribe(ts); + ts.unsubscribe(); + + sourceNoCancel.onNext(1); + sourceNoCancel.onNext(2); + sourceNoCancel.onError(new TestException()); + + ts.requestMore(1); + + ts.assertNoValues(); + ts.assertNoErrors(); + ts.assertNotCompleted(); + } + + @Test + public void unsubscribedNoCancelLatest() { + Observable.create(sourceNoCancel, Emitter.BackpressureMode.LATEST).subscribe(ts); + ts.unsubscribe(); + + sourceNoCancel.onNext(1); + sourceNoCancel.onNext(2); + sourceNoCancel.onError(new TestException()); + + ts.requestMore(1); + + ts.assertNoValues(); + ts.assertNoErrors(); + ts.assertNotCompleted(); + } + + @Test + public void unsubscribedNoCancelError() { + Observable.create(sourceNoCancel, Emitter.BackpressureMode.ERROR).subscribe(ts); + ts.unsubscribe(); + + sourceNoCancel.onNext(1); + sourceNoCancel.onNext(2); + sourceNoCancel.onError(new TestException()); + + ts.requestMore(1); + + ts.assertNoValues(); + ts.assertNoErrors(); + ts.assertNotCompleted(); + } + + @Test + public void unsubscribedNoCancelDrop() { + Observable.create(sourceNoCancel, Emitter.BackpressureMode.DROP).subscribe(ts); + ts.unsubscribe(); + + sourceNoCancel.onNext(1); + sourceNoCancel.onNext(2); + sourceNoCancel.onError(new TestException()); + + ts.requestMore(1); + + ts.assertNoValues(); + ts.assertNoErrors(); + ts.assertNotCompleted(); + } + + @Test + public void unsubscribedNoCancelNone() { + Observable.create(sourceNoCancel, Emitter.BackpressureMode.NONE).subscribe(ts); + ts.unsubscribe(); + + sourceNoCancel.onNext(1); + sourceNoCancel.onNext(2); + sourceNoCancel.onError(new TestException()); + + ts.requestMore(1); + + ts.assertNoValues(); + ts.assertNoErrors(); + ts.assertNotCompleted(); + } + + @Test + public void deferredRequest() { + Observable.create(source, Emitter.BackpressureMode.BUFFER).subscribe(ts); + + source.onNext(1); + source.onNext(2); + source.onCompleted(); + + ts.requestMore(2); + + ts.assertValues(1, 2); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @Test + public void take() { + Observable.create(source, Emitter.BackpressureMode.BUFFER).take(2).subscribe(ts); + + source.onNext(1); + source.onNext(2); + source.onCompleted(); + + ts.requestMore(2); + + ts.assertValues(1, 2); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @Test + public void takeOne() { + Observable.create(source, Emitter.BackpressureMode.BUFFER).take(1).subscribe(ts); + ts.requestMore(2); + + source.onNext(1); + source.onNext(2); + source.onCompleted(); + + ts.assertValues(1); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @Test + public void requestExact() { + Observable.create(source, Emitter.BackpressureMode.BUFFER).subscribe(ts); + ts.requestMore(2); + + source.onNext(1); + source.onNext(2); + source.onCompleted(); + + ts.assertValues(1, 2); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @Test + public void takeNoCancel() { + Observable.create(sourceNoCancel, Emitter.BackpressureMode.BUFFER).take(2).subscribe(ts); + + sourceNoCancel.onNext(1); + sourceNoCancel.onNext(2); + sourceNoCancel.onCompleted(); + + ts.requestMore(2); + + ts.assertValues(1, 2); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @Test + public void takeOneNoCancel() { + Observable.create(sourceNoCancel, Emitter.BackpressureMode.BUFFER).take(1).subscribe(ts); + ts.requestMore(2); + + sourceNoCancel.onNext(1); + sourceNoCancel.onNext(2); + sourceNoCancel.onCompleted(); + + ts.assertValues(1); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @Test + public void unsubscribeNoCancel() { + Observable.create(sourceNoCancel, Emitter.BackpressureMode.BUFFER).subscribe(ts); + ts.requestMore(2); + + sourceNoCancel.onNext(1); + + ts.unsubscribe(); + + sourceNoCancel.onNext(2); + + ts.assertValues(1); + ts.assertNoErrors(); + ts.assertNotCompleted(); + } + + + @Test + public void unsubscribeInline() { + TestSubscriber ts1 = new TestSubscriber() { + @Override + public void onNext(Integer t) { + super.onNext(t); + unsubscribe(); + } + }; + + Observable.create(sourceNoCancel, Emitter.BackpressureMode.BUFFER).subscribe(ts1); + + sourceNoCancel.onNext(1); + + ts1.assertValues(1); + ts1.assertNoErrors(); + ts1.assertNotCompleted(); + } + + @Test + public void completeInline() { + Observable.create(sourceNoCancel, Emitter.BackpressureMode.BUFFER).subscribe(ts); + + sourceNoCancel.onNext(1); + sourceNoCancel.onCompleted(); + + ts.requestMore(2); + + ts.assertValues(1); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @Test + public void errorInline() { + Observable.create(sourceNoCancel, Emitter.BackpressureMode.BUFFER).subscribe(ts); + + sourceNoCancel.onNext(1); + sourceNoCancel.onError(new TestException()); + + ts.requestMore(2); + + ts.assertValues(1); + ts.assertError(TestException.class); + ts.assertNotCompleted(); + } + + @Test + public void requestInline() { + TestSubscriber ts1 = new TestSubscriber(1) { + @Override + public void onNext(Integer t) { + super.onNext(t); + requestMore(1); + } + }; + + Observable.create(sourceNoCancel, Emitter.BackpressureMode.BUFFER).subscribe(ts1); + + sourceNoCancel.onNext(1); + sourceNoCancel.onNext(2); + + ts1.assertValues(1, 2); + ts1.assertNoErrors(); + ts1.assertNotCompleted(); + } + + @Test + public void unsubscribeInlineLatest() { + TestSubscriber ts1 = new TestSubscriber() { + @Override + public void onNext(Integer t) { + super.onNext(t); + unsubscribe(); + } + }; + + Observable.create(sourceNoCancel, Emitter.BackpressureMode.LATEST).subscribe(ts1); + + sourceNoCancel.onNext(1); + + ts1.assertValues(1); + ts1.assertNoErrors(); + ts1.assertNotCompleted(); + } + + @Test + public void unsubscribeInlineExactLatest() { + TestSubscriber ts1 = new TestSubscriber(1L) { + @Override + public void onNext(Integer t) { + super.onNext(t); + unsubscribe(); + } + }; + + Observable.create(sourceNoCancel, Emitter.BackpressureMode.LATEST).subscribe(ts1); + + sourceNoCancel.onNext(1); + + ts1.assertValues(1); + ts1.assertNoErrors(); + ts1.assertNotCompleted(); + } + + @Test + public void completeInlineLatest() { + Observable.create(sourceNoCancel, Emitter.BackpressureMode.LATEST).subscribe(ts); + + sourceNoCancel.onNext(1); + sourceNoCancel.onCompleted(); + + ts.requestMore(2); + + ts.assertValues(1); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @Test + public void completeInlineExactLatest() { + Observable.create(sourceNoCancel, Emitter.BackpressureMode.LATEST).subscribe(ts); + + sourceNoCancel.onNext(1); + sourceNoCancel.onCompleted(); + + ts.requestMore(1); + + ts.assertValues(1); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @Test + public void errorInlineLatest() { + Observable.create(sourceNoCancel, Emitter.BackpressureMode.LATEST).subscribe(ts); + + sourceNoCancel.onNext(1); + sourceNoCancel.onError(new TestException()); + + ts.requestMore(2); + + ts.assertValues(1); + ts.assertError(TestException.class); + ts.assertNotCompleted(); + } + + @Test + public void requestInlineLatest() { + TestSubscriber ts1 = new TestSubscriber(1) { + @Override + public void onNext(Integer t) { + super.onNext(t); + requestMore(1); + } + }; + + Observable.create(sourceNoCancel, Emitter.BackpressureMode.LATEST).subscribe(ts1); + + sourceNoCancel.onNext(1); + sourceNoCancel.onNext(2); + + ts1.assertValues(1, 2); + ts1.assertNoErrors(); + ts1.assertNotCompleted(); + } + + static final class PublishEmitter implements Action1>, Observer { + + final PublishSubject subject; + + Emitter current; + + public PublishEmitter() { + this.subject = PublishSubject.create(); + } + + long requested() { + return current.requested(); + } + + @Override + public void call(final Emitter t) { + + this.current = t; + + final Subscription s = subject.subscribe(new Observer() { + + @Override + public void onCompleted() { + t.onCompleted(); + } + + @Override + public void onError(Throwable e) { + t.onError(e); + } + + @Override + public void onNext(Integer v) { + t.onNext(v); + } + + }); + + t.setCancellation(new Cancellable() { + @Override + public void cancel() throws Exception { + s.unsubscribe(); + } + });; + } + + @Override + public void onNext(Integer t) { + subject.onNext(t); + } + + @Override + public void onError(Throwable e) { + subject.onError(e); + } + + @Override + public void onCompleted() { + subject.onCompleted(); + } + } + + static final class PublishEmitterNoCancel implements Action1>, Observer { + + final PublishSubject subject; + + public PublishEmitterNoCancel() { + this.subject = PublishSubject.create(); + } + + @Override + public void call(final Emitter t) { + + subject.subscribe(new Observer() { + + @Override + public void onCompleted() { + t.onCompleted(); + } + + @Override + public void onError(Throwable e) { + t.onError(e); + } + + @Override + public void onNext(Integer v) { + t.onNext(v); + } + + }); + } + + @Override + public void onNext(Integer t) { + subject.onNext(t); + } + + @Override + public void onError(Throwable e) { + subject.onError(e); + } + + @Override + public void onCompleted() { + subject.onCompleted(); + } + } + +} diff --git a/src/test/java/rx/internal/operators/OnSubscribeDeferTest.java b/src/test/java/rx/internal/operators/OnSubscribeDeferTest.java index dfff5d9381..0cb70da7d3 100644 --- a/src/test/java/rx/internal/operators/OnSubscribeDeferTest.java +++ b/src/test/java/rx/internal/operators/OnSubscribeDeferTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -67,19 +67,19 @@ public void testDefer() throws Throwable { verify(secondObserver, times(1)).onCompleted(); } - + @Test public void testDeferFunctionThrows() { Func0> factory = mock(Func0.class); - + when(factory.call()).thenThrow(new TestException()); - + Observable result = Observable.defer(factory); - + Observer o = mock(Observer.class); - + result.subscribe(o); - + verify(o).onError(any(TestException.class)); verify(o, never()).onNext(any(String.class)); verify(o, never()).onCompleted(); diff --git a/src/test/java/rx/internal/operators/OnSubscribeDelaySubscriptionOtherTest.java b/src/test/java/rx/internal/operators/OnSubscribeDelaySubscriptionOtherTest.java index b44b720b41..07b8511a0a 100644 --- a/src/test/java/rx/internal/operators/OnSubscribeDelaySubscriptionOtherTest.java +++ b/src/test/java/rx/internal/operators/OnSubscribeDelaySubscriptionOtherTest.java @@ -30,11 +30,11 @@ public class OnSubscribeDelaySubscriptionOtherTest { @Test public void testNoPrematureSubscription() { PublishSubject other = PublishSubject.create(); - + TestSubscriber ts = TestSubscriber.create(); - + final AtomicInteger subscribed = new AtomicInteger(); - + Observable.just(1) .doOnSubscribe(new Action0() { @Override @@ -44,30 +44,30 @@ public void call() { }) .delaySubscription(other) .subscribe(ts); - + ts.assertNotCompleted(); ts.assertNoErrors(); ts.assertNoValues(); - + Assert.assertEquals("Premature subscription", 0, subscribed.get()); - + other.onNext(1); - + Assert.assertEquals("No subscription", 1, subscribed.get()); - + ts.assertValue(1); ts.assertNoErrors(); ts.assertCompleted(); } - + @Test public void testNoMultipleSubscriptions() { PublishSubject other = PublishSubject.create(); - + TestSubscriber ts = TestSubscriber.create(); - + final AtomicInteger subscribed = new AtomicInteger(); - + Observable.just(1) .doOnSubscribe(new Action0() { @Override @@ -77,31 +77,31 @@ public void call() { }) .delaySubscription(other) .subscribe(ts); - + ts.assertNotCompleted(); ts.assertNoErrors(); ts.assertNoValues(); - + Assert.assertEquals("Premature subscription", 0, subscribed.get()); - + other.onNext(1); other.onNext(2); - + Assert.assertEquals("No subscription", 1, subscribed.get()); - + ts.assertValue(1); ts.assertNoErrors(); ts.assertCompleted(); } - + @Test public void testCompleteTriggersSubscription() { PublishSubject other = PublishSubject.create(); - + TestSubscriber ts = TestSubscriber.create(); - + final AtomicInteger subscribed = new AtomicInteger(); - + Observable.just(1) .doOnSubscribe(new Action0() { @Override @@ -111,30 +111,30 @@ public void call() { }) .delaySubscription(other) .subscribe(ts); - + ts.assertNotCompleted(); ts.assertNoErrors(); ts.assertNoValues(); - + Assert.assertEquals("Premature subscription", 0, subscribed.get()); - + other.onCompleted(); - + Assert.assertEquals("No subscription", 1, subscribed.get()); - + ts.assertValue(1); ts.assertNoErrors(); ts.assertCompleted(); } - + @Test public void testNoPrematureSubscriptionToError() { PublishSubject other = PublishSubject.create(); - + TestSubscriber ts = TestSubscriber.create(); - + final AtomicInteger subscribed = new AtomicInteger(); - + Observable.error(new TestException()) .doOnSubscribe(new Action0() { @Override @@ -144,30 +144,30 @@ public void call() { }) .delaySubscription(other) .subscribe(ts); - + ts.assertNotCompleted(); ts.assertNoErrors(); ts.assertNoValues(); - + Assert.assertEquals("Premature subscription", 0, subscribed.get()); - + other.onCompleted(); - + Assert.assertEquals("No subscription", 1, subscribed.get()); - + ts.assertNoValues(); ts.assertNotCompleted(); ts.assertError(TestException.class); } - + @Test public void testNoSubscriptionIfOtherErrors() { PublishSubject other = PublishSubject.create(); - + TestSubscriber ts = TestSubscriber.create(); - + final AtomicInteger subscribed = new AtomicInteger(); - + Observable.error(new TestException()) .doOnSubscribe(new Action0() { @Override @@ -177,31 +177,31 @@ public void call() { }) .delaySubscription(other) .subscribe(ts); - + ts.assertNotCompleted(); ts.assertNoErrors(); ts.assertNoValues(); - + Assert.assertEquals("Premature subscription", 0, subscribed.get()); - + other.onError(new TestException()); - + Assert.assertEquals("Premature subscription", 0, subscribed.get()); - + ts.assertNoValues(); ts.assertNotCompleted(); ts.assertError(TestException.class); } - + @Test public void testBackpressurePassesThrough() { - + PublishSubject other = PublishSubject.create(); - + TestSubscriber ts = TestSubscriber.create(0L); - + final AtomicInteger subscribed = new AtomicInteger(); - + Observable.just(1, 2, 3, 4, 5) .doOnSubscribe(new Action0() { @Override @@ -211,23 +211,23 @@ public void call() { }) .delaySubscription(other) .subscribe(ts); - + ts.assertNotCompleted(); ts.assertNoErrors(); ts.assertNoValues(); - + Assert.assertEquals("Premature subscription", 0, subscribed.get()); - + other.onNext(1); - + Assert.assertEquals("No subscription", 1, subscribed.get()); Assert.assertFalse("Not unsubscribed from other", other.hasObservers()); - + ts.assertNotCompleted(); ts.assertNoErrors(); ts.assertNoValues(); - + ts.requestMore(1); ts.assertValue(1); ts.assertNoErrors(); @@ -243,21 +243,21 @@ public void call() { ts.assertNoErrors(); ts.assertCompleted(); } - + @Test public void unsubscriptionPropagatesBeforeSubscribe() { PublishSubject source = PublishSubject.create(); PublishSubject other = PublishSubject.create(); - + TestSubscriber ts = new TestSubscriber(); - + source.delaySubscription(other).subscribe(ts); - + Assert.assertFalse("source subscribed?", source.hasObservers()); Assert.assertTrue("other not subscribed?", other.hasObservers()); - + ts.unsubscribe(); - + Assert.assertFalse("source subscribed?", source.hasObservers()); Assert.assertFalse("other still subscribed?", other.hasObservers()); } @@ -266,21 +266,21 @@ public void unsubscriptionPropagatesBeforeSubscribe() { public void unsubscriptionPropagatesAfterSubscribe() { PublishSubject source = PublishSubject.create(); PublishSubject other = PublishSubject.create(); - + TestSubscriber ts = new TestSubscriber(); - + source.delaySubscription(other).subscribe(ts); - + Assert.assertFalse("source subscribed?", source.hasObservers()); Assert.assertTrue("other not subscribed?", other.hasObservers()); - + other.onCompleted(); - + Assert.assertTrue("source not subscribed?", source.hasObservers()); Assert.assertFalse("other still subscribed?", other.hasObservers()); - + ts.unsubscribe(); - + Assert.assertFalse("source subscribed?", source.hasObservers()); Assert.assertFalse("other still subscribed?", other.hasObservers()); } @@ -308,4 +308,8 @@ public void call() { Assert.assertFalse(subscribed.get()); } + @Test(expected = NullPointerException.class) + public void otherNull() { + Observable.just(1).delaySubscription((Observable)null); + } } diff --git a/src/test/java/rx/internal/operators/OnSubscribeDetachTest.java b/src/test/java/rx/internal/operators/OnSubscribeDetachTest.java index 6257e4d227..7d435bff79 100644 --- a/src/test/java/rx/internal/operators/OnSubscribeDetachTest.java +++ b/src/test/java/rx/internal/operators/OnSubscribeDetachTest.java @@ -1,12 +1,12 @@ /** * Copyright 2016 Netflix, Inc. - * + * * 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. @@ -28,47 +28,47 @@ public class OnSubscribeDetachTest { Object o; - + @Test public void just() throws Exception { o = new Object(); - + WeakReference wr = new WeakReference(o); - + TestSubscriber ts = new TestSubscriber(); - + Observable.just(o).count().onTerminateDetach().subscribe(ts); - + ts.assertValue(1); ts.assertCompleted(); ts.assertNoErrors(); - + o = null; - + System.gc(); Thread.sleep(200); - + Assert.assertNull("Object retained!", wr.get()); - + } - + @Test public void error() { TestSubscriber ts = new TestSubscriber(); - + Observable.error(new TestException()).onTerminateDetach().subscribe(ts); - + ts.assertNoValues(); ts.assertError(TestException.class); ts.assertNotCompleted(); } - + @Test public void empty() { TestSubscriber ts = new TestSubscriber(); - + Observable.empty().onTerminateDetach().subscribe(ts); - + ts.assertNoValues(); ts.assertNoErrors(); ts.assertCompleted(); @@ -77,82 +77,82 @@ public void empty() { @Test public void range() { TestSubscriber ts = new TestSubscriber(); - + Observable.range(1, 1000).onTerminateDetach().subscribe(ts); - + ts.assertValueCount(1000); ts.assertNoErrors(); ts.assertCompleted(); } - + @Test public void backpressured() throws Exception { o = new Object(); - + WeakReference wr = new WeakReference(o); - + TestSubscriber ts = new TestSubscriber(0L); - + Observable.just(o).count().onTerminateDetach().subscribe(ts); ts.assertNoValues(); ts.requestMore(1); - + ts.assertValue(1); ts.assertCompleted(); ts.assertNoErrors(); - + o = null; - + System.gc(); Thread.sleep(200); - + Assert.assertNull("Object retained!", wr.get()); } @Test public void justUnsubscribed() throws Exception { o = new Object(); - + WeakReference wr = new WeakReference(o); - + TestSubscriber ts = new TestSubscriber(0); - + Observable.just(o).count().onTerminateDetach().subscribe(ts); - + ts.unsubscribe(); o = null; - + System.gc(); Thread.sleep(200); - + Assert.assertNull("Object retained!", wr.get()); - + } @Test public void deferredUpstreamProducer() { final AtomicReference> subscriber = new AtomicReference>(); - + TestSubscriber ts = new TestSubscriber(0); - - Observable.create(new OnSubscribe() { + + Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber t) { subscriber.set(t); } }).onTerminateDetach().subscribe(ts); - + ts.requestMore(2); - + new OnSubscribeRange(1, 3).call(subscriber.get()); - + ts.assertValues(1, 2); - + ts.requestMore(1); - + ts.assertValues(1, 2, 3); ts.assertCompleted(); ts.assertNoErrors(); diff --git a/src/test/java/rx/internal/operators/OperatorDoOnEachTest.java b/src/test/java/rx/internal/operators/OnSubscribeDoOnEachTest.java similarity index 60% rename from src/test/java/rx/internal/operators/OperatorDoOnEachTest.java rename to src/test/java/rx/internal/operators/OnSubscribeDoOnEachTest.java index 3c4cf9f9bb..bec5c4daab 100644 --- a/src/test/java/rx/internal/operators/OperatorDoOnEachTest.java +++ b/src/test/java/rx/internal/operators/OnSubscribeDoOnEachTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -19,18 +19,23 @@ import static org.mockito.Matchers.any; import static org.mockito.Mockito.*; +import java.util.Arrays; import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import org.junit.*; import org.mockito.*; import rx.*; +import rx.Observable.OnSubscribe; import rx.exceptions.*; import rx.functions.*; import rx.observers.TestSubscriber; +import rx.plugins.RxJavaHooks; -public class OperatorDoOnEachTest { +public class OnSubscribeDoOnEachTest { @Mock Observer subscribedObserver; @@ -119,7 +124,7 @@ public void testIssue1451Case1() { // https://github.com/Netflix/RxJava/issues/1451 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 Func1() { @@ -145,7 +150,7 @@ public void testIssue1451Case2() { // https://github.com/Netflix/RxJava/issues/1451 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, Boolean.FALSE) .takeWhile(new Func1() { @@ -173,7 +178,7 @@ public void testFatalError() { .flatMap(new Func1>() { @Override public Observable call(Integer integer) { - return Observable.create(new Observable.OnSubscribe() { + return Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber o) { throw new NullPointerException("Test NPE"); @@ -195,11 +200,11 @@ public void call(Object o) { System.out.println("Received exception: " + e); } } - + @Test public void testOnErrorThrows() { TestSubscriber ts = TestSubscriber.create(); - + Observable.error(new TestException()) .doOnError(new Action1() { @Override @@ -207,16 +212,158 @@ public void call(Throwable e) { throw new TestException(); } }).subscribe(ts); - + ts.assertNoValues(); ts.assertNotCompleted(); ts.assertError(CompositeException.class); - + CompositeException ex = (CompositeException)ts.getOnErrorEvents().get(0); - + List exceptions = ex.getExceptions(); assertEquals(2, exceptions.size()); assertTrue(exceptions.get(0) instanceof TestException); assertTrue(exceptions.get(1) instanceof TestException); } + + @Test + public void testIfOnNextActionFailsEmitsErrorAndDoesNotFollowWithCompleted() { + TestSubscriber ts = TestSubscriber.create(); + final RuntimeException e1 = new RuntimeException(); + Observable.unsafeCreate(new OnSubscribe() { + + @Override + public void call(final Subscriber subscriber) { + subscriber.setProducer(new Producer() { + + @Override + public void request(long n) { + if (n > 0) { + subscriber.onNext(1); + subscriber.onCompleted(); + } + }}); + }}) + .doOnNext(new Action1() { + + @Override + public void call(Integer t) { + throw e1; + }}) + .unsafeSubscribe(ts); + ts.assertNoValues(); + ts.assertError(e1); + ts.assertNotCompleted(); + } + + @Test + public void testIfOnNextActionFailsEmitsErrorAndDoesNotFollowWithOnNext() { + TestSubscriber ts = TestSubscriber.create(); + final RuntimeException e1 = new RuntimeException(); + Observable.unsafeCreate(new OnSubscribe() { + + @Override + public void call(final Subscriber subscriber) { + subscriber.setProducer(new Producer() { + + @Override + public void request(long n) { + if (n > 2) { + subscriber.onNext(1); + subscriber.onNext(2); + } + }}); + }}) + .doOnNext(new Action1() { + + @Override + public void call(Integer t) { + throw e1; + }}) + .unsafeSubscribe(ts); + ts.assertNoValues(); + assertEquals(1, ts.getOnErrorEvents().size()); + ts.assertNotCompleted(); + } + + @Test + public void testIfOnNextActionFailsEmitsErrorAndReportsMoreErrorsToRxJavaHooksNotDownstream() { + try { + final List list = new CopyOnWriteArrayList(); + RxJavaHooks.setOnError(new Action1() { + + @Override + public void call(Throwable e) { + list.add(e); + }}); + TestSubscriber ts = TestSubscriber.create(); + final RuntimeException e1 = new RuntimeException(); + final RuntimeException e2 = new RuntimeException(); + Observable.unsafeCreate(new OnSubscribe() { + + @Override + public void call(final Subscriber subscriber) { + subscriber.setProducer(new Producer() { + + @Override + public void request(long n) { + if (n > 2) { + subscriber.onNext(1); + subscriber.onError(e2); + } + } + }); + } + }).doOnNext(new Action1() { + + @Override + public void call(Integer t) { + throw e1; + } + }).unsafeSubscribe(ts); + ts.assertNoValues(); + assertEquals(1, ts.getOnErrorEvents().size()); + ts.assertNotCompleted(); + assertEquals(Arrays.asList(e2), list); + } finally { + RxJavaHooks.reset(); + } + } + + @Test + public void testIfCompleteActionFailsEmitsError() { + TestSubscriber ts = TestSubscriber.create(); + final RuntimeException e1 = new RuntimeException(); + Observable.empty() + .doOnCompleted(new Action0() { + + @Override + public void call() { + throw e1; + }}) + .unsafeSubscribe(ts); + ts.assertNoValues(); + ts.assertError(e1); + ts.assertNotCompleted(); + } + + @Test + public void testUnsubscribe() { + TestSubscriber ts = TestSubscriber.create(0); + final AtomicBoolean unsub = new AtomicBoolean(); + Observable.just(1,2,3,4) + .doOnUnsubscribe(new Action0() { + + @Override + public void call() { + unsub.set(true); + }}) + .doOnNext(Actions.empty()) + .subscribe(ts); + ts.requestMore(1); + ts.unsubscribe(); + ts.assertNotCompleted(); + ts.assertValueCount(1); + assertTrue(unsub.get()); + } + } \ No newline at end of file diff --git a/src/test/java/rx/internal/operators/OperatorFilterTest.java b/src/test/java/rx/internal/operators/OnSubscribeFilterTest.java similarity index 95% rename from src/test/java/rx/internal/operators/OperatorFilterTest.java rename to src/test/java/rx/internal/operators/OnSubscribeFilterTest.java index 5a8ebeb9f6..f2aa08caf3 100644 --- a/src/test/java/rx/internal/operators/OperatorFilterTest.java +++ b/src/test/java/rx/internal/operators/OnSubscribeFilterTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -30,7 +30,7 @@ import rx.observers.TestSubscriber; import rx.subjects.PublishSubject; -public class OperatorFilterTest { +public class OnSubscribeFilterTest { @Test public void testFilter() { @@ -117,19 +117,19 @@ public Boolean call(Integer t1) { final CountDownLatch latch = new CountDownLatch(1); final TestSubscriber ts = new TestSubscriber(0L) { - + @Override public void onCompleted() { System.out.println("onCompleted"); latch.countDown(); } - + @Override public void onError(Throwable e) { e.printStackTrace(); latch.countDown(); } - + @Override public void onNext(Integer t) { System.out.println("Received: " + t); @@ -145,7 +145,7 @@ public void onNext(Integer t) { // this will wait forever unless OperatorTake handles the request(n) on filtered items latch.await(); } - + @Test public void testFatalError() { try { @@ -173,37 +173,37 @@ public void call(Integer t) { @Test public void functionCrashUnsubscribes() { - + PublishSubject ps = PublishSubject.create(); - + TestSubscriber ts = new TestSubscriber(); - + ps.filter(new Func1() { @Override - public Boolean call(Integer v) { - throw new TestException(); + public Boolean call(Integer v) { + throw new TestException(); } }).unsafeSubscribe(ts); - + Assert.assertTrue("Not subscribed?", ps.hasObservers()); - + ps.onNext(1); - + Assert.assertFalse("Subscribed?", ps.hasObservers()); - + ts.assertError(TestException.class); } @Test public void doesntRequestOnItsOwn() { TestSubscriber ts = TestSubscriber.create(0L); - + Observable.range(1, 10).filter(UtilityFunctions.alwaysTrue()).unsafeSubscribe(ts); - + ts.assertNoValues(); - + ts.requestMore(10); - + ts.assertValues(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); ts.assertNoErrors(); ts.assertCompleted(); diff --git a/src/test/java/rx/internal/operators/OnSubscribeFlatMapCompletableTest.java b/src/test/java/rx/internal/operators/OnSubscribeFlatMapCompletableTest.java new file mode 100644 index 0000000000..2b88387400 --- /dev/null +++ b/src/test/java/rx/internal/operators/OnSubscribeFlatMapCompletableTest.java @@ -0,0 +1,557 @@ +/** + * Copyright 2017 Netflix, Inc. + * + * 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 rx.internal.operators; + +import static org.junit.Assert.*; + +import java.util.*; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.*; + +import rx.*; +import rx.CompletableTest.*; +import rx.Observable; +import rx.exceptions.*; +import rx.functions.*; +import rx.internal.util.UtilityFunctions; +import rx.observers.*; +import rx.schedulers.Schedulers; +import rx.subjects.PublishSubject; +import rx.subscriptions.Subscriptions; + +public class OnSubscribeFlatMapCompletableTest implements Action0, Action1 { + + final AtomicInteger calls = new AtomicInteger(); + + /** A normal Completable object. */ + final NormalCompletable normal = new NormalCompletable(); + + /** An error Completable object. */ + final ErrorCompletable error = new ErrorCompletable(); + + final Func1 identity = UtilityFunctions.identity(); + + @Override + public void call() { + calls.getAndIncrement(); + } + + @Override + public void call(Object t) { + calls.getAndIncrement(); + } + + void assertCalls(int n) { + assertEquals(n, calls.get()); + } + + @Test + public void normal() { + Observable.range(1, 10) + .flatMapCompletable(new Func1() { + @Override + public Completable call(Integer v) { + return Completable.complete().doOnCompleted(OnSubscribeFlatMapCompletableTest.this); + } + }) + .test() + .assertResult(); + + assertCalls(10); + } + + @Test + public void normalMaxConcurrent() { + for (int i = 1; i < 10; i++) { + calls.set(0); + + Observable.range(1, 10) + .flatMapCompletable(new Func1() { + @Override + public Completable call(Integer v) { + return Completable.complete() + .observeOn(Schedulers.computation()) + .doOnCompleted(OnSubscribeFlatMapCompletableTest.this); + } + }, false, i) + .test() + .awaitTerminalEvent(5, TimeUnit.SECONDS) + .assertResult(); + + assertCalls(10); + } + } + + @Test + public void error() { + Observable.range(1, 10) + .flatMapCompletable(new Func1() { + @Override + public Completable call(Integer v) { + return Completable.error(new TestException()).doOnError(OnSubscribeFlatMapCompletableTest.this); + } + }) + .test() + .assertFailure(TestException.class); + + assertCalls(1); + } + + @Test + public void errorDelayed() { + AssertableSubscriber as = Observable.range(1, 10) + .flatMapCompletable(new Func1() { + @Override + public Completable call(Integer v) { + return Completable.error(new TestException()).doOnError(OnSubscribeFlatMapCompletableTest.this); + } + }, true) + .test() + .assertFailure(CompositeException.class); + + List onErrorEvents = as.getOnErrorEvents(); + + assertEquals(onErrorEvents.toString(), 1, onErrorEvents.size()); + + onErrorEvents = ((CompositeException)onErrorEvents.get(0)).getExceptions(); + + assertEquals(onErrorEvents.toString(), 10, onErrorEvents.size()); + + for (Throwable ex : onErrorEvents) { + assertTrue(ex.toString(), ex instanceof TestException); + } + + assertCalls(10); + } + + @Test + public void errorDelayedMaxConcurrency() { + AssertableSubscriber as = Observable.range(1, 10) + .flatMapCompletable(new Func1() { + @Override + public Completable call(Integer v) { + return Completable.error(new TestException()).doOnError(OnSubscribeFlatMapCompletableTest.this); + } + }, true, 1) + .test() + .assertFailure(CompositeException.class); + + List onErrorEvents = as.getOnErrorEvents(); + + assertEquals(onErrorEvents.toString(), 1, onErrorEvents.size()); + + onErrorEvents = ((CompositeException)onErrorEvents.get(0)).getExceptions(); + + assertEquals(onErrorEvents.toString(), 10, onErrorEvents.size()); + + for (Throwable ex : onErrorEvents) { + assertTrue(ex.toString(), ex instanceof TestException); + } + + assertCalls(10); + } + + @Test + public void mapperThrows() { + Observable.range(1, 10) + .flatMapCompletable(new Func1() { + @Override + public Completable call(Integer v) { + throw new TestException(); + } + }) + .test() + .assertFailure(TestException.class); + } + + @Test + public void mapperNull() { + Observable.range(1, 10) + .flatMapCompletable(new Func1() { + @Override + public Completable call(Integer v) { + return null; + } + }) + .test() + .assertFailure(NullPointerException.class); + } + + @Test + public void paramValidation() { + try { + Observable.range(1, 10) + .flatMapCompletable(null); + fail("Should have thrown"); + } catch (NullPointerException ex) { + assertEquals("mapper is null", ex.getMessage()); + } + + try { + Observable.range(1, 10) + .flatMapCompletable(new Func1() { + @Override + public Completable call(Integer v) { + return Completable.complete(); + } + }, false, 0); + fail("Should have thrown"); + } catch (IllegalArgumentException ex) { + assertEquals("maxConcurrency > 0 required but it was 0", ex.getMessage()); + } + + try { + Observable.range(1, 10) + .flatMapCompletable(new Func1() { + @Override + public Completable call(Integer v) { + return Completable.complete(); + } + }, true, -99); + fail("Should have thrown"); + } catch (IllegalArgumentException ex) { + assertEquals("maxConcurrency > 0 required but it was -99", ex.getMessage()); + } + } + + @Test + public void mainErrorDelayed() { + Observable.range(1, 10).concatWith(Observable.error(new TestException())) + .flatMapCompletable(new Func1() { + @Override + public Completable call(Integer v) { + return Completable.complete().doOnCompleted(OnSubscribeFlatMapCompletableTest.this); + } + }, true) + .test() + .assertFailure(TestException.class); + + assertCalls(10); + } + + @Test + public void innerDoubleOnSubscribe() { + final CompletableSubscriber[] inner = { null }; + + AssertableSubscriber as = Observable.just(1) + .flatMapCompletable(new Func1() { + @Override + public Completable call(Integer t) { + return Completable.create(new Completable.OnSubscribe() { + @Override + public void call(CompletableSubscriber t) { + OnSubscribeFlatMapCompletableTest.this.call(); + Subscription s1 = Subscriptions.empty(); + + t.onSubscribe(s1); + + Subscription s2 = Subscriptions.empty(); + + t.onSubscribe(s2); + + if (s2.isUnsubscribed()) { + OnSubscribeFlatMapCompletableTest.this.call(); + } + + t.onCompleted(); + + inner[0] = t; + } + }); + } + }) + .test() + .assertResult(); + + assertCalls(2); + + inner[0].onError(new TestException()); + + as.assertResult(); + } + + @Test + public void mainErrorUnsubscribes() { + PublishSubject ps0 = PublishSubject.create(); + final PublishSubject ps1 = PublishSubject.create(); + final PublishSubject ps2 = PublishSubject.create(); + + TestSubscriber as = TestSubscriber.create(); + + ps0.flatMapCompletable(new Func1() { + @Override + public Completable call(Integer v) { + return v == 0 ? ps1.toCompletable() : ps2.toCompletable(); + } + }).unsafeSubscribe(as); + + assertTrue(ps0.hasObservers()); + assertFalse(ps1.hasObservers()); + assertFalse(ps2.hasObservers()); + as.assertNoValues(); + as.assertNoErrors(); + as.assertNotCompleted(); + + ps0.onNext(0); + + assertTrue(ps0.hasObservers()); + assertTrue(ps1.hasObservers()); + assertFalse(ps2.hasObservers()); + as.assertNoValues(); + as.assertNoErrors(); + as.assertNotCompleted(); + + ps0.onNext(1); + + assertTrue(ps0.hasObservers()); + assertTrue(ps1.hasObservers()); + assertTrue(ps2.hasObservers()); + as.assertNoValues(); + as.assertNoErrors(); + as.assertNotCompleted(); + + ps0.onError(new TestException()); + + assertFalse(ps0.hasObservers()); + assertFalse(ps1.hasObservers()); + assertFalse(ps2.hasObservers()); + as.assertNoValues(); + as.assertError(TestException.class); + as.assertNotCompleted(); + } + + @Test + public void innerErrorUnsubscribes() { + PublishSubject ps0 = PublishSubject.create(); + final PublishSubject ps1 = PublishSubject.create(); + final PublishSubject ps2 = PublishSubject.create(); + + TestSubscriber as = TestSubscriber.create(); + + ps0.flatMapCompletable(new Func1() { + @Override + public Completable call(Integer v) { + return v == 0 ? ps1.toCompletable() : ps2.toCompletable(); + } + }).unsafeSubscribe(as); + + assertTrue(ps0.hasObservers()); + assertFalse(ps1.hasObservers()); + assertFalse(ps2.hasObservers()); + as.assertNoValues(); + as.assertNoErrors(); + as.assertNotCompleted(); + + ps0.onNext(0); + + assertTrue(ps0.hasObservers()); + assertTrue(ps1.hasObservers()); + assertFalse(ps2.hasObservers()); + as.assertNoValues(); + as.assertNoErrors(); + as.assertNotCompleted(); + + ps0.onNext(1); + + assertTrue(ps0.hasObservers()); + assertTrue(ps1.hasObservers()); + assertTrue(ps2.hasObservers()); + as.assertNoValues(); + as.assertNoErrors(); + as.assertNotCompleted(); + + ps1.onError(new TestException()); + + assertFalse(ps0.hasObservers()); + assertFalse(ps1.hasObservers()); + assertFalse(ps2.hasObservers()); + as.assertNoValues(); + as.assertError(TestException.class); + as.assertNotCompleted(); + } + + + @Test(timeout = 5000) + public void mergeObservableEmpty() { + Completable c = Observable.empty().flatMapCompletable(identity).toCompletable(); + + c.await(); + } + + @Test(timeout = 5000, expected = TestException.class) + public void mergeObservableError() { + Completable c = Observable.error(new TestException()).flatMapCompletable(identity).toCompletable(); + + c.await(); + } + + @Test(timeout = 5000) + public void mergeObservableSingle() { + Completable c = Observable.just(normal.completable).flatMapCompletable(identity).toCompletable(); + + c.await(); + + normal.assertSubscriptions(1); + } + + @Test(timeout = 5000, expected = TestException.class) + public void mergeObservableSingleThrows() { + Completable c = Observable.just(error.completable).flatMapCompletable(identity).toCompletable(); + + c.await(); + } + + @Test(timeout = 5000) + public void mergeObservableMany() { + Completable c = Observable.just(normal.completable).repeat(3).flatMapCompletable(identity).toCompletable(); + + c.await(); + + normal.assertSubscriptions(3); + } + + @Test(timeout = 5000, expected = TestException.class) + public void mergeObservableManyOneThrows() { + Completable c = Observable.just(normal.completable, error.completable).flatMapCompletable(identity).toCompletable(); + + c.await(); + } + + @Test(timeout = 5000) + public void mergeObservableMaxConcurrent() { + final List requested = new ArrayList(); + Observable cs = Observable + .just(normal.completable) + .repeat(10) + .doOnRequest(new Action1() { + @Override + public void call(Long v) { + requested.add(v); + } + }); + + Completable c = cs.flatMapCompletable(identity, false, 5).toCompletable(); + + c.await(); + + // FIXME this request pattern looks odd because all 10 completions trigger 1 requests + Assert.assertEquals(Arrays.asList(5L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L), requested); + } + + @Test(timeout = 5000) + public void mergeDelayErrorObservableEmpty() { + Completable c = Observable.empty().flatMapCompletable(identity, true).toCompletable(); + + c.await(); + } + + @Test(timeout = 5000, expected = TestException.class) + public void mergeDelayErrorObservableError() { + Completable c = Observable.error(new TestException()).flatMapCompletable(identity, true).toCompletable(); + + c.await(); + } + + @Test(timeout = 5000) + public void mergeDelayErrorObservableSingle() { + Completable c = Observable.just(normal.completable).flatMapCompletable(identity, true).toCompletable(); + + c.await(); + + normal.assertSubscriptions(1); + } + + @Test(timeout = 5000, expected = TestException.class) + public void mergeDelayErrorObservableSingleThrows() { + Completable c = Observable.just(error.completable).flatMapCompletable(identity, true).toCompletable(); + + c.await(); + } + + @Test(timeout = 5000) + public void mergeDelayErrorObservableMany() { + Completable c = Observable.just(normal.completable).repeat(3).flatMapCompletable(identity, true).toCompletable(); + + c.await(); + + normal.assertSubscriptions(3); + } + + @Test(timeout = 5000, expected = TestException.class) + public void mergeDelayErrorObservableManyOneThrows() { + Completable c = Observable.just(normal.completable, error.completable).flatMapCompletable(identity, true).toCompletable(); + + c.await(); + } + + @Test(timeout = 5000) + public void mergeDelayErrorObservableMaxConcurrent() { + final List requested = new ArrayList(); + Observable cs = Observable + .just(normal.completable) + .repeat(10) + .doOnRequest(new Action1() { + @Override + public void call(Long v) { + requested.add(v); + } + }); + + Completable c = cs.flatMapCompletable(identity, true, 5).toCompletable(); + + c.await(); + + // FIXME this request pattern looks odd because all 10 completions trigger 1 requests + Assert.assertEquals(Arrays.asList(5L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L), requested); + } + + @Test + public void asyncObservables() { + + final int[] calls = { 0 }; + + Observable.range(1, 5).map(new Func1() { + @Override + public Completable call(final Integer v) { + System.out.println("Mapping " + v); + return Completable.fromAction(new Action0() { + @Override + public void call() { + System.out.println("Processing " + (calls[0] + 1)); + calls[0]++; + } + }) + .subscribeOn(Schedulers.io()) + .doOnCompleted(new Action0() { + @Override + public void call() { + System.out.println("Inner complete " + v); + } + }) + .observeOn(Schedulers.computation()); + } + }).flatMapCompletable(identity, false, 1).toCompletable() + .test() + .awaitTerminalEventAndUnsubscribeOnTimeout(5, TimeUnit.SECONDS) + .assertResult(); + + Assert.assertEquals(5, calls[0]); + } + +} diff --git a/src/test/java/rx/internal/operators/OnSubscribeFlatMapSingleTest.java b/src/test/java/rx/internal/operators/OnSubscribeFlatMapSingleTest.java new file mode 100644 index 0000000000..3da2ac389c --- /dev/null +++ b/src/test/java/rx/internal/operators/OnSubscribeFlatMapSingleTest.java @@ -0,0 +1,782 @@ +/** + * Copyright 2017 Netflix, Inc. + * + * 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 rx.internal.operators; + +import java.util.*; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.junit.Assert.*; +import org.junit.Test; + +import rx.*; +import rx.Observable; +import rx.exceptions.*; +import rx.functions.*; +import rx.observers.*; +import rx.schedulers.Schedulers; +import rx.subjects.PublishSubject; + +public class OnSubscribeFlatMapSingleTest implements Action0, Action1 { + + final AtomicInteger calls = new AtomicInteger(); + + final Action1 errorConsumer = new Action1() { + @Override + public void call(Throwable e) { + OnSubscribeFlatMapSingleTest.this.call(e); + } + }; + + @Override + public void call() { + calls.getAndIncrement(); + } + + @Override + public void call(Object t) { + calls.getAndIncrement(); + } + + void assertCalls(int n) { + assertEquals(n, calls.get()); + } + + @Test + public void normal() { + Observable.range(1, 10) + .flatMapSingle(new Func1>() { + @Override + public Single call(Integer v) { + return Single.just(v); + } + }) + .test() + .assertResult(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + } + + @Test + public void normalBackpressured() { + Observable.range(1, 10) + .flatMapSingle(new Func1>() { + @Override + public Single call(Integer v) { + return Single.just(v); + } + }) + .test(0L) + .assertNoValues() + .requestMore(1) + .assertValues(1) + .requestMore(2) + .assertValues(1, 2, 3) + .requestMore(3) + .assertValues(1, 2, 3, 4, 5, 6) + .requestMore(4) + .assertResult(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + } + + @Test + public void normalMaxConcurrencyBackpressured() { + for (int i = 1; i < 16; i++) { + Observable.range(1, 10) + .flatMapSingle(new Func1>() { + @Override + public Single call(Integer v) { + return Single.just(v); + } + }, false, i) + .test(0L) + .assertNoValues() + .requestMore(1) + .assertValues(1) + .requestMore(2) + .assertValues(1, 2, 3) + .requestMore(3) + .assertValues(1, 2, 3, 4, 5, 6) + .requestMore(4) + .assertResult(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + } + } + + @Test + public void normalMaxConcurrent() { + for (int i = 1; i < 16; i++) { + Observable.range(1, 10) + .flatMapSingle(new Func1>() { + @Override + public Single call(Integer v) { + return Single.just(v); + } + }, false, i) + .test() + .assertResult(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + } + } + + @Test + public void normalMaxConcurrentAsync() { + for (int i = 1; i < 2; i++) { + AssertableSubscriber as = Observable.range(1, 10) + .flatMapSingle(new Func1>() { + @Override + public Single call(Integer v) { + return Single.just(v).observeOn(Schedulers.computation()); + } + }, false, i) + .test(); + + as.awaitTerminalEvent(5, TimeUnit.SECONDS) + .assertValueCount(10) + .assertNoErrors() + .assertCompleted(); + + Set set = new HashSet(as.getOnNextEvents()); + + for (int j = 1; j < 11; j++) { + assertTrue("" + set, set.contains(j)); + } + } + } + + @Test + public void justMaxConcurrentAsync() { + AssertableSubscriber as = Observable.just(1) + .flatMapSingle(new Func1>() { + @Override + public Single call(Integer v) { + return Single.just(v).observeOn(Schedulers.computation()); + } + }, false, 1) + .test(); + + as.awaitTerminalEvent(5, TimeUnit.SECONDS) + .assertResult(1); + } + + @Test + public void error() { + Observable.range(1, 10) + .flatMapSingle(new Func1>() { + @Override + public Single call(Integer v) { + return Single.error(new TestException()).doOnError(errorConsumer); + } + }) + .test() + .assertFailure(TestException.class); + + assertCalls(1); + } + + @Test + public void errorDelayed() { + AssertableSubscriber as = Observable.range(1, 10) + .flatMapSingle(new Func1>() { + @Override + public Single call(Integer v) { + return Single.error(new TestException()).doOnError(errorConsumer); + } + }, true) + .test() + .assertFailure(CompositeException.class); + + List onErrorEvents = as.getOnErrorEvents(); + + assertEquals(onErrorEvents.toString(), 1, onErrorEvents.size()); + + onErrorEvents = ((CompositeException)onErrorEvents.get(0)).getExceptions(); + + assertEquals(onErrorEvents.toString(), 10, onErrorEvents.size()); + + for (Throwable ex : onErrorEvents) { + assertTrue(ex.toString(), ex instanceof TestException); + } + + assertCalls(10); + } + + @Test + public void errorDelayedMaxConcurrency() { + AssertableSubscriber as = Observable.range(1, 10) + .flatMapSingle(new Func1>() { + @Override + public Single call(Integer v) { + return Single.error(new TestException()).doOnError(errorConsumer); + } + }, true, 1) + .test() + .assertFailure(CompositeException.class); + + List onErrorEvents = as.getOnErrorEvents(); + + assertEquals(onErrorEvents.toString(), 1, onErrorEvents.size()); + + onErrorEvents = ((CompositeException)onErrorEvents.get(0)).getExceptions(); + + assertEquals(onErrorEvents.toString(), 10, onErrorEvents.size()); + + for (Throwable ex : onErrorEvents) { + assertTrue(ex.toString(), ex instanceof TestException); + } + + assertCalls(10); + } + + @Test + public void mapperThrows() { + Observable.range(1, 10) + .flatMapSingle(new Func1>() { + @Override + public Single call(Integer v) { + throw new TestException(); + } + }) + .test() + .assertFailure(TestException.class); + } + + @Test + public void mapperNull() { + Observable.range(1, 10) + .flatMapSingle(new Func1>() { + @Override + public Single call(Integer v) { + return null; + } + }) + .test() + .assertFailure(NullPointerException.class); + } + + @Test + public void paramValidation() { + try { + Observable.range(1, 10) + .flatMapSingle(null); + fail("Should have thrown"); + } catch (NullPointerException ex) { + assertEquals("mapper is null", ex.getMessage()); + } + + try { + Observable.range(1, 10) + .flatMapSingle(new Func1>() { + @Override + public Single call(Integer v) { + return Single.just(v); + } + }, false, 0); + fail("Should have thrown"); + } catch (IllegalArgumentException ex) { + assertEquals("maxConcurrency > 0 required but it was 0", ex.getMessage()); + } + + try { + Observable.range(1, 10) + .flatMapSingle(new Func1>() { + @Override + public Single call(Integer v) { + return Single.just(v); + } + }, true, -99); + fail("Should have thrown"); + } catch (IllegalArgumentException ex) { + assertEquals("maxConcurrency > 0 required but it was -99", ex.getMessage()); + } + } + + @Test + public void mainErrorDelayed() { + Observable.range(1, 10).concatWith(Observable.error(new TestException())) + .flatMapSingle(new Func1>() { + @Override + public Single call(Integer v) { + return Single.just(v).doOnSuccess(OnSubscribeFlatMapSingleTest.this); + } + }, true) + .test() + .assertFailure(TestException.class, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + + assertCalls(10); + } + + @Test + public void mainErrorUnsubscribes() { + PublishSubject ps0 = PublishSubject.create(); + final PublishSubject ps1 = PublishSubject.create(); + final PublishSubject ps2 = PublishSubject.create(); + + TestSubscriber as = TestSubscriber.create(); + + ps0.flatMapSingle(new Func1>() { + @Override + public Single call(Integer v) { + return v == 0 ? ps1.toSingle() : ps2.toSingle(); + } + }).unsafeSubscribe(as); + + assertTrue(ps0.hasObservers()); + assertFalse(ps1.hasObservers()); + assertFalse(ps2.hasObservers()); + as.assertNoValues(); + as.assertNoErrors(); + as.assertNotCompleted(); + + ps0.onNext(0); + + assertTrue(ps0.hasObservers()); + assertTrue(ps1.hasObservers()); + assertFalse(ps2.hasObservers()); + as.assertNoValues(); + as.assertNoErrors(); + as.assertNotCompleted(); + + ps0.onNext(1); + + assertTrue(ps0.hasObservers()); + assertTrue(ps1.hasObservers()); + assertTrue(ps2.hasObservers()); + as.assertNoValues(); + as.assertNoErrors(); + as.assertNotCompleted(); + + ps0.onError(new TestException()); + + assertFalse(ps0.hasObservers()); + assertFalse(ps1.hasObservers()); + assertFalse(ps2.hasObservers()); + as.assertNoValues(); + as.assertError(TestException.class); + as.assertNotCompleted(); + } + + @Test + public void innerErrorUnsubscribes() { + PublishSubject ps0 = PublishSubject.create(); + final PublishSubject ps1 = PublishSubject.create(); + final PublishSubject ps2 = PublishSubject.create(); + + TestSubscriber as = TestSubscriber.create(); + + ps0.flatMapSingle(new Func1>() { + @Override + public Single call(Integer v) { + return v == 0 ? ps1.toSingle() : ps2.toSingle(); + } + }).unsafeSubscribe(as); + + assertTrue(ps0.hasObservers()); + assertFalse(ps1.hasObservers()); + assertFalse(ps2.hasObservers()); + as.assertNoValues(); + as.assertNoErrors(); + as.assertNotCompleted(); + + ps0.onNext(0); + + assertTrue(ps0.hasObservers()); + assertTrue(ps1.hasObservers()); + assertFalse(ps2.hasObservers()); + as.assertNoValues(); + as.assertNoErrors(); + as.assertNotCompleted(); + + ps0.onNext(1); + + assertTrue(ps0.hasObservers()); + assertTrue(ps1.hasObservers()); + assertTrue(ps2.hasObservers()); + as.assertNoValues(); + as.assertNoErrors(); + as.assertNotCompleted(); + + ps1.onError(new TestException()); + + assertFalse(ps0.hasObservers()); + assertFalse(ps1.hasObservers()); + assertFalse(ps2.hasObservers()); + as.assertNoValues(); + as.assertError(TestException.class); + as.assertNotCompleted(); + } + + @Test + public void take() { + Observable.range(1, 10) + .flatMapSingle(new Func1>() { + @Override + public Single call(Integer v) { + return Single.just(v); + } + }) + .take(5) + .test() + .assertResult(1, 2, 3, 4, 5); + } + + + @Test + public void unsubscribe() { + AssertableSubscriber as = Observable.range(1, 10) + .flatMapSingle(new Func1>() { + @Override + public Single call(Integer v) { + return Single.just(v); + } + }) + .test(0) + ; + + as.unsubscribe(); + + as.assertNoValues().assertNoErrors().assertNotCompleted(); + } + + @Test + public void mainErrorUnsubscribesNoRequest() { + PublishSubject ps0 = PublishSubject.create(); + final PublishSubject ps1 = PublishSubject.create(); + final PublishSubject ps2 = PublishSubject.create(); + + TestSubscriber as = TestSubscriber.create(0L); + + ps0.flatMapSingle(new Func1>() { + @Override + public Single call(Integer v) { + return v == 0 ? ps1.toSingle() : ps2.toSingle(); + } + }).unsafeSubscribe(as); + + assertTrue(ps0.hasObservers()); + assertFalse(ps1.hasObservers()); + assertFalse(ps2.hasObservers()); + as.assertNoValues(); + as.assertNoErrors(); + as.assertNotCompleted(); + + ps0.onNext(0); + + assertTrue(ps0.hasObservers()); + assertTrue(ps1.hasObservers()); + assertFalse(ps2.hasObservers()); + as.assertNoValues(); + as.assertNoErrors(); + as.assertNotCompleted(); + + ps0.onNext(1); + + assertTrue(ps0.hasObservers()); + assertTrue(ps1.hasObservers()); + assertTrue(ps2.hasObservers()); + as.assertNoValues(); + as.assertNoErrors(); + as.assertNotCompleted(); + + ps0.onError(new TestException()); + + assertFalse(ps0.hasObservers()); + assertFalse(ps1.hasObservers()); + assertFalse(ps2.hasObservers()); + as.assertNoValues(); + as.assertError(TestException.class); + as.assertNotCompleted(); + } + + @Test + public void innerErrorUnsubscribesNoRequest() { + PublishSubject ps0 = PublishSubject.create(); + final PublishSubject ps1 = PublishSubject.create(); + final PublishSubject ps2 = PublishSubject.create(); + + TestSubscriber as = TestSubscriber.create(0L); + + ps0.flatMapSingle(new Func1>() { + @Override + public Single call(Integer v) { + return v == 0 ? ps1.toSingle() : ps2.toSingle(); + } + }).unsafeSubscribe(as); + + assertTrue(ps0.hasObservers()); + assertFalse(ps1.hasObservers()); + assertFalse(ps2.hasObservers()); + as.assertNoValues(); + as.assertNoErrors(); + as.assertNotCompleted(); + + ps0.onNext(0); + + assertTrue(ps0.hasObservers()); + assertTrue(ps1.hasObservers()); + assertFalse(ps2.hasObservers()); + as.assertNoValues(); + as.assertNoErrors(); + as.assertNotCompleted(); + + ps0.onNext(1); + + assertTrue(ps0.hasObservers()); + assertTrue(ps1.hasObservers()); + assertTrue(ps2.hasObservers()); + as.assertNoValues(); + as.assertNoErrors(); + as.assertNotCompleted(); + + ps1.onError(new TestException()); + + assertFalse(ps0.hasObservers()); + assertFalse(ps1.hasObservers()); + assertFalse(ps2.hasObservers()); + as.assertNoValues(); + as.assertError(TestException.class); + as.assertNotCompleted(); + } + @Test + public void mainErrorUnsubscribesNoRequestDelayError() { + PublishSubject ps0 = PublishSubject.create(); + final PublishSubject ps1 = PublishSubject.create(); + final PublishSubject ps2 = PublishSubject.create(); + + TestSubscriber as = TestSubscriber.create(0L); + + ps0.flatMapSingle(new Func1>() { + @Override + public Single call(Integer v) { + return v == 0 ? ps1.toSingle() : ps2.toSingle(); + } + }, true).unsafeSubscribe(as); + + assertTrue(ps0.hasObservers()); + assertFalse(ps1.hasObservers()); + assertFalse(ps2.hasObservers()); + as.assertNoValues(); + as.assertNoErrors(); + as.assertNotCompleted(); + + ps0.onNext(0); + + assertTrue(ps0.hasObservers()); + assertTrue(ps1.hasObservers()); + assertFalse(ps2.hasObservers()); + as.assertNoValues(); + as.assertNoErrors(); + as.assertNotCompleted(); + + ps0.onNext(1); + + assertTrue(ps0.hasObservers()); + assertTrue(ps1.hasObservers()); + assertTrue(ps2.hasObservers()); + as.assertNoValues(); + as.assertNoErrors(); + as.assertNotCompleted(); + + ps0.onError(new TestException()); + ps1.onNext(3); + ps1.onCompleted(); + ps2.onNext(4); + ps2.onCompleted(); + + assertFalse(ps0.hasObservers()); + assertFalse(ps1.hasObservers()); + assertFalse(ps2.hasObservers()); + + as.requestMore(2); + as.assertValues(3, 4); + as.assertError(TestException.class); + as.assertNotCompleted(); + } + + @Test + public void innerErrorUnsubscribesNoRequestDelayError() { + PublishSubject ps0 = PublishSubject.create(); + final PublishSubject ps1 = PublishSubject.create(); + final PublishSubject ps2 = PublishSubject.create(); + + TestSubscriber as = TestSubscriber.create(0L); + + ps0.flatMapSingle(new Func1>() { + @Override + public Single call(Integer v) { + return v == 0 ? ps1.toSingle() : ps2.toSingle(); + } + }, true).unsafeSubscribe(as); + + assertTrue(ps0.hasObservers()); + assertFalse(ps1.hasObservers()); + assertFalse(ps2.hasObservers()); + as.assertNoValues(); + as.assertNoErrors(); + as.assertNotCompleted(); + + ps0.onNext(0); + + assertTrue(ps0.hasObservers()); + assertTrue(ps1.hasObservers()); + assertFalse(ps2.hasObservers()); + as.assertNoValues(); + as.assertNoErrors(); + as.assertNotCompleted(); + + ps0.onNext(1); + + assertTrue(ps0.hasObservers()); + assertTrue(ps1.hasObservers()); + assertTrue(ps2.hasObservers()); + as.assertNoValues(); + as.assertNoErrors(); + as.assertNotCompleted(); + + ps0.onCompleted(); + ps1.onError(new TestException()); + ps2.onNext(4); + ps2.onCompleted(); + + assertFalse(ps0.hasObservers()); + assertFalse(ps1.hasObservers()); + assertFalse(ps2.hasObservers()); + + as.requestMore(1); + as.assertValues(4); + as.assertError(TestException.class); + as.assertNotCompleted(); + } + + @Test + public void justBackpressured() { + Observable.just(1) + .flatMapSingle(new Func1>() { + @Override + public Single call(Integer v) { + return Single.just(v); + } + }) + .test(1L) + .assertResult(1); + } + + @Test + public void justBackpressuredDelayError() { + Observable.just(1) + .flatMapSingle(new Func1>() { + @Override + public Single call(Integer v) { + return Single.just(v); + } + }, true) + .test(1L) + .assertResult(1); + } + + @Test + public void singleMerge() { + Single.merge(Observable.range(1, 10).map(new Func1>() { + @Override + public Single call(Integer v) { + return Single.just(v); + } + })) + .test() + .assertResult(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + } + + @Test + public void singleMergeMaxConcurrent() { + AssertableSubscriber as = Single.merge(Observable.range(1, 10).map(new Func1>() { + @Override + public Single call(Integer v) { + return Single.just(v).observeOn(Schedulers.computation()); + } + }), 2) + .test() + .awaitTerminalEvent(5, TimeUnit.SECONDS); + + Set set = new HashSet(as.getOnNextEvents()); + + assertEquals("" + set, 10, set.size()); + for (int j = 1; j < 11; j++) { + assertTrue("" + set, set.contains(j)); + } + } + + @Test + public void singleMergeDelayError() { + Single.mergeDelayError(Observable.range(1, 10).map(new Func1>() { + @Override + public Single call(Integer v) { + return Single.just(v); + } + })) + .test() + .assertResult(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + } + + @Test + public void singleMergeDelayErrorMaxConcurrent() { + AssertableSubscriber as = Single.mergeDelayError(Observable.range(1, 10).map(new Func1>() { + @Override + public Single call(Integer v) { + return Single.just(v).observeOn(Schedulers.computation()); + } + }), 2) + .test() + .awaitTerminalEvent(5, TimeUnit.SECONDS); + + Set set = new HashSet(as.getOnNextEvents()); + + assertEquals("" + set, 10, set.size()); + for (int j = 1; j < 11; j++) { + assertTrue("" + set, set.contains(j)); + } + } + + @Test + public void singleMergeDelayErrorWithError() { + Single.mergeDelayError(Observable.range(1, 10) + .concatWith(Observable.error(new TestException())) + .map(new Func1>() { + @Override + public Single call(Integer v) { + return Single.just(v); + } + })) + .test() + .assertFailure(TestException.class, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + } + + @Test + public void singleMergeDelayMaxConcurrentErrorWithError() { + AssertableSubscriber as = Single.mergeDelayError(Observable.range(1, 10) + .concatWith(Observable.error(new TestException())) + .map(new Func1>() { + @Override + public Single call(Integer v) { + return Single.just(v).observeOn(Schedulers.computation()); + } + }), 2) + .test() + .awaitTerminalEvent(5, TimeUnit.SECONDS); + + Set set = new HashSet(as.getOnNextEvents()); + + assertEquals("" + set, 10, set.size()); + for (int j = 1; j < 11; j++) { + assertTrue("" + set, set.contains(j)); + } + } +} diff --git a/src/test/java/rx/internal/operators/OnSubscribeFlattenIterableTest.java b/src/test/java/rx/internal/operators/OnSubscribeFlattenIterableTest.java index 7158456a35..66347a43c4 100644 --- a/src/test/java/rx/internal/operators/OnSubscribeFlattenIterableTest.java +++ b/src/test/java/rx/internal/operators/OnSubscribeFlattenIterableTest.java @@ -1,12 +1,12 @@ /** * Copyright 2016 Netflix, Inc. - * + * * 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. @@ -23,26 +23,38 @@ import rx.Observable; import rx.exceptions.TestException; -import rx.functions.Func1; +import rx.functions.*; import rx.observers.TestSubscriber; import rx.subjects.PublishSubject; public class OnSubscribeFlattenIterableTest { - + final Func1> mapper = new Func1>() { @Override public Iterable call(Integer v) { return Arrays.asList(v, v + 1); } }; - + @Test public void normal() { TestSubscriber ts = new TestSubscriber(); - + Observable.range(1, 5).concatMapIterable(mapper) .subscribe(ts); - + + ts.assertValues(1, 2, 2, 3, 3, 4, 4, 5, 5, 6); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @Test + public void normalViaFlatMap() { + TestSubscriber ts = new TestSubscriber(); + + Observable.range(1, 5).flatMapIterable(mapper) + .subscribe(ts); + ts.assertValues(1, 2, 2, 3, 3, 4, 4, 5, 5, 6); ts.assertNoErrors(); ts.assertCompleted(); @@ -51,28 +63,28 @@ public void normal() { @Test public void normalBackpressured() { TestSubscriber ts = new TestSubscriber(0); - + Observable.range(1, 5).concatMapIterable(mapper) .subscribe(ts); - + ts.assertNoValues(); ts.assertNoErrors(); ts.assertNotCompleted(); - + ts.requestMore(1); - + ts.assertValue(1); ts.assertNoErrors(); ts.assertNotCompleted(); - + ts.requestMore(2); - + ts.assertValues(1, 2, 2); ts.assertNoErrors(); ts.assertNotCompleted(); - + ts.requestMore(7); - + ts.assertValues(1, 2, 2, 3, 3, 4, 4, 5, 5, 6); ts.assertNoErrors(); ts.assertCompleted(); @@ -81,9 +93,9 @@ public void normalBackpressured() { @Test public void longRunning() { TestSubscriber ts = new TestSubscriber(); - + int n = 1000 * 1000; - + Observable.range(1, n).concatMapIterable(mapper) .subscribe(ts); @@ -95,9 +107,9 @@ public void longRunning() { @Test public void asIntermediate() { TestSubscriber ts = new TestSubscriber(); - + int n = 1000 * 1000; - + Observable.range(1, n).concatMapIterable(mapper).concatMap(new Func1>() { @Override public Observable call(Integer v) { @@ -114,10 +126,10 @@ public Observable call(Integer v) { @Test public void just() { TestSubscriber ts = new TestSubscriber(); - + Observable.just(1).concatMapIterable(mapper) .subscribe(ts); - + ts.assertValues(1, 2); ts.assertNoErrors(); ts.assertCompleted(); @@ -126,10 +138,10 @@ public void just() { @Test public void justHidden() { TestSubscriber ts = new TestSubscriber(); - + Observable.just(1).asObservable().concatMapIterable(mapper) .subscribe(ts); - + ts.assertValues(1, 2); ts.assertNoErrors(); ts.assertCompleted(); @@ -138,23 +150,23 @@ public void justHidden() { @Test public void empty() { TestSubscriber ts = new TestSubscriber(); - + Observable.empty().concatMapIterable(mapper) .subscribe(ts); - + ts.assertNoValues(); ts.assertNoErrors(); ts.assertCompleted(); } - + @Test public void error() { TestSubscriber ts = new TestSubscriber(); - + Observable.just(1).concatWith(Observable.error(new TestException())) .concatMapIterable(mapper) .subscribe(ts); - + ts.assertValues(1, 2); ts.assertError(TestException.class); ts.assertNotCompleted(); @@ -163,7 +175,7 @@ public void error() { @Test public void iteratorHasNextThrowsImmediately() { TestSubscriber ts = new TestSubscriber(); - + final Iterable it = new Iterable() { @Override public Iterator iterator() { @@ -172,12 +184,12 @@ public Iterator iterator() { public boolean hasNext() { throw new TestException(); } - + @Override public Integer next() { return 1; } - + @Override public void remove() { throw new UnsupportedOperationException(); @@ -185,7 +197,7 @@ public void remove() { }; } }; - + Observable.range(1, 2) .concatMapIterable(new Func1>() { @Override @@ -194,7 +206,7 @@ public Iterable call(Integer v) { } }) .subscribe(ts); - + ts.assertNoValues(); ts.assertError(TestException.class); ts.assertNotCompleted(); @@ -203,7 +215,7 @@ public Iterable call(Integer v) { @Test public void iteratorHasNextThrowsImmediatelyJust() { TestSubscriber ts = new TestSubscriber(); - + final Iterable it = new Iterable() { @Override public Iterator iterator() { @@ -212,12 +224,12 @@ public Iterator iterator() { public boolean hasNext() { throw new TestException(); } - + @Override public Integer next() { return 1; } - + @Override public void remove() { throw new UnsupportedOperationException(); @@ -225,7 +237,7 @@ public void remove() { }; } }; - + Observable.just(1) .concatMapIterable(new Func1>() { @Override @@ -234,7 +246,7 @@ public Iterable call(Integer v) { } }) .subscribe(ts); - + ts.assertNoValues(); ts.assertError(TestException.class); ts.assertNotCompleted(); @@ -243,7 +255,7 @@ public Iterable call(Integer v) { @Test public void iteratorHasNextThrowsSecondCall() { TestSubscriber ts = new TestSubscriber(); - + final Iterable it = new Iterable() { @Override public Iterator iterator() { @@ -256,12 +268,12 @@ public boolean hasNext() { } return true; } - + @Override public Integer next() { return 1; } - + @Override public void remove() { throw new UnsupportedOperationException(); @@ -269,7 +281,7 @@ public void remove() { }; } }; - + Observable.range(1, 2) .concatMapIterable(new Func1>() { @Override @@ -278,7 +290,7 @@ public Iterable call(Integer v) { } }) .subscribe(ts); - + ts.assertValue(1); ts.assertError(TestException.class); ts.assertNotCompleted(); @@ -287,7 +299,7 @@ public Iterable call(Integer v) { @Test public void iteratorNextThrows() { TestSubscriber ts = new TestSubscriber(); - + final Iterable it = new Iterable() { @Override public Iterator iterator() { @@ -296,12 +308,12 @@ public Iterator iterator() { public boolean hasNext() { return true; } - + @Override public Integer next() { throw new TestException(); } - + @Override public void remove() { throw new UnsupportedOperationException(); @@ -309,7 +321,7 @@ public void remove() { }; } }; - + Observable.range(1, 2) .concatMapIterable(new Func1>() { @Override @@ -318,7 +330,7 @@ public Iterable call(Integer v) { } }) .subscribe(ts); - + ts.assertNoValues(); ts.assertError(TestException.class); ts.assertNotCompleted(); @@ -327,7 +339,7 @@ public Iterable call(Integer v) { @Test public void iteratorNextThrowsAndUnsubscribes() { TestSubscriber ts = new TestSubscriber(); - + final Iterable it = new Iterable() { @Override public Iterator iterator() { @@ -336,12 +348,12 @@ public Iterator iterator() { public boolean hasNext() { return true; } - + @Override public Integer next() { throw new TestException(); } - + @Override public void remove() { throw new UnsupportedOperationException(); @@ -349,9 +361,9 @@ public void remove() { }; } }; - + PublishSubject ps = PublishSubject.create(); - + ps .concatMapIterable(new Func1>() { @Override @@ -360,20 +372,20 @@ public Iterable call(Integer v) { } }) .unsafeSubscribe(ts); - + ps.onNext(1); - + ts.assertNoValues(); ts.assertError(TestException.class); ts.assertNotCompleted(); - + Assert.assertFalse("PublishSubject has Observers?!", ps.hasObservers()); } @Test public void mixture() { TestSubscriber ts = new TestSubscriber(); - + Observable.range(0, 1000) .concatMapIterable(new Func1>() { @Override @@ -382,16 +394,16 @@ public Iterable call(Integer v) { } }) .subscribe(ts); - + ts.assertValueCount(500); ts.assertNoErrors(); ts.assertCompleted(); } - + @Test public void emptyInnerThenSingleBackpressured() { TestSubscriber ts = new TestSubscriber(1); - + Observable.range(1, 2) .concatMapIterable(new Func1>() { @Override @@ -400,16 +412,16 @@ public Iterable call(Integer v) { } }) .subscribe(ts); - + ts.assertValue(1); ts.assertNoErrors(); ts.assertCompleted(); } - + @Test public void manyEmptyInnerThenSingleBackpressured() { TestSubscriber ts = new TestSubscriber(1); - + Observable.range(1, 1000) .concatMapIterable(new Func1>() { @Override @@ -418,18 +430,18 @@ public Iterable call(Integer v) { } }) .subscribe(ts); - + ts.assertValue(1); ts.assertNoErrors(); ts.assertCompleted(); } - + @Test public void hasNextIsNotCalledAfterChildUnsubscribedOnNext() { TestSubscriber ts = new TestSubscriber(); - + final AtomicInteger counter = new AtomicInteger(); - + final Iterable it = new Iterable() { @Override public Iterator iterator() { @@ -439,12 +451,12 @@ public boolean hasNext() { counter.getAndIncrement(); return true; } - + @Override public Integer next() { return 1; } - + @Override public void remove() { throw new UnsupportedOperationException(); @@ -452,9 +464,9 @@ public void remove() { }; } }; - + PublishSubject ps = PublishSubject.create(); - + ps .concatMapIterable(new Func1>() { @Override @@ -464,14 +476,49 @@ public Iterable call(Integer v) { }) .take(1) .unsafeSubscribe(ts); - + ps.onNext(1); - + ts.assertValue(1); ts.assertNoErrors(); ts.assertCompleted(); - + Assert.assertFalse("PublishSubject has Observers?!", ps.hasObservers()); Assert.assertEquals(1, counter.get()); } + + @Test + public void normalPrefetchViaFlatMap() { + TestSubscriber ts = new TestSubscriber(); + + Observable.range(1, 5).flatMapIterable(mapper, 2) + .subscribe(ts); + + ts.assertValues(1, 2, 2, 3, 3, 4, 4, 5, 5, 6); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @Test + public void withResultSelectorMaxConcurrent() { + TestSubscriber ts = TestSubscriber.create(); + + Observable.range(1, 5).flatMapIterable(new Func1>() { + @Override + public Iterable call(Integer v) { + return Collections.singletonList(1); + } + }, new Func2() { + @Override + public Integer call(Integer a, Integer b) { + return a * 10 + b; + } + }, 2) + .subscribe(ts) + ; + + ts.assertValues(11, 21, 31, 41, 51); + ts.assertNoErrors(); + ts.assertCompleted(); + } } diff --git a/src/test/java/rx/internal/operators/OnSubscribeFromArrayTest.java b/src/test/java/rx/internal/operators/OnSubscribeFromArrayTest.java index 3b7ec5220b..1d5368f097 100644 --- a/src/test/java/rx/internal/operators/OnSubscribeFromArrayTest.java +++ b/src/test/java/rx/internal/operators/OnSubscribeFromArrayTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -16,26 +16,27 @@ package rx.internal.operators; -import org.junit.Test; +import org.junit.*; import rx.Observable; +import rx.internal.util.ScalarSynchronousObservable; import rx.observers.TestSubscriber; public class OnSubscribeFromArrayTest { - + Observable create(int n) { Integer[] array = new Integer[n]; for (int i = 0; i < n; i++) { array[i] = i; } - return Observable.create(new OnSubscribeFromArray(array)); + return Observable.unsafeCreate(new OnSubscribeFromArray(array)); } @Test public void simple() { TestSubscriber ts = new TestSubscriber(); - + create(1000).subscribe(ts); - + ts.assertNoErrors(); ts.assertValueCount(1000); ts.assertCompleted(); @@ -44,24 +45,35 @@ public void simple() { @Test public void backpressure() { TestSubscriber ts = TestSubscriber.create(0); - + create(1000).subscribe(ts); - + ts.assertNoErrors(); ts.assertNoValues(); ts.assertNotCompleted(); - + ts.requestMore(10); - + ts.assertNoErrors(); ts.assertValueCount(10); ts.assertNotCompleted(); - + ts.requestMore(1000); - + ts.assertNoErrors(); ts.assertValueCount(1000); ts.assertCompleted(); } + @Test + public void empty() { + Assert.assertSame(Observable.empty(), Observable.from(new Object[0])); + } + + @Test + public void just() { + Observable source = Observable.from(new Integer[] { 1 }); + Assert.assertTrue(source.getClass().toString(), source instanceof ScalarSynchronousObservable); + } + } diff --git a/src/test/java/rx/internal/operators/OnSubscribeFromIterableTest.java b/src/test/java/rx/internal/operators/OnSubscribeFromIterableTest.java index 00956b9cae..0464ed8dc0 100644 --- a/src/test/java/rx/internal/operators/OnSubscribeFromIterableTest.java +++ b/src/test/java/rx/internal/operators/OnSubscribeFromIterableTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -38,12 +38,12 @@ public class OnSubscribeFromIterableTest { @Test(expected = NullPointerException.class) public void testNull() { - Observable.create(new OnSubscribeFromIterable(null)); + Observable.unsafeCreate(new OnSubscribeFromIterable(null)); } - + @Test public void testListIterable() { - Observable observable = Observable.create(new OnSubscribeFromIterable(Arrays. asList("one", "two", "three"))); + Observable observable = Observable.unsafeCreate(new OnSubscribeFromIterable(Arrays. asList("one", "two", "three"))); @SuppressWarnings("unchecked") Observer observer = mock(Observer.class); @@ -66,7 +66,7 @@ public void testRawIterable() { public Iterator iterator() { return new Iterator() { - int i = 0; + int i; @Override public boolean hasNext() { @@ -86,7 +86,7 @@ public void remove() { } }; - Observable observable = Observable.create(new OnSubscribeFromIterable(it)); + Observable observable = Observable.unsafeCreate(new OnSubscribeFromIterable(it)); @SuppressWarnings("unchecked") Observer observer = mock(Observer.class); @@ -158,14 +158,14 @@ public void testSubscribeMultipleTimes() { o.call(ts); ts.assertReceivedOnNext(Arrays.asList(1, 2, 3)); } - + @Test public void testFromIterableRequestOverflow() throws InterruptedException { Observable o = Observable.from(Arrays.asList(1,2,3,4)); final int expectedCount = 4; final CountDownLatch latch = new CountDownLatch(expectedCount); o.subscribeOn(Schedulers.computation()).subscribe(new Subscriber() { - + @Override public void onStart() { request(2); @@ -184,7 +184,7 @@ public void onError(Throwable e) { @Override public void onNext(Integer t) { latch.countDown(); - request(Long.MAX_VALUE-1); + request(Long.MAX_VALUE - 1); }}); assertTrue(latch.await(10, TimeUnit.SECONDS)); } @@ -198,7 +198,7 @@ public void testFromEmptyIterableWhenZeroRequestedShouldStillEmitOnCompletedEage public void onStart() { request(0); } - + @Override public void onCompleted() { completed.set(true); @@ -206,16 +206,16 @@ public void onCompleted() { @Override public void onError(Throwable e) { - + } @Override public void onNext(Object t) { - + }}); assertTrue(completed.get()); } - + @Test public void testDoesNotCallIteratorHasNextMoreThanRequiredWithBackpressure() { final AtomicBoolean called = new AtomicBoolean(false); @@ -226,7 +226,7 @@ public Iterator iterator() { return new Iterator() { int count = 1; - + @Override public void remove() { // ignore @@ -237,8 +237,9 @@ public boolean hasNext() { if (count > 1) { called.set(true); return false; - } else + } else { return true; + } } @Override @@ -274,8 +275,9 @@ public boolean hasNext() { if (count > 1) { called.set(true); return false; - } else + } else { return true; + } } @Override @@ -315,11 +317,11 @@ public Iterator iterator() { throw new TestException("Forced failure"); } }; - + TestSubscriber ts = new TestSubscriber(); - + Observable.from(it).unsafeSubscribe(ts); - + ts.assertNoValues(); ts.assertError(TestException.class); ts.assertNotCompleted(); @@ -335,12 +337,12 @@ public Iterator iterator() { public boolean hasNext() { throw new TestException("Forced failure"); } - + @Override public Integer next() { return null; } - + @Override public void remove() { // ignored @@ -348,11 +350,11 @@ public void remove() { }; } }; - + TestSubscriber ts = new TestSubscriber(); - + Observable.from(it).unsafeSubscribe(ts); - + ts.assertNoValues(); ts.assertError(TestException.class); ts.assertNotCompleted(); @@ -372,12 +374,12 @@ public boolean hasNext() { } return true; } - + @Override public Integer next() { return 1; } - + @Override public void remove() { // ignored @@ -385,11 +387,11 @@ public void remove() { }; } }; - + TestSubscriber ts = new TestSubscriber(); - + Observable.from(it).unsafeSubscribe(ts); - + ts.assertValues(1); ts.assertError(TestException.class); ts.assertNotCompleted(); @@ -409,12 +411,12 @@ public boolean hasNext() { } return true; } - + @Override public Integer next() { return 1; } - + @Override public void remove() { // ignored @@ -422,16 +424,16 @@ public void remove() { }; } }; - + TestSubscriber ts = new TestSubscriber(5); - + Observable.from(it).unsafeSubscribe(ts); - + ts.assertValues(1); ts.assertError(TestException.class); ts.assertNotCompleted(); } - + @Test public void nextThrowsFastpath() { Iterable it = new Iterable() { @@ -442,12 +444,12 @@ public Iterator iterator() { public boolean hasNext() { return true; } - + @Override public Integer next() { throw new TestException("Forced failure"); } - + @Override public void remove() { // ignored @@ -455,11 +457,11 @@ public void remove() { }; } }; - + TestSubscriber ts = new TestSubscriber(); - + Observable.from(it).unsafeSubscribe(ts); - + ts.assertNoValues(); ts.assertError(TestException.class); ts.assertNotCompleted(); @@ -475,12 +477,12 @@ public Iterator iterator() { public boolean hasNext() { return true; } - + @Override public Integer next() { throw new TestException("Forced failure"); } - + @Override public void remove() { // ignored @@ -488,11 +490,11 @@ public void remove() { }; } }; - + TestSubscriber ts = new TestSubscriber(5); - + Observable.from(it).unsafeSubscribe(ts); - + ts.assertNoValues(); ts.assertError(TestException.class); ts.assertNotCompleted(); @@ -508,12 +510,12 @@ public Iterator iterator() { public boolean hasNext() { return false; } - + @Override public Integer next() { throw new NoSuchElementException(); } - + @Override public void remove() { // ignored @@ -521,15 +523,15 @@ public void remove() { }; } }; - + TestSubscriber ts = new TestSubscriber(5); ts.unsubscribe(); - + Observable.from(it).unsafeSubscribe(ts); - + ts.assertNoValues(); ts.assertNoErrors(); ts.assertNotCompleted(); - + } } diff --git a/src/test/java/rx/internal/operators/OnSubscribeGroupJoinTest.java b/src/test/java/rx/internal/operators/OnSubscribeGroupJoinTest.java index 9a1d7aa9ef..f053929c47 100644 --- a/src/test/java/rx/internal/operators/OnSubscribeGroupJoinTest.java +++ b/src/test/java/rx/internal/operators/OnSubscribeGroupJoinTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. diff --git a/src/test/java/rx/internal/operators/OnSubscribeJoinTest.java b/src/test/java/rx/internal/operators/OnSubscribeJoinTest.java index 76f12728f9..6f5cc80ae2 100644 --- a/src/test/java/rx/internal/operators/OnSubscribeJoinTest.java +++ b/src/test/java/rx/internal/operators/OnSubscribeJoinTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. diff --git a/src/test/java/rx/internal/operators/OperatorMapTest.java b/src/test/java/rx/internal/operators/OnSubscribeMapTest.java similarity index 94% rename from src/test/java/rx/internal/operators/OperatorMapTest.java rename to src/test/java/rx/internal/operators/OnSubscribeMapTest.java index 18e3e523e3..12c24cb303 100644 --- a/src/test/java/rx/internal/operators/OperatorMapTest.java +++ b/src/test/java/rx/internal/operators/OnSubscribeMapTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -32,7 +32,7 @@ import rx.schedulers.Schedulers; import rx.subjects.PublishSubject; -public class OperatorMapTest { +public class OnSubscribeMapTest { @Mock Observer stringObserver; @@ -57,14 +57,14 @@ public void testMap() { Map m2 = getMap("Two"); Observable> observable = Observable.just(m1, m2); - Observable m = observable.lift(new OperatorMap, String>(new Func1, String>() { + Observable m = observable.map(new Func1, String>() { @Override public String call(Map map) { return map.get("firstName"); } - })); + }); m.subscribe(stringObserver); verify(stringObserver, never()).onError(any(Throwable.class)); @@ -155,7 +155,7 @@ public String call(Map map) { @Test public void testMapWithError() { Observable w = Observable.just("one", "fail", "two", "three", "fail"); - Observable m = w.lift(new OperatorMap(new Func1() { + Observable m = w.map(new Func1() { @Override public String call(String s) { if ("fail".equals(s)) { @@ -163,7 +163,7 @@ public String call(String s) { } return s; } - })).doOnError(new Action1() { + }).doOnError(new Action1() { @Override public void call(Throwable t1) { @@ -278,7 +278,7 @@ public Observable call(Object object) { }; Func1 mapper = new Func1() { - private int count = 0; + private int count; @Override public Object call(Object object) { @@ -299,7 +299,7 @@ public void call(Object object) { }; try { - Observable.create(creator).flatMap(manyMapper).map(mapper).subscribe(onNext); + Observable.unsafeCreate(creator).flatMap(manyMapper).map(mapper).subscribe(onNext); } catch (RuntimeException e) { e.printStackTrace(); throw e; @@ -332,27 +332,27 @@ public void call(String s) { } }); } - + @Test public void functionCrashUnsubscribes() { - + PublishSubject ps = PublishSubject.create(); - + TestSubscriber ts = new TestSubscriber(); - + ps.map(new Func1() { @Override - public Integer call(Integer v) { - throw new TestException(); + public Integer call(Integer v) { + throw new TestException(); } }).unsafeSubscribe(ts); - + Assert.assertTrue("Not subscribed?", ps.hasObservers()); - + ps.onNext(1); - + Assert.assertFalse("Subscribed?", ps.hasObservers()); - + ts.assertError(TestException.class); } } diff --git a/src/test/java/rx/internal/operators/OnSubscribeRangeTest.java b/src/test/java/rx/internal/operators/OnSubscribeRangeTest.java index acc2f6ff75..291d351943 100644 --- a/src/test/java/rx/internal/operators/OnSubscribeRangeTest.java +++ b/src/test/java/rx/internal/operators/OnSubscribeRangeTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -15,22 +15,13 @@ */ package rx.internal.operators; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; +import java.util.*; +import java.util.concurrent.atomic.*; -import org.junit.Test; +import org.junit.*; import rx.Observable; import rx.Observer; @@ -136,11 +127,11 @@ public void testNoBackpressure() { } void testWithBackpressureOneByOne(int start) { Observable source = Observable.range(start, 100); - + TestSubscriber ts = TestSubscriber.create(); ts.requestMore(1); source.subscribe(ts); - + List list = new ArrayList(100); for (int i = 0; i < 100; i++) { list.add(i + start); @@ -151,11 +142,11 @@ void testWithBackpressureOneByOne(int start) { } void testWithBackpressureAllAtOnce(int start) { Observable source = Observable.range(start, 100); - + TestSubscriber ts = TestSubscriber.create(); ts.requestMore(100); source.subscribe(ts); - + List list = new ArrayList(100); for (int i = 0; i < 100; i++) { list.add(i + start); @@ -178,22 +169,22 @@ public void testWithBackpressureAllAtOnce() { @Test public void testWithBackpressureRequestWayMore() { Observable source = Observable.range(50, 100); - + TestSubscriber ts = TestSubscriber.create(); ts.requestMore(150); source.subscribe(ts); - + List list = new ArrayList(100); for (int i = 0; i < 100; i++) { list.add(i + 50); } - + ts.requestMore(50); // and then some - + ts.assertReceivedOnNext(list); ts.assertTerminalEvent(); } - + @Test public void testRequestOverflow() { final AtomicInteger count = new AtomicInteger(); @@ -204,7 +195,7 @@ public void testRequestOverflow() { public void onStart() { request(2); } - + @Override public void onCompleted() { //do nothing @@ -222,7 +213,7 @@ public void onNext(Integer t) { }}); assertEquals(n, count.get()); } - + @Test public void testEmptyRangeSendsOnCompleteEagerlyWithRequestZero() { final AtomicBoolean completed = new AtomicBoolean(false); @@ -232,7 +223,7 @@ public void testEmptyRangeSendsOnCompleteEagerlyWithRequestZero() { public void onStart() { request(0); } - + @Override public void onCompleted() { completed.set(true); @@ -240,21 +231,21 @@ public void onCompleted() { @Override public void onError(Throwable e) { - + } @Override public void onNext(Integer t) { - + }}); assertTrue(completed.get()); } - + @Test(timeout = 1000) public void testNearMaxValueWithoutBackpressure() { TestSubscriber ts = TestSubscriber.create(); Observable.range(Integer.MAX_VALUE - 1, 2).subscribe(ts); - + ts.assertCompleted(); ts.assertNoErrors(); ts.assertValues(Integer.MAX_VALUE - 1, Integer.MAX_VALUE); @@ -263,9 +254,19 @@ public void testNearMaxValueWithoutBackpressure() { public void testNearMaxValueWithBackpressure() { TestSubscriber ts = TestSubscriber.create(3); Observable.range(Integer.MAX_VALUE - 1, 2).subscribe(ts); - + ts.assertCompleted(); ts.assertNoErrors(); ts.assertValues(Integer.MAX_VALUE - 1, Integer.MAX_VALUE); } + + @Test + public void negativeCount() { + try { + Observable.range(1, -1); + Assert.fail("Should have thrown IllegalArgumentException"); + } catch (IllegalArgumentException ex) { + Assert.assertEquals("Count can not be negative", ex.getMessage()); + } + } } diff --git a/src/test/java/rx/internal/operators/OnSubscribeReduceTest.java b/src/test/java/rx/internal/operators/OnSubscribeReduceTest.java new file mode 100644 index 0000000000..7d0f1a6cc3 --- /dev/null +++ b/src/test/java/rx/internal/operators/OnSubscribeReduceTest.java @@ -0,0 +1,276 @@ +/** + * Copyright 2014 Netflix, Inc. + * + * 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 rx.internal.operators; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import java.util.Arrays; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.concurrent.CopyOnWriteArrayList; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import rx.*; +import rx.Observable.OnSubscribe; +import rx.Observer; +import rx.exceptions.TestException; +import rx.functions.Action1; +import rx.functions.Func1; +import rx.functions.Func2; +import rx.internal.util.UtilityFunctions; +import rx.observers.TestSubscriber; +import rx.plugins.RxJavaHooks; + +public class OnSubscribeReduceTest { + @Mock + Observer observer; + + @Before + public void before() { + MockitoAnnotations.initMocks(this); + } + + Func2 sum = new Func2() { + @Override + public Integer call(Integer t1, Integer t2) { + return t1 + t2; + } + }; + + @Test + public void testAggregateAsIntSum() { + + Observable result = Observable.just(1, 2, 3, 4, 5).reduce(0, sum).map(UtilityFunctions. identity()); + + result.subscribe(observer); + + verify(observer).onNext(1 + 2 + 3 + 4 + 5); + verify(observer).onCompleted(); + verify(observer, never()).onError(any(Throwable.class)); + } + + @Test + public void testAggregateAsIntSumSourceThrows() { + Observable result = Observable.concat(Observable.just(1, 2, 3, 4, 5), + Observable. error(new TestException())) + .reduce(0, sum).map(UtilityFunctions. identity()); + + result.subscribe(observer); + + verify(observer, never()).onNext(any()); + verify(observer, never()).onCompleted(); + verify(observer, times(1)).onError(any(TestException.class)); + } + + @Test + public void testAggregateAsIntSumAccumulatorThrows() { + Func2 sumErr = new Func2() { + @Override + public Integer call(Integer t1, Integer t2) { + throw new TestException(); + } + }; + + Observable result = Observable.just(1, 2, 3, 4, 5) + .reduce(0, sumErr).map(UtilityFunctions. identity()); + + result.subscribe(observer); + + verify(observer, never()).onNext(any()); + verify(observer, never()).onCompleted(); + verify(observer, times(1)).onError(any(TestException.class)); + } + + @Test + public void testAggregateAsIntSumResultSelectorThrows() { + + Func1 error = new Func1() { + + @Override + public Integer call(Integer t1) { + throw new TestException(); + } + }; + + Observable result = Observable.just(1, 2, 3, 4, 5) + .reduce(0, sum).map(error); + + result.subscribe(observer); + + verify(observer, never()).onNext(any()); + verify(observer, never()).onCompleted(); + verify(observer, times(1)).onError(any(TestException.class)); + } + + @Test + public void testBackpressureWithNoInitialValue() throws InterruptedException { + Observable source = Observable.just(1, 2, 3, 4, 5, 6); + Observable reduced = source.reduce(sum); + + Integer r = reduced.toBlocking().first(); + assertEquals(21, r.intValue()); + } + + @Test + public void testBackpressureWithInitialValue() throws InterruptedException { + Observable source = Observable.just(1, 2, 3, 4, 5, 6); + Observable reduced = source.reduce(0, sum); + + Integer r = reduced.toBlocking().first(); + assertEquals(21, r.intValue()); + } + + @Test + public void testNoInitialValueDoesNotEmitMultipleTerminalEvents() { + TestSubscriber ts = TestSubscriber.create(); + Observable.unsafeCreate(new OnSubscribe() { + + @Override + public void call(final Subscriber sub) { + sub.setProducer(new Producer() { + + @Override + public void request(long n) { + if (n > 0) { + sub.onNext(1); + sub.onNext(2); + sub.onCompleted(); + } + } + }); + } + }) + .reduce(new Func2() { + + @Override + public Integer call(Integer a, Integer b) { + throw new RuntimeException("boo"); + }}) + .unsafeSubscribe(ts); + ts.assertError(RuntimeException.class); + ts.assertNotCompleted(); + } + + @Test + public void testNoInitialValueUpstreamEmitsMoreOnNextDespiteUnsubscription() { + TestSubscriber ts = TestSubscriber.create(); + Observable.unsafeCreate(new OnSubscribe() { + + @Override + public void call(final Subscriber sub) { + sub.setProducer(new Producer() { + + @Override + public void request(long n) { + if (n > 2) { + sub.onNext(1); + sub.onNext(2); + sub.onNext(3); + sub.onCompleted(); + } + } + }); + } + }) + .reduce(new Func2() { + boolean once = true; + + @Override + public Integer call(Integer a, Integer b) { + if (once) { + throw new RuntimeException("boo"); + } else { + once = false; + return a + b; + } + }}) + .unsafeSubscribe(ts); + ts.assertNoValues(); + ts.assertError(RuntimeException.class); + ts.assertNotCompleted(); + } + + @Test + public void testNoInitialValueDoesNotEmitMultipleErrorEventsAndReportsSecondErrorToHooks() { + try { + final List list = new CopyOnWriteArrayList(); + RxJavaHooks.setOnError(new Action1() { + + @Override + public void call(Throwable t) { + list.add(t); + } + }); + TestSubscriber ts = TestSubscriber.create(); + final RuntimeException e1 = new RuntimeException("e1"); + final Throwable e2 = new RuntimeException("e2"); + Observable.unsafeCreate(new OnSubscribe() { + + @Override + public void call(final Subscriber sub) { + sub.setProducer(new Producer() { + + @Override + public void request(long n) { + if (n > 1) { + sub.onNext(1); + sub.onNext(2); + sub.onError(e2); + } + } + }); + } + }) + .reduce(new Func2() { + + @Override + public Integer call(Integer a, Integer b) { + throw e1; + }}) + .unsafeSubscribe(ts); + ts.assertNotCompleted(); + System.out.println(ts.getOnErrorEvents()); + assertEquals(Arrays.asList(e1), ts.getOnErrorEvents()); + assertEquals(Arrays.asList(e2), list); + } finally { + RxJavaHooks.reset(); + } + } + + + @Test + public void testNoInitialValueEmitsNoSuchElementExceptionIfEmptyStream() { + TestSubscriber ts = TestSubscriber.create(); + Observable.empty().reduce(new Func2() { + + @Override + public Integer call(Integer a, Integer b) { + return a + b; + } + }).subscribe(ts); + ts.assertError(NoSuchElementException.class); + } + +} diff --git a/src/test/java/rx/internal/operators/OnSubscribeRefCountTest.java b/src/test/java/rx/internal/operators/OnSubscribeRefCountTest.java index ab076ce411..a824f3191b 100644 --- a/src/test/java/rx/internal/operators/OnSubscribeRefCountTest.java +++ b/src/test/java/rx/internal/operators/OnSubscribeRefCountTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -19,6 +19,7 @@ import static org.mockito.Matchers.any; import static org.mockito.Mockito.*; +import java.lang.management.ManagementFactory; import java.util.*; import java.util.concurrent.*; import java.util.concurrent.atomic.*; @@ -31,6 +32,7 @@ import rx.Observable.OnSubscribe; import rx.Observer; import rx.functions.*; +import rx.observables.ConnectableObservable; import rx.observers.*; import rx.schedulers.*; import rx.subjects.ReplaySubject; @@ -276,7 +278,7 @@ public void testConnectUnsubscribeRaceConditionLoop() throws InterruptedExceptio testConnectUnsubscribeRaceCondition(); } } - + @Test public void testConnectUnsubscribeRaceCondition() throws InterruptedException { final AtomicInteger subUnsubCount = new AtomicInteger(); @@ -302,7 +304,7 @@ public void call() { }); TestSubscriber s = new TestSubscriber(); - + o.publish().refCount().subscribeOn(Schedulers.computation()).subscribe(s); System.out.println("send unsubscribe"); // now immediately unsubscribe while subscribeOn is racing to subscribe @@ -322,7 +324,7 @@ public void call() { } private Observable synchronousInterval() { - return Observable.create(new OnSubscribe() { + return Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber subscriber) { @@ -341,7 +343,7 @@ public void call(Subscriber subscriber) { public void onlyFirstShouldSubscribeAndLastUnsubscribe() { final AtomicInteger subscriptionCount = new AtomicInteger(); final AtomicInteger unsubscriptionCount = new AtomicInteger(); - Observable observable = Observable.create(new OnSubscribe() { + Observable observable = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber observer) { subscriptionCount.incrementAndGet(); @@ -528,10 +530,10 @@ public Integer call(Integer t1, Integer t2) { @Test(timeout = 10000) public void testUpstreamErrorAllowsRetry() throws InterruptedException { - + final AtomicReference err1 = new AtomicReference(); final AtomicReference err2 = new AtomicReference(); - + final AtomicInteger intervalSubscribed = new AtomicInteger(); Observable interval = Observable.interval(200,TimeUnit.MILLISECONDS) @@ -602,13 +604,164 @@ public void call(Throwable t) { err2.set(t); } }); - + Thread.sleep(1300); - + System.out.println(intervalSubscribed.get()); assertEquals(6, intervalSubscribed.get()); - + assertNotNull("First subscriber didn't get the error", err1); assertNotNull("Second subscriber didn't get the error", err2); } + + Observable source; + + @Test + public void replayNoLeak() throws Exception { + System.gc(); + Thread.sleep(100); + + long start = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getUsed(); + + source = Observable.fromCallable(new Callable() { + @Override + public Object call() throws Exception { + return new byte[100 * 1000 * 1000]; + } + }) + .replay(1) + .refCount(); + + source.subscribe(); + + 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 { + System.gc(); + Thread.sleep(100); + + long start = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getUsed(); + + source = Observable.fromCallable(new Callable() { + @Override + public Object call() throws Exception { + return new byte[100 * 1000 * 1000]; + } + }).concatWith(Observable.never()) + .replay(1) + .refCount(); + + Subscription s1 = source.subscribe(); + Subscription s2 = source.subscribe(); + + s1.unsubscribe(); + s2.unsubscribe(); + + s1 = null; + s2 = 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); + } + + static final class ExceptionData extends Exception { + private static final long serialVersionUID = -6763898015338136119L; + + public final Object data; + + public ExceptionData(Object data) { + this.data = data; + } + } + + @Test + public void publishNoLeak() throws Exception { + System.gc(); + Thread.sleep(100); + + long start = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getUsed(); + + source = Observable.fromCallable(new Callable() { + @Override + public Object call() throws Exception { + throw new ExceptionData(new byte[100 * 1000 * 1000]); + } + }) + .publish() + .refCount(); + + Action1 err = Actions.empty(); + source.subscribe(Actions.empty(), err); + + 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 publishNoLeak2() throws Exception { + System.gc(); + Thread.sleep(100); + + long start = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getUsed(); + + source = Observable.fromCallable(new Callable() { + @Override + public Object call() throws Exception { + return new byte[100 * 1000 * 1000]; + } + }).concatWith(Observable.never()) + .publish() + .refCount(); + + Subscription s1 = source.test(0); + Subscription s2 = source.test(0); + + s1.unsubscribe(); + s2.unsubscribe(); + + s1 = null; + s2 = 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() { + ConnectableObservable co = Observable.just(1) + .replay(); + + assertTrue(((Subscription)co).isUnsubscribed()); + + Subscription s = co.connect(); + + assertFalse(((Subscription)co).isUnsubscribed()); + + s.unsubscribe(); + + assertTrue(((Subscription)co).isUnsubscribed()); + } } diff --git a/src/test/java/rx/internal/operators/OnSubscribeSingleTest.java b/src/test/java/rx/internal/operators/OnSubscribeSingleTest.java index 8b3dbf910e..2f7687da8d 100644 --- a/src/test/java/rx/internal/operators/OnSubscribeSingleTest.java +++ b/src/test/java/rx/internal/operators/OnSubscribeSingleTest.java @@ -75,7 +75,7 @@ public void testRepeatObservableThrowsError() { subscriber.assertError(IllegalArgumentException.class); } - + @Test public void testShouldUseUnsafeSubscribeInternallyNotSubscribe() { TestSubscriber subscriber = TestSubscriber.create(); diff --git a/src/test/java/rx/internal/operators/OnSubscribeTimerTest.java b/src/test/java/rx/internal/operators/OnSubscribeTimerTest.java index 99c677cedb..98e7a05e90 100644 --- a/src/test/java/rx/internal/operators/OnSubscribeTimerTest.java +++ b/src/test/java/rx/internal/operators/OnSubscribeTimerTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -37,6 +37,7 @@ import rx.Subscription; import rx.exceptions.TestException; import rx.observables.ConnectableObservable; +import rx.observers.TestSubscriber; import rx.schedulers.TestScheduler; public class OnSubscribeTimerTest { @@ -233,7 +234,7 @@ public void testWithMultipleStaggeredSubscribersAndPublish() { @Test public void testOnceObserverThrows() { Observable source = Observable.timer(100, TimeUnit.MILLISECONDS, scheduler); - + source.subscribe(new Subscriber() { @Override @@ -251,9 +252,9 @@ public void onCompleted() { observer.onCompleted(); } }); - + scheduler.advanceTimeBy(1, TimeUnit.SECONDS); - + verify(observer).onError(any(TestException.class)); verify(observer, never()).onNext(anyLong()); verify(observer, never()).onCompleted(); @@ -261,9 +262,9 @@ public void onCompleted() { @Test public void testPeriodicObserverThrows() { Observable source = Observable.interval(100, 100, TimeUnit.MILLISECONDS, scheduler); - + InOrder inOrder = inOrder(observer); - + source.subscribe(new Subscriber() { @Override @@ -284,12 +285,52 @@ public void onCompleted() { observer.onCompleted(); } }); - + scheduler.advanceTimeBy(1, TimeUnit.SECONDS); - + inOrder.verify(observer).onNext(0L); inOrder.verify(observer).onError(any(TestException.class)); inOrder.verifyNoMoreInteractions(); verify(observer, never()).onCompleted(); } + + @Test + public void timerInterval() { + @SuppressWarnings("deprecation") + Observable w = Observable.timer(1, 1, TimeUnit.SECONDS, scheduler); + Subscription sub = w.subscribe(observer); + + verify(observer, never()).onNext(0L); + verify(observer, never()).onCompleted(); + verify(observer, never()).onError(any(Throwable.class)); + + scheduler.advanceTimeTo(2, TimeUnit.SECONDS); + + InOrder inOrder = inOrder(observer); + inOrder.verify(observer, times(1)).onNext(0L); + inOrder.verify(observer, times(1)).onNext(1L); + inOrder.verify(observer, never()).onNext(2L); + verify(observer, never()).onCompleted(); + verify(observer, never()).onError(any(Throwable.class)); + + sub.unsubscribe(); + scheduler.advanceTimeTo(4, TimeUnit.SECONDS); + verify(observer, never()).onNext(2L); + verify(observer, never()).onCompleted(); + verify(observer, never()).onError(any(Throwable.class)); + } + + @Test + public void timerIntervalDefaultScheduler() { + @SuppressWarnings("deprecation") + Observable source = Observable.timer(1, 1, TimeUnit.MILLISECONDS).take(100); + + TestSubscriber ts = TestSubscriber.create(); + source.subscribe(ts); + + ts.awaitTerminalEventAndUnsubscribeOnTimeout(5, TimeUnit.SECONDS); + ts.assertValueCount(100); + ts.assertNoErrors(); + ts.assertCompleted(); + } } \ No newline at end of file diff --git a/src/test/java/rx/internal/operators/OperatorToMapTest.java b/src/test/java/rx/internal/operators/OnSubscribeToMapTest.java similarity index 65% rename from src/test/java/rx/internal/operators/OperatorToMapTest.java rename to src/test/java/rx/internal/operators/OnSubscribeToMapTest.java index 466cff0df8..97b7f33673 100644 --- a/src/test/java/rx/internal/operators/OperatorToMapTest.java +++ b/src/test/java/rx/internal/operators/OnSubscribeToMapTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -15,22 +15,37 @@ */ package rx.internal.operators; +import static org.junit.Assert.assertEquals; import static org.mockito.Matchers.any; import static org.mockito.Mockito.*; +import static org.mockito.Mockito.verify; -import java.util.*; +import java.util.Arrays; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CopyOnWriteArrayList; -import org.junit.*; -import org.mockito.*; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; import rx.Observable; +import rx.Observable.OnSubscribe; import rx.Observer; +import rx.Producer; +import rx.Subscriber; import rx.exceptions.TestException; -import rx.functions.*; +import rx.functions.Action1; +import rx.functions.Func0; +import rx.functions.Func1; import rx.internal.util.UtilityFunctions; import rx.observers.TestSubscriber; +import rx.plugins.RxJavaHooks; -public class OperatorToMapTest { +public class OnSubscribeToMapTest { @Mock Observer objectObserver; @@ -222,23 +237,23 @@ public Integer call(String t1) { @Test public void testKeySelectorThrows() { TestSubscriber ts = TestSubscriber.create(); - + Observable.just(1, 2).toMap(new Func1() { @Override public Integer call(Integer v) { throw new TestException(); } }).subscribe(ts); - + ts.assertError(TestException.class); ts.assertNoValues(); ts.assertNotCompleted(); } - + @Test public void testValueSelectorThrows() { TestSubscriber ts = TestSubscriber.create(); - + Observable.just(1, 2).toMap(new Func1() { @Override public Integer call(Integer v) { @@ -250,16 +265,16 @@ public Integer call(Integer v) { throw new TestException(); } }).subscribe(ts); - + ts.assertError(TestException.class); ts.assertNoValues(); ts.assertNotCompleted(); } - + @Test public void testMapFactoryThrows() { TestSubscriber ts = TestSubscriber.create(); - + Observable.just(1, 2).toMap(new Func1() { @Override public Integer call(Integer v) { @@ -276,9 +291,132 @@ public Map call() { throw new TestException(); } }).subscribe(ts); - + ts.assertError(TestException.class); ts.assertNoValues(); ts.assertNotCompleted(); } + + @Test + public void testFactoryFailureDoesNotAllowErrorAndCompletedEmissions() { + TestSubscriber> ts = TestSubscriber.create(0); + final RuntimeException e = new RuntimeException(); + Observable.unsafeCreate(new OnSubscribe() { + + @Override + public void call(final Subscriber sub) { + sub.setProducer(new Producer() { + + @Override + public void request(long n) { + if (n > 1) { + sub.onNext(1); + sub.onCompleted(); + } + } + }); + } + }).toMap(new Func1() { + + @Override + public Integer call(Integer t) { + throw e; + } + }).unsafeSubscribe(ts); + ts.assertNoValues(); + ts.assertError(e); + ts.assertNotCompleted(); + } + + @Test + public void testFactoryFailureDoesNotAllowTwoErrorEmissions() { + try { + final List list = new CopyOnWriteArrayList(); + RxJavaHooks.setOnError(new Action1() { + + @Override + public void call(Throwable t) { + list.add(t); + } + }); + TestSubscriber> ts = TestSubscriber.create(0); + final RuntimeException e1 = new RuntimeException(); + final RuntimeException e2 = new RuntimeException(); + Observable.unsafeCreate(new OnSubscribe() { + + @Override + public void call(final Subscriber sub) { + sub.setProducer(new Producer() { + + @Override + public void request(long n) { + if (n > 1) { + sub.onNext(1); + sub.onError(e2); + } + } + }); + } + }).toMap(new Func1() { + + @Override + public Integer call(Integer t) { + throw e1; + } + }).unsafeSubscribe(ts); + ts.assertNoValues(); + assertEquals(Arrays.asList(e1), ts.getOnErrorEvents()); + assertEquals(Arrays.asList(e2), list); + ts.assertNotCompleted(); + } finally { + RxJavaHooks.reset(); + } + } + + @Test + public void testFactoryFailureDoesNotAllowErrorThenOnNextEmissions() { + TestSubscriber> ts = TestSubscriber.create(0); + final RuntimeException e = new RuntimeException(); + Observable.unsafeCreate(new OnSubscribe() { + + @Override + public void call(final Subscriber sub) { + sub.setProducer(new Producer() { + + @Override + public void request(long n) { + if (n > 1) { + sub.onNext(1); + sub.onNext(2); + } + } + }); + } + }).toMap(new Func1() { + + @Override + public Integer call(Integer t) { + throw e; + } + }).unsafeSubscribe(ts); + ts.assertNoValues(); + ts.assertError(e); + ts.assertNotCompleted(); + } + + @Test + public void testBackpressure() { + TestSubscriber ts = TestSubscriber.create(0); + Observable + .just("a", "bb", "ccc", "dddd") + .toMap(lengthFunc) + .subscribe(ts); + ts.assertNoErrors(); + ts.assertNotCompleted(); + ts.assertNoValues(); + ts.requestMore(1); + ts.assertValueCount(1); + ts.assertNoErrors(); + ts.assertCompleted(); + } } diff --git a/src/test/java/rx/internal/operators/OperatorToMultimapTest.java b/src/test/java/rx/internal/operators/OnSubscribeToMultimapTest.java similarity index 70% rename from src/test/java/rx/internal/operators/OperatorToMultimapTest.java rename to src/test/java/rx/internal/operators/OnSubscribeToMultimapTest.java index f93f57500d..a27c2ae662 100644 --- a/src/test/java/rx/internal/operators/OperatorToMultimapTest.java +++ b/src/test/java/rx/internal/operators/OnSubscribeToMultimapTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -15,6 +15,7 @@ */ package rx.internal.operators; +import static org.junit.Assert.assertEquals; import static org.mockito.Matchers.any; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; @@ -27,7 +28,9 @@ import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; +import java.util.concurrent.CopyOnWriteArrayList; import org.junit.Before; import org.junit.Test; @@ -36,15 +39,18 @@ import rx.Observable; import rx.Observer; +import rx.Producer; +import rx.Subscriber; +import rx.Observable.OnSubscribe; import rx.exceptions.TestException; +import rx.functions.Action1; import rx.functions.Func0; import rx.functions.Func1; -import rx.internal.operators.OperatorToMultimap.DefaultMultimapCollectionFactory; -import rx.internal.operators.OperatorToMultimap.DefaultToMultimapFactory; import rx.internal.util.UtilityFunctions; import rx.observers.TestSubscriber; +import rx.plugins.RxJavaHooks; -public class OperatorToMultimapTest { +public class OnSubscribeToMultimapTest { @Mock Observer objectObserver; @@ -121,7 +127,7 @@ protected boolean removeEldestEntry(Map.Entry> eldes Observable>> mapped = source.toMultimap( lengthFunc, UtilityFunctions.identity(), - mapFactory, new DefaultMultimapCollectionFactory()); + mapFactory, OnSubscribeToMultimapTest.arrayListCollectionFactory()); Map> expected = new HashMap>(); expected.put(2, Arrays.asList("cc", "dd")); @@ -134,6 +140,25 @@ protected boolean removeEldestEntry(Map.Entry> eldes verify(objectObserver, times(1)).onCompleted(); } + private static final Func1> arrayListCollectionFactory() { + return new Func1>() { + + @Override + public Collection call(K k) { + return new ArrayList(); + }}; + } + + private static final Func0>> multimapFactory() { + return new Func0>>() { + + @Override + public Map> call() { + return new HashMap>(); + } + }; + } + @Test public void testToMultimapWithCollectionFactory() { Observable source = Observable.just("cc", "dd", "eee", "eee"); @@ -152,7 +177,7 @@ public Collection call(Integer t1) { Observable>> mapped = source.toMultimap( lengthFunc, UtilityFunctions.identity(), - new DefaultToMultimapFactory(), collectionFactory); + OnSubscribeToMultimapTest.multimapFactory(), collectionFactory); Map> expected = new HashMap>(); expected.put(2, Arrays.asList("cc", "dd")); @@ -259,7 +284,7 @@ public Collection call(Integer t1) { } }; - Observable>> mapped = source.toMultimap(lengthFunc, UtilityFunctions.identity(), new DefaultToMultimapFactory(), collectionFactory); + Observable>> mapped = source.toMultimap(lengthFunc, UtilityFunctions.identity(), OnSubscribeToMultimapTest.multimapFactory(), collectionFactory); Map> expected = new HashMap>(); expected.put(2, Arrays.asList("cc", "dd")); @@ -271,27 +296,27 @@ public Collection call(Integer t1) { verify(objectObserver, never()).onNext(expected); verify(objectObserver, never()).onCompleted(); } - + @Test public void testKeySelectorThrows() { TestSubscriber ts = TestSubscriber.create(); - + Observable.just(1, 2).toMultimap(new Func1() { @Override public Integer call(Integer v) { throw new TestException(); } }).subscribe(ts); - + ts.assertError(TestException.class); ts.assertNoValues(); ts.assertNotCompleted(); } - + @Test public void testValueSelectorThrows() { TestSubscriber ts = TestSubscriber.create(); - + Observable.just(1, 2).toMultimap(new Func1() { @Override public Integer call(Integer v) { @@ -303,16 +328,16 @@ public Integer call(Integer v) { throw new TestException(); } }).subscribe(ts); - + ts.assertError(TestException.class); ts.assertNoValues(); ts.assertNotCompleted(); } - + @Test public void testMapFactoryThrows() { TestSubscriber ts = TestSubscriber.create(); - + Observable.just(1, 2).toMultimap(new Func1() { @Override public Integer call(Integer v) { @@ -329,16 +354,16 @@ public Map> call() { throw new TestException(); } }).subscribe(ts); - + ts.assertError(TestException.class); ts.assertNoValues(); ts.assertNotCompleted(); } - + @Test public void testCollectionFactoryThrows() { TestSubscriber ts = TestSubscriber.create(); - + Observable.just(1, 2).toMultimap(new Func1() { @Override public Integer call(Integer v) { @@ -360,9 +385,132 @@ public Collection call(Integer k) { throw new TestException(); } }).subscribe(ts); - + ts.assertError(TestException.class); ts.assertNoValues(); ts.assertNotCompleted(); } + + @Test + public void testKeySelectorFailureDoesNotAllowErrorAndCompletedEmissions() { + TestSubscriber>> ts = TestSubscriber.create(0); + final RuntimeException e = new RuntimeException(); + Observable.unsafeCreate(new OnSubscribe() { + + @Override + public void call(final Subscriber sub) { + sub.setProducer(new Producer() { + + @Override + public void request(long n) { + if (n > 1) { + sub.onNext(1); + sub.onCompleted(); + } + } + }); + } + }).toMultimap(new Func1() { + + @Override + public Integer call(Integer t) { + throw e; + } + }).unsafeSubscribe(ts); + ts.assertNoValues(); + ts.assertError(e); + ts.assertNotCompleted(); + } + + @Test + public void testKeySelectorFailureDoesNotAllowTwoErrorEmissions() { + try { + final List list = new CopyOnWriteArrayList(); + RxJavaHooks.setOnError(new Action1() { + + @Override + public void call(Throwable t) { + list.add(t); + } + }); + TestSubscriber>> ts = TestSubscriber.create(0); + final RuntimeException e1 = new RuntimeException(); + final RuntimeException e2 = new RuntimeException(); + Observable.unsafeCreate(new OnSubscribe() { + + @Override + public void call(final Subscriber sub) { + sub.setProducer(new Producer() { + + @Override + public void request(long n) { + if (n > 1) { + sub.onNext(1); + sub.onError(e2); + } + } + }); + } + }).toMultimap(new Func1() { + + @Override + public Integer call(Integer t) { + throw e1; + } + }).unsafeSubscribe(ts); + ts.assertNoValues(); + assertEquals(Arrays.asList(e1), ts.getOnErrorEvents()); + assertEquals(Arrays.asList(e2), list); + ts.assertNotCompleted(); + } finally { + RxJavaHooks.reset(); + } + } + + @Test + public void testFactoryFailureDoesNotAllowErrorThenOnNextEmissions() { + TestSubscriber>> ts = TestSubscriber.create(0); + final RuntimeException e = new RuntimeException(); + Observable.unsafeCreate(new OnSubscribe() { + + @Override + public void call(final Subscriber sub) { + sub.setProducer(new Producer() { + + @Override + public void request(long n) { + if (n > 1) { + sub.onNext(1); + sub.onNext(2); + } + } + }); + } + }).toMultimap(new Func1() { + + @Override + public Integer call(Integer t) { + throw e; + } + }).unsafeSubscribe(ts); + ts.assertNoValues(); + ts.assertError(e); + ts.assertNotCompleted(); + } + + @Test + public void testBackpressure() { + TestSubscriber ts = TestSubscriber.create(0); + Observable + .just("a", "bb", "ccc", "dddd") + .toMultimap(lengthFunc) + .subscribe(ts); + ts.assertNoErrors(); + ts.assertNotCompleted(); + ts.assertNoValues(); + ts.requestMore(1); + ts.assertValueCount(1); + ts.assertNoErrors(); + ts.assertCompleted(); + } } diff --git a/src/test/java/rx/internal/operators/OnSubscribeToObservableFutureTest.java b/src/test/java/rx/internal/operators/OnSubscribeToObservableFutureTest.java index 9734cc4c41..48d64b3b99 100644 --- a/src/test/java/rx/internal/operators/OnSubscribeToObservableFutureTest.java +++ b/src/test/java/rx/internal/operators/OnSubscribeToObservableFutureTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -25,10 +25,14 @@ import org.junit.Test; import rx.*; -import rx.observers.*; +import rx.observers.TestSubscriber; import rx.schedulers.Schedulers; public class OnSubscribeToObservableFutureTest { + @Test + public void constructorShouldBePrivate() { + TestUtil.checkUtilityClass(OnSubscribeToObservableFuture.class); + } @Test public void testSuccess() throws Exception { @@ -128,26 +132,85 @@ public Object get(long timeout, TimeUnit unit) throws InterruptedException, Exec assertEquals(0, testSubscriber.getCompletions()); assertEquals(0, testSubscriber.getOnNextEvents().size()); } - + @Test public void backpressure() { TestSubscriber ts = new TestSubscriber(0); - + FutureTask f = new FutureTask(new Runnable() { @Override public void run() { - + } }, 1); - + f.run(); - + Observable.from(f).subscribe(ts); - + ts.assertNoValues(); - + ts.requestMore(1); - + + ts.assertValue(1); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @Test + public void withTimeoutNoTimeout() { + FutureTask task = new FutureTask(new Runnable() { + @Override + public void run() { + + } + }, 1); + + task.run(); + + TestSubscriber ts = TestSubscriber.create(); + + Observable.from(task, 1, TimeUnit.SECONDS).subscribe(ts); + + ts.assertValue(1); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @Test + public void withTimeoutTimeout() { + FutureTask task = new FutureTask(new Runnable() { + @Override + public void run() { + + } + }, 1); + + TestSubscriber ts = TestSubscriber.create(); + + Observable.from(task, 10, TimeUnit.MILLISECONDS).subscribe(ts); + + ts.assertNoValues(); + ts.assertError(TimeoutException.class); + ts.assertNotCompleted(); + } + + @Test + public void withTimeoutNoTimeoutScheduler() { + FutureTask task = new FutureTask(new Runnable() { + @Override + public void run() { + + } + }, 1); + + TestSubscriber ts = TestSubscriber.create(); + + Observable.from(task, Schedulers.computation()).subscribe(ts); + + task.run(); + + ts.awaitTerminalEventAndUnsubscribeOnTimeout(5, TimeUnit.SECONDS); ts.assertValue(1); ts.assertNoErrors(); ts.assertCompleted(); diff --git a/src/test/java/rx/internal/operators/OnSubscribeUsingTest.java b/src/test/java/rx/internal/operators/OnSubscribeUsingTest.java index 47b2338bbc..3a93d4cc8f 100644 --- a/src/test/java/rx/internal/operators/OnSubscribeUsingTest.java +++ b/src/test/java/rx/internal/operators/OnSubscribeUsingTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -37,7 +37,7 @@ public class OnSubscribeUsingTest { private interface Resource { String getTextFromWeb(); - + void dispose(); } @@ -254,7 +254,7 @@ public Subscription call() { Func1> observableFactory = new Func1>() { @Override public Observable call(Subscription subscription) { - return Observable.create(new OnSubscribe() { + return Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber t1) { throw new TestException(); @@ -280,7 +280,7 @@ public void testUsingDisposesEagerlyBeforeCompletion() { final List events = new ArrayList(); Func0 resourceFactory = createResourceFactory(events); final Action0 completion = createOnCompletedAction(events); - final Action0 unsub =createUnsubAction(events); + final Action0 unsub = createUnsubAction(events); Func1> observableFactory = new Func1>() { @Override @@ -305,7 +305,7 @@ public void testUsingDoesNotDisposesEagerlyBeforeCompletion() { final List events = new ArrayList(); Func0 resourceFactory = createResourceFactory(events); final Action0 completion = createOnCompletedAction(events); - final Action0 unsub =createUnsubAction(events); + final Action0 unsub = createUnsubAction(events); Func1> observableFactory = new Func1>() { @Override @@ -325,15 +325,15 @@ public Observable call(Resource resource) { } - - + + @Test public void testUsingDisposesEagerlyBeforeError() { final List events = new ArrayList(); Func0 resourceFactory = createResourceFactory(events); final Action1 onError = createOnErrorAction(events); final Action0 unsub = createUnsubAction(events); - + Func1> observableFactory = new Func1>() { @Override public Observable call(Resource resource) { @@ -351,14 +351,14 @@ public Observable call(Resource resource) { assertEquals(Arrays.asList("disposed", "error", "unsub"), events); } - + @Test public void testUsingDoesNotDisposesEagerlyBeforeError() { final List events = new ArrayList(); Func0 resourceFactory = createResourceFactory(events); final Action1 onError = createOnErrorAction(events); final Action0 unsub = createUnsubAction(events); - + Func1> observableFactory = new Func1>() { @Override public Observable call(Resource resource) { @@ -413,7 +413,7 @@ public void dispose() { } }; } - + private static Action0 createOnCompletedAction(final List events) { return new Action0() { @Override @@ -422,27 +422,27 @@ public void call() { } }; } - + @Test public void factoryThrows() { - + TestSubscriber ts = TestSubscriber.create(); - + final AtomicInteger count = new AtomicInteger(); - + Observable.using( new Func0() { @Override public Integer call() { return 1; } - }, + }, new Func1>() { @Override - public Observable call(Integer v) { - throw new TestException("forced failure"); + public Observable call(Integer v) { + throw new TestException("forced failure"); } - }, + }, new Action1() { @Override public void call(Integer c) { @@ -451,32 +451,32 @@ public void call(Integer c) { } ) .unsafeSubscribe(ts); - + ts.assertError(TestException.class); - + Assert.assertEquals(1, count.get()); } - + @Test public void nonEagerTermination() { - + TestSubscriber ts = TestSubscriber.create(); - + final AtomicInteger count = new AtomicInteger(); - + Observable.using( new Func0() { @Override public Integer call() { return 1; } - }, + }, new Func1>() { @Override - public Observable call(Integer v) { + public Observable call(Integer v) { return Observable.just(v); } - }, + }, new Action1() { @Override public void call(Integer c) { @@ -485,11 +485,11 @@ public void call(Integer c) { }, false ) .unsafeSubscribe(ts); - + ts.assertValue(1); ts.assertNoErrors(); ts.assertCompleted(); - + Assert.assertEquals(1, count.get()); } } diff --git a/src/test/java/rx/internal/operators/OperatorAllTest.java b/src/test/java/rx/internal/operators/OperatorAllTest.java index 1fd84bc129..5ac74526fa 100644 --- a/src/test/java/rx/internal/operators/OperatorAllTest.java +++ b/src/test/java/rx/internal/operators/OperatorAllTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -20,13 +20,17 @@ import java.util.Arrays; import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.TimeUnit; import org.junit.Test; import rx.*; +import rx.Observable.OnSubscribe; +import rx.functions.Action1; import rx.functions.Func1; import rx.observers.TestSubscriber; +import rx.plugins.RxJavaHooks; public class OperatorAllTest { @@ -130,7 +134,7 @@ public Observable call(Boolean t1) { }); assertEquals((Object)2, source.toBlocking().first()); } - + @Test public void testBackpressureIfNoneRequestedNoneShouldBeDelivered() { TestSubscriber ts = new TestSubscriber(0); @@ -144,7 +148,7 @@ public Boolean call(Object t1) { ts.assertNoErrors(); ts.assertNotCompleted(); } - + @Test public void testBackpressureIfOneRequestedOneShouldBeDelivered() { TestSubscriber ts = new TestSubscriber(1); @@ -159,7 +163,7 @@ public Boolean call(Object object) { ts.assertCompleted(); ts.assertValue(true); } - + @Test public void testPredicateThrowsExceptionAndValueInCauseMessage() { TestSubscriber ts = new TestSubscriber(0); @@ -178,4 +182,114 @@ public Boolean call(Object object) { assertEquals(ex, errors.get(0)); assertTrue(ex.getCause().getMessage().contains("Boo!")); } + + @Test + public void testDoesNotEmitMultipleTerminalEvents() { + TestSubscriber ts = TestSubscriber.create(); + Observable.unsafeCreate(new OnSubscribe() { + + @Override + public void call(final Subscriber sub) { + sub.setProducer(new Producer() { + + @Override + public void request(long n) { + if (n > 0) { + sub.onNext(1); + sub.onCompleted(); + } + } + }); + } + }) + .all(new Func1() { + + @Override + public Boolean call(Integer t) { + throw new RuntimeException("boo"); + }}) + .unsafeSubscribe(ts); + ts.assertError(RuntimeException.class); + ts.assertNotCompleted(); + } + + @Test + public void testUpstreamEmitsOnNextAfterFailureWithoutCheckingSubscription() { + TestSubscriber ts = TestSubscriber.create(); + Observable.unsafeCreate(new OnSubscribe() { + + @Override + public void call(final Subscriber sub) { + sub.setProducer(new Producer() { + + @Override + public void request(long n) { + if (n > 1) { + sub.onNext(1); + sub.onNext(2); + } + } + }); + } + }) + .all(new Func1() { + boolean once = true; + @Override + public Boolean call(Integer t) { + if (once) { + throw new RuntimeException("boo"); + } else { + once = false; + return true; + } + }}) + .unsafeSubscribe(ts); + ts.assertNoValues(); + ts.assertError(RuntimeException.class); + ts.assertNotCompleted(); + } + + @Test + public void testDoesNotEmitMultipleErrorEventsAndReportsSecondErrorToHooks() { + try { + final List list = new CopyOnWriteArrayList(); + RxJavaHooks.setOnError(new Action1() { + + @Override + public void call(Throwable t) { + list.add(t); + } + }); + TestSubscriber ts = TestSubscriber.create(); + final RuntimeException e1 = new RuntimeException(); + final Throwable e2 = new RuntimeException(); + Observable.unsafeCreate(new OnSubscribe() { + + @Override + public void call(final Subscriber sub) { + sub.setProducer(new Producer() { + + @Override + public void request(long n) { + if (n > 0) { + sub.onNext(1); + sub.onError(e2); + } + } + }); + } + }).all(new Func1() { + + @Override + public Boolean call(Integer t) { + throw e1; + } + }).unsafeSubscribe(ts); + ts.assertNotCompleted(); + assertEquals(Arrays.asList(e1), ts.getOnErrorEvents()); + assertEquals(Arrays.asList(e2), list); + } finally { + RxJavaHooks.reset(); + } + } } diff --git a/src/test/java/rx/internal/operators/OperatorAnyTest.java b/src/test/java/rx/internal/operators/OperatorAnyTest.java index b48928d800..0eaf6b07ec 100644 --- a/src/test/java/rx/internal/operators/OperatorAnyTest.java +++ b/src/test/java/rx/internal/operators/OperatorAnyTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -20,14 +20,18 @@ import java.util.Arrays; import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.TimeUnit; import org.junit.Test; import rx.*; +import rx.Observable.OnSubscribe; +import rx.functions.Action1; import rx.functions.Func1; import rx.internal.util.UtilityFunctions; import rx.observers.TestSubscriber; +import rx.plugins.RxJavaHooks; public class OperatorAnyTest { @@ -222,7 +226,7 @@ public Observable call(Boolean t1) { }); assertEquals((Object)2, source.toBlocking().first()); } - + @Test public void testBackpressureIfNoneRequestedNoneShouldBeDelivered() { TestSubscriber ts = new TestSubscriber(0); @@ -236,7 +240,7 @@ public Boolean call(Object t1) { ts.assertNoErrors(); ts.assertNotCompleted(); } - + @Test public void testBackpressureIfOneRequestedOneShouldBeDelivered() { TestSubscriber ts = new TestSubscriber(1); @@ -251,7 +255,7 @@ public Boolean call(Object object) { ts.assertCompleted(); ts.assertValue(true); } - + @Test public void testPredicateThrowsExceptionAndValueInCauseMessage() { TestSubscriber ts = new TestSubscriber(0); @@ -270,4 +274,115 @@ public Boolean call(Object object) { assertEquals(ex, errors.get(0)); assertTrue(ex.getCause().getMessage().contains("Boo!")); } + + @Test + public void testUpstreamEmitsOnNextAfterFailureWithoutCheckingSubscription() { + TestSubscriber ts = TestSubscriber.create(); + Observable.unsafeCreate(new OnSubscribe() { + + @Override + public void call(final Subscriber sub) { + sub.setProducer(new Producer() { + + @Override + public void request(long n) { + if (n > 1) { + sub.onNext(1); + sub.onNext(2); + } + } + }); + } + }) + .exists(new Func1() { + boolean once = true; + @Override + public Boolean call(Integer t) { + if (once) { + throw new RuntimeException("boo"); + } else { + once = false; + return true; + } + }}) + .unsafeSubscribe(ts); + ts.assertNoValues(); + ts.assertError(RuntimeException.class); + ts.assertNotCompleted(); + } + + @Test + public void testUpstreamEmitsOnNextWithoutCheckingSubscription() { + TestSubscriber ts = TestSubscriber.create(); + Observable.unsafeCreate(new OnSubscribe() { + + @Override + public void call(final Subscriber sub) { + sub.setProducer(new Producer() { + + @Override + public void request(long n) { + if (n > 1) { + sub.onNext(1); + sub.onNext(2); + sub.onCompleted(); + } + } + }); + } + }) + .exists(new Func1() { + @Override + public Boolean call(Integer t) { + return true; + }}) + .unsafeSubscribe(ts); + ts.assertValue(true); + assertEquals(1, ts.getCompletions()); + ts.assertNoErrors(); + } + + @Test + public void testDoesNotEmitMultipleErrorEventsAndReportsSecondErrorToHooks() { + try { + final List list = new CopyOnWriteArrayList(); + RxJavaHooks.setOnError(new Action1() { + + @Override + public void call(Throwable t) { + list.add(t); + } + }); + TestSubscriber ts = TestSubscriber.create(); + final RuntimeException e1 = new RuntimeException(); + final Throwable e2 = new RuntimeException(); + Observable.unsafeCreate(new OnSubscribe() { + + @Override + public void call(final Subscriber sub) { + sub.setProducer(new Producer() { + + @Override + public void request(long n) { + if (n > 0) { + sub.onNext(1); + sub.onError(e2); + } + } + }); + } + }).exists(new Func1() { + + @Override + public Boolean call(Integer t) { + throw e1; + } + }).unsafeSubscribe(ts); + ts.assertNotCompleted(); + assertEquals(Arrays.asList(e1), ts.getOnErrorEvents()); + assertEquals(Arrays.asList(e2), list); + } finally { + RxJavaHooks.reset(); + } + } } diff --git a/src/test/java/rx/internal/operators/OperatorAsObservableTest.java b/src/test/java/rx/internal/operators/OperatorAsObservableTest.java index 30f5d3cf84..1b19d6f054 100644 --- a/src/test/java/rx/internal/operators/OperatorAsObservableTest.java +++ b/src/test/java/rx/internal/operators/OperatorAsObservableTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -33,19 +33,19 @@ public class OperatorAsObservableTest { @Test public void testHiding() { PublishSubject src = PublishSubject.create(); - + Observable dst = src.asObservable(); - + assertFalse(dst instanceof PublishSubject); - + @SuppressWarnings("unchecked") Observer o = mock(Observer.class); - + dst.subscribe(o); - + src.onNext(1); src.onCompleted(); - + verify(o).onNext(1); verify(o).onCompleted(); verify(o, never()).onError(any(Throwable.class)); @@ -53,18 +53,18 @@ public void testHiding() { @Test public void testHidingError() { PublishSubject src = PublishSubject.create(); - + Observable dst = src.asObservable(); - + assertFalse(dst instanceof PublishSubject); - + @SuppressWarnings("unchecked") Observer o = mock(Observer.class); - + dst.subscribe(o); - + src.onError(new TestException()); - + verify(o, never()).onNext(any()); verify(o, never()).onCompleted(); verify(o).onError(any(TestException.class)); diff --git a/src/test/java/rx/internal/operators/OperatorBufferTest.java b/src/test/java/rx/internal/operators/OperatorBufferTest.java index ad8b1bc9c7..5cd3b2fa96 100644 --- a/src/test/java/rx/internal/operators/OperatorBufferTest.java +++ b/src/test/java/rx/internal/operators/OperatorBufferTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -15,39 +15,24 @@ */ package rx.internal.operators; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; +import static org.junit.Assert.*; import static org.mockito.Matchers.any; -import static org.mockito.Mockito.inOrder; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; +import static org.mockito.Mockito.*; + +import java.util.*; +import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicLong; -import org.junit.Before; -import org.junit.Test; -import org.mockito.InOrder; -import org.mockito.Mockito; +import org.junit.*; +import org.mockito.*; +import rx.*; import rx.Observable; import rx.Observer; -import rx.Producer; -import rx.Scheduler; -import rx.Subscriber; -import rx.Subscription; import rx.exceptions.TestException; -import rx.functions.Action0; -import rx.functions.Action1; -import rx.functions.Func0; -import rx.functions.Func1; +import rx.functions.*; import rx.observers.TestSubscriber; +import rx.plugins.RxJavaHooks; import rx.schedulers.TestScheduler; import rx.subjects.PublishSubject; @@ -67,7 +52,7 @@ public void before() { @Test public void testComplete() { - Observable source = Observable.create(new Observable.OnSubscribe() { + Observable source = Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber observer) { observer.onCompleted(); @@ -84,7 +69,7 @@ public void call(Subscriber observer) { @Test public void testSkipAndCountOverlappingBuffers() { - Observable source = Observable.create(new Observable.OnSubscribe() { + Observable source = Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber observer) { observer.onNext("one"); @@ -109,7 +94,7 @@ public void call(Subscriber observer) { @Test public void testSkipAndCountGaplessBuffers() { - Observable source = Observable.create(new Observable.OnSubscribe() { + Observable source = Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber observer) { observer.onNext("one"); @@ -134,7 +119,7 @@ public void call(Subscriber observer) { @Test public void testSkipAndCountBuffersWithGaps() { - Observable source = Observable.create(new Observable.OnSubscribe() { + Observable source = Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber observer) { observer.onNext("one"); @@ -159,7 +144,7 @@ public void call(Subscriber observer) { @Test public void testTimedAndCount() { - Observable source = Observable.create(new Observable.OnSubscribe() { + Observable source = Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber observer) { push(observer, "one", 10); @@ -190,7 +175,7 @@ public void call(Subscriber observer) { @Test public void testTimed() { - Observable source = Observable.create(new Observable.OnSubscribe() { + Observable source = Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber observer) { push(observer, "one", 97); @@ -223,7 +208,7 @@ public void call(Subscriber observer) { @Test public void testObservableBasedOpenerAndCloser() { - Observable source = Observable.create(new Observable.OnSubscribe() { + Observable source = Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber observer) { push(observer, "one", 10); @@ -235,7 +220,7 @@ public void call(Subscriber observer) { } }); - Observable openings = Observable.create(new Observable.OnSubscribe() { + Observable openings = Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber observer) { push(observer, new Object(), 50); @@ -247,7 +232,7 @@ public void call(Subscriber observer) { Func1> closer = new Func1>() { @Override public Observable call(Object opening) { - return Observable.create(new Observable.OnSubscribe() { + return Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber observer) { push(observer, new Object(), 100); @@ -271,7 +256,7 @@ public void call(Subscriber observer) { @Test public void testObservableBasedCloser() { - Observable source = Observable.create(new Observable.OnSubscribe() { + Observable source = Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber observer) { push(observer, "one", 10); @@ -286,7 +271,7 @@ public void call(Subscriber observer) { Func0> closer = new Func0>() { @Override public Observable call() { - return Observable.create(new Observable.OnSubscribe() { + return Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber observer) { push(observer, new Object(), 100); @@ -324,7 +309,7 @@ public void testLongTimeAction() throws InterruptedException { private static class LongTimeAction implements Action1> { CountDownLatch latch; - boolean fail = false; + boolean fail; public LongTimeAction(CountDownLatch latch) { this.latch = latch; @@ -527,30 +512,30 @@ public void bufferWithBOBoundaryThrows() { @Test(timeout = 2000) public void bufferWithSizeTake1() { Observable source = Observable.just(1).repeat(); - + Observable> result = source.buffer(2).take(1); - + @SuppressWarnings("unchecked") Observer o = mock(Observer.class); - + result.subscribe(o); - + verify(o).onNext(Arrays.asList(1, 1)); verify(o).onCompleted(); verify(o, never()).onError(any(Throwable.class)); } - + @Test(timeout = 2000) public void bufferWithSizeSkipTake1() { Observable source = Observable.just(1).repeat(); - + Observable> result = source.buffer(2, 3).take(1); - + @SuppressWarnings("unchecked") Observer o = mock(Observer.class); - + result.subscribe(o); - + verify(o).onNext(Arrays.asList(1, 1)); verify(o).onCompleted(); verify(o, never()).onError(any(Throwable.class)); @@ -558,16 +543,16 @@ public void bufferWithSizeSkipTake1() { @Test(timeout = 2000) public void bufferWithTimeTake1() { Observable source = Observable.interval(40, 40, TimeUnit.MILLISECONDS, scheduler); - + Observable> result = source.buffer(100, TimeUnit.MILLISECONDS, scheduler).take(1); - + @SuppressWarnings("unchecked") Observer o = mock(Observer.class); - + result.subscribe(o); - + scheduler.advanceTimeBy(5, TimeUnit.SECONDS); - + verify(o).onNext(Arrays.asList(0L, 1L)); verify(o).onCompleted(); verify(o, never()).onError(any(Throwable.class)); @@ -575,17 +560,17 @@ public void bufferWithTimeTake1() { @Test(timeout = 2000) public void bufferWithTimeSkipTake2() { Observable source = Observable.interval(40, 40, TimeUnit.MILLISECONDS, scheduler); - + Observable> result = source.buffer(100, 60, TimeUnit.MILLISECONDS, scheduler).take(2); - + @SuppressWarnings("unchecked") Observer o = mock(Observer.class); InOrder inOrder = inOrder(o); - + result.subscribe(o); - + scheduler.advanceTimeBy(5, TimeUnit.SECONDS); - + inOrder.verify(o).onNext(Arrays.asList(0L, 1L)); inOrder.verify(o).onNext(Arrays.asList(1L, 2L)); inOrder.verify(o).onCompleted(); @@ -595,24 +580,24 @@ public void bufferWithTimeSkipTake2() { public void bufferWithBoundaryTake2() { Observable boundary = Observable.interval(60, 60, TimeUnit.MILLISECONDS, scheduler); Observable source = Observable.interval(40, 40, TimeUnit.MILLISECONDS, scheduler); - + Observable> result = source.buffer(boundary).take(2); - + @SuppressWarnings("unchecked") Observer o = mock(Observer.class); InOrder inOrder = inOrder(o); - + result.subscribe(o); - + scheduler.advanceTimeBy(5, TimeUnit.SECONDS); - + inOrder.verify(o).onNext(Arrays.asList(0L)); inOrder.verify(o).onNext(Arrays.asList(1L)); inOrder.verify(o).onCompleted(); verify(o, never()).onError(any(Throwable.class)); - + } - + @Test(timeout = 2000) public void bufferWithStartEndBoundaryTake2() { Observable start = Observable.interval(61, 61, TimeUnit.MILLISECONDS, scheduler); @@ -622,19 +607,19 @@ public Observable call(Long t1) { return Observable.interval(100, 100, TimeUnit.MILLISECONDS, scheduler); } }; - + Observable source = Observable.interval(40, 40, TimeUnit.MILLISECONDS, scheduler); - + Observable> result = source.buffer(start, end).take(2); - + @SuppressWarnings("unchecked") Observer o = mock(Observer.class); InOrder inOrder = inOrder(o); - + result.subscribe(o); - + 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).onCompleted(); @@ -643,68 +628,68 @@ public Observable call(Long t1) { @Test public void bufferWithSizeThrows() { PublishSubject source = PublishSubject.create(); - + Observable> result = source.buffer(2); - + @SuppressWarnings("unchecked") Observer o = mock(Observer.class); InOrder inOrder = inOrder(o); - + result.subscribe(o); - + 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.verifyNoMoreInteractions(); verify(o, never()).onNext(Arrays.asList(3)); verify(o, never()).onCompleted(); - + } - + @Test public void bufferWithTimeThrows() { PublishSubject source = PublishSubject.create(); - + Observable> result = source.buffer(100, TimeUnit.MILLISECONDS, scheduler); - + @SuppressWarnings("unchecked") Observer o = mock(Observer.class); InOrder inOrder = inOrder(o); - + result.subscribe(o); - + source.onNext(1); source.onNext(2); scheduler.advanceTimeBy(100, TimeUnit.MILLISECONDS); source.onNext(3); 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.verifyNoMoreInteractions(); verify(o, never()).onNext(Arrays.asList(3)); verify(o, never()).onCompleted(); - + } @Test public void bufferWithTimeAndSize() { Observable source = Observable.interval(30, 30, TimeUnit.MILLISECONDS, scheduler); - + Observable> result = source.buffer(100, TimeUnit.MILLISECONDS, 2, scheduler).take(3); - + @SuppressWarnings("unchecked") Observer o = mock(Observer.class); InOrder inOrder = inOrder(o); - + result.subscribe(o); - + scheduler.advanceTimeBy(5, TimeUnit.SECONDS); - + inOrder.verify(o).onNext(Arrays.asList(0L, 1L)); inOrder.verify(o).onNext(Arrays.asList(2L)); inOrder.verify(o).onCompleted(); @@ -713,7 +698,7 @@ public void bufferWithTimeAndSize() { @Test public void bufferWithStartEndStartThrows() { PublishSubject start = PublishSubject.create(); - + Func1> end = new Func1>() { @Override public Observable call(Integer t1) { @@ -724,17 +709,17 @@ public Observable call(Integer t1) { PublishSubject source = PublishSubject.create(); Observable> result = source.buffer(start, end); - + @SuppressWarnings("unchecked") Observer o = mock(Observer.class); - + result.subscribe(o); - + start.onNext(1); source.onNext(1); source.onNext(2); start.onError(new TestException()); - + verify(o, never()).onNext(any()); verify(o, never()).onCompleted(); verify(o).onError(any(TestException.class)); @@ -742,7 +727,7 @@ public Observable call(Integer t1) { @Test public void bufferWithStartEndEndFunctionThrows() { PublishSubject start = PublishSubject.create(); - + Func1> end = new Func1>() { @Override public Observable call(Integer t1) { @@ -753,16 +738,16 @@ public Observable call(Integer t1) { PublishSubject source = PublishSubject.create(); Observable> result = source.buffer(start, end); - + @SuppressWarnings("unchecked") Observer o = mock(Observer.class); - + result.subscribe(o); - + start.onNext(1); source.onNext(1); source.onNext(2); - + verify(o, never()).onNext(any()); verify(o, never()).onCompleted(); verify(o).onError(any(TestException.class)); @@ -770,7 +755,7 @@ public Observable call(Integer t1) { @Test public void bufferWithStartEndEndThrows() { PublishSubject start = PublishSubject.create(); - + Func1> end = new Func1>() { @Override public Observable call(Integer t1) { @@ -781,16 +766,16 @@ public Observable call(Integer t1) { PublishSubject source = PublishSubject.create(); Observable> result = source.buffer(start, end); - + @SuppressWarnings("unchecked") Observer o = mock(Observer.class); - + result.subscribe(o); - + start.onNext(1); source.onNext(1); source.onNext(2); - + verify(o, never()).onNext(any()); verify(o, never()).onCompleted(); verify(o).onError(any(TestException.class)); @@ -801,7 +786,7 @@ public void testProducerRequestThroughBufferWithSize1() { TestSubscriber> ts = new TestSubscriber>(); ts.requestMore(3); final AtomicLong requested = new AtomicLong(); - Observable.create(new Observable.OnSubscribe() { + Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber s) { @@ -826,7 +811,7 @@ public void request(long n) { public void testProducerRequestThroughBufferWithSize2() { TestSubscriber> ts = new TestSubscriber>(); final AtomicLong requested = new AtomicLong(); - Observable.create(new Observable.OnSubscribe() { + Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber s) { @@ -849,7 +834,7 @@ public void testProducerRequestThroughBufferWithSize3() { TestSubscriber> ts = new TestSubscriber>(); ts.requestMore(3); final AtomicLong requested = new AtomicLong(); - Observable.create(new Observable.OnSubscribe() { + Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber s) { @@ -873,7 +858,7 @@ public void request(long n) { public void testProducerRequestThroughBufferWithSize4() { TestSubscriber> ts = new TestSubscriber>(); final AtomicLong requested = new AtomicLong(); - Observable.create(new Observable.OnSubscribe() { + Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber s) { @@ -897,7 +882,7 @@ public void testProducerRequestOverflowThroughBufferWithSize1() { TestSubscriber> ts = new TestSubscriber>(); ts.requestMore(Long.MAX_VALUE / 2); final AtomicLong requested = new AtomicLong(); - Observable.create(new Observable.OnSubscribe() { + Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber s) { @@ -920,7 +905,7 @@ public void testProducerRequestOverflowThroughBufferWithSize2() { TestSubscriber> ts = new TestSubscriber>(); ts.requestMore(Long.MAX_VALUE / 2); final AtomicLong requested = new AtomicLong(); - Observable.create(new Observable.OnSubscribe() { + Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber s) { @@ -941,7 +926,7 @@ public void request(long n) { @Test public void testProducerRequestOverflowThroughBufferWithSize3() { final AtomicLong requested = new AtomicLong(); - Observable.create(new Observable.OnSubscribe() { + Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(final Subscriber s) { @@ -986,8 +971,8 @@ public void onNext(List t) { public void testBufferWithTimeDoesntUnsubscribeDownstream() throws InterruptedException { @SuppressWarnings("unchecked") final Observer o = mock(Observer.class); - - + + final CountDownLatch cdl = new CountDownLatch(1); Subscriber s = new Subscriber() { @Override @@ -1005,33 +990,33 @@ public void onCompleted() { cdl.countDown(); } }; - + Observable.range(1, 1).delay(1, TimeUnit.SECONDS).buffer(2, TimeUnit.SECONDS).unsafeSubscribe(s); - + cdl.await(); - + verify(o).onNext(Arrays.asList(1)); verify(o).onCompleted(); verify(o, never()).onError(any(Throwable.class)); - + assertFalse(s.isUnsubscribed()); } - + @SuppressWarnings("unchecked") @Test public void testPostCompleteBackpressure() { Observable> source = Observable.range(1, 10).buffer(3, 1); - + TestSubscriber> ts = TestSubscriber.create(0L); - + source.subscribe(ts); - + ts.assertNoValues(); ts.assertNotCompleted(); ts.assertNoErrors(); - + ts.requestMore(7); - + ts.assertValues( Arrays.asList(1, 2, 3), Arrays.asList(2, 3, 4), @@ -1058,7 +1043,7 @@ public void testPostCompleteBackpressure() { ); ts.assertNotCompleted(); ts.assertNoErrors(); - + ts.requestMore(1); ts.assertValues( @@ -1074,7 +1059,7 @@ public void testPostCompleteBackpressure() { ); ts.assertNotCompleted(); ts.assertNoErrors(); - + ts.requestMore(1); ts.assertValues( @@ -1092,4 +1077,180 @@ public void testPostCompleteBackpressure() { ts.assertCompleted(); ts.assertNoErrors(); } + + @SuppressWarnings("unchecked") + @Test + public void timeAndSkipOverlap() { + + PublishSubject ps = PublishSubject.create(); + + TestSubscriber> ts = TestSubscriber.create(); + + ps.buffer(2, 1, TimeUnit.SECONDS, scheduler).subscribe(ts); + + ps.onNext(1); + + scheduler.advanceTimeBy(1, TimeUnit.SECONDS); + + ps.onNext(2); + + scheduler.advanceTimeBy(1, TimeUnit.SECONDS); + + ps.onNext(3); + + scheduler.advanceTimeBy(1, TimeUnit.SECONDS); + + ps.onNext(4); + + scheduler.advanceTimeBy(1, TimeUnit.SECONDS); + + ps.onCompleted(); + + ts.assertValues( + Arrays.asList(1, 2), + Arrays.asList(2, 3), + Arrays.asList(3, 4), + Arrays.asList(4), + Collections.emptyList() + ); + + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @SuppressWarnings("unchecked") + @Test + public void timeAndSkipSkip() { + + PublishSubject ps = PublishSubject.create(); + + TestSubscriber> ts = TestSubscriber.create(); + + ps.buffer(2, 3, TimeUnit.SECONDS, scheduler).subscribe(ts); + + ps.onNext(1); + + scheduler.advanceTimeBy(1, TimeUnit.SECONDS); + + ps.onNext(2); + + scheduler.advanceTimeBy(1, TimeUnit.SECONDS); + + ps.onNext(3); + + scheduler.advanceTimeBy(1, TimeUnit.SECONDS); + + ps.onNext(4); + + scheduler.advanceTimeBy(1, TimeUnit.SECONDS); + + ps.onCompleted(); + + ts.assertValues( + Arrays.asList(1, 2), + Arrays.asList(4) + ); + + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @SuppressWarnings("unchecked") + @Test + public void timeAndSkipOverlapScheduler() { + + RxJavaHooks.setOnComputationScheduler(new Func1() { + @Override + public Scheduler call(Scheduler t) { + return scheduler; + } + }); + + try { + PublishSubject ps = PublishSubject.create(); + + TestSubscriber> ts = TestSubscriber.create(); + + ps.buffer(2, 1, TimeUnit.SECONDS).subscribe(ts); + + ps.onNext(1); + + scheduler.advanceTimeBy(1, TimeUnit.SECONDS); + + ps.onNext(2); + + scheduler.advanceTimeBy(1, TimeUnit.SECONDS); + + ps.onNext(3); + + scheduler.advanceTimeBy(1, TimeUnit.SECONDS); + + ps.onNext(4); + + scheduler.advanceTimeBy(1, TimeUnit.SECONDS); + + ps.onCompleted(); + + ts.assertValues( + Arrays.asList(1, 2), + Arrays.asList(2, 3), + Arrays.asList(3, 4), + Arrays.asList(4), + Collections.emptyList() + ); + + ts.assertNoErrors(); + ts.assertCompleted(); + } finally { + RxJavaHooks.reset(); + } + } + + @SuppressWarnings("unchecked") + @Test + public void timeAndSkipSkipDefaultScheduler() { + RxJavaHooks.setOnComputationScheduler(new Func1() { + @Override + public Scheduler call(Scheduler t) { + return scheduler; + } + }); + + try { + + PublishSubject ps = PublishSubject.create(); + + TestSubscriber> ts = TestSubscriber.create(); + + ps.buffer(2, 3, TimeUnit.SECONDS).subscribe(ts); + + ps.onNext(1); + + scheduler.advanceTimeBy(1, TimeUnit.SECONDS); + + ps.onNext(2); + + scheduler.advanceTimeBy(1, TimeUnit.SECONDS); + + ps.onNext(3); + + scheduler.advanceTimeBy(1, TimeUnit.SECONDS); + + ps.onNext(4); + + scheduler.advanceTimeBy(1, TimeUnit.SECONDS); + + ps.onCompleted(); + + ts.assertValues( + Arrays.asList(1, 2), + Arrays.asList(4) + ); + + ts.assertNoErrors(); + ts.assertCompleted(); + } finally { + RxJavaHooks.reset(); + } + } } diff --git a/src/test/java/rx/internal/operators/OperatorCastTest.java b/src/test/java/rx/internal/operators/OperatorCastTest.java index d67c1abfa4..e832d21c11 100644 --- a/src/test/java/rx/internal/operators/OperatorCastTest.java +++ b/src/test/java/rx/internal/operators/OperatorCastTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -51,22 +51,22 @@ public void testCastWithWrongType() { verify(observer, times(1)).onError( org.mockito.Matchers.any(ClassCastException.class)); } - + @Test public void castCrashUnsubscribes() { - + PublishSubject ps = PublishSubject.create(); - + TestSubscriber ts = TestSubscriber.create(); - + ps.cast(String.class).unsafeSubscribe(ts); - + Assert.assertTrue("Not subscribed?", ps.hasObservers()); - + ps.onNext(1); - + Assert.assertFalse("Subscribed?", ps.hasObservers()); - + ts.assertError(ClassCastException.class); } } diff --git a/src/test/java/rx/internal/operators/OperatorConcatTest.java b/src/test/java/rx/internal/operators/OperatorConcatTest.java index 65487798b9..1e818accda 100644 --- a/src/test/java/rx/internal/operators/OperatorConcatTest.java +++ b/src/test/java/rx/internal/operators/OperatorConcatTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -15,33 +15,26 @@ */ package rx.internal.operators; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyString; -import static org.mockito.Mockito.inOrder; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; +import static org.junit.Assert.*; +import static org.mockito.Matchers.*; +import static org.mockito.Mockito.*; + +import java.lang.reflect.Method; +import java.util.*; import java.util.concurrent.*; import java.util.concurrent.atomic.*; import org.junit.Test; import org.mockito.InOrder; -import rx.Observable.OnSubscribe; import rx.*; -import rx.functions.Func1; -import rx.internal.util.RxRingBuffer; +import rx.Observable; +import rx.Observable.OnSubscribe; +import rx.Observer; +import rx.functions.*; +import rx.internal.util.*; import rx.observers.TestSubscriber; -import rx.schedulers.Schedulers; -import rx.schedulers.TestScheduler; +import rx.schedulers.*; import rx.subjects.*; import rx.subscriptions.BooleanSubscription; @@ -82,24 +75,24 @@ public void testConcatWithList() { verify(observer, times(7)).onNext(anyString()); } - + @Test public void testConcatMapIterable() { @SuppressWarnings("unchecked") Observer observer = mock(Observer.class); final String[] l = { "a", "b", "c", "d", "e" }; - + Func1,List> identity = new Func1, List>() { - @Override - public List call(List t) { - return t; - } - }; + @Override + public List call(List t) { + return t; + } + }; final Observable> listObs = Observable.just(Arrays.asList(l)); final Observable concatMap = listObs.concatMapIterable(identity); - + concatMap.subscribe(observer); InOrder inOrder = inOrder(observer); @@ -122,7 +115,7 @@ public void testConcatObservableOfObservables() { final Observable odds = Observable.from(o); final Observable even = Observable.from(e); - Observable> observableOfObservables = Observable.create(new Observable.OnSubscribe>() { + Observable> observableOfObservables = Observable.unsafeCreate(new Observable.OnSubscribe>() { @Override public void call(Subscriber> observer) { @@ -151,7 +144,7 @@ public void testSimpleAsyncConcat() { TestObservable o1 = new TestObservable("one", "two", "three"); TestObservable o2 = new TestObservable("four", "five", "six"); - Observable.concat(Observable.create(o1), Observable.create(o2)).subscribe(observer); + Observable.concat(Observable.unsafeCreate(o1), Observable.unsafeCreate(o2)).subscribe(observer); try { // wait for async observables to complete @@ -186,7 +179,7 @@ public void testNestedAsyncConcat() throws Throwable { final AtomicReference parent = new AtomicReference(); final CountDownLatch parentHasStarted = new CountDownLatch(1); - Observable> observableOfObservables = Observable.create(new Observable.OnSubscribe>() { + Observable> observableOfObservables = Observable.unsafeCreate(new Observable.OnSubscribe>() { @Override public void call(final Subscriber> observer) { @@ -200,12 +193,12 @@ public void run() { // emit first if (!s.isUnsubscribed()) { System.out.println("Emit o1"); - observer.onNext(Observable.create(o1)); + observer.onNext(Observable.unsafeCreate(o1)); } // emit second if (!s.isUnsubscribed()) { System.out.println("Emit o2"); - observer.onNext(Observable.create(o2)); + observer.onNext(Observable.unsafeCreate(o2)); } // wait until sometime later and emit third @@ -216,7 +209,7 @@ public void run() { } if (!s.isUnsubscribed()) { System.out.println("Emit o3"); - observer.onNext(Observable.create(o3)); + observer.onNext(Observable.unsafeCreate(o3)); } } catch (Throwable e) { @@ -292,7 +285,7 @@ public void testBlockedObservableOfObservables() { final CountDownLatch callOnce = new CountDownLatch(1); final CountDownLatch okToContinue = new CountDownLatch(1); TestObservable> observableOfObservables = new TestObservable>(callOnce, okToContinue, odds, even); - Observable concatF = Observable.concat(Observable.create(observableOfObservables)); + Observable concatF = Observable.concat(Observable.unsafeCreate(observableOfObservables)); concatF.subscribe(observer); try { //Block main thread to allow observables to serve up o1. @@ -330,8 +323,8 @@ public void testConcatConcurrentWithInfinity() { @SuppressWarnings("unchecked") Observer observer = mock(Observer.class); @SuppressWarnings("unchecked") - TestObservable> observableOfObservables = new TestObservable>(Observable.create(w1), Observable.create(w2)); - Observable concatF = Observable.concat(Observable.create(observableOfObservables)); + TestObservable> observableOfObservables = new TestObservable>(Observable.unsafeCreate(w1), Observable.unsafeCreate(w2)); + Observable concatF = Observable.concat(Observable.unsafeCreate(observableOfObservables)); concatF.take(50).subscribe(observer); @@ -364,13 +357,13 @@ public void testConcatNonBlockingObservables() { @SuppressWarnings("unchecked") Observer observer = mock(Observer.class); - Observable> observableOfObservables = Observable.create(new Observable.OnSubscribe>() { + Observable> observableOfObservables = Observable.unsafeCreate(new Observable.OnSubscribe>() { @Override public void call(Subscriber> observer) { // simulate what would happen in an observable - observer.onNext(Observable.create(w1)); - observer.onNext(Observable.create(w2)); + observer.onNext(Observable.unsafeCreate(w1)); + observer.onNext(Observable.unsafeCreate(w2)); observer.onCompleted(); } @@ -415,7 +408,7 @@ public void testConcatUnsubscribe() { @SuppressWarnings("unchecked") final Observer observer = mock(Observer.class); - final Observable concat = Observable.concat(Observable.create(w1), Observable.create(w2)); + final Observable concat = Observable.concat(Observable.unsafeCreate(w1), Observable.unsafeCreate(w2)); try { // Subscribe @@ -457,8 +450,8 @@ public void testConcatUnsubscribeConcurrent() { @SuppressWarnings("unchecked") Observer observer = mock(Observer.class); @SuppressWarnings("unchecked") - TestObservable> observableOfObservables = new TestObservable>(Observable.create(w1), Observable.create(w2)); - Observable concatF = Observable.concat(Observable.create(observableOfObservables)); + TestObservable> observableOfObservables = new TestObservable>(Observable.unsafeCreate(w1), Observable.unsafeCreate(w2)); + Observable concatF = Observable.concat(Observable.unsafeCreate(observableOfObservables)); Subscription s1 = concatF.subscribe(observer); @@ -504,8 +497,8 @@ public boolean isUnsubscribed() { }; private final List values; - private Thread t = null; - private int count = 0; + private Thread t; + private int count; private boolean subscribed = true; private final CountDownLatch once; private final CountDownLatch okToContinue; @@ -542,20 +535,24 @@ public void call(final Subscriber observer) { public void run() { try { while (count < size && subscribed) { - if (null != values) + if (null != values) { observer.onNext(values.get(count)); - else + } else { observer.onNext(seed); + } count++; //Unblock the main thread to call unsubscribe. - if (null != once) + if (null != once) { once.countDown(); + } //Block until the main thread has called unsubscribe. - if (null != okToContinue) + if (null != okToContinue) { okToContinue.await(5, TimeUnit.SECONDS); + } } - if (subscribed) + if (subscribed) { observer.onCompleted(); + } } catch (InterruptedException e) { e.printStackTrace(); fail(e.getMessage()); @@ -617,11 +614,11 @@ public void testMultipleObservers() { verify(o1, never()).onError(any(Throwable.class)); verify(o2, never()).onError(any(Throwable.class)); } - + @Test public void concatVeryLongObservableOfObservables() { final int n = 10000; - Observable> source = Observable.create(new OnSubscribe>() { + Observable> source = Observable.unsafeCreate(new OnSubscribe>() { @Override public void call(Subscriber> s) { for (int i = 0; i < n; i++) { @@ -633,13 +630,13 @@ public void call(Subscriber> s) { s.onCompleted(); } }); - + Observable> result = Observable.concat(source).toList(); - + @SuppressWarnings("unchecked") Observer> o = mock(Observer.class); InOrder inOrder = inOrder(o); - + result.subscribe(o); List list = new ArrayList(n); @@ -653,7 +650,7 @@ public void call(Subscriber> s) { @Test public void concatVeryLongObservableOfObservablesTakeHalf() { final int n = 10000; - Observable> source = Observable.create(new OnSubscribe>() { + Observable> source = Observable.unsafeCreate(new OnSubscribe>() { @Override public void call(Subscriber> s) { for (int i = 0; i < n; i++) { @@ -665,13 +662,13 @@ public void call(Subscriber> s) { s.onCompleted(); } }); - + Observable> result = Observable.concat(source).take(n / 2).toList(); - + @SuppressWarnings("unchecked") Observer> o = mock(Observer.class); InOrder inOrder = inOrder(o); - + result.subscribe(o); List list = new ArrayList(n); @@ -682,7 +679,7 @@ public void call(Subscriber> s) { inOrder.verify(o).onCompleted(); verify(o, never()).onError(any(Throwable.class)); } - + @Test public void testConcatOuterBackpressure() { assertEquals(1, @@ -691,7 +688,7 @@ public void testConcatOuterBackpressure() { .take(1) .toBlocking().single()); } - + @Test public void testInnerBackpressureWithAlignedBoundaries() { TestSubscriber ts = new TestSubscriber(); @@ -708,7 +705,7 @@ public void testInnerBackpressureWithAlignedBoundaries() { /* * Testing without counts aligned with buffer sizes because concat must prevent the subscription * to the next Observable if request == 0 which can happen at the end of a subscription - * if the request size == emitted size. It needs to delay subscription until the next request when aligned, + * if the request size == emitted size. It needs to delay subscription until the next request when aligned, * when not aligned, it just subscribesNext with the outstanding request amount. */ @Test @@ -723,11 +720,11 @@ public void testInnerBackpressureWithoutAlignedBoundaries() { ts.assertNoErrors(); assertEquals((RxRingBuffer.SIZE * 4) + 20, ts.getOnNextEvents().size()); } - + // https://github.com/ReactiveX/RxJava/issues/1818 @Test public void testConcatWithNonCompliantSourceDoubleOnComplete() { - Observable o = Observable.create(new OnSubscribe() { + Observable o = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber s) { @@ -735,9 +732,9 @@ public void call(Subscriber s) { s.onCompleted(); s.onCompleted(); } - + }); - + TestSubscriber ts = new TestSubscriber(); Observable.concat(o, o).subscribe(ts); ts.awaitTerminalEvent(500, TimeUnit.MILLISECONDS); @@ -793,10 +790,10 @@ public void onError(Throwable e) { }); executor.awaitTermination(12000, TimeUnit.MILLISECONDS); - + assertEquals(n, counter.get()); } - + @Test public void testRequestOverflowDoesNotStallStream() { Observable o1 = Observable.just(1,2,3); @@ -811,25 +808,26 @@ public void onCompleted() { @Override public void onError(Throwable e) { - + } @Override public void onNext(Integer t) { request(2); }}); - + assertTrue(completed.get()); } - + @Test//(timeout = 100000) public void concatMapRangeAsyncLoopIssue2876() { final long durationSeconds = 2; final long startTime = System.currentTimeMillis(); for (int i = 0;; i++) { //only run this for a max of ten seconds - if (System.currentTimeMillis()-startTime > TimeUnit.SECONDS.toMillis(durationSeconds)) + if (System.currentTimeMillis() - startTime > TimeUnit.SECONDS.toMillis(durationSeconds)) { return; + } if (i % 1000 == 0) { System.out.println("concatMapRangeAsyncLoop > " + i); } @@ -850,32 +848,32 @@ public Observable call(Integer t) { assertEquals((Integer)999, ts.getOnNextEvents().get(999)); } } - + @Test public void scalarAndRangeBackpressured() { TestSubscriber ts = TestSubscriber.create(0); - + Observable.just(1).concatWith(Observable.range(2, 3)).subscribe(ts); - + ts.assertNoValues(); - + ts.requestMore(5); - + ts.assertValues(1, 2, 3, 4); ts.assertCompleted(); ts.assertNoErrors(); } - + @Test public void scalarAndEmptyBackpressured() { TestSubscriber ts = TestSubscriber.create(0); - + Observable.just(1).concatWith(Observable.empty()).subscribe(ts); - + ts.assertNoValues(); - + ts.requestMore(5); - + ts.assertValue(1); ts.assertCompleted(); ts.assertNoErrors(); @@ -884,13 +882,13 @@ public void scalarAndEmptyBackpressured() { @Test public void rangeAndEmptyBackpressured() { TestSubscriber ts = TestSubscriber.create(0); - + Observable.range(1, 2).concatWith(Observable.empty()).subscribe(ts); - + ts.assertNoValues(); - + ts.requestMore(5); - + ts.assertValues(1, 2); ts.assertCompleted(); ts.assertNoErrors(); @@ -899,16 +897,114 @@ public void rangeAndEmptyBackpressured() { @Test public void emptyAndScalarBackpressured() { TestSubscriber ts = TestSubscriber.create(0); - + Observable.empty().concatWith(Observable.just(1)).subscribe(ts); - + ts.assertNoValues(); - + ts.requestMore(5); - + ts.assertValue(1); ts.assertCompleted(); ts.assertNoErrors(); } + @SuppressWarnings("unchecked") + @Test + public void concatMany() throws Exception { + for (int i = 2; i < 10; i++) { + Class[] clazz = new Class[i]; + Arrays.fill(clazz, Observable.class); + + Observable[] obs = new Observable[i]; + Arrays.fill(obs, Observable.just(1)); + + Integer[] expected = new Integer[i]; + Arrays.fill(expected, 1); + + Method m = Observable.class.getMethod("concat", clazz); + + TestSubscriber ts = TestSubscriber.create(); + + ((Observable)m.invoke(null, (Object[])obs)).subscribe(ts); + + ts.assertValues(expected); + ts.assertNoErrors(); + ts.assertCompleted(); + } + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Test + public void concatMapJustJust() { + TestSubscriber ts = TestSubscriber.create(); + + Observable.just(Observable.just(1)).concatMap((Func1)UtilityFunctions.identity()).subscribe(ts); + + ts.assertValue(1); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Test + public void concatMapJustRange() { + TestSubscriber ts = TestSubscriber.create(); + + Observable.just(Observable.range(1, 5)).concatMap((Func1)UtilityFunctions.identity()).subscribe(ts); + + ts.assertValues(1, 2, 3, 4, 5); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Test + public void concatMapDelayErrorJustJust() { + TestSubscriber ts = TestSubscriber.create(); + + Observable.just(Observable.just(1)).concatMapDelayError((Func1)UtilityFunctions.identity()).subscribe(ts); + + ts.assertValue(1); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Test + public void concatMapDelayErrorJustRange() { + TestSubscriber ts = TestSubscriber.create(); + + Observable.just(Observable.range(1, 5)).concatMapDelayError((Func1)UtilityFunctions.identity()).subscribe(ts); + + ts.assertValues(1, 2, 3, 4, 5); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @SuppressWarnings("unchecked") + @Test + public void startWith() throws Exception { + for (int i = 2; i < 10; i++) { + Class[] clazz = new Class[i]; + Arrays.fill(clazz, Object.class); + + Object[] obs = new Object[i]; + Arrays.fill(obs, 1); + + Integer[] expected = new Integer[i]; + Arrays.fill(expected, 1); + + Method m = Observable.class.getMethod("startWith", clazz); + + TestSubscriber ts = TestSubscriber.create(); + + ((Observable)m.invoke(Observable.empty(), obs)).subscribe(ts); + + ts.assertValues(expected); + ts.assertNoErrors(); + ts.assertCompleted(); + } + } + } \ No newline at end of file diff --git a/src/test/java/rx/internal/operators/OperatorCountTest.java b/src/test/java/rx/internal/operators/OperatorCountTest.java new file mode 100644 index 0000000000..0a6d56683b --- /dev/null +++ b/src/test/java/rx/internal/operators/OperatorCountTest.java @@ -0,0 +1,36 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * 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 rx.internal.operators; + +import org.junit.*; + +import rx.Observable; + +public class OperatorCountTest { + + @Test + public void simple() { + Assert.assertEquals(0, Observable.empty().count().toBlocking().last().intValue()); + Assert.assertEquals(0L, Observable.empty().countLong().toBlocking().last().intValue()); + + Assert.assertEquals(1, Observable.just(1).count().toBlocking().last().intValue()); + Assert.assertEquals(1L, Observable.just(1).countLong().toBlocking().last().intValue()); + + Assert.assertEquals(10, Observable.range(1, 10).count().toBlocking().last().intValue()); + Assert.assertEquals(10L, Observable.range(1, 10).countLong().toBlocking().last().intValue()); + + } +} diff --git a/src/test/java/rx/internal/operators/OperatorDebounceTest.java b/src/test/java/rx/internal/operators/OperatorDebounceTest.java index e43efa2f7f..bf3cad52d5 100644 --- a/src/test/java/rx/internal/operators/OperatorDebounceTest.java +++ b/src/test/java/rx/internal/operators/OperatorDebounceTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -57,7 +57,7 @@ public void before() { @Test public void testDebounceWithCompleted() { - Observable source = Observable.create(new Observable.OnSubscribe() { + Observable source = Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber observer) { publishNext(observer, 100, "one"); // Should be skipped since "two" will arrive before the timeout expires. @@ -82,7 +82,7 @@ public void call(Subscriber observer) { @Test public void testDebounceNeverEmits() { - Observable source = Observable.create(new Observable.OnSubscribe() { + Observable source = Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber observer) { // all should be skipped since they are happening faster than the 200ms timeout @@ -111,7 +111,7 @@ public void call(Subscriber observer) { @Test public void testDebounceWithError() { - Observable source = Observable.create(new Observable.OnSubscribe() { + Observable source = Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber observer) { Exception error = new TestException(); @@ -247,17 +247,17 @@ public Observable call(Integer t1) { @Test public void debounceTimedLastIsNotLost() { PublishSubject source = PublishSubject.create(); - + @SuppressWarnings("unchecked") Observer o = mock(Observer.class); source.debounce(100, TimeUnit.MILLISECONDS, scheduler).subscribe(o); - + source.onNext(1); source.onCompleted(); - + scheduler.advanceTimeBy(1, TimeUnit.SECONDS); - + verify(o).onNext(1); verify(o).onCompleted(); verify(o, never()).onError(any(Throwable.class)); @@ -279,7 +279,7 @@ public Observable call(Integer t1) { Observer o = mock(Observer.class); source.debounce(debounceSel).subscribe(o); - + source.onNext(1); source.onCompleted(); @@ -305,4 +305,17 @@ public void debounceWithTimeBackpressure() throws InterruptedException { subscriber.assertTerminalEvent(); subscriber.assertNoErrors(); } + + @Test + public void debounceDefaultScheduler() throws Exception { + + TestSubscriber ts = new TestSubscriber(); + + Observable.range(1, 1000).debounce(1, TimeUnit.SECONDS).subscribe(ts); + + ts.awaitTerminalEventAndUnsubscribeOnTimeout(5, TimeUnit.SECONDS); + ts.assertValue(1000); + ts.assertNoErrors(); + ts.assertCompleted(); + } } \ No newline at end of file diff --git a/src/test/java/rx/internal/operators/OperatorDefaultIfEmptyTest.java b/src/test/java/rx/internal/operators/OperatorDefaultIfEmptyTest.java index ec6bb0486f..fc20f350df 100644 --- a/src/test/java/rx/internal/operators/OperatorDefaultIfEmptyTest.java +++ b/src/test/java/rx/internal/operators/OperatorDefaultIfEmptyTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -54,17 +54,17 @@ public void testDefaultIfEmptyWithEmpty() { @SuppressWarnings("unchecked") Observer observer = mock(Observer.class); observable.subscribe(observer); - + verify(observer).onNext(10); verify(observer).onCompleted(); verify(observer, never()).onError(any(Throwable.class)); } - + @Test public void testEmptyButClientThrows() { @SuppressWarnings("unchecked") final Observer o = mock(Observer.class); - + Observable.empty().defaultIfEmpty(1).subscribe(new Subscriber() { @Override public void onNext(Integer t) { @@ -81,12 +81,12 @@ public void onCompleted() { o.onCompleted(); } }); - + verify(o).onError(any(TestException.class)); verify(o, never()).onNext(any(Integer.class)); verify(o, never()).onCompleted(); } - + @Test public void testBackpressureEmpty() { TestSubscriber ts = TestSubscriber.create(0); @@ -97,7 +97,7 @@ public void testBackpressureEmpty() { ts.assertValue(1); ts.assertCompleted(); } - + @Test public void testBackpressureNonEmpty() { TestSubscriber ts = TestSubscriber.create(0); diff --git a/src/test/java/rx/internal/operators/OperatorDelayTest.java b/src/test/java/rx/internal/operators/OperatorDelayTest.java index 470e83ddc5..8c2bfbf9f2 100644 --- a/src/test/java/rx/internal/operators/OperatorDelayTest.java +++ b/src/test/java/rx/internal/operators/OperatorDelayTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -671,7 +671,7 @@ public void testBackpressureWithTimedDelay() { .observeOn(Schedulers.computation()) .map(new Func1() { - int c = 0; + int c; @Override public Integer call(Integer t) { @@ -690,7 +690,7 @@ public Integer call(Integer t) { ts.assertNoErrors(); assertEquals(RxRingBuffer.SIZE * 2, ts.getOnNextEvents().size()); } - + @Test public void testBackpressureWithSubscriptionTimedDelay() { TestSubscriber ts = new TestSubscriber(); @@ -700,7 +700,7 @@ public void testBackpressureWithSubscriptionTimedDelay() { .observeOn(Schedulers.computation()) .map(new Func1() { - int c = 0; + int c; @Override public Integer call(Integer t) { @@ -735,7 +735,7 @@ public Observable call(Integer i) { .observeOn(Schedulers.computation()) .map(new Func1() { - int c = 0; + int c; @Override public Integer call(Integer t) { @@ -776,7 +776,7 @@ public Observable call(Integer i) { .observeOn(Schedulers.computation()) .map(new Func1() { - int c = 0; + int c; @Override public Integer call(Integer t) { @@ -795,25 +795,25 @@ public Integer call(Integer t) { ts.assertNoErrors(); assertEquals(RxRingBuffer.SIZE * 2, ts.getOnNextEvents().size()); } - + @Test public void testErrorRunsBeforeOnNext() { TestScheduler test = Schedulers.test(); - + PublishSubject ps = PublishSubject.create(); - + TestSubscriber ts = TestSubscriber.create(); - + ps.delay(1, TimeUnit.SECONDS, test).subscribe(ts); - + ps.onNext(1); - + test.advanceTimeBy(500, TimeUnit.MILLISECONDS); - + ps.onError(new TestException()); - + test.advanceTimeBy(1, TimeUnit.SECONDS); - + ts.assertNoValues(); ts.assertError(TestException.class); ts.assertNotCompleted(); @@ -822,13 +822,13 @@ public void testErrorRunsBeforeOnNext() { @Test public void delaySubscriptionCancelBeforeTime() { PublishSubject source = PublishSubject.create(); - + TestSubscriber ts = new TestSubscriber(); - + source.delaySubscription(100, TimeUnit.MILLISECONDS, scheduler).subscribe(ts); - + Assert.assertFalse("source subscribed?", source.hasObservers()); - + ts.unsubscribe(); Assert.assertFalse("source subscribed?", source.hasObservers()); diff --git a/src/test/java/rx/internal/operators/OperatorDematerializeTest.java b/src/test/java/rx/internal/operators/OperatorDematerializeTest.java index 7b805d63c0..8a21a34938 100644 --- a/src/test/java/rx/internal/operators/OperatorDematerializeTest.java +++ b/src/test/java/rx/internal/operators/OperatorDematerializeTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -112,30 +112,30 @@ public void testCompletePassThru() { @Test public void testHonorsContractWhenCompleted() { Observable source = Observable.just(1); - + Observable result = source.materialize().dematerialize(); - + @SuppressWarnings("unchecked") Observer o = mock(Observer.class); - + result.unsafeSubscribe(Subscribers.from(o)); - + verify(o).onNext(1); verify(o).onCompleted(); verify(o, never()).onError(any(Throwable.class)); } - + @Test public void testHonorsContractWhenThrows() { Observable source = Observable.error(new TestException()); - + Observable result = source.materialize().dematerialize(); - + @SuppressWarnings("unchecked") Observer o = mock(Observer.class); - + result.unsafeSubscribe(Subscribers.from(o)); - + verify(o, never()).onNext(any(Integer.class)); verify(o, never()).onCompleted(); verify(o).onError(any(TestException.class)); diff --git a/src/test/java/rx/internal/operators/OperatorDistinctTest.java b/src/test/java/rx/internal/operators/OperatorDistinctTest.java index b0f128d29f..eee3a9d502 100644 --- a/src/test/java/rx/internal/operators/OperatorDistinctTest.java +++ b/src/test/java/rx/internal/operators/OperatorDistinctTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. diff --git a/src/test/java/rx/internal/operators/OperatorDistinctUntilChangedTest.java b/src/test/java/rx/internal/operators/OperatorDistinctUntilChangedTest.java index eddee0cc09..70f9abd45c 100644 --- a/src/test/java/rx/internal/operators/OperatorDistinctUntilChangedTest.java +++ b/src/test/java/rx/internal/operators/OperatorDistinctUntilChangedTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -47,7 +47,7 @@ public String call(String s) { return s.toUpperCase(); } }; - + private final static Func1 THROWS_NON_FATAL = new Func1() { @Override public String call(String s) { @@ -142,7 +142,7 @@ public void testDistinctUntilChangedOfSourceWithExceptionsFromKeySelector() { inOrder.verify(w, never()).onNext(anyString()); inOrder.verify(w, never()).onCompleted(); } - + @Test public void testDistinctUntilChangedWhenNonFatalExceptionThrownByKeySelectorIsNotReportedByUpstream() { Observable src = Observable.just("a", "b", null, "c"); @@ -158,13 +158,13 @@ public void call(Throwable t) { .subscribe(w); assertFalse(errorOccurred.get()); } - + @Test public void customComparator() { Observable source = Observable.just("a", "b", "B", "A","a", "C"); - + TestSubscriber ts = TestSubscriber.create(); - + source.distinctUntilChanged(new Func2() { @Override public Boolean call(String a, String b) { @@ -172,7 +172,7 @@ public Boolean call(String a, String b) { } }) .subscribe(ts); - + ts.assertValues("a", "b", "A", "C"); ts.assertNoErrors(); ts.assertCompleted(); @@ -181,9 +181,9 @@ public Boolean call(String a, String b) { @Test public void customComparatorThrows() { Observable source = Observable.just("a", "b", "B", "A","a", "C"); - + TestSubscriber ts = TestSubscriber.create(); - + source.distinctUntilChanged(new Func2() { @Override public Boolean call(String a, String b) { @@ -191,7 +191,7 @@ public Boolean call(String a, String b) { } }) .subscribe(ts); - + ts.assertValue("a"); ts.assertNotCompleted(); ts.assertError(TestException.class); diff --git a/src/test/java/rx/internal/operators/OperatorDoAfterTerminateTest.java b/src/test/java/rx/internal/operators/OperatorDoAfterTerminateTest.java index 397451161d..dd538ec552 100644 --- a/src/test/java/rx/internal/operators/OperatorDoAfterTerminateTest.java +++ b/src/test/java/rx/internal/operators/OperatorDoAfterTerminateTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -61,7 +61,7 @@ public void nullActionShouldBeCheckedInConstructor() { assertEquals("Action can not be null", expected.getMessage()); } } - + @Test public void nullFinallyActionShouldBeCheckedASAP() { try { @@ -94,4 +94,12 @@ public void ifFinallyActionThrowsExceptionShouldNotBeSwallowedAndActionShouldBeC // Not only IllegalStateException was swallowed // But finallyAction was called twice! } + + @SuppressWarnings("deprecation") + @Test + public void finallyDo() { + Observable.empty().finallyDo(aAction0).subscribe(); + + verify(aAction0).call(); + } } diff --git a/src/test/java/rx/internal/operators/OperatorDoOnRequestTest.java b/src/test/java/rx/internal/operators/OperatorDoOnRequestTest.java index 371b0826ed..d1ddd22c85 100644 --- a/src/test/java/rx/internal/operators/OperatorDoOnRequestTest.java +++ b/src/test/java/rx/internal/operators/OperatorDoOnRequestTest.java @@ -27,6 +27,7 @@ import rx.Observable; import rx.Observable.OnSubscribe; import rx.functions.*; +import rx.observers.TestSubscriber; public class OperatorDoOnRequestTest { @@ -89,16 +90,16 @@ public void onNext(Integer t) { }); assertEquals(Arrays.asList(3L,1L,2L,3L,4L,5L), requests); } - + @Test public void dontRequestIfDownstreamRequestsLate() { final List requested = new ArrayList(); Action1 empty = Actions.empty(); - + final AtomicReference producer = new AtomicReference(); - - Observable.create(new OnSubscribe() { + + Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber t) { t.setProducer(new Producer() { @@ -111,25 +112,25 @@ public void request(long n) { }).doOnRequest(empty).subscribe(new Subscriber() { @Override public void onNext(Object t) { - + } - + @Override public void onError(Throwable e) { - + } - + @Override public void onCompleted() { - + } - + @Override public void setProducer(Producer p) { producer.set(p); } }); - + producer.get().request(1); int s = requested.size(); @@ -140,4 +141,19 @@ public void setProducer(Producer p) { Assert.assertEquals(Arrays.asList(0L, 1L), requested); } } + + @Test + public void canCallDoOnRequestWithActionOfTypeObject() { + final AtomicReference r = new AtomicReference(); + TestSubscriber ts = TestSubscriber.create(); + Observable.just("a").doOnRequest( + new Action1() { + + @Override + public void call(Object v) { + r.set(true); + } + }).subscribe(ts); + assertTrue(r.get()); + } } diff --git a/src/test/java/rx/internal/operators/OperatorDoOnSubscribeTest.java b/src/test/java/rx/internal/operators/OperatorDoOnSubscribeTest.java index 49831352e1..2f60a9e8c5 100644 --- a/src/test/java/rx/internal/operators/OperatorDoOnSubscribeTest.java +++ b/src/test/java/rx/internal/operators/OperatorDoOnSubscribeTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -76,7 +76,7 @@ public void testDoOnUnSubscribeWorksWithRefCount() throws Exception { final AtomicInteger countBefore = new AtomicInteger(); final AtomicInteger countAfter = new AtomicInteger(); final AtomicReference> sref = new AtomicReference>(); - Observable o = Observable.create(new OnSubscribe() { + Observable o = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber s) { diff --git a/src/test/java/rx/internal/operators/OperatorDoOnUnsubscribeTest.java b/src/test/java/rx/internal/operators/OperatorDoOnUnsubscribeTest.java index de2a9d93d5..7dc0a369d9 100644 --- a/src/test/java/rx/internal/operators/OperatorDoOnUnsubscribeTest.java +++ b/src/test/java/rx/internal/operators/OperatorDoOnUnsubscribeTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -32,7 +32,7 @@ import rx.observers.TestSubscriber; public class OperatorDoOnUnsubscribeTest { - + @Test public void testDoOnUnsubscribe() throws Exception { int subCount = 3; diff --git a/src/test/java/rx/internal/operators/OperatorEagerConcatMapTest.java b/src/test/java/rx/internal/operators/OperatorEagerConcatMapTest.java index e6298230e4..acdf7b13fc 100644 --- a/src/test/java/rx/internal/operators/OperatorEagerConcatMapTest.java +++ b/src/test/java/rx/internal/operators/OperatorEagerConcatMapTest.java @@ -16,8 +16,8 @@ package rx.internal.operators; -import java.util.ArrayList; -import java.util.List; +import java.lang.reflect.Method; +import java.util.*; import java.util.concurrent.atomic.*; import org.junit.*; @@ -25,15 +25,17 @@ import rx.Observable; import rx.exceptions.TestException; import rx.functions.*; -import rx.internal.util.RxRingBuffer; +import rx.internal.util.*; import rx.observers.TestSubscriber; import rx.schedulers.Schedulers; import rx.subjects.PublishSubject; +import static org.junit.Assert.*; + public class OperatorEagerConcatMapTest { TestSubscriber ts; TestSubscriber tsBp; - + Func1> toJust = new Func1>() { @Override public Observable call(Integer t) { @@ -53,11 +55,11 @@ public void before() { ts = new TestSubscriber(); tsBp = new TestSubscriber(0L); } - + @Test public void testSimple() { Observable.range(1, 100).concatMapEager(toJust).subscribe(ts); - + ts.assertNoErrors(); ts.assertValueCount(100); ts.assertCompleted(); @@ -66,12 +68,12 @@ public void testSimple() { @Test public void testSimple2() { Observable.range(1, 100).concatMapEager(toRange).subscribe(ts); - + ts.assertNoErrors(); ts.assertValueCount(200); ts.assertCompleted(); } - + @Test public void testEagerness2() { final AtomicInteger count = new AtomicInteger(); @@ -81,21 +83,21 @@ public void call(Integer t) { count.getAndIncrement(); } }); - + Observable.concatEager(source, source).subscribe(tsBp); - + Assert.assertEquals(2, count.get()); tsBp.assertNoErrors(); tsBp.assertNotCompleted(); tsBp.assertNoValues(); - + tsBp.requestMore(Long.MAX_VALUE); - + tsBp.assertValueCount(count.get()); tsBp.assertNoErrors(); tsBp.assertCompleted(); } - + @Test public void testEagerness3() { final AtomicInteger count = new AtomicInteger(); @@ -105,16 +107,16 @@ public void call(Integer t) { count.getAndIncrement(); } }); - + Observable.concatEager(source, source, source).subscribe(tsBp); - + Assert.assertEquals(3, count.get()); tsBp.assertNoErrors(); tsBp.assertNotCompleted(); tsBp.assertNoValues(); - + tsBp.requestMore(Long.MAX_VALUE); - + tsBp.assertValueCount(count.get()); tsBp.assertNoErrors(); tsBp.assertCompleted(); @@ -129,16 +131,16 @@ public void call(Integer t) { count.getAndIncrement(); } }); - + Observable.concatEager(source, source, source, source).subscribe(tsBp); - + Assert.assertEquals(4, count.get()); tsBp.assertNoErrors(); tsBp.assertNotCompleted(); tsBp.assertNoValues(); - + tsBp.requestMore(Long.MAX_VALUE); - + tsBp.assertValueCount(count.get()); tsBp.assertNoErrors(); tsBp.assertCompleted(); @@ -153,16 +155,16 @@ public void call(Integer t) { count.getAndIncrement(); } }); - + Observable.concatEager(source, source, source, source, source).subscribe(tsBp); - + Assert.assertEquals(5, count.get()); tsBp.assertNoErrors(); tsBp.assertNotCompleted(); tsBp.assertNoValues(); - + tsBp.requestMore(Long.MAX_VALUE); - + tsBp.assertValueCount(count.get()); tsBp.assertNoErrors(); tsBp.assertCompleted(); @@ -177,16 +179,16 @@ public void call(Integer t) { count.getAndIncrement(); } }); - + Observable.concatEager(source, source, source, source, source, source).subscribe(tsBp); - + Assert.assertEquals(6, count.get()); tsBp.assertNoErrors(); tsBp.assertNotCompleted(); tsBp.assertNoValues(); - + tsBp.requestMore(Long.MAX_VALUE); - + tsBp.assertValueCount(count.get()); tsBp.assertNoErrors(); tsBp.assertCompleted(); @@ -201,16 +203,16 @@ public void call(Integer t) { count.getAndIncrement(); } }); - + Observable.concatEager(source, source, source, source, source, source, source).subscribe(tsBp); - + Assert.assertEquals(7, count.get()); tsBp.assertNoErrors(); tsBp.assertNotCompleted(); tsBp.assertNoValues(); - + tsBp.requestMore(Long.MAX_VALUE); - + tsBp.assertValueCount(count.get()); tsBp.assertNoErrors(); tsBp.assertCompleted(); @@ -225,16 +227,16 @@ public void call(Integer t) { count.getAndIncrement(); } }); - + Observable.concatEager(source, source, source, source, source, source, source, source).subscribe(tsBp); - + Assert.assertEquals(8, count.get()); tsBp.assertNoErrors(); tsBp.assertNotCompleted(); tsBp.assertNoValues(); - + tsBp.requestMore(Long.MAX_VALUE); - + tsBp.assertValueCount(count.get()); tsBp.assertNoErrors(); tsBp.assertCompleted(); @@ -249,16 +251,16 @@ public void call(Integer t) { count.getAndIncrement(); } }); - + Observable.concatEager(source, source, source, source, source, source, source, source, source).subscribe(tsBp); - + Assert.assertEquals(9, count.get()); tsBp.assertNoErrors(); tsBp.assertNotCompleted(); tsBp.assertNoValues(); - + tsBp.requestMore(Long.MAX_VALUE); - + tsBp.assertValueCount(count.get()); tsBp.assertNoErrors(); tsBp.assertCompleted(); @@ -267,39 +269,39 @@ public void call(Integer t) { @Test public void testMainError() { Observable.error(new TestException()).concatMapEager(toJust).subscribe(ts); - + ts.assertNoValues(); ts.assertError(TestException.class); ts.assertNotCompleted(); } - + @Test public void testInnerError() { Observable.concatEager(Observable.just(1), Observable.error(new TestException())).subscribe(ts); - + ts.assertValue(1); ts.assertError(TestException.class); ts.assertNotCompleted(); } - + @Test public void testInnerEmpty() { Observable.concatEager(Observable.empty(), Observable.empty()).subscribe(ts); - + ts.assertNoValues(); ts.assertNoErrors(); ts.assertCompleted(); } - + @Test public void testMapperThrows() { Observable.just(1).concatMapEager(new Func1>() { @Override public Observable call(Integer t) { throw new TestException(); - } + } }).subscribe(ts); - + ts.assertNoValues(); ts.assertNotCompleted(); ts.assertError(TestException.class); @@ -314,7 +316,7 @@ public void testInvalidCapacityHint() { public void testInvalidMaxConcurrent() { Observable.just(1).concatMapEager(toJust, RxRingBuffer.SIZE, 0); } - + @Test public void testBackpressure() { Observable.concatEager(Observable.just(1), Observable.just(1)).subscribe(tsBp); @@ -322,18 +324,18 @@ public void testBackpressure() { tsBp.assertNoErrors(); tsBp.assertNoValues(); tsBp.assertNotCompleted(); - + tsBp.requestMore(1); tsBp.assertValue(1); tsBp.assertNoErrors(); tsBp.assertNotCompleted(); - + tsBp.requestMore(1); tsBp.assertValues(1, 1); tsBp.assertNoErrors(); tsBp.assertCompleted(); } - + @Test public void testAsynchronousRun() { Observable.range(1, 2).concatMapEager(new Func1>() { @@ -342,18 +344,18 @@ public Observable call(Integer t) { return Observable.range(1, 1000).subscribeOn(Schedulers.computation()); } }).observeOn(Schedulers.newThread()).subscribe(ts); - + ts.awaitTerminalEvent(); ts.assertNoErrors(); ts.assertValueCount(2000); } - + @Test public void testReentrantWork() { final PublishSubject subject = PublishSubject.create(); - + final AtomicBoolean once = new AtomicBoolean(); - + subject.concatMapEager(new Func1>() { @Override public Observable call(Integer t) { @@ -369,20 +371,20 @@ public void call(Integer t) { } }) .subscribe(ts); - + subject.onNext(1); - + ts.assertNoErrors(); ts.assertNotCompleted(); ts.assertValues(1, 2); } - + @Test public void testPrefetchIsBounded() { final AtomicInteger count = new AtomicInteger(); - + TestSubscriber ts = TestSubscriber.create(0); - + Observable.just(1).concatMapEager(new Func1>() { @Override public Observable call(Integer t) { @@ -395,13 +397,13 @@ public void call(Integer t) { }); } }).subscribe(ts); - + ts.assertNoErrors(); ts.assertNoValues(); ts.assertNotCompleted(); Assert.assertEquals(RxRingBuffer.SIZE, count.get()); } - + @Test public void testInnerNull() { Observable.just(1).concatMapEager(new Func1>() { @@ -438,4 +440,91 @@ public void call(Long reqCount) { Assert.assertEquals(1, (long) requests.get(4)); Assert.assertEquals(1, (long) requests.get(5)); } + + @SuppressWarnings("unchecked") + @Test + public void many() throws Exception { + for (int i = 2; i < 10; i++) { + Class[] clazz = new Class[i]; + Arrays.fill(clazz, Observable.class); + + Observable[] obs = new Observable[i]; + Arrays.fill(obs, Observable.just(1)); + + Integer[] expected = new Integer[i]; + Arrays.fill(expected, 1); + + Method m = Observable.class.getMethod("concatEager", clazz); + + TestSubscriber ts = TestSubscriber.create(); + + ((Observable)m.invoke(null, (Object[])obs)).subscribe(ts); + + ts.assertValues(expected); + ts.assertNoErrors(); + ts.assertCompleted(); + } + } + + @SuppressWarnings("unchecked") + @Test + public void capacityHint() { + Observable source = Observable.just(1); + TestSubscriber ts = TestSubscriber.create(); + + Observable.concatEager(Arrays.asList(source, source, source), 1).subscribe(ts); + + ts.assertValues(1, 1, 1); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @Test + public void observable() { + Observable source = Observable.just(1); + TestSubscriber ts = TestSubscriber.create(); + + Observable.concatEager(Observable.just(source, source, source)).subscribe(ts); + + ts.assertValues(1, 1, 1); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @Test + public void observableCapacityHint() { + Observable source = Observable.just(1); + TestSubscriber ts = TestSubscriber.create(); + + Observable.concatEager(Observable.just(source, source, source), 1).subscribe(ts); + + ts.assertValues(1, 1, 1); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @SuppressWarnings("unchecked") + @Test + public void badCapacityHint() throws Exception { + Observable source = Observable.just(1); + try { + Observable.concatEager(Arrays.asList(source, source, source), -99); + } catch (IllegalArgumentException ex) { + assertEquals("capacityHint > 0 required but it was -99", ex.getMessage()); + } + + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Test + public void mappingBadCapacityHint() throws Exception { + Observable source = Observable.just(1); + try { + Observable.just(source, source, source).concatMapEager((Func1)UtilityFunctions.identity(), -99, 10); + } catch (IllegalArgumentException ex) { + assertEquals("capacityHint > 0 required but it was -99", ex.getMessage()); + } + + } + } diff --git a/src/test/java/rx/internal/operators/OperatorElementAtTest.java b/src/test/java/rx/internal/operators/OperatorElementAtTest.java index b248da2797..49aa4d6f5f 100644 --- a/src/test/java/rx/internal/operators/OperatorElementAtTest.java +++ b/src/test/java/rx/internal/operators/OperatorElementAtTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. diff --git a/src/test/java/rx/internal/operators/OperatorFirstTest.java b/src/test/java/rx/internal/operators/OperatorFirstTest.java index c3b6d32263..2d6d20d031 100644 --- a/src/test/java/rx/internal/operators/OperatorFirstTest.java +++ b/src/test/java/rx/internal/operators/OperatorFirstTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. diff --git a/src/test/java/rx/internal/operators/OperatorFlatMapTest.java b/src/test/java/rx/internal/operators/OperatorFlatMapTest.java index ce9712851a..286a2a03bc 100644 --- a/src/test/java/rx/internal/operators/OperatorFlatMapTest.java +++ b/src/test/java/rx/internal/operators/OperatorFlatMapTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -207,7 +207,7 @@ public void testFlatMapTransformsException() { Observable.from(Arrays.asList(10, 20, 30)), Observable. error(new RuntimeException("Forced failure!")) ); - + @SuppressWarnings("unchecked") Observer o = mock(Observer.class); @@ -342,11 +342,11 @@ public Observable call(Integer t1) { .subscribeOn(Schedulers.computation()); } }, m); - + TestSubscriber ts = new TestSubscriber(); - + source.subscribe(ts); - + ts.awaitTerminalEvent(); ts.assertNoErrors(); Set expected = new HashSet(Arrays.asList( @@ -371,22 +371,22 @@ public Integer call(Integer t1, Integer t2) { return t1 * 1000 + t2; } }, m); - + TestSubscriber ts = new TestSubscriber(); - + source.subscribe(ts); - + ts.awaitTerminalEvent(); ts.assertNoErrors(); Set expected = new HashSet(Arrays.asList( - 1010, 1011, 2020, 2021, 3030, 3031, 4040, 4041, 5050, 5051, + 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.getOnNextEvents().size()); System.out.println("--> testFlatMapSelectorMaxConcurrent: " + ts.getOnNextEvents()); Assert.assertTrue(expected.containsAll(ts.getOnNextEvents())); } - + @Test public void testFlatMapTransformsMaxConcurrentNormalLoop() { for (int i = 0; i < 1000; i++) { @@ -396,12 +396,12 @@ public void testFlatMapTransformsMaxConcurrentNormalLoop() { testFlatMapTransformsMaxConcurrentNormal(); } } - + @Test public void testFlatMapTransformsMaxConcurrentNormal() { final int m = 2; final AtomicInteger subscriptionCount = new AtomicInteger(); - Observable onNext = + Observable onNext = compose(Observable.from(Arrays.asList(1, 2, 3)).observeOn(Schedulers.computation()), subscriptionCount, m) .subscribeOn(Schedulers.computation()); Observable onCompleted = compose(Observable.from(Arrays.asList(4)), subscriptionCount, m) @@ -415,7 +415,7 @@ public void testFlatMapTransformsMaxConcurrentNormal() { TestSubscriber ts = new TestSubscriber(o); source.flatMap(just(onNext), just(onError), just0(onCompleted), m).subscribe(ts); - + ts.awaitTerminalEvent(1, TimeUnit.SECONDS); ts.assertNoErrors(); ts.assertTerminalEvent(); @@ -429,8 +429,8 @@ public void testFlatMapTransformsMaxConcurrentNormal() { verify(o, never()).onNext(5); verify(o, never()).onError(any(Throwable.class)); } - - @Ignore // don't care for any reordering + + @Ignore("Don't care for any reordering") @Test(timeout = 10000) public void flatMapRangeAsyncLoop() { for (int i = 0; i < 2000; i++) { @@ -468,7 +468,7 @@ public Observable call(Integer t) { } } } - @Test(timeout = 30000) + @Test(timeout = 60000) public void flatMapRangeMixedAsyncLoop() { for (int i = 0; i < 2000; i++) { if (i % 10 == 0) { @@ -508,19 +508,19 @@ public Observable call(Integer t) { assertEquals(1000, list.size()); } } - + @Test public void flatMapIntPassthruAsync() { for (int i = 0;i < 1000; i++) { TestSubscriber ts = new TestSubscriber(); - + Observable.range(1, 1000).flatMap(new Func1>() { @Override public Observable call(Integer t) { return Observable.just(1).subscribeOn(Schedulers.computation()); } }).subscribe(ts); - + ts.awaitTerminalEvent(5, TimeUnit.SECONDS); ts.assertNoErrors(); ts.assertCompleted(); @@ -531,25 +531,25 @@ public Observable call(Integer t) { public void flatMapTwoNestedSync() { for (final int n : new int[] { 1, 1000, 1000000 }) { TestSubscriber ts = new TestSubscriber(); - + Observable.just(1, 2).flatMap(new Func1>() { @Override public Observable call(Integer t) { return Observable.range(1, n); } }).subscribe(ts); - + System.out.println("flatMapTwoNestedSync >> @ " + n); ts.assertNoErrors(); ts.assertCompleted(); ts.assertValueCount(n * 2); } } - + @Test public void justEmptyMixture() { TestSubscriber ts = TestSubscriber.create(); - + Observable.range(0, 4 * RxRingBuffer.SIZE) .flatMap(new Func1>() { @Override @@ -558,15 +558,15 @@ public Observable call(Integer v) { } }) .subscribe(ts); - + ts.assertValueCount(2 * RxRingBuffer.SIZE); ts.assertNoErrors(); ts.assertCompleted(); - + int j = 1; for (Integer v : ts.getOnNextEvents()) { Assert.assertEquals(j, v.intValue()); - + j += 2; } } @@ -574,7 +574,7 @@ public Observable call(Integer v) { @Test public void rangeEmptyMixture() { TestSubscriber ts = TestSubscriber.create(); - + Observable.range(0, 4 * RxRingBuffer.SIZE) .flatMap(new Func1>() { @Override @@ -583,17 +583,17 @@ public Observable call(Integer v) { } }) .subscribe(ts); - + ts.assertValueCount(4 * RxRingBuffer.SIZE); ts.assertNoErrors(); ts.assertCompleted(); - + int j = 1; List list = ts.getOnNextEvents(); for (int i = 0; i < list.size(); i += 2) { Assert.assertEquals(j, list.get(i).intValue()); Assert.assertEquals(j + 1, list.get(i + 1).intValue()); - + j += 2; } } @@ -601,7 +601,7 @@ public Observable call(Integer v) { @Test public void justEmptyMixtureMaxConcurrent() { TestSubscriber ts = TestSubscriber.create(); - + Observable.range(0, 4 * RxRingBuffer.SIZE) .flatMap(new Func1>() { @Override @@ -610,15 +610,15 @@ public Observable call(Integer v) { } }, 16) .subscribe(ts); - + ts.assertValueCount(2 * RxRingBuffer.SIZE); ts.assertNoErrors(); ts.assertCompleted(); - + int j = 1; for (Integer v : ts.getOnNextEvents()) { Assert.assertEquals(j, v.intValue()); - + j += 2; } } @@ -626,7 +626,7 @@ public Observable call(Integer v) { @Test public void rangeEmptyMixtureMaxConcurrent() { TestSubscriber ts = TestSubscriber.create(); - + Observable.range(0, 4 * RxRingBuffer.SIZE) .flatMap(new Func1>() { @Override @@ -635,17 +635,17 @@ public Observable call(Integer v) { } }, 16) .subscribe(ts); - + ts.assertValueCount(4 * RxRingBuffer.SIZE); ts.assertNoErrors(); ts.assertCompleted(); - + int j = 1; List list = ts.getOnNextEvents(); for (int i = 0; i < list.size(); i += 2) { Assert.assertEquals(j, list.get(i).intValue()); Assert.assertEquals(j + 1, list.get(i + 1).intValue()); - + j += 2; } } diff --git a/src/test/java/rx/internal/operators/OperatorGroupByTest.java b/src/test/java/rx/internal/operators/OperatorGroupByTest.java index 438595416f..b9c2bc6ece 100644 --- a/src/test/java/rx/internal/operators/OperatorGroupByTest.java +++ b/src/test/java/rx/internal/operators/OperatorGroupByTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -26,16 +26,23 @@ import org.junit.*; import org.mockito.*; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.RemovalListener; +import com.google.common.cache.RemovalNotification; + import rx.*; import rx.Observable; import rx.Observable.OnSubscribe; import rx.Observer; +import rx.exceptions.OnErrorNotImplementedException; import rx.exceptions.TestException; import rx.functions.*; import rx.internal.util.*; import rx.observables.GroupedObservable; +import rx.observers.AssertableSubscriber; import rx.observers.TestSubscriber; import rx.schedulers.Schedulers; +import rx.subjects.PublishSubject; public class OperatorGroupByTest { @@ -171,7 +178,7 @@ public void call(V v) { /** * Assert that only a single subscription to a stream occurs and that all events are received. - * + * * @throws Throwable */ @Test @@ -184,7 +191,7 @@ public void testGroupedEventStream() throws Throwable { final int count = 100; final int groupCount = 2; - Observable es = Observable.create(new Observable.OnSubscribe() { + Observable es = Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(final Subscriber observer) { @@ -597,7 +604,7 @@ public void call(String s) { public void testFirstGroupsCompleteAndParentSlowToThenEmitFinalGroupsAndThenComplete() throws InterruptedException { final CountDownLatch first = new CountDownLatch(2); // there are two groups to first complete final ArrayList results = new ArrayList(); - Observable.create(new OnSubscribe() { + Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber sub) { @@ -675,7 +682,7 @@ public void testFirstGroupsCompleteAndParentSlowToThenEmitFinalGroupsWhichThenSu System.err.println("----------------------------------------------------------------------------------------------"); final CountDownLatch first = new CountDownLatch(2); // there are two groups to first complete final ArrayList results = new ArrayList(); - Observable.create(new OnSubscribe() { + Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber sub) { @@ -766,7 +773,7 @@ public void call(String s) { public void testFirstGroupsCompleteAndParentSlowToThenEmitFinalGroupsWhichThenObservesOnAndDelaysAndThenCompletes() throws InterruptedException { final CountDownLatch first = new CountDownLatch(2); // there are two groups to first complete final ArrayList results = new ArrayList(); - Observable.create(new OnSubscribe() { + Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber sub) { @@ -842,7 +849,7 @@ public void call(String s) { @Test public void testGroupsWithNestedSubscribeOn() throws InterruptedException { final ArrayList results = new ArrayList(); - Observable.create(new OnSubscribe() { + Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber sub) { @@ -898,7 +905,7 @@ public void call(String s) { @Test public void testGroupsWithNestedObserveOn() throws InterruptedException { final ArrayList results = new ArrayList(); - Observable.create(new OnSubscribe() { + Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber sub) { @@ -958,7 +965,7 @@ Observable ASYNC_INFINITE_OBSERVABLE_OF_EVENT(final int numGroups, final } Observable SYNC_INFINITE_OBSERVABLE_OF_EVENT(final int numGroups, final AtomicInteger subscribeCounter, final AtomicInteger sentEventCounter) { - return Observable.create(new OnSubscribe() { + return Observable.unsafeCreate(new OnSubscribe() { @Override public void call(final Subscriber op) { @@ -1114,7 +1121,7 @@ public void normalBehavior() { * baR bar BAR * Baz baz bAZ * qux - * + * */ Func1 keysel = new Func1() { @Override @@ -1137,7 +1144,7 @@ public Observable call(final GroupedObservable g) { System.out.println("-----------> NEXT: " + g.getKey()); return g.take(2).map(new Func1() { - int count = 0; + int count; @Override public String call(String v) { @@ -1243,7 +1250,7 @@ public void testError2() { } @Test - public void testgroupByBackpressure() throws InterruptedException { + public void testGroupByBackpressure2() throws InterruptedException { TestSubscriber ts = new TestSubscriber(); Observable.range(1, 4000).groupBy(IS_EVEN2).flatMap(new Func1, Observable>() { @@ -1259,7 +1266,7 @@ public void call() { }).observeOn(Schedulers.computation()).map(new Func1() { - int c = 0; + int c; @Override public String call(Integer l) { @@ -1370,7 +1377,7 @@ public void call(String s) { @Test public void testGroupByUnsubscribe() { final Subscription s = mock(Subscription.class); - Observable o = Observable.create( + Observable o = Observable.unsafeCreate( new OnSubscribe() { @Override public void call(Subscriber subscriber) { @@ -1414,7 +1421,7 @@ public void onNext(GroupedObservable o) { } } }); - Observable.create( + Observable.unsafeCreate( new OnSubscribe() { @Override public void call(Subscriber subscriber) { @@ -1434,7 +1441,7 @@ public Integer call(Integer i) { assertEquals(Arrays.asList(e), inner1.getOnErrorEvents()); assertEquals(Arrays.asList(e), inner2.getOnErrorEvents()); } - + @Test public void testRequestOverflow() { final AtomicBoolean completed = new AtomicBoolean(false); @@ -1455,7 +1462,7 @@ public Observable call(GroupedObservable g) { } }) .subscribe(new Subscriber() { - + @Override public void onStart() { request(2); @@ -1464,26 +1471,26 @@ public void onStart() { @Override public void onCompleted() { completed.set(true); - + } @Override public void onError(Throwable e) { - + } @Override public void onNext(Integer t) { System.out.println(t); //provoke possible request overflow - request(Long.MAX_VALUE-1); + request(Long.MAX_VALUE - 1); }}); assertTrue(completed.get()); } - + /** * Issue #3425. - * + * * The problem is that a request of 1 may create a new group, emit to the desired group * or emit to a completely different group. In this test, the merge requests N which * must be produced by the range, however it will create a bunch of groups before the actual @@ -1504,14 +1511,14 @@ public Object call(Integer i) { ).toBlocking().last(); } } - + /** * Synchronous verification of issue #3425. */ @Test public void testBackpressureInnerDoesntOverflowOuter() { TestSubscriber ts = TestSubscriber.create(0); - + Observable.just(1, 2) .groupBy(new Func1() { @Override @@ -1530,17 +1537,17 @@ public void call(GroupedObservable g) { .subscribe(ts) ; ts.requestMore(1); - + ts.assertNotCompleted(); ts.assertNoErrors(); ts.assertValueCount(1); } - + @Test public void testOneGroupInnerRequestsTwiceBuffer() { TestSubscriber ts1 = TestSubscriber.create(0); final TestSubscriber ts2 = TestSubscriber.create(0); - + Observable.range(1, RxRingBuffer.SIZE * 2) .groupBy(new Func1() { @Override @@ -1555,27 +1562,27 @@ public void call(GroupedObservable g) { } }) .subscribe(ts1); - + ts1.assertNoValues(); ts1.assertNoErrors(); ts1.assertNotCompleted(); - + ts2.assertNoValues(); ts2.assertNoErrors(); ts2.assertNotCompleted(); - + ts1.requestMore(1); - + ts1.assertValueCount(1); ts1.assertNoErrors(); ts1.assertNotCompleted(); - + ts2.assertNoValues(); ts2.assertNoErrors(); ts2.assertNotCompleted(); - + ts2.requestMore(RxRingBuffer.SIZE * 2); - + ts2.assertValueCount(RxRingBuffer.SIZE * 2); ts2.assertNoErrors(); ts2.assertNotCompleted(); @@ -1804,4 +1811,468 @@ public Integer call(Integer pair) { outer.assertValueCount(2); } + + @Test + public void mapFactoryEvictionWorks() { + Func1 keySelector = new Func1 () { + @Override + public Integer call(Integer t) { + return t / 10; + }}; + Func1 elementSelector = UtilityFunctions.identity(); + final List evictedKeys = new ArrayList(); + //normally would use Guava CacheBuilder or similar but for a bit more + //control make something custom + Func1, Map> mapFactory = new Func1, Map>() { + @Override + public Map call(final Action1 evicted) { + // is a bit risky to override the put method because + // of possible side-effects (e.g. remove could call put and we did not know it) + // to fix just need to use composition but needs a verbose implementation of Map + // interface + return new ConcurrentHashMap() { + private static final long serialVersionUID = -7519109652858021153L; + + Integer lastKey; + + @Override + public Object put(Integer key, Object value) { + if (this.size() >= 5) { + super.remove(lastKey); + evicted.call(lastKey); + evictedKeys.add(lastKey); + } + Object result = super.put(key, value); + lastKey = key; + return result; + }}; + }}; + TestSubscriber ts = TestSubscriber.create(); + Observable + .range(1, 100) + .groupBy(keySelector,elementSelector, mapFactory) + .flatMap(new Func1, Observable>() { + @Override + public Observable call(final GroupedObservable g) { + return g.map(new Func1() { + @Override + public String call(Integer x) { + return g.getKey() + ":" + x; + } + }); + } + }) + .subscribe(ts); + assertEquals(Arrays.asList(4, 5, 6, 7, 8, 9), evictedKeys); + List expected = Observable + .range(1, 100) + .map(new Func1() { + @Override + public String call(Integer x) { + return (x / 10) + ":" + x; + } + }) + .toList().toBlocking().single(); + assertEquals(expected, ts.getOnNextEvents()); + } + + private static final Func1 EVICTING_MAP_ELEMENT_SELECTOR = UtilityFunctions.identity(); + + private static final Func1 EVICTING_MAP_KEY_SELECTOR = new Func1 () { + @Override + public Integer call(Integer t) { + return t / 10; + }}; + + @Test + public void testEvictingMapFactoryIfMapPutThrowsRuntimeExceptionThenErrorEmittedByStream() { + final RuntimeException exception = new RuntimeException("boo"); + Func1, Map> mapFactory = createMapFactoryThatThrowsOnPut(exception); + TestSubscriber ts = TestSubscriber.create(); + Observable + .range(1, 100) + .groupBy(EVICTING_MAP_KEY_SELECTOR, EVICTING_MAP_ELEMENT_SELECTOR, mapFactory) + .flatMap(UtilityFunctions.>identity()) + .subscribe(ts); + ts.assertError(exception); + } + + @Test(expected = OnErrorNotImplementedException.class) + public void testEvictingMapFactoryIfMapPutThrowsFatalErrorThenErrorThrownBySubscribe() { + final RuntimeException exception = new OnErrorNotImplementedException("boo", new RuntimeException()); + Func1, Map> mapFactory = createMapFactoryThatThrowsOnPut(exception); + TestSubscriber ts = TestSubscriber.create(); + Observable + .range(1, 100) + .groupBy(EVICTING_MAP_KEY_SELECTOR, EVICTING_MAP_ELEMENT_SELECTOR, mapFactory) + .flatMap(UtilityFunctions.>identity()) + .subscribe(ts); + } + + private static Func1, Map> createMapFactoryThatThrowsOnPut( + final RuntimeException exception) { + return new Func1, Map>() { + @SuppressWarnings("serial") + @Override + public Map call(final Action1 evicted) { + // is a bit risky to override the put method because + // of possible side-effects (e.g. remove could call put and we did not know it) + // to fix just need to use composition but needs a verbose implementation of Map + // interface + return new ConcurrentHashMap() { + + @Override + public Object put(Integer key, Object value) { + throw exception; + }}; + }}; + } + + @Test + public void mapFactoryEvictionWorksWithGuavaCache() { + final List evictedKeys = new ArrayList(); + Func1, Map> mapFactory = + new Func1, Map>() { + @Override + public Map call(final Action1 action) { + return CacheBuilder.newBuilder() + .maximumSize(5) + .removalListener(new RemovalListener() { + @Override + public void onRemoval(RemovalNotification notification) { + action.call(notification.getKey()); + evictedKeys.add(notification.getKey()); + } + }) + .build().asMap(); + } + }; + TestSubscriber ts = TestSubscriber.create(); + Observable + .range(1, 100) + .groupBy(EVICTING_MAP_KEY_SELECTOR, EVICTING_MAP_ELEMENT_SELECTOR, mapFactory) + .flatMap(new Func1, Observable>() { + @Override + public Observable call(final GroupedObservable g) { + return g.map(new Func1() { + @Override + public String call(Integer x) { + return g.getKey() + ":" + x; + } + }); + } + }) + .subscribe(ts); + assertEquals( + new HashSet(Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)), + new HashSet(evictedKeys)); + List expected = Observable + .range(1, 100) + .map(new Func1() { + @Override + public String call(Integer x) { + return (x / 10) + ":" + x; + } + }) + .toList().toBlocking().single(); + assertEquals(expected, ts.getOnNextEvents()); + } + + @Test(expected = NullPointerException.class) + public void testGroupByThrowsNpeIfEvictingMapFactoryNull() { + Observable + .range(1, 100) + .groupBy(EVICTING_MAP_KEY_SELECTOR, EVICTING_MAP_ELEMENT_SELECTOR, null); + } + + @Test + public void testEvictingMapFactoryIfMapCreateThrowsRuntimeExceptionThenErrorEmittedByStream() { + final RuntimeException exception = new RuntimeException("boo"); + Func1, Map> mapFactory = createMapFactoryThatThrowsOnCreate(exception); + TestSubscriber ts = TestSubscriber.create(); + Observable + .range(1, 100) + .groupBy(EVICTING_MAP_KEY_SELECTOR, EVICTING_MAP_ELEMENT_SELECTOR, mapFactory) + .flatMap(UtilityFunctions.>identity()) + .subscribe(ts); + ts.assertError(exception); + } + + @Test(expected = OnErrorNotImplementedException.class) + public void testEvictingMapFactoryIfMapCreateThrowsFatalErrorThenSubscribeThrows() { + final OnErrorNotImplementedException exception = new OnErrorNotImplementedException("boo", new RuntimeException()); + Func1, Map> mapFactory = createMapFactoryThatThrowsOnCreate(exception); + TestSubscriber ts = TestSubscriber.create(); + Observable + .range(1, 100) + .groupBy(EVICTING_MAP_KEY_SELECTOR, EVICTING_MAP_ELEMENT_SELECTOR, mapFactory) + .flatMap(UtilityFunctions.>identity()) + .subscribe(ts); + } + + private static Func1, Map> createMapFactoryThatThrowsOnCreate( + final RuntimeException exception) { + return new Func1, Map>() { + + @Override + public Map call(Action1 t) { + throw exception; + }}; + } + + @Test + public void outerConsumedInABoundedManner() { + final int[] counter = { 0 }; + + Observable.range(1, 10000) + .doOnRequest(new Action1() { + @Override + public void call(Long v) { + counter[0] += v; + } + }) + .groupBy(new Func1() { + @Override + public Integer call(Integer v) { + return 1; + } + }) + .flatMap(new Func1, Observable>() { + @Override + public Observable call(GroupedObservable v) { + return v; + } + }) + .test(0); + + int c = counter[0]; + assertTrue("" + c, c > 0); + assertTrue("" + c, c < 10000); + } + + @Test + public void groupByEvictingMapFactoryThrows() { + final RuntimeException ex = new RuntimeException("boo"); + Func1, Map> evictingMapFactory = // + new Func1, Map>() { + + @Override + public Map call(final Action1 notify) { + throw ex; + } + }; + Observable.just(1) + .groupBy(UtilityFunctions.identity(), UtilityFunctions.identity(), 16, true, evictingMapFactory) + .test() + .assertNoValues() + .assertError(ex); + } + + @Test + public void groupByEvictingMapFactoryExpiryCompletesGroupedFlowable() { + final List completed = new CopyOnWriteArrayList(); + Func1, Map> evictingMapFactory = createEvictingMapFactorySynchronousOnly(1); + PublishSubject subject = PublishSubject.create(); + AssertableSubscriber ts = subject + .groupBy(UtilityFunctions.identity(), UtilityFunctions.identity(), 16, true, evictingMapFactory) + .flatMap(addCompletedKey(completed)) + .test(); + subject.onNext(1); + subject.onNext(2); + subject.onNext(3); + ts.assertValues(1, 2, 3) + .assertNoTerminalEvent(); + assertEquals(Arrays.asList(1, 2), completed); + //ensure coverage of the code that clears the evicted queue + subject.onCompleted(); + ts.assertCompleted(); + ts.assertValueCount(3); + } + + private static final Func1 mod5 = new Func1() { + + @Override + public Integer call(Integer n) { + return n % 5; + } + }; + + @Test + public void groupByEvictingMapFactoryWithExpiringGuavaCacheDemonstrationCodeForUseInJavadoc() { + //javadoc will be a version of this using lambdas and without assertions + final List completed = new CopyOnWriteArrayList(); + //size should be less than 5 to notice the effect + Func1, Map> evictingMapFactory = createEvictingMapFactoryGuava(3); + int numValues = 1000; + Observable.range(1, numValues) + .groupBy(mod5, UtilityFunctions.identity(), 16, true, evictingMapFactory) + .flatMap(addCompletedKey(completed)) + .test() + .assertCompleted() + .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 groupByEvictingMapFactoryEvictionQueueClearedOnErrorCoverageOnly() { + Func1, Map> evictingMapFactory = createEvictingMapFactorySynchronousOnly(1); + PublishSubject subject = PublishSubject.create(); + AssertableSubscriber ts = subject + .groupBy(UtilityFunctions.identity(), UtilityFunctions.identity(), 16, true, evictingMapFactory) + .flatMap(new Func1, Observable>() { + @Override + public Observable call(GroupedObservable g) { + 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 Func1, Observable> addCompletedKey( + final List completed) { + return new Func1, Observable>() { + @Override + public Observable call(final GroupedObservable g) { + return g.doOnCompleted(new Action0() { + @Override + public void call() { + completed.add(g.getKey()); + } + }); + } + }; + } + + //not thread safe + private static final class SingleThreadEvictingHashMap implements Map { + + private final List list = new ArrayList(); + private final Map map = new HashMap(); + private final int maxSize; + private final Action1 evictedListener; + + SingleThreadEvictingHashMap(int maxSize, Action1 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) { + evictedListener.call(v); + } + return result; + } + + @Override + public V remove(Object key) { + list.remove(key); + return map.remove(key); + } + + @Override + public void putAll(Map m) { + for (Entry entry: m.entrySet()) { + put(entry.getKey(), entry.getValue()); + } + } + + @Override + public void clear() { + list.clear(); + map.clear(); + } + + @Override + public Set keySet() { + return map.keySet(); + } + + @Override + public Collection values() { + return map.values(); + } + + @Override + public Set> entrySet() { + return map.entrySet(); + } + } + + private static Func1, Map> createEvictingMapFactoryGuava(final int maxSize) { + Func1, Map> evictingMapFactory = // + new Func1, Map>() { + + @Override + public Map call(final Action1 notify) { + return CacheBuilder.newBuilder() // + .maximumSize(maxSize) // + .removalListener(new RemovalListener() { + @Override + public void onRemoval(RemovalNotification notification) { + notify.call(notification.getValue()); + }}) + . build() + .asMap(); + }}; + return evictingMapFactory; + } + + private static Func1, Map> createEvictingMapFactorySynchronousOnly(final int maxSize) { + Func1, Map> evictingMapFactory = // + new Func1, Map>() { + + @Override + public Map call(final Action1 notify) { + return new SingleThreadEvictingHashMap(maxSize, new Action1() { + @Override + public void call(Object object) { + notify.call(object); + }}); + }}; + return evictingMapFactory; + } } diff --git a/src/test/java/rx/internal/operators/OperatorIgnoreElementsTest.java b/src/test/java/rx/internal/operators/OperatorIgnoreElementsTest.java index 92424da031..832237496f 100644 --- a/src/test/java/rx/internal/operators/OperatorIgnoreElementsTest.java +++ b/src/test/java/rx/internal/operators/OperatorIgnoreElementsTest.java @@ -59,7 +59,7 @@ public void call(Integer t) { assertEquals(num, upstreamCount.get()); assertEquals(0, count); } - + @Test public void testCompletedOk() { TestSubscriber ts = new TestSubscriber(); @@ -69,7 +69,7 @@ public void testCompletedOk() { ts.assertTerminalEvent(); ts.assertUnsubscribed(); } - + @Test public void testErrorReceived() { TestSubscriber ts = new TestSubscriber(); @@ -81,7 +81,7 @@ public void testErrorReceived() { assertEquals(1, ts.getOnErrorEvents().size()); assertEquals("boo", ts.getOnErrorEvents().get(0).getMessage()); } - + @Test public void testUnsubscribesFromUpstream() { final AtomicBoolean unsub = new AtomicBoolean(); @@ -142,5 +142,5 @@ public void onNext(Integer t) { assertEquals(num, upstreamCount.get()); assertEquals(0, count.get()); } - + } diff --git a/src/test/java/rx/internal/operators/OperatorLastTest.java b/src/test/java/rx/internal/operators/OperatorLastTest.java index 48d866e338..1b5638eb08 100644 --- a/src/test/java/rx/internal/operators/OperatorLastTest.java +++ b/src/test/java/rx/internal/operators/OperatorLastTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. diff --git a/src/test/java/rx/internal/operators/OperatorMapNotificationTest.java b/src/test/java/rx/internal/operators/OperatorMapNotificationTest.java index 3e94a20b8b..c8b0a11581 100644 --- a/src/test/java/rx/internal/operators/OperatorMapNotificationTest.java +++ b/src/test/java/rx/internal/operators/OperatorMapNotificationTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -48,12 +48,12 @@ public Observable call() { } } ).subscribe(ts); - + ts.assertNoErrors(); ts.assertNotCompleted(); ts.assertValue(2); } - + @Test public void backpressure() { TestSubscriber ts = TestSubscriber.create(0L); @@ -78,19 +78,19 @@ public Integer call() { } } )).subscribe(ts); - + ts.assertNoValues(); ts.assertNoErrors(); ts.assertNotCompleted(); - + ts.requestMore(3); - + ts.assertValues(2, 3, 4); ts.assertNoErrors(); ts.assertNotCompleted(); ts.requestMore(1); - + ts.assertValues(2, 3, 4, 5); ts.assertNoErrors(); ts.assertCompleted(); @@ -101,7 +101,7 @@ public void noBackpressure() { TestSubscriber ts = TestSubscriber.create(0L); PublishSubject ps = PublishSubject.create(); - + ps.lift(new OperatorMapNotification( new Func1() { @Override @@ -122,19 +122,26 @@ public Integer call() { } } )).subscribe(ts); - + ts.assertNoValues(); ts.assertNoErrors(); ts.assertNotCompleted(); - + ps.onNext(1); ps.onNext(2); ps.onNext(3); ps.onCompleted(); - - ts.assertValues(2, 3, 4, 5); + + ts.assertNoValues(); + ts.assertNoErrors(); + ts.assertNotCompleted(); + + ts.requestMore(1); + + ts.assertValue(0); ts.assertNoErrors(); ts.assertCompleted(); + } } diff --git a/src/test/java/rx/internal/operators/OperatorMapPairTest.java b/src/test/java/rx/internal/operators/OperatorMapPairTest.java index 2554b5e2f3..3217f05f0e 100644 --- a/src/test/java/rx/internal/operators/OperatorMapPairTest.java +++ b/src/test/java/rx/internal/operators/OperatorMapPairTest.java @@ -1,3 +1,18 @@ +/** + * Copyright 2014 Netflix, Inc. + * + * 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 rx.internal.operators; import org.junit.*; @@ -11,11 +26,11 @@ public class OperatorMapPairTest { @Test public void castCrashUnsubscribes() { - + PublishSubject ps = PublishSubject.create(); - + TestSubscriber ts = TestSubscriber.create(); - + ps.flatMap(new Func1>() { @Override public Observable call(Integer t) { @@ -27,13 +42,13 @@ public Integer call(Integer t1, Integer t2) { return t1; } }).unsafeSubscribe(ts); - + Assert.assertTrue("Not subscribed?", ps.hasObservers()); - + ps.onNext(1); - + Assert.assertFalse("Subscribed?", ps.hasObservers()); - + ts.assertError(TestException.class); } } diff --git a/src/test/java/rx/internal/operators/OperatorMaterializeTest.java b/src/test/java/rx/internal/operators/OperatorMaterializeTest.java index 06f231cf90..a55758d25c 100644 --- a/src/test/java/rx/internal/operators/OperatorMaterializeTest.java +++ b/src/test/java/rx/internal/operators/OperatorMaterializeTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -29,9 +29,12 @@ import rx.Notification; import rx.Observable; import rx.Subscriber; +import rx.TestUtil; +import rx.functions.Action0; import rx.functions.Action1; import rx.observers.TestSubscriber; import rx.schedulers.Schedulers; +import rx.subjects.PublishSubject; public class OperatorMaterializeTest { @@ -43,7 +46,7 @@ public void testMaterialize1() { "three"); TestObserver Observer = new TestObserver(); - Observable> m = Observable.create(o1).materialize(); + Observable> m = Observable.unsafeCreate(o1).materialize(); m.subscribe(Observer); try { @@ -69,7 +72,7 @@ public void testMaterialize2() { final TestAsyncErrorObservable o1 = new TestAsyncErrorObservable("one", "two", "three"); TestObserver Observer = new TestObserver(); - Observable> m = Observable.create(o1).materialize(); + Observable> m = Observable.unsafeCreate(o1).materialize(); m.subscribe(Observer); try { @@ -94,7 +97,7 @@ public void testMaterialize2() { public void testMultipleSubscribes() throws InterruptedException, ExecutionException { final TestAsyncErrorObservable o = new TestAsyncErrorObservable("one", "two", null, "three"); - Observable> m = Observable.create(o).materialize(); + Observable> m = Observable.unsafeCreate(o).materialize(); assertEquals(3, m.toList().toBlocking().toFuture().get().size()); assertEquals(3, m.toList().toBlocking().toFuture().get().size()); @@ -124,7 +127,7 @@ public void testBackpressureNoError() { ts.assertValueCount(4); ts.assertCompleted(); } - + @Test public void testBackpressureNoErrorAsync() throws InterruptedException { TestSubscriber> ts = TestSubscriber.create(0); @@ -187,7 +190,7 @@ public void call(Object t) { ts.assertNoValues(); ts.assertTerminalEvent(); } - + @Test public void testUnsubscribeJustBeforeCompletionNotificationShouldPreventThatNotificationArriving() { TestSubscriber> ts = TestSubscriber.create(0); @@ -201,10 +204,37 @@ public void testUnsubscribeJustBeforeCompletionNotificationShouldPreventThatNoti ts.assertUnsubscribed(); } + @Test + public void testConcurrency() { + for (int i = 0; i < 1000; i++) { + final TestSubscriber> ts = TestSubscriber.create(0); + final PublishSubject ps = PublishSubject.create(); + Action0 publishAction = new Action0() { + @Override + public void call() { + ps.onCompleted(); + } + }; + + Action0 requestAction = new Action0() { + @Override + public void call() { + ts.requestMore(1); + } + }; + + ps.materialize().subscribe(ts); + TestUtil.race(publishAction, requestAction); + ts.assertValueCount(1); + ts.assertTerminalEvent(); + ts.assertNoErrors(); + } + } + private static class TestObserver extends Subscriber> { - boolean onCompleted = false; - boolean onError = false; + boolean onCompleted; + boolean onError; List> notifications = new Vector>(); @Override diff --git a/src/test/java/rx/internal/operators/OperatorMergeDelayErrorTest.java b/src/test/java/rx/internal/operators/OperatorMergeDelayErrorTest.java index e5088a0a4a..05326cf75a 100644 --- a/src/test/java/rx/internal/operators/OperatorMergeDelayErrorTest.java +++ b/src/test/java/rx/internal/operators/OperatorMergeDelayErrorTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -20,6 +20,7 @@ import static org.mockito.Matchers.anyInt; import static org.mockito.Mockito.*; +import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -40,6 +41,7 @@ import rx.exceptions.TestException; import rx.functions.Action1; import rx.observers.TestSubscriber; +import rx.subjects.PublishSubject; public class OperatorMergeDelayErrorTest { @@ -53,8 +55,8 @@ public void before() { @Test public void testErrorDelayed1() { - final Observable o1 = Observable.create(new TestErrorObservable("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 Observable o2 = Observable.create(new TestErrorObservable("one", "two", "three")); + final Observable o1 = Observable.unsafeCreate(new TestErrorObservable("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 Observable o2 = Observable.unsafeCreate(new TestErrorObservable("one", "two", "three")); Observable m = Observable.mergeDelayError(o1, o2); m.subscribe(stringObserver); @@ -74,10 +76,10 @@ public void testErrorDelayed1() { @Test public void testErrorDelayed2() { - final Observable o1 = Observable.create(new TestErrorObservable("one", "two", "three")); - final Observable o2 = Observable.create(new TestErrorObservable("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 Observable o3 = Observable.create(new TestErrorObservable("seven", "eight", null)); - final Observable o4 = Observable.create(new TestErrorObservable("nine")); + final Observable o1 = Observable.unsafeCreate(new TestErrorObservable("one", "two", "three")); + final Observable o2 = Observable.unsafeCreate(new TestErrorObservable("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 Observable o3 = Observable.unsafeCreate(new TestErrorObservable("seven", "eight", null)); + final Observable o4 = Observable.unsafeCreate(new TestErrorObservable("nine")); Observable m = Observable.mergeDelayError(o1, o2, o3, o4); m.subscribe(stringObserver); @@ -99,10 +101,10 @@ public void testErrorDelayed2() { @Test public void testErrorDelayed3() { - final Observable o1 = Observable.create(new TestErrorObservable("one", "two", "three")); - final Observable o2 = Observable.create(new TestErrorObservable("four", "five", "six")); - final Observable o3 = Observable.create(new TestErrorObservable("seven", "eight", null)); - final Observable o4 = Observable.create(new TestErrorObservable("nine")); + final Observable o1 = Observable.unsafeCreate(new TestErrorObservable("one", "two", "three")); + final Observable o2 = Observable.unsafeCreate(new TestErrorObservable("four", "five", "six")); + final Observable o3 = Observable.unsafeCreate(new TestErrorObservable("seven", "eight", null)); + final Observable o4 = Observable.unsafeCreate(new TestErrorObservable("nine")); Observable m = Observable.mergeDelayError(o1, o2, o3, o4); m.subscribe(stringObserver); @@ -122,10 +124,10 @@ public void testErrorDelayed3() { @Test public void testErrorDelayed4() { - final Observable o1 = Observable.create(new TestErrorObservable("one", "two", "three")); - final Observable o2 = Observable.create(new TestErrorObservable("four", "five", "six")); - final Observable o3 = Observable.create(new TestErrorObservable("seven", "eight")); - final Observable o4 = Observable.create(new TestErrorObservable("nine", null)); + final Observable o1 = Observable.unsafeCreate(new TestErrorObservable("one", "two", "three")); + final Observable o2 = Observable.unsafeCreate(new TestErrorObservable("four", "five", "six")); + final Observable o3 = Observable.unsafeCreate(new TestErrorObservable("seven", "eight")); + final Observable o4 = Observable.unsafeCreate(new TestErrorObservable("nine", null)); Observable m = Observable.mergeDelayError(o1, o2, o3, o4); m.subscribe(stringObserver); @@ -151,7 +153,7 @@ public void testErrorDelayed4WithThreading() { // throw the error at the very end so no onComplete will be called after it final TestAsyncErrorObservable o4 = new TestAsyncErrorObservable("nine", null); - Observable m = Observable.mergeDelayError(Observable.create(o1), Observable.create(o2), Observable.create(o3), Observable.create(o4)); + Observable m = Observable.mergeDelayError(Observable.unsafeCreate(o1), Observable.unsafeCreate(o2), Observable.unsafeCreate(o3), Observable.unsafeCreate(o4)); m.subscribe(stringObserver); try { @@ -178,8 +180,8 @@ public void testErrorDelayed4WithThreading() { @Test public void testCompositeErrorDelayed1() { - final Observable o1 = Observable.create(new TestErrorObservable("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 Observable o2 = Observable.create(new TestErrorObservable("one", "two", null)); + final Observable o1 = Observable.unsafeCreate(new TestErrorObservable("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 Observable o2 = Observable.unsafeCreate(new TestErrorObservable("one", "two", null)); Observable m = Observable.mergeDelayError(o1, o2); m.subscribe(stringObserver); @@ -198,8 +200,8 @@ public void testCompositeErrorDelayed1() { @Test public void testCompositeErrorDelayed2() { - final Observable o1 = Observable.create(new TestErrorObservable("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 Observable o2 = Observable.create(new TestErrorObservable("one", "two", null)); + final Observable o1 = Observable.unsafeCreate(new TestErrorObservable("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 Observable o2 = Observable.unsafeCreate(new TestErrorObservable("one", "two", null)); Observable m = Observable.mergeDelayError(o1, o2); CaptureObserver w = new CaptureObserver(); @@ -221,10 +223,10 @@ public void testCompositeErrorDelayed2() { @Test public void testMergeObservableOfObservables() { - final Observable o1 = Observable.create(new TestSynchronousObservable()); - final Observable o2 = Observable.create(new TestSynchronousObservable()); + final Observable o1 = Observable.unsafeCreate(new TestSynchronousObservable()); + final Observable o2 = Observable.unsafeCreate(new TestSynchronousObservable()); - Observable> observableOfObservables = Observable.create(new Observable.OnSubscribe>() { + Observable> observableOfObservables = Observable.unsafeCreate(new Observable.OnSubscribe>() { @Override public void call(Subscriber> observer) { @@ -245,8 +247,8 @@ public void call(Subscriber> observer) { @Test public void testMergeArray() { - final Observable o1 = Observable.create(new TestSynchronousObservable()); - final Observable o2 = Observable.create(new TestSynchronousObservable()); + final Observable o1 = Observable.unsafeCreate(new TestSynchronousObservable()); + final Observable o2 = Observable.unsafeCreate(new TestSynchronousObservable()); Observable m = Observable.mergeDelayError(o1, o2); m.subscribe(stringObserver); @@ -258,8 +260,8 @@ public void testMergeArray() { @Test public void testMergeList() { - final Observable o1 = Observable.create(new TestSynchronousObservable()); - final Observable o2 = Observable.create(new TestSynchronousObservable()); + final Observable o1 = Observable.unsafeCreate(new TestSynchronousObservable()); + final Observable o2 = Observable.unsafeCreate(new TestSynchronousObservable()); List> listOfObservables = new ArrayList>(); listOfObservables.add(o1); listOfObservables.add(o2); @@ -273,10 +275,10 @@ public void testMergeList() { } // This is pretty much a clone of testMergeList but with the overloaded MergeDelayError for Iterables - @Test + @Test public void mergeIterable() { - final Observable o1 = Observable.create(new TestSynchronousObservable()); - final Observable o2 = Observable.create(new TestSynchronousObservable()); + final Observable o1 = Observable.unsafeCreate(new TestSynchronousObservable()); + final Observable o2 = Observable.unsafeCreate(new TestSynchronousObservable()); List> listOfObservables = new ArrayList>(); listOfObservables.add(o1); listOfObservables.add(o2); @@ -286,7 +288,7 @@ public void mergeIterable() { verify(stringObserver, never()).onError(any(Throwable.class)); verify(stringObserver, times(1)).onCompleted(); - verify(stringObserver, times(2)).onNext("hello"); + verify(stringObserver, times(2)).onNext("hello"); } @Test @@ -294,7 +296,7 @@ public void testMergeArrayWithThreading() { final TestASynchronousObservable o1 = new TestASynchronousObservable(); final TestASynchronousObservable o2 = new TestASynchronousObservable(); - Observable m = Observable.mergeDelayError(Observable.create(o1), Observable.create(o2)); + Observable m = Observable.mergeDelayError(Observable.unsafeCreate(o1), Observable.unsafeCreate(o2)); m.subscribe(stringObserver); try { @@ -453,25 +455,25 @@ public void onNext(String args) { } @Test public void testMergeSourceWhichDoesntPropagateExceptionBack() { - Observable source = Observable.create(new OnSubscribe() { + Observable source = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber t1) { try { t1.onNext(0); } catch (Throwable swallow) { - + } t1.onNext(1); t1.onCompleted(); } }); - + Observable result = Observable.mergeDelayError(source, Observable.just(2)); - + @SuppressWarnings("unchecked") final Observer o = mock(Observer.class); InOrder inOrder = inOrder(o); - + result.unsafeSubscribe(new Subscriber() { int calls; @Override @@ -491,9 +493,9 @@ public void onError(Throwable e) { public void onCompleted() { o.onCompleted(); } - + }); - + /* * If the child onNext throws, why would we keep accepting values from * other sources? @@ -525,25 +527,25 @@ public void testErrorInParentObservableDelayed() throws Exception { for (int i = 0; i < 50; i++) { final TestASynchronous1sDelayedObservable o1 = new TestASynchronous1sDelayedObservable(); final TestASynchronous1sDelayedObservable o2 = new TestASynchronous1sDelayedObservable(); - Observable> parentObservable = Observable.create(new Observable.OnSubscribe>() { + Observable> parentObservable = Observable.unsafeCreate(new Observable.OnSubscribe>() { @Override public void call(Subscriber> op) { - op.onNext(Observable.create(o1)); - op.onNext(Observable.create(o2)); + op.onNext(Observable.unsafeCreate(o1)); + op.onNext(Observable.unsafeCreate(o2)); op.onError(new NullPointerException("throwing exception in parent")); } }); - + @SuppressWarnings("unchecked") Observer stringObserver = mock(Observer.class); - + TestSubscriber ts = new TestSubscriber(stringObserver); Observable m = Observable.mergeDelayError(parentObservable); m.subscribe(ts); System.out.println("testErrorInParentObservableDelayed | " + i); ts.awaitTerminalEvent(2000, TimeUnit.MILLISECONDS); ts.assertTerminalEvent(); - + verify(stringObserver, times(2)).onNext("hello"); verify(stringObserver, times(1)).onError(any(NullPointerException.class)); verify(stringObserver, never()).onCompleted(); @@ -576,22 +578,140 @@ public void run() { public void testDelayErrorMaxConcurrent() { final List requests = new ArrayList(); Observable source = Observable.mergeDelayError(Observable.just( - Observable.just(1).asObservable(), + Observable.just(1).asObservable(), Observable.error(new TestException())).doOnRequest(new Action1() { @Override public void call(Long t1) { requests.add(t1); } }), 1); - + TestSubscriber ts = new TestSubscriber(); - + source.subscribe(ts); - + ts.assertReceivedOnNext(Arrays.asList(1)); ts.assertTerminalEvent(); assertEquals(1, ts.getOnErrorEvents().size()); assertTrue(ts.getOnErrorEvents().get(0) instanceof TestException); assertEquals(Arrays.asList(1L, 1L, 1L), requests); } + + @SuppressWarnings("unchecked") + @Test + public void iterableMaxConcurrent() { + TestSubscriber ts = TestSubscriber.create(); + + PublishSubject ps1 = PublishSubject.create(); + PublishSubject ps2 = PublishSubject.create(); + + Observable.mergeDelayError(Arrays.asList(ps1, ps2), 1).subscribe(ts); + + assertTrue("ps1 has no subscribers?!", ps1.hasObservers()); + assertFalse("ps2 has subscribers?!", ps2.hasObservers()); + + ps1.onNext(1); + ps1.onCompleted(); + + assertFalse("ps1 has subscribers?!", ps1.hasObservers()); + assertTrue("ps2 has no subscribers?!", ps2.hasObservers()); + + ps2.onNext(2); + ps2.onCompleted(); + + ts.assertValues(1, 2); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @SuppressWarnings("unchecked") + @Test + public void iterableMaxConcurrentError() { + TestSubscriber ts = TestSubscriber.create(); + + PublishSubject ps1 = PublishSubject.create(); + PublishSubject ps2 = PublishSubject.create(); + + Observable.mergeDelayError(Arrays.asList(ps1, ps2), 1).subscribe(ts); + + assertTrue("ps1 has no subscribers?!", ps1.hasObservers()); + assertFalse("ps2 has subscribers?!", ps2.hasObservers()); + + ps1.onNext(1); + ps1.onError(new TestException()); + + assertFalse("ps1 has subscribers?!", ps1.hasObservers()); + assertTrue("ps2 has no subscribers?!", ps2.hasObservers()); + + ps2.onNext(2); + ps2.onError(new TestException()); + + ts.assertValues(1, 2); + ts.assertError(CompositeException.class); + ts.assertNotCompleted(); + + CompositeException ce = (CompositeException)ts.getOnErrorEvents().get(0); + + assertEquals(2, ce.getExceptions().size()); + } + + @SuppressWarnings("unchecked") + @Test + public void mergeMany() throws Exception { + for (int i = 2; i < 10; i++) { + Class[] clazz = new Class[i]; + Arrays.fill(clazz, Observable.class); + + Observable[] obs = new Observable[i]; + Arrays.fill(obs, Observable.just(1)); + + Integer[] expected = new Integer[i]; + Arrays.fill(expected, 1); + + Method m = Observable.class.getMethod("mergeDelayError", clazz); + + TestSubscriber ts = TestSubscriber.create(); + + ((Observable)m.invoke(null, (Object[])obs)).subscribe(ts); + + ts.assertValues(expected); + ts.assertNoErrors(); + ts.assertCompleted(); + } + } + + static Observable withError(Observable source) { + return source.concatWith(Observable.error(new TestException())); + } + + @SuppressWarnings("unchecked") + @Test + public void mergeManyError() throws Exception { + for (int i = 2; i < 10; i++) { + Class[] clazz = new Class[i]; + Arrays.fill(clazz, Observable.class); + + Observable[] obs = new Observable[i]; + for (int j = 0; j < i; j++) { + obs[j] = withError(Observable.just(1)); + } + + Integer[] expected = new Integer[i]; + Arrays.fill(expected, 1); + + Method m = Observable.class.getMethod("mergeDelayError", clazz); + + TestSubscriber ts = TestSubscriber.create(); + + ((Observable)m.invoke(null, (Object[])obs)).subscribe(ts); + + ts.assertValues(expected); + ts.assertError(CompositeException.class); + ts.assertNotCompleted(); + + CompositeException ce = (CompositeException)ts.getOnErrorEvents().get(0); + + assertEquals(i, ce.getExceptions().size()); + } + } } diff --git a/src/test/java/rx/internal/operators/OperatorMergeMaxConcurrentTest.java b/src/test/java/rx/internal/operators/OperatorMergeMaxConcurrentTest.java index f383853f6f..5be03a3b6f 100644 --- a/src/test/java/rx/internal/operators/OperatorMergeMaxConcurrentTest.java +++ b/src/test/java/rx/internal/operators/OperatorMergeMaxConcurrentTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -72,7 +72,7 @@ public void testMaxConcurrent() { for (int i = 0; i < observableCount; i++) { SubscriptionCheckObservable sco = new SubscriptionCheckObservable(subscriptionCount, maxConcurrent); scos.add(sco); - os.add(Observable.create(sco)); + os.add(Observable.unsafeCreate(sco)); } Iterator iter = Observable.merge(os, maxConcurrent).toBlocking().toIterable().iterator(); @@ -92,7 +92,7 @@ private static class SubscriptionCheckObservable implements Observable.OnSubscri private final AtomicInteger subscriptionCount; private final int maxConcurrent; - volatile boolean failed = false; + volatile boolean failed; SubscriptionCheckObservable(AtomicInteger subscriptionCount, int maxConcurrent) { this.subscriptionCount = subscriptionCount; @@ -123,7 +123,7 @@ public void run() { } } - + @Test public void testMergeALotOfSourcesOneByOneSynchronously() { int n = 10000; @@ -154,7 +154,7 @@ public void testMergeALotOfSourcesOneByOneSynchronouslyTakeHalf() { } assertEquals(j, n / 2); } - + @Test public void testSimple() { for (int i = 1; i < 100; i++) { @@ -165,9 +165,9 @@ public void testSimple() { sourceList.add(Observable.just(j)); result.add(j); } - + Observable.merge(sourceList, i).subscribe(ts); - + ts.assertNoErrors(); ts.assertTerminalEvent(); ts.assertReceivedOnNext(result); @@ -183,9 +183,9 @@ public void testSimpleOneLess() { sourceList.add(Observable.just(j)); result.add(j); } - + Observable.merge(sourceList, i - 1).subscribe(ts); - + ts.assertNoErrors(); ts.assertTerminalEvent(); ts.assertReceivedOnNext(result); @@ -197,7 +197,7 @@ public void testSimpleAsyncLoop() { testSimpleAsync(); } } - @Test(timeout = 10000) + @Test(timeout = 30000) public void testSimpleAsync() { for (int i = 1; i < 50; i++) { TestSubscriber ts = new TestSubscriber(); @@ -207,17 +207,17 @@ public void testSimpleAsync() { sourceList.add(Observable.just(j).subscribeOn(Schedulers.io())); expected.add(j); } - + Observable.merge(sourceList, i).subscribe(ts); - + ts.awaitTerminalEvent(1, TimeUnit.SECONDS); ts.assertNoErrors(); Set actual = new HashSet(ts.getOnNextEvents()); - + assertEquals(expected, actual); } } - @Test(timeout = 10000) + @Test(timeout = 30000) public void testSimpleOneLessAsyncLoop() { int max = 200; if (PlatformDependent.isAndroid()) { @@ -227,7 +227,7 @@ public void testSimpleOneLessAsyncLoop() { testSimpleOneLessAsync(); } } - @Test(timeout = 10000) + @Test(timeout = 30000) public void testSimpleOneLessAsync() { long t = System.currentTimeMillis(); for (int i = 2; i < 50; i++) { @@ -241,26 +241,26 @@ public void testSimpleOneLessAsync() { sourceList.add(Observable.just(j).subscribeOn(Schedulers.io())); expected.add(j); } - + Observable.merge(sourceList, i - 1).subscribe(ts); - + ts.awaitTerminalEvent(1, TimeUnit.SECONDS); ts.assertNoErrors(); Set actual = new HashSet(ts.getOnNextEvents()); - + assertEquals(expected, actual); } } @Test(timeout = 5000) public void testBackpressureHonored() throws Exception { List> sourceList = new ArrayList>(3); - + sourceList.add(Observable.range(0, 100000).subscribeOn(Schedulers.io())); sourceList.add(Observable.range(0, 100000).subscribeOn(Schedulers.io())); sourceList.add(Observable.range(0, 100000).subscribeOn(Schedulers.io())); - + final CountDownLatch cdl = new CountDownLatch(5); - + TestSubscriber ts = new TestSubscriber() { @Override public void onStart() { @@ -272,31 +272,31 @@ public void onNext(Integer t) { cdl.countDown(); } }; - + Observable.merge(sourceList, 2).subscribe(ts); - + ts.requestMore(5); - + cdl.await(); - + ts.assertNoErrors(); assertEquals(5, ts.getOnNextEvents().size()); assertEquals(0, ts.getCompletions()); - + ts.unsubscribe(); } @Test(timeout = 5000) public void testTake() throws Exception { List> sourceList = new ArrayList>(3); - + sourceList.add(Observable.range(0, 100000).subscribeOn(Schedulers.io())); sourceList.add(Observable.range(0, 100000).subscribeOn(Schedulers.io())); sourceList.add(Observable.range(0, 100000).subscribeOn(Schedulers.io())); - + TestSubscriber ts = new TestSubscriber(); - + Observable.merge(sourceList, 2).take(5).subscribe(ts); - + ts.awaitTerminalEvent(); ts.assertNoErrors(); assertEquals(5, ts.getOnNextEvents().size()); diff --git a/src/test/java/rx/internal/operators/OperatorMergeTest.java b/src/test/java/rx/internal/operators/OperatorMergeTest.java index f410a55685..bc13673f5e 100644 --- a/src/test/java/rx/internal/operators/OperatorMergeTest.java +++ b/src/test/java/rx/internal/operators/OperatorMergeTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -20,6 +20,7 @@ import static org.mockito.Matchers.any; import static org.mockito.Mockito.*; +import java.lang.reflect.Method; import java.util.*; import java.util.concurrent.*; import java.util.concurrent.atomic.*; @@ -33,9 +34,11 @@ import rx.Observer; import rx.Scheduler.Worker; import rx.functions.*; -import rx.internal.util.RxRingBuffer; +import rx.internal.operators.OperatorMerge.*; +import rx.internal.util.*; import rx.observers.TestSubscriber; import rx.schedulers.*; +import rx.subjects.PublishSubject; import rx.subscriptions.Subscriptions; public class OperatorMergeTest { @@ -50,10 +53,10 @@ public void before() { @Test public void testMergeObservableOfObservables() { - final Observable o1 = Observable.create(new TestSynchronousObservable()); - final Observable o2 = Observable.create(new TestSynchronousObservable()); + final Observable o1 = Observable.unsafeCreate(new TestSynchronousObservable()); + final Observable o2 = Observable.unsafeCreate(new TestSynchronousObservable()); - Observable> observableOfObservables = Observable.create(new Observable.OnSubscribe>() { + Observable> observableOfObservables = Observable.unsafeCreate(new Observable.OnSubscribe>() { @Override public void call(Subscriber> observer) { @@ -74,8 +77,8 @@ public void call(Subscriber> observer) { @Test public void testMergeArray() { - final Observable o1 = Observable.create(new TestSynchronousObservable()); - final Observable o2 = Observable.create(new TestSynchronousObservable()); + final Observable o1 = Observable.unsafeCreate(new TestSynchronousObservable()); + final Observable o2 = Observable.unsafeCreate(new TestSynchronousObservable()); Observable m = Observable.merge(o1, o2); m.subscribe(stringObserver); @@ -87,8 +90,8 @@ public void testMergeArray() { @Test public void testMergeList() { - final Observable o1 = Observable.create(new TestSynchronousObservable()); - final Observable o2 = Observable.create(new TestSynchronousObservable()); + final Observable o1 = Observable.unsafeCreate(new TestSynchronousObservable()); + final Observable o2 = Observable.unsafeCreate(new TestSynchronousObservable()); List> listOfObservables = new ArrayList>(); listOfObservables.add(o1); listOfObservables.add(o2); @@ -107,7 +110,7 @@ public void testUnSubscribeObservableOfObservables() throws InterruptedException final AtomicBoolean unsubscribed = new AtomicBoolean(); final CountDownLatch latch = new CountDownLatch(1); - Observable> source = Observable.create(new Observable.OnSubscribe>() { + Observable> source = Observable.unsafeCreate(new Observable.OnSubscribe>() { @Override public void call(final Subscriber> observer) { @@ -169,7 +172,7 @@ public void testMergeArrayWithThreading() { final TestASynchronousObservable o1 = new TestASynchronousObservable(); final TestASynchronousObservable o2 = new TestASynchronousObservable(); - Observable m = Observable.merge(Observable.create(o1), Observable.create(o2)); + Observable m = Observable.merge(Observable.unsafeCreate(o1), Observable.unsafeCreate(o2)); TestSubscriber ts = new TestSubscriber(stringObserver); m.subscribe(ts); @@ -192,7 +195,7 @@ public void testSynchronizationOfMultipleSequences() throws Throwable { final AtomicInteger concurrentCounter = new AtomicInteger(); final AtomicInteger totalCounter = new AtomicInteger(); - Observable m = Observable.merge(Observable.create(o1), Observable.create(o2)); + Observable m = Observable.merge(Observable.unsafeCreate(o1), Observable.unsafeCreate(o2)); m.subscribe(new Subscriber() { @Override @@ -259,8 +262,8 @@ 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 Observable o1 = Observable.create(new TestErrorObservable("four", null, "six")); // we expect to lose "six" - final Observable o2 = Observable.create(new TestErrorObservable("one", "two", "three")); // we expect to lose all of these since o1 is done first and fails + final Observable o1 = Observable.unsafeCreate(new TestErrorObservable("four", null, "six")); // we expect to lose "six" + final Observable o2 = Observable.unsafeCreate(new TestErrorObservable("one", "two", "three")); // we expect to lose all of these since o1 is done first and fails Observable m = Observable.merge(o1, o2); m.subscribe(stringObserver); @@ -281,10 +284,10 @@ public void testError1() { @Test public void testError2() { // we are using synchronous execution to test this exactly rather than non-deterministic concurrent behavior - final Observable o1 = Observable.create(new TestErrorObservable("one", "two", "three")); - final Observable o2 = Observable.create(new TestErrorObservable("four", null, "six")); // we expect to lose "six" - final Observable o3 = Observable.create(new TestErrorObservable("seven", "eight", null));// we expect to lose all of these since o2 is done first and fails - final Observable o4 = Observable.create(new TestErrorObservable("nine"));// we expect to lose all of these since o2 is done first and fails + final Observable o1 = Observable.unsafeCreate(new TestErrorObservable("one", "two", "three")); + final Observable o2 = Observable.unsafeCreate(new TestErrorObservable("four", null, "six")); // we expect to lose "six" + final Observable o3 = Observable.unsafeCreate(new TestErrorObservable("seven", "eight", null));// we expect to lose all of these since o2 is done first and fails + final Observable o4 = Observable.unsafeCreate(new TestErrorObservable("nine"));// we expect to lose all of these since o2 is done first and fails Observable m = Observable.merge(o1, o2, o3, o4); m.subscribe(stringObserver); @@ -305,7 +308,7 @@ public void testError2() { @Test public void testThrownErrorHandling() { TestSubscriber ts = new TestSubscriber(); - Observable o1 = Observable.create(new OnSubscribe() { + Observable o1 = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber s) { @@ -457,7 +460,7 @@ public void testEarlyUnsubscribe() { } private Observable createObservableOf5IntervalsOf1SecondIncrementsWithSubscriptionHook(final Scheduler scheduler, final AtomicBoolean unsubscribed) { - return Observable.create(new OnSubscribe() { + return Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber s) { @@ -496,7 +499,7 @@ public void testConcurrency() { @Test public void testConcurrencyWithSleeping() { - Observable o = Observable.create(new OnSubscribe() { + Observable o = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(final Subscriber s) { @@ -540,7 +543,7 @@ public void call() { @Test public void testConcurrencyWithBrokenOnCompleteContract() { - Observable o = Observable.create(new OnSubscribe() { + Observable o = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(final Subscriber s) { @@ -618,7 +621,7 @@ public void testBackpressureUpstream2InLoop() throws InterruptedException { testBackpressureUpstream2(); } } - + @Test public void testBackpressureUpstream2() throws InterruptedException { final AtomicInteger generated1 = new AtomicInteger(); @@ -633,9 +636,9 @@ public void onNext(Integer t) { Observable.merge(o1.take(RxRingBuffer.SIZE * 2), Observable.just(-99)).subscribe(testSubscriber); testSubscriber.awaitTerminalEvent(); - + List onNextEvents = testSubscriber.getOnNextEvents(); - + System.out.println("Generated 1: " + generated1.get() + " / received: " + onNextEvents.size()); System.out.println(onNextEvents); @@ -650,7 +653,7 @@ public void onNext(Integer t) { /** * This is the same as the upstreams ones, but now adds the downstream as well by using observeOn. - * + * * This requires merge to also obey the Product.request values coming from it's child subscriber. * @throws InterruptedException if the wait is interrupted */ @@ -664,13 +667,14 @@ public void testBackpressureDownstreamWithConcurrentStreams() throws Interrupted TestSubscriber testSubscriber = new TestSubscriber() { @Override public void onNext(Integer t) { - if (t < 100) + if (t < 100) { try { // force a slow consumer Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } + } // System.err.println("testSubscriber received => " + t + " on thread " + Thread.currentThread()); super.onNext(t); } @@ -705,13 +709,14 @@ public Observable call(Integer t1) { TestSubscriber testSubscriber = new TestSubscriber() { @Override public void onNext(Integer t) { - if (t < 100) + if (t < 100) { try { // force a slow consumer Thread.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } + } // System.err.println("testSubscriber received => " + t + " on thread " + Thread.currentThread()); super.onNext(t); } @@ -734,13 +739,13 @@ public void onNext(Integer t) { /** * Currently there is no solution to this ... we can't exert backpressure on the outer Observable if we * can't know if the ones we've received so far are going to emit or not, otherwise we could starve the system. - * + * * For example, 10,000 Observables are being merged (bad use case to begin with, but ...) and it's only one of them * that will ever emit. If backpressure only allowed the first 1,000 to be sent, we would hang and never receive an event. - * + * * Thus, we must allow all Observables to be sent. The ScalarSynchronousObservable use case is an exception to this since * we can grab the value synchronously. - * + * * @throws InterruptedException */ @Test(timeout = 5000) @@ -756,17 +761,18 @@ public Observable call(Integer t1) { }); TestSubscriber testSubscriber = new TestSubscriber() { - int i = 0; + int i; @Override public void onNext(Integer t) { - if (i++ < 400) + if (i++ < 400) { try { // force a slow consumer Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } + } // System.err.println("testSubscriber received => " + t + " on thread " + Thread.currentThread()); super.onNext(t); } @@ -801,7 +807,7 @@ public void mergeWithNullValues() { public void mergeWithTerminalEventAfterUnsubscribe() { System.out.println("mergeWithTerminalEventAfterUnsubscribe"); TestSubscriber ts = new TestSubscriber(); - Observable bad = Observable.create(new OnSubscribe() { + Observable bad = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber s) { @@ -978,7 +984,7 @@ public void mergeManyAsyncSingle() { @Override public Observable call(final Integer i) { - return Observable.create(new OnSubscribe() { + return Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber s) { @@ -1107,7 +1113,7 @@ public void shouldNotReceivedDelayedErrorWhileThereAreStillNormalEmissionsInTheQ subscriber.assertReceivedOnNext(asList(1, 2, 3, 4)); assertEquals(asList(exception), subscriber.getOnErrorEvents()); } - + @Test public void testMergeKeepsRequesting() throws InterruptedException { //for (int i = 0; i < 5000; i++) { @@ -1166,7 +1172,7 @@ public void call() { assertTrue(a); //} } - + @Test public void testMergeRequestOverflow() throws InterruptedException { //do a non-trivial merge so that future optimisations with EMPTY don't invalidate this test @@ -1174,7 +1180,7 @@ public void testMergeRequestOverflow() throws InterruptedException { final int expectedCount = 4; final CountDownLatch latch = new CountDownLatch(expectedCount); o.subscribeOn(Schedulers.computation()).subscribe(new Subscriber() { - + @Override public void onStart() { request(1); @@ -1194,11 +1200,39 @@ public void onError(Throwable e) { public void onNext(Integer t) { latch.countDown(); request(2); - request(Long.MAX_VALUE-1); + request(Long.MAX_VALUE - 1); }}); assertTrue(latch.await(10, TimeUnit.SECONDS)); } + @Test + public void testConcurrentMergeInnerError() { + for (int i = 0; i < 1000; i++) { + final TestSubscriber ts = TestSubscriber.create(); + final PublishSubject ps1 = PublishSubject.create(); + final PublishSubject ps2 = PublishSubject.create(); + final Exception error = new NullPointerException(); + Action0 action1 = new Action0() { + @Override + public void call() { + ps1.onNext(1); + ps1.onCompleted(); + } + }; + Action0 action2 = new Action0() { + @Override + public void call() { + ps2.onError(error); + } + }; + + Observable.mergeDelayError(ps1, ps2).subscribe(ts); + TestUtil.race(action1, action2); + ts.assertTerminalEvent(); + ts.assertError(error); + } + } + private static Action1 printCount() { return new Action1() { long count; @@ -1223,7 +1257,7 @@ public void call(Integer s) { } }; } - + Func1> toScalar = new Func1>() { @Override public Observable call(Integer t) { @@ -1236,7 +1270,7 @@ public Observable call(Integer t) { return Observable.just(t).asObservable(); } }; - + void runMerge(Func1> func, TestSubscriber ts) { List list = new ArrayList(); for (int i = 0; i < 1000; i++) { @@ -1244,16 +1278,16 @@ void runMerge(Func1> func, TestSubscriber } Observable source = Observable.from(list); source.flatMap(func).subscribe(ts); - + if (ts.getOnNextEvents().size() != 1000) { System.out.println(ts.getOnNextEvents()); } - + ts.assertTerminalEvent(); ts.assertNoErrors(); ts.assertReceivedOnNext(list); } - + @Test public void testFastMergeFullScalar() { runMerge(toScalar, new TestSubscriber()); @@ -1304,14 +1338,14 @@ public void onNext(Integer t) { runMerge(toHiddenScalar, ts); } } - + @Test public void testUnboundedDefaultConcurrency() { List> os = new ArrayList>(); - for(int i=0; i < 2000; i++) { + for (int i = 0; i < 2000; i++) { os.add(Observable.never()); } - os.add(Observable.range(0, 100)); + os.add(Observable.range(0, 100)); TestSubscriber ts = TestSubscriber.create(); Observable.merge(os).take(1).subscribe(ts); @@ -1323,10 +1357,10 @@ public void testUnboundedDefaultConcurrency() { @Test public void testConcurrencyLimit() { List> os = new ArrayList>(); - for(int i=0; i < 2000; i++) { + for (int i = 0; i < 2000; i++) { os.add(Observable.never()); } - os.add(Observable.range(0, 100)); + os.add(Observable.range(0, 100)); TestSubscriber ts = TestSubscriber.create(); Observable.merge(os, Integer.MAX_VALUE).take(1).subscribe(ts); @@ -1356,21 +1390,184 @@ public void zeroMaxConcurrent() { assertEquals("maxConcurrent > 0 required but it was 0", e.getMessage()); } } - + @Test public void mergeJustNull() { TestSubscriber ts = new TestSubscriber(0); - + Observable.range(1, 2).flatMap(new Func1>() { @Override public Observable call(Integer t) { return Observable.just(null); } }).subscribe(ts); - + ts.requestMore(2); ts.assertValues(null, null); ts.assertNoErrors(); ts.assertCompleted(); } + + @Test + public void mergeConcurrentJustJust() { + TestSubscriber ts = TestSubscriber.create(); + + Observable.merge(Observable.just(Observable.just(1)), 5).subscribe(ts); + + ts.assertValue(1); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @Test + public void mergeConcurrentJustRange() { + TestSubscriber ts = TestSubscriber.create(); + + Observable.merge(Observable.just(Observable.range(1, 5)), 5).subscribe(ts); + + ts.assertValues(1, 2, 3, 4, 5); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + + @SuppressWarnings("unchecked") + @Test + public void mergeMany() throws Exception { + for (int i = 2; i < 10; i++) { + Class[] clazz = new Class[i]; + Arrays.fill(clazz, Observable.class); + + Observable[] obs = new Observable[i]; + Arrays.fill(obs, Observable.just(1)); + + Integer[] expected = new Integer[i]; + Arrays.fill(expected, 1); + + Method m = Observable.class.getMethod("merge", clazz); + + TestSubscriber ts = TestSubscriber.create(); + + ((Observable)m.invoke(null, (Object[])obs)).subscribe(ts); + + ts.assertValues(expected); + ts.assertNoErrors(); + ts.assertCompleted(); + } + } + + @SuppressWarnings("unchecked") + @Test + public void mergeArrayMaxConcurrent() { + TestSubscriber ts = TestSubscriber.create(); + + PublishSubject ps1 = PublishSubject.create(); + PublishSubject ps2 = PublishSubject.create(); + + Observable.merge(new Observable[] { ps1, ps2 }, 1).subscribe(ts); + + assertTrue("ps1 has no subscribers?!", ps1.hasObservers()); + assertFalse("ps2 has subscribers?!", ps2.hasObservers()); + + ps1.onNext(1); + ps1.onCompleted(); + + assertFalse("ps1 has subscribers?!", ps1.hasObservers()); + assertTrue("ps2 has no subscribers?!", ps2.hasObservers()); + + ps2.onNext(2); + ps2.onCompleted(); + + ts.assertValues(1, 2); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Test + public void flatMapJustJust() { + TestSubscriber ts = TestSubscriber.create(); + + Observable.just(Observable.just(1)).flatMap((Func1)UtilityFunctions.identity()).subscribe(ts); + + ts.assertValue(1); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Test + public void flatMapJustRange() { + TestSubscriber ts = TestSubscriber.create(); + + Observable.just(Observable.range(1, 5)).flatMap((Func1)UtilityFunctions.identity()).subscribe(ts); + + ts.assertValues(1, 2, 3, 4, 5); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Test + public void flatMapMaxConcurrentJustJust() { + TestSubscriber ts = TestSubscriber.create(); + + Observable.just(Observable.just(1)).flatMap((Func1)UtilityFunctions.identity(), 5).subscribe(ts); + + ts.assertValue(1); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Test + public void flatMapMaxConcurrentJustRange() { + TestSubscriber ts = TestSubscriber.create(); + + Observable.just(Observable.range(1, 5)).flatMap((Func1)UtilityFunctions.identity(), 5).subscribe(ts); + + ts.assertValues(1, 2, 3, 4, 5); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @Test + public void noInnerReordering() { + TestSubscriber ts = TestSubscriber.create(0); + MergeSubscriber ms = new MergeSubscriber(ts, false, 128); + ms.producer = new MergeProducer(ms); + ts.setProducer(ms.producer); + + PublishSubject ps = PublishSubject.create(); + + ms.onNext(ps); + + ps.onNext(1); + + BackpressureUtils.getAndAddRequest(ms.producer, 2); + + ps.onNext(2); + + ms.emit(); + + ts.assertValues(1, 2); + } + + @Test + public void noOuterScalarReordering() { + TestSubscriber ts = TestSubscriber.create(0); + MergeSubscriber ms = new MergeSubscriber(ts, false, 128); + ms.producer = new MergeProducer(ms); + ts.setProducer(ms.producer); + + ms.onNext(Observable.just(1)); + + BackpressureUtils.getAndAddRequest(ms.producer, 2); + + ms.onNext(Observable.just(2)); + + ms.emit(); + + ts.assertValues(1, 2); + } } diff --git a/src/test/java/rx/internal/operators/OperatorMulticastTest.java b/src/test/java/rx/internal/operators/OperatorMulticastTest.java index bbde4a3751..79ca19a86c 100644 --- a/src/test/java/rx/internal/operators/OperatorMulticastTest.java +++ b/src/test/java/rx/internal/operators/OperatorMulticastTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -73,14 +73,14 @@ public void testMulticastConnectTwice() { Subscription sub = multicasted.connect(); Subscription sub2 = multicasted.connect(); - + source.onNext("two"); source.onCompleted(); verify(observer, never()).onNext("one"); verify(observer, times(1)).onNext("two"); verify(observer, times(1)).onCompleted(); - + assertEquals(sub, sub2); } @@ -117,13 +117,13 @@ public void testMulticastDisconnect() { verify(observer, times(1)).onCompleted(); } - + private static final class PublishSubjectFactory implements Func0> { @Override public Subject call() { return PublishSubject. create(); } - + } } diff --git a/src/test/java/rx/internal/operators/OperatorObserveOnTest.java b/src/test/java/rx/internal/operators/OperatorObserveOnTest.java index ef5ab6d332..df2e21e5c8 100644 --- a/src/test/java/rx/internal/operators/OperatorObserveOnTest.java +++ b/src/test/java/rx/internal/operators/OperatorObserveOnTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -265,8 +265,8 @@ public void call(Integer t1) { * Attempts to confirm that when pauses exist between events, the ScheduledObserver * does not lose or reorder any events since the scheduler will not block, but will * be re-scheduled when it receives new events after each pause. - * - * + * + * * This is non-deterministic in proving success, but if it ever fails (non-deterministically) * it is a sign of potential issues as thread-races and scheduling should not affect output. */ @@ -526,7 +526,7 @@ public boolean hasNext() { @Test public void testQueueFullEmitsError() { final CountDownLatch latch = new CountDownLatch(1); - Observable observable = Observable.create(new OnSubscribe() { + Observable observable = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber o) { @@ -585,22 +585,22 @@ public void testQueueFullEmitsErrorWithVaryingBufferSize() { for (int i = 1; i <= 1024; i = i * 2) { final int capacity = i; System.out.println(">> testQueueFullEmitsErrorWithVaryingBufferSize @ " + i); - + PublishSubject ps = PublishSubject.create(); - + TestSubscriber ts = new TestSubscriber(0); - + TestScheduler test = Schedulers.test(); - + ps.observeOn(test, capacity).subscribe(ts); - + for (int j = 0; j < capacity + 10; j++) { ps.onNext(j); } ps.onCompleted(); - + test.advanceTimeBy(1, TimeUnit.SECONDS); - + ts.assertNoValues(); ts.assertError(MissingBackpressureException.class); ts.assertNotCompleted(); @@ -619,20 +619,20 @@ public void testAsyncChild() { public void testOnErrorCutsAheadOfOnNext() { for (int i = 0; i < 50; i++) { final PublishSubject subject = PublishSubject.create(); - + final AtomicLong counter = new AtomicLong(); TestSubscriber ts = new TestSubscriber(new Observer() { - + @Override public void onCompleted() { - + } - + @Override public void onError(Throwable e) { - + } - + @Override public void onNext(Long t) { // simulate slow consumer to force backpressure failure @@ -641,16 +641,16 @@ public void onNext(Long t) { } catch (InterruptedException e) { } } - + }); subject.observeOn(Schedulers.computation()).subscribe(ts); - + // this will blow up with backpressure while (counter.get() < 102400) { subject.onNext(counter.get()); counter.incrementAndGet(); } - + ts.awaitTerminalEvent(); assertEquals(1, ts.getOnErrorEvents().size()); assertTrue(ts.getOnErrorEvents().get(0) instanceof MissingBackpressureException); @@ -741,7 +741,7 @@ public void testRequestOverflow() throws InterruptedException { .subscribe(new Subscriber() { boolean first = true; - + @Override public void onStart() { request(2); @@ -772,7 +772,7 @@ public void onNext(Integer t) { assertEquals(100, count.get()); } - + @Test public void testNoMoreRequestsAfterUnsubscribe() throws InterruptedException { final CountDownLatch latch = new CountDownLatch(1); @@ -814,18 +814,18 @@ public void onNext(Integer t) { @Test public void testErrorDelayed() { TestScheduler s = Schedulers.test(); - + Observable source = Observable.just(1, 2 ,3) .concatWith(Observable.error(new TestException())); - + TestSubscriber ts = TestSubscriber.create(0); source.observeOn(s, true).subscribe(ts); - + ts.assertNoValues(); ts.assertNoErrors(); ts.assertNotCompleted(); - + s.advanceTimeBy(1, TimeUnit.SECONDS); ts.assertNoValues(); @@ -834,38 +834,38 @@ public void testErrorDelayed() { ts.requestMore(1); s.advanceTimeBy(1, TimeUnit.SECONDS); - + ts.assertValues(1); ts.assertNoErrors(); ts.assertNotCompleted(); - + ts.requestMore(3); // requesting 2 doesn't switch to the error() source for some reason in concat. s.advanceTimeBy(1, TimeUnit.SECONDS); - + ts.assertValues(1, 2, 3); ts.assertError(TestException.class); ts.assertNotCompleted(); } - + @Test public void testErrorDelayedAsync() { Observable source = Observable.just(1, 2 ,3) .concatWith(Observable.error(new TestException())); - + TestSubscriber ts = TestSubscriber.create(); source.observeOn(Schedulers.computation(), true).subscribe(ts); - + ts.awaitTerminalEvent(2, TimeUnit.SECONDS); ts.assertValues(1, 2, 3); ts.assertError(TestException.class); ts.assertNotCompleted(); } - + @Test public void requestExactCompletesImmediately() { TestSubscriber ts = TestSubscriber.create(0); - + TestScheduler test = Schedulers.test(); Observable.range(1, 10).observeOn(test).subscribe(ts); @@ -875,24 +875,24 @@ public void requestExactCompletesImmediately() { ts.assertNoValues(); ts.assertNoErrors(); ts.assertNotCompleted(); - + ts.requestMore(10); test.advanceTimeBy(1, TimeUnit.SECONDS); - + ts.assertValueCount(10); ts.assertNoErrors(); ts.assertCompleted(); } - + @Test public void fixedReplenishPattern() { TestSubscriber ts = TestSubscriber.create(0); TestScheduler test = Schedulers.test(); - + final List requests = new ArrayList(); - + Observable.range(1, 100) .doOnRequest(new Action1() { @Override @@ -901,7 +901,7 @@ public void call(Long v) { } }) .observeOn(test, 16).subscribe(ts); - + test.advanceTimeBy(1, TimeUnit.SECONDS); ts.requestMore(20); test.advanceTimeBy(1, TimeUnit.SECONDS); @@ -911,35 +911,35 @@ public void call(Long v) { test.advanceTimeBy(1, TimeUnit.SECONDS); ts.requestMore(35); test.advanceTimeBy(1, TimeUnit.SECONDS); - + ts.assertValueCount(100); ts.assertCompleted(); ts.assertNoErrors(); - + assertEquals(Arrays.asList(16L, 12L, 12L, 12L, 12L, 12L, 12L, 12L, 12L), requests); } - + @Test public void bufferSizesWork() { for (int i = 1; i <= 1024; i = i * 2) { TestSubscriber ts = TestSubscriber.create(); - + Observable.range(1, 1000 * 1000).observeOn(Schedulers.computation(), i) .subscribe(ts); - + ts.awaitTerminalEvent(); ts.assertValueCount(1000 * 1000); ts.assertCompleted(); ts.assertNoErrors(); } } - + @Test public void synchronousRebatching() { final List requests = new ArrayList(); - + TestSubscriber ts = new TestSubscriber(); - + Observable.range(1, 50) .doOnRequest(new Action1() { @Override @@ -949,14 +949,14 @@ public void call(Long r) { }) .rebatchRequests(20) .subscribe(ts); - + ts.assertValueCount(50); ts.assertNoErrors(); ts.assertCompleted(); - + assertEquals(Arrays.asList(20L, 15L, 15L, 15L), requests); } - + @Test public void rebatchRequestsArgumentCheck() { try { diff --git a/src/test/java/rx/internal/operators/OperatorOnBackpressureBufferTest.java b/src/test/java/rx/internal/operators/OperatorOnBackpressureBufferTest.java index ac01daa591..b76405b12c 100644 --- a/src/test/java/rx/internal/operators/OperatorOnBackpressureBufferTest.java +++ b/src/test/java/rx/internal/operators/OperatorOnBackpressureBufferTest.java @@ -1,12 +1,12 @@ /** * Copyright 2016 Netflix, Inc. - * + * * 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. @@ -129,7 +129,7 @@ public void call() { int size = ts.getOnNextEvents().size(); assertTrue(size <= 150); // will get up to 50 more - assertTrue(ts.getOnNextEvents().get(size-1) == size-1); + assertTrue(ts.getOnNextEvents().get(size - 1) == size - 1); assertTrue(s.isUnsubscribed()); } @@ -227,7 +227,7 @@ public void onNext(T t) { }); } - static final Observable infinite = Observable.create(new OnSubscribe() { + static final Observable infinite = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber s) { @@ -238,14 +238,14 @@ public void call(Subscriber s) { } }); - + private static final Action0 THROWS_NON_FATAL = new Action0() { @Override public void call() { throw new RuntimeException(); - }}; - + }}; + @Test public void testNonFatalExceptionThrownByOnOverflowIsNotReportedByUpstream() { final AtomicBoolean errorOccurred = new AtomicBoolean(false); @@ -264,4 +264,14 @@ public void call(Throwable t) { assertFalse(errorOccurred.get()); } + @Test + public void maxSize() { + TestSubscriber ts = TestSubscriber.create(0); + + Observable.range(1, 10).onBackpressureBuffer(1).subscribe(ts); + + ts.assertNoValues(); + ts.assertError(MissingBackpressureException.class); + ts.assertNotCompleted(); + } } diff --git a/src/test/java/rx/internal/operators/OperatorOnBackpressureDropTest.java b/src/test/java/rx/internal/operators/OperatorOnBackpressureDropTest.java index 568ccfd985..07174570b5 100644 --- a/src/test/java/rx/internal/operators/OperatorOnBackpressureDropTest.java +++ b/src/test/java/rx/internal/operators/OperatorOnBackpressureDropTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -18,19 +18,24 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import org.junit.Test; -import rx.Observable; +import rx.*; import rx.Observable.OnSubscribe; import rx.Observer; import rx.Subscriber; import rx.functions.Action1; import rx.internal.util.RxRingBuffer; import rx.observers.TestSubscriber; +import rx.plugins.RxJavaHooks; import rx.schedulers.Schedulers; public class OperatorOnBackpressureDropTest { @@ -91,7 +96,7 @@ public void onNext(Long t) { ts.assertNoErrors(); assertEquals(0, ts.getOnNextEvents().get(0).intValue()); } - + @Test public void testRequestOverflow() throws InterruptedException { final AtomicInteger count = new AtomicInteger(); @@ -102,7 +107,7 @@ public void testRequestOverflow() throws InterruptedException { public void onStart() { request(10); } - + @Override public void onCompleted() { } @@ -116,15 +121,15 @@ public void onError(Throwable e) { public void onNext(Long t) { count.incrementAndGet(); //cause overflow of requested if not handled properly in onBackpressureDrop operator - request(Long.MAX_VALUE-1); + request(Long.MAX_VALUE - 1); }}); assertEquals(n, count.get()); } - + @Test public void testNonFatalExceptionFromOverflowActionIsNotReportedFromUpstreamOperator() { final AtomicBoolean errorOccurred = new AtomicBoolean(false); - //request 0 + //request 0 TestSubscriber ts = TestSubscriber.create(0); //range method emits regardless of requests so should trigger onBackpressureDrop action range(2) @@ -140,15 +145,153 @@ public void call(Throwable t) { .subscribe(ts); assertFalse(errorOccurred.get()); } - + + @Test + public void testOnDropMethodIsCalled() { + final List list = new ArrayList(); + // request 0 + TestSubscriber ts = TestSubscriber.create(0); + Observable.unsafeCreate(new OnSubscribe() { + + @Override + public void call(final Subscriber sub) { + sub.setProducer(new Producer() { + + @Override + public void request(long n) { + if (n > 1) { + sub.onNext(1); + sub.onNext(2); + sub.onCompleted(); + } + } + }); + } + }).onBackpressureDrop(new Action1() { + @Override + public void call(Integer t) { + list.add(t); + } + }).subscribe(ts); + assertEquals(Arrays.asList(1, 2), list); + } + + @Test + public void testUpstreamEmitsOnCompletedAfterFailureWithoutCheckingSubscription() { + TestSubscriber ts = TestSubscriber.create(0); + final RuntimeException e = new RuntimeException(); + Observable.unsafeCreate(new OnSubscribe() { + + @Override + public void call(final Subscriber sub) { + sub.setProducer(new Producer() { + + @Override + public void request(long n) { + if (n > 1) { + sub.onNext(1); + sub.onCompleted(); + } + } + }); + } + }) + .onBackpressureDrop(new Action1() { + @Override + public void call(Integer t) { + throw e; + }}) + .unsafeSubscribe(ts); + ts.assertNoValues(); + ts.assertError(e); + ts.assertNotCompleted(); + } + + @Test + public void testUpstreamEmitsErrorAfterFailureWithoutCheckingSubscriptionResultsInHooksOnErrorCalled() { + try { + final List list = new CopyOnWriteArrayList(); + RxJavaHooks.setOnError(new Action1() { + + @Override + public void call(Throwable t) { + list.add(t); + } + }); + TestSubscriber ts = TestSubscriber.create(0); + final RuntimeException e1 = new RuntimeException(); + final RuntimeException e2 = new RuntimeException(); + Observable.unsafeCreate(new OnSubscribe() { + + @Override + public void call(final Subscriber sub) { + sub.setProducer(new Producer() { + + @Override + public void request(long n) { + if (n > 1) { + sub.onNext(1); + sub.onError(e2); + } + } + }); + } + }).onBackpressureDrop(new Action1() { + @Override + public void call(Integer t) { + throw e1; + } + }).unsafeSubscribe(ts); + ts.assertNoValues(); + assertEquals(Arrays.asList(e1), ts.getOnErrorEvents()); + ts.assertNotCompleted(); + assertEquals(Arrays.asList(e2), list); + } finally { + RxJavaHooks.setOnError(null); + } + } + + @Test + public void testUpstreamEmitsOnNextAfterFailureWithoutCheckingSubscription() { + TestSubscriber ts = TestSubscriber.create(0); + final RuntimeException e = new RuntimeException(); + Observable.unsafeCreate(new OnSubscribe() { + + @Override + public void call(final Subscriber sub) { + sub.setProducer(new Producer() { + + @Override + public void request(long n) { + if (n > 1) { + sub.onNext(1); + sub.onNext(2); + } + } + }); + } + }) + .onBackpressureDrop(new Action1() { + @Override + public void call(Integer t) { + throw e; + }}) + .unsafeSubscribe(ts); + ts.assertNoValues(); + ts.assertError(e); + ts.assertNotCompleted(); + } + + + private static final Action1 THROW_NON_FATAL = new Action1() { @Override public void call(Long n) { throw new RuntimeException(); } - }; + }; - static final Observable infinite = Observable.create(new OnSubscribe() { + static final Observable infinite = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber s) { @@ -159,13 +302,13 @@ public void call(Subscriber s) { } }); - + private static final Observable range(final long n) { - return Observable.create(new OnSubscribe() { + return Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber s) { - for (long i=0;i < n;i++) { + for (long i = 0;i < n;i++) { if (s.isUnsubscribed()) { break; } @@ -173,8 +316,8 @@ public void call(Subscriber s) { } s.onCompleted(); } - + }); } - + } diff --git a/src/test/java/rx/internal/operators/OperatorOnBackpressureLatestTest.java b/src/test/java/rx/internal/operators/OperatorOnBackpressureLatestTest.java index 30751f311f..a226159529 100644 --- a/src/test/java/rx/internal/operators/OperatorOnBackpressureLatestTest.java +++ b/src/test/java/rx/internal/operators/OperatorOnBackpressureLatestTest.java @@ -30,9 +30,9 @@ public class OperatorOnBackpressureLatestTest { @Test public void testSimple() { TestSubscriber ts = new TestSubscriber(); - + Observable.range(1, 5).onBackpressureLatest().subscribe(ts); - + ts.assertNoErrors(); ts.assertTerminalEvent(); ts.assertReceivedOnNext(Arrays.asList(1, 2, 3, 4, 5)); @@ -40,10 +40,10 @@ public void testSimple() { @Test public void testSimpleError() { TestSubscriber ts = new TestSubscriber(); - + Observable.range(1, 5).concatWith(Observable.error(new TestException())) .onBackpressureLatest().subscribe(ts); - + ts.assertTerminalEvent(); Assert.assertEquals(1, ts.getOnErrorEvents().size()); Assert.assertTrue(ts.getOnErrorEvents().get(0) instanceof TestException); @@ -57,9 +57,9 @@ public void onStart() { request(2); } }; - + Observable.range(1, 5).onBackpressureLatest().subscribe(ts); - + ts.assertNoErrors(); ts.assertReceivedOnNext(Arrays.asList(1, 2)); Assert.assertEquals(0, ts.getCompletions()); @@ -73,16 +73,16 @@ public void onStart() { request(0); } }; - + source.onBackpressureLatest().subscribe(ts); ts.assertReceivedOnNext(Collections.emptyList()); source.onNext(1); ts.requestMore(2); - + ts.assertReceivedOnNext(Arrays.asList(1)); - + source.onNext(2); ts.assertReceivedOnNext(Arrays.asList(1, 2)); @@ -95,17 +95,17 @@ public void onStart() { ts.requestMore(2); ts.assertReceivedOnNext(Arrays.asList(1, 2, 6)); - + source.onNext(7); ts.assertReceivedOnNext(Arrays.asList(1, 2, 6, 7)); - + source.onNext(8); source.onNext(9); source.onCompleted(); - + ts.requestMore(1); - + ts.assertReceivedOnNext(Arrays.asList(1, 2, 6, 7, 9)); ts.assertNoErrors(); ts.assertTerminalEvent(); @@ -124,7 +124,7 @@ public void onNext(Integer t) { if (rnd.nextDouble() < 0.001) { try { Thread.sleep(1); - } catch(InterruptedException ex) { + } catch (InterruptedException ex) { ex.printStackTrace(); } } @@ -137,7 +137,7 @@ public void onNext(Integer t) { .onBackpressureLatest() .observeOn(Schedulers.io()) .subscribe(ts); - + ts.awaitTerminalEvent(2, TimeUnit.SECONDS); ts.assertTerminalEvent(); int n = ts.getOnNextEvents().size(); diff --git a/src/test/java/rx/internal/operators/OperatorOnErrorResumeNextViaFunctionTest.java b/src/test/java/rx/internal/operators/OperatorOnErrorResumeNextViaFunctionTest.java index a7cee6966f..e127334588 100644 --- a/src/test/java/rx/internal/operators/OperatorOnErrorResumeNextViaFunctionTest.java +++ b/src/test/java/rx/internal/operators/OperatorOnErrorResumeNextViaFunctionTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -44,7 +44,7 @@ public class OperatorOnErrorResumeNextViaFunctionTest { @Test public void testResumeNextWithSynchronousExecution() { final AtomicReference receivedException = new AtomicReference(); - Observable w = Observable.create(new Observable.OnSubscribe() { + Observable w = Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber observer) { @@ -94,7 +94,7 @@ public Observable call(Throwable t1) { } }; - Observable observable = Observable.create(w).onErrorResumeNext(resume); + Observable observable = Observable.unsafeCreate(w).onErrorResumeNext(resume); @SuppressWarnings("unchecked") Observer observer = mock(Observer.class); @@ -131,7 +131,7 @@ public Observable call(Throwable t1) { } }; - Observable observable = Observable.create(w).onErrorResumeNext(resume); + Observable observable = Observable.unsafeCreate(w).onErrorResumeNext(resume); @SuppressWarnings("unchecked") Observer observer = mock(Observer.class); @@ -231,7 +231,7 @@ public Observable call(Throwable t1) { System.out.println(ts.getOnNextEvents()); ts.assertReceivedOnNext(Arrays.asList("success")); } - + @Test public void testMapResumeAsyncNext() { // Trigger multiple failures @@ -242,8 +242,9 @@ public void testMapResumeAsyncNext() { w = w.map(new Func1() { @Override public String call(String s) { - if ("fail".equals(s)) + if ("fail".equals(s)) { throw new RuntimeException("Forced Failure"); + } System.out.println("BadMapper:" + s); return s; } @@ -255,7 +256,7 @@ public String call(String s) { public Observable call(Throwable t1) { return Observable.just("twoResume", "threeResume").subscribeOn(Schedulers.computation()); } - + }); @SuppressWarnings("unchecked") @@ -277,7 +278,7 @@ private static class TestObservable implements Observable.OnSubscribe { final Subscription s; final String[] values; - Thread t = null; + Thread t; public TestObservable(Subscription s, String... values) { this.s = s; @@ -311,7 +312,7 @@ public void run() { } } - + @Test public void testBackpressure() { TestSubscriber ts = new TestSubscriber(); @@ -326,7 +327,7 @@ public Observable call(Throwable t1) { }) .observeOn(Schedulers.computation()) .map(new Func1() { - int c = 0; + int c; @Override public Integer call(Integer t1) { @@ -350,18 +351,18 @@ public Integer call(Integer t1) { @Test public void normalBackpressure() { TestSubscriber ts = TestSubscriber.create(0); - + PublishSubject ps = PublishSubject.create(); - + ps.onErrorResumeNext(new Func1>() { @Override public Observable call(Throwable v) { return Observable.range(3, 2); } }).subscribe(ts); - + ts.requestMore(2); - + ps.onNext(1); ps.onNext(2); ps.onError(new TestException("Forced failure")); @@ -371,7 +372,7 @@ public Observable call(Throwable v) { ts.assertNotCompleted(); ts.requestMore(2); - + ts.assertValues(1, 2, 3, 4); ts.assertNoErrors(); ts.assertCompleted(); diff --git a/src/test/java/rx/internal/operators/OperatorOnErrorResumeNextViaObservableTest.java b/src/test/java/rx/internal/operators/OperatorOnErrorResumeNextViaObservableTest.java index d67e1d3814..41f910c2fc 100644 --- a/src/test/java/rx/internal/operators/OperatorOnErrorResumeNextViaObservableTest.java +++ b/src/test/java/rx/internal/operators/OperatorOnErrorResumeNextViaObservableTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -42,7 +42,7 @@ public void testResumeNext() { Subscription s = mock(Subscription.class); // Trigger failure on second element TestObservable f = new TestObservable(s, "one", "fail", "two", "three"); - Observable w = Observable.create(f); + Observable w = Observable.unsafeCreate(f); Observable resume = Observable.just("twoResume", "threeResume"); Observable observable = w.onErrorResumeNext(resume); @@ -72,15 +72,16 @@ public void testMapResumeAsyncNext() { Observable w = Observable.just("one", "fail", "two", "three", "fail"); // Resume Observable is async TestObservable f = new TestObservable(sr, "twoResume", "threeResume"); - Observable resume = Observable.create(f); + Observable resume = Observable.unsafeCreate(f); // Introduce map function that fails intermittently (Map does not prevent this when the observer is a // rx.operator incl onErrorResumeNextViaObservable) w = w.map(new Func1() { @Override public String call(String s) { - if ("fail".equals(s)) + if ("fail".equals(s)) { throw new RuntimeException("Forced Failure"); + } System.out.println("BadMapper:" + s); return s; } @@ -106,16 +107,16 @@ public String call(String s) { verify(observer, times(1)).onNext("twoResume"); verify(observer, times(1)).onNext("threeResume"); } - + @Test public void testResumeNextWithFailedOnSubscribe() { - Observable testObservable = Observable.create(new OnSubscribe() { + Observable testObservable = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber t1) { throw new RuntimeException("force failure"); } - + }); Observable resume = Observable.just("resume"); Observable observable = testObservable.onErrorResumeNext(resume); @@ -128,16 +129,16 @@ public void call(Subscriber t1) { verify(observer, times(1)).onCompleted(); verify(observer, times(1)).onNext("resume"); } - + @Test public void testResumeNextWithFailedOnSubscribeAsync() { - Observable testObservable = Observable.create(new OnSubscribe() { + Observable testObservable = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber t1) { throw new RuntimeException("force failure"); } - + }); Observable resume = Observable.just("resume"); Observable observable = testObservable.subscribeOn(Schedulers.io()).onErrorResumeNext(resume); @@ -148,7 +149,7 @@ public void call(Subscriber t1) { observable.subscribe(ts); ts.awaitTerminalEvent(); - + verify(observer, Mockito.never()).onError(any(Throwable.class)); verify(observer, times(1)).onCompleted(); verify(observer, times(1)).onNext("resume"); @@ -158,7 +159,7 @@ private static class TestObservable implements Observable.OnSubscribe { final Subscription s; final String[] values; - Thread t = null; + Thread t; public TestObservable(Subscription s, String... values) { this.s = s; @@ -176,8 +177,9 @@ public void run() { try { System.out.println("running TestObservable thread"); for (String s : values) { - if ("fail".equals(s)) + if ("fail".equals(s)) { throw new RuntimeException("Forced Failure"); + } System.out.println("TestObservable onNext: " + s); observer.onNext(s); } @@ -195,7 +197,7 @@ public void run() { System.out.println("done starting TestObservable thread"); } } - + @Test public void testBackpressure() { TestSubscriber ts = new TestSubscriber(); @@ -203,7 +205,7 @@ public void testBackpressure() { .onErrorResumeNext(Observable.just(1)) .observeOn(Schedulers.computation()) .map(new Func1() { - int c = 0; + int c; @Override public Integer call(Integer t1) { @@ -227,13 +229,13 @@ public Integer call(Integer t1) { @Test public void normalBackpressure() { TestSubscriber ts = TestSubscriber.create(0); - + PublishSubject ps = PublishSubject.create(); - + ps.onErrorResumeNext(Observable.range(3, 2)).subscribe(ts); - + ts.requestMore(2); - + ps.onNext(1); ps.onNext(2); ps.onError(new TestException("Forced failure")); @@ -243,7 +245,7 @@ public void normalBackpressure() { ts.assertNotCompleted(); ts.requestMore(2); - + ts.assertValues(1, 2, 3, 4); ts.assertNoErrors(); ts.assertCompleted(); diff --git a/src/test/java/rx/internal/operators/OperatorOnErrorReturnTest.java b/src/test/java/rx/internal/operators/OperatorOnErrorReturnTest.java index 4124d8d344..196d5d49ce 100644 --- a/src/test/java/rx/internal/operators/OperatorOnErrorReturnTest.java +++ b/src/test/java/rx/internal/operators/OperatorOnErrorReturnTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -41,7 +41,7 @@ public class OperatorOnErrorReturnTest { @Test public void testResumeNext() { TestObservable f = new TestObservable("one"); - Observable w = Observable.create(f); + Observable w = Observable.unsafeCreate(f); final AtomicReference capturedException = new AtomicReference(); Observable observable = w.onErrorReturn(new Func1() { @@ -77,7 +77,7 @@ public String call(Throwable e) { @Test public void testFunctionThrowsError() { TestObservable f = new TestObservable("one"); - Observable w = Observable.create(f); + Observable w = Observable.unsafeCreate(f); final AtomicReference capturedException = new AtomicReference(); Observable observable = w.onErrorReturn(new Func1() { @@ -108,7 +108,7 @@ public String call(Throwable e) { verify(observer, times(0)).onCompleted(); assertNotNull(capturedException.get()); } - + @Test public void testMapResumeAsyncNext() { // Trigger multiple failures @@ -119,8 +119,9 @@ public void testMapResumeAsyncNext() { w = w.map(new Func1() { @Override public String call(String s) { - if ("fail".equals(s)) + if ("fail".equals(s)) { throw new RuntimeException("Forced Failure"); + } System.out.println("BadMapper:" + s); return s; } @@ -132,7 +133,7 @@ public String call(String s) { public String call(Throwable t1) { return "resume"; } - + }); @SuppressWarnings("unchecked") @@ -148,7 +149,7 @@ public String call(Throwable t1) { verify(observer, Mockito.never()).onNext("three"); verify(observer, times(1)).onNext("resume"); } - + @Test public void testBackpressure() { TestSubscriber ts = new TestSubscriber(); @@ -159,11 +160,11 @@ public void testBackpressure() { public Integer call(Throwable t1) { return 1; } - + }) .observeOn(Schedulers.computation()) .map(new Func1() { - int c = 0; + int c; @Override public Integer call(Integer t1) { @@ -187,7 +188,7 @@ public Integer call(Integer t1) { private static class TestObservable implements Observable.OnSubscribe { final String[] values; - Thread t = null; + Thread t; public TestObservable(String... values) { this.values = values; @@ -218,22 +219,22 @@ public void run() { System.out.println("done starting TestObservable thread"); } } - + @Test public void normalBackpressure() { TestSubscriber ts = TestSubscriber.create(0); - + PublishSubject ps = PublishSubject.create(); - + ps.onErrorReturn(new Func1() { @Override public Integer call(Throwable e) { return 3; } }).subscribe(ts); - + ts.requestMore(2); - + ps.onNext(1); ps.onNext(2); ps.onError(new TestException("Forced failure")); @@ -243,7 +244,7 @@ public Integer call(Throwable e) { ts.assertNotCompleted(); ts.requestMore(2); - + ts.assertValues(1, 2, 3); ts.assertNoErrors(); ts.assertCompleted(); diff --git a/src/test/java/rx/internal/operators/OperatorOnExceptionResumeNextViaObservableTest.java b/src/test/java/rx/internal/operators/OperatorOnExceptionResumeNextViaObservableTest.java index 6b2d792e9c..b54c7d4c46 100644 --- a/src/test/java/rx/internal/operators/OperatorOnExceptionResumeNextViaObservableTest.java +++ b/src/test/java/rx/internal/operators/OperatorOnExceptionResumeNextViaObservableTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -35,7 +35,7 @@ public class OperatorOnExceptionResumeNextViaObservableTest { public void testResumeNextWithException() { // Trigger failure on second element TestObservable f = new TestObservable("one", "EXCEPTION", "two", "three"); - Observable w = Observable.create(f); + Observable w = Observable.unsafeCreate(f); Observable resume = Observable.just("twoResume", "threeResume"); Observable observable = w.onExceptionResumeNext(resume); @@ -63,7 +63,7 @@ public void testResumeNextWithException() { public void testResumeNextWithRuntimeException() { // Trigger failure on second element TestObservable f = new TestObservable("one", "RUNTIMEEXCEPTION", "two", "three"); - Observable w = Observable.create(f); + Observable w = Observable.unsafeCreate(f); Observable resume = Observable.just("twoResume", "threeResume"); Observable observable = w.onExceptionResumeNext(resume); @@ -91,7 +91,7 @@ public void testResumeNextWithRuntimeException() { public void testThrowablePassesThru() { // Trigger failure on second element TestObservable f = new TestObservable("one", "THROWABLE", "two", "three"); - Observable w = Observable.create(f); + Observable w = Observable.unsafeCreate(f); Observable resume = Observable.just("twoResume", "threeResume"); Observable observable = w.onExceptionResumeNext(resume); @@ -119,7 +119,7 @@ public void testThrowablePassesThru() { public void testErrorPassesThru() { // Trigger failure on second element TestObservable f = new TestObservable("one", "ON_OVERFLOW_ERROR", "two", "three"); - Observable w = Observable.create(f); + Observable w = Observable.unsafeCreate(f); Observable resume = Observable.just("twoResume", "threeResume"); Observable observable = w.onExceptionResumeNext(resume); @@ -149,15 +149,16 @@ public void testMapResumeAsyncNext() { Observable w = Observable.just("one", "fail", "two", "three", "fail"); // Resume Observable is async TestObservable f = new TestObservable("twoResume", "threeResume"); - Observable resume = Observable.create(f); + Observable resume = Observable.unsafeCreate(f); // Introduce map function that fails intermittently (Map does not prevent this when the observer is a // rx.operator incl onErrorResumeNextViaObservable) w = w.map(new Func1() { @Override public String call(String s) { - if ("fail".equals(s)) + if ("fail".equals(s)) { throw new RuntimeException("Forced Failure"); + } System.out.println("BadMapper:" + s); return s; } @@ -186,8 +187,8 @@ public String call(String s) { verify(observer, Mockito.never()).onError(any(Throwable.class)); verify(observer, times(1)).onCompleted(); } - - + + @Test public void testBackpressure() { TestSubscriber ts = new TestSubscriber(); @@ -195,7 +196,7 @@ public void testBackpressure() { .onExceptionResumeNext(Observable.just(1)) .observeOn(Schedulers.computation()) .map(new Func1() { - int c = 0; + int c; @Override public Integer call(Integer t1) { @@ -220,7 +221,7 @@ public Integer call(Integer t1) { private static class TestObservable implements Observable.OnSubscribe { final String[] values; - Thread t = null; + Thread t; public TestObservable(String... values) { this.values = values; @@ -236,14 +237,15 @@ public void run() { try { System.out.println("running TestObservable thread"); for (String s : values) { - if ("EXCEPTION".equals(s)) + if ("EXCEPTION".equals(s)) { throw new Exception("Forced Exception"); - else if ("RUNTIMEEXCEPTION".equals(s)) + } else if ("RUNTIMEEXCEPTION".equals(s)) { throw new RuntimeException("Forced RuntimeException"); - else if ("ON_OVERFLOW_ERROR".equals(s)) + } else if ("ON_OVERFLOW_ERROR".equals(s)) { throw new Error("Forced Error"); - else if ("THROWABLE".equals(s)) + } else if ("THROWABLE".equals(s)) { throw new Throwable("Forced Throwable"); + } System.out.println("TestObservable onNext: " + s); observer.onNext(s); } @@ -261,17 +263,17 @@ else if ("THROWABLE".equals(s)) System.out.println("done starting TestObservable thread"); } } - + @Test public void normalBackpressure() { TestSubscriber ts = TestSubscriber.create(0); - + PublishSubject ps = PublishSubject.create(); - + ps.onExceptionResumeNext(Observable.range(3, 2)).subscribe(ts); - + ts.requestMore(2); - + ps.onNext(1); ps.onNext(2); ps.onError(new TestException("Forced failure")); @@ -281,7 +283,7 @@ public void normalBackpressure() { ts.assertNotCompleted(); ts.requestMore(2); - + ts.assertValues(1, 2, 3, 4); ts.assertNoErrors(); ts.assertCompleted(); diff --git a/src/test/java/rx/internal/operators/OperatorPublishFunctionTest.java b/src/test/java/rx/internal/operators/OperatorPublishFunctionTest.java index 0221c921ac..fca83aee19 100644 --- a/src/test/java/rx/internal/operators/OperatorPublishFunctionTest.java +++ b/src/test/java/rx/internal/operators/OperatorPublishFunctionTest.java @@ -1,12 +1,12 @@ /** * Copyright 2016 Netflix, Inc. - * + * * 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. @@ -33,14 +33,14 @@ public class OperatorPublishFunctionTest { @Test public void concatTakeFirstLastCompletes() { TestSubscriber ts = new TestSubscriber(); - + Observable.range(1, 3).publish(new Func1, Observable>() { @Override public Observable call(Observable o) { return Observable.concat(o.take(5), o.takeLast(5)); } }).subscribe(ts); - + ts.assertValues(1, 2, 3); ts.assertNoErrors(); ts.assertCompleted(); @@ -49,24 +49,24 @@ public Observable call(Observable o) { @Test public void concatTakeFirstLastBackpressureCompletes() { TestSubscriber ts = TestSubscriber.create(0L); - + Observable.range(1, 6).publish(new Func1, Observable>() { @Override public Observable call(Observable o) { return Observable.concat(o.take(5), o.takeLast(5)); } }).subscribe(ts); - + ts.assertNoValues(); ts.assertNoErrors(); ts.assertNotCompleted(); - + ts.requestMore(5); - + ts.assertValues(1, 2, 3, 4, 5); ts.assertNoErrors(); ts.assertNotCompleted(); - + ts.requestMore(5); ts.assertValues(1, 2, 3, 4, 5, 6); @@ -79,26 +79,26 @@ public void canBeCancelled() { TestSubscriber ts = TestSubscriber.create(); PublishSubject ps = PublishSubject.create(); - + ps.publish(new Func1, Observable>() { @Override public Observable call(Observable o) { return Observable.concat(o.take(5), o.takeLast(5)); } }).subscribe(ts); - + ps.onNext(1); ps.onNext(2); - + ts.assertValues(1, 2); ts.assertNoErrors(); ts.assertNotCompleted(); - + ts.unsubscribe(); - + Assert.assertFalse("Source has subscribers?", ps.hasObservers()); } - + @Test public void invalidPrefetch() { try { @@ -108,35 +108,35 @@ public void invalidPrefetch() { Assert.assertEquals("prefetch > 0 required but it was -99", ex.getMessage()); } } - + @Test public void takeCompletes() { TestSubscriber ts = TestSubscriber.create(); PublishSubject ps = PublishSubject.create(); - + ps.publish(new Func1, Observable>() { @Override public Observable call(Observable o) { return o.take(1); } }).subscribe(ts); - + ps.onNext(1); - + ts.assertValues(1); ts.assertNoErrors(); ts.assertCompleted(); - + Assert.assertFalse("Source has subscribers?", ps.hasObservers()); - + } - + @Test public void oneStartOnly() { - + final AtomicInteger startCount = new AtomicInteger(); - + TestSubscriber ts = new TestSubscriber() { @Override public void onStart() { @@ -145,36 +145,36 @@ public void onStart() { }; PublishSubject ps = PublishSubject.create(); - + ps.publish(new Func1, Observable>() { @Override public Observable call(Observable o) { return o.take(1); } }).subscribe(ts); - + Assert.assertEquals(1, startCount.get()); } - + @Test public void takeCompletesUnsafe() { TestSubscriber ts = TestSubscriber.create(); PublishSubject ps = PublishSubject.create(); - + ps.publish(new Func1, Observable>() { @Override public Observable call(Observable o) { return o.take(1); } }).unsafeSubscribe(ts); - + ps.onNext(1); - + ts.assertValues(1); ts.assertNoErrors(); ts.assertCompleted(); - + Assert.assertFalse("Source has subscribers?", ps.hasObservers()); } @@ -183,73 +183,73 @@ public void directCompletesUnsafe() { TestSubscriber ts = TestSubscriber.create(); PublishSubject ps = PublishSubject.create(); - + ps.publish(new Func1, Observable>() { @Override public Observable call(Observable o) { return o; } }).unsafeSubscribe(ts); - + ps.onNext(1); ps.onCompleted(); - + ts.assertValues(1); ts.assertNoErrors(); ts.assertCompleted(); - + Assert.assertFalse("Source has subscribers?", ps.hasObservers()); } - + @Test public void overflowMissingBackpressureException() { TestSubscriber ts = TestSubscriber.create(0); PublishSubject ps = PublishSubject.create(); - + ps.publish(new Func1, Observable>() { @Override public Observable call(Observable o) { return o; } }).unsafeSubscribe(ts); - + for (int i = 0; i < RxRingBuffer.SIZE * 2; i++) { ps.onNext(i); } - + ts.assertNoValues(); ts.assertError(MissingBackpressureException.class); ts.assertNotCompleted(); - - Assert.assertEquals("Queue full?!", ts.getOnErrorEvents().get(0).getMessage()); + + Assert.assertEquals("PublishSubject: could not emit value due to lack of requests", ts.getOnErrorEvents().get(0).getMessage()); Assert.assertFalse("Source has subscribers?", ps.hasObservers()); } - + @Test public void overflowMissingBackpressureExceptionDelayed() { TestSubscriber ts = TestSubscriber.create(0); PublishSubject ps = PublishSubject.create(); - + OperatorPublish.create(ps, new Func1, Observable>() { @Override public Observable call(Observable o) { return o; } }, true).unsafeSubscribe(ts); - + for (int i = 0; i < RxRingBuffer.SIZE * 2; i++) { ps.onNext(i); } - + ts.requestMore(RxRingBuffer.SIZE); - + ts.assertValueCount(RxRingBuffer.SIZE); ts.assertError(MissingBackpressureException.class); ts.assertNotCompleted(); - - Assert.assertEquals("Queue full?!", ts.getOnErrorEvents().get(0).getMessage()); + + Assert.assertEquals("PublishSubject: could not emit value due to lack of requests", ts.getOnErrorEvents().get(0).getMessage()); Assert.assertFalse("Source has subscribers?", ps.hasObservers()); } } diff --git a/src/test/java/rx/internal/operators/OperatorPublishTest.java b/src/test/java/rx/internal/operators/OperatorPublishTest.java index 0ba6e757bd..9855143447 100644 --- a/src/test/java/rx/internal/operators/OperatorPublishTest.java +++ b/src/test/java/rx/internal/operators/OperatorPublishTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -37,7 +37,7 @@ public class OperatorPublishTest { @Test public void testPublish() throws InterruptedException { final AtomicInteger counter = new AtomicInteger(); - ConnectableObservable o = Observable.create(new OnSubscribe() { + ConnectableObservable o = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(final Subscriber observer) { @@ -98,7 +98,7 @@ public void call() { }); Observable slow = is.observeOn(Schedulers.computation()).map(new Func1() { - int c = 0; + int c; @Override public Integer call(Integer i) { @@ -200,7 +200,7 @@ public void call() { } }).share(); - + final AtomicBoolean child1Unsubscribed = new AtomicBoolean(); final AtomicBoolean child2Unsubscribed = new AtomicBoolean(); @@ -220,7 +220,7 @@ public void call() { super.onNext(t); } }; - + source.doOnUnsubscribe(new Action0() { @Override public void call() { @@ -228,20 +228,20 @@ public void call() { } }).take(5) .subscribe(ts1); - + ts1.awaitTerminalEvent(); ts2.awaitTerminalEvent(); - + ts1.assertNoErrors(); ts2.assertNoErrors(); - + assertTrue(sourceUnsubscribed.get()); assertTrue(child1Unsubscribed.get()); assertTrue(child2Unsubscribed.get()); - + ts1.assertReceivedOnNext(Arrays.asList(1, 2, 3, 4, 5)); ts2.assertReceivedOnNext(Arrays.asList(4, 5, 6, 7, 8)); - + assertEquals(8, sourceEmission.get()); } @@ -260,7 +260,7 @@ public void testConnectWithNoSubscriber() { subscriber.assertNoErrors(); subscriber.assertTerminalEvent(); } - + @Test public void testSubscribeAfterDisconnectThenConnect() { ConnectableObservable source = Observable.just(1).publish(); @@ -288,7 +288,7 @@ public void testSubscribeAfterDisconnectThenConnect() { System.out.println(s); System.out.println(s2); } - + @Test public void testNoSubscriberRetentionOnCompleted() { OperatorPublish source = (OperatorPublish)Observable.just(1).publish(); @@ -300,7 +300,7 @@ public void testNoSubscriberRetentionOnCompleted() { ts1.assertReceivedOnNext(Arrays.asList()); ts1.assertNoErrors(); assertEquals(0, ts1.getCompletions()); - + source.connect(); ts1.assertReceivedOnNext(Arrays.asList(1)); @@ -309,58 +309,58 @@ public void testNoSubscriberRetentionOnCompleted() { assertNull(source.current.get()); } - + @Test public void testNonNullConnection() { ConnectableObservable source = Observable.never().publish(); - + assertNotNull(source.connect()); assertNotNull(source.connect()); } - + @Test public void testNoDisconnectSomeoneElse() { ConnectableObservable source = Observable.never().publish(); Subscription s1 = source.connect(); Subscription s2 = source.connect(); - + s1.unsubscribe(); - + Subscription s3 = source.connect(); - + s2.unsubscribe(); - + assertTrue(s1.isUnsubscribed()); assertTrue(s2.isUnsubscribed()); assertFalse(s3.isUnsubscribed()); } - + @Test public void testZeroRequested() { ConnectableObservable source = Observable.just(1).publish(); - + TestSubscriber ts = new TestSubscriber() { @Override public void onStart() { request(0); } }; - + source.subscribe(ts); - + ts.assertReceivedOnNext(Arrays.asList()); ts.assertNoErrors(); assertEquals(0, ts.getCompletions()); - + source.connect(); ts.assertReceivedOnNext(Arrays.asList()); ts.assertNoErrors(); assertEquals(0, ts.getCompletions()); - + ts.requestMore(5); - + ts.assertReceivedOnNext(Arrays.asList(1)); ts.assertNoErrors(); ts.assertTerminalEvent(); @@ -368,24 +368,24 @@ public void onStart() { @Test public void testConnectIsIdempotent() { final AtomicInteger calls = new AtomicInteger(); - Observable source = Observable.create(new OnSubscribe() { + Observable source = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber t) { calls.getAndIncrement(); } }); - + ConnectableObservable conn = source.publish(); assertEquals(0, calls.get()); conn.connect(); conn.connect(); - + assertEquals(1, calls.get()); - + conn.connect().unsubscribe(); - + conn.connect(); conn.connect(); @@ -403,9 +403,9 @@ public void testObserveOn() { tss.add(ts); obs.subscribe(ts); } - + Subscription s = co.connect(); - + for (TestSubscriber ts : tss) { ts.awaitTerminalEvent(2, TimeUnit.SECONDS); ts.assertTerminalEvent(); diff --git a/src/test/java/rx/internal/operators/OperatorReduceTest.java b/src/test/java/rx/internal/operators/OperatorReduceTest.java deleted file mode 100644 index c550c835ea..0000000000 --- a/src/test/java/rx/internal/operators/OperatorReduceTest.java +++ /dev/null @@ -1,138 +0,0 @@ -/** - * Copyright 2014 Netflix, Inc. - * - * 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 rx.internal.operators; - -import static org.junit.Assert.assertEquals; -import static org.mockito.Matchers.any; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - -import org.junit.Before; -import org.junit.Test; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import rx.Observable; -import rx.Observer; -import rx.exceptions.TestException; -import rx.functions.Func1; -import rx.functions.Func2; -import rx.internal.util.UtilityFunctions; - -public class OperatorReduceTest { - @Mock - Observer observer; - - @Before - public void before() { - MockitoAnnotations.initMocks(this); - } - - Func2 sum = new Func2() { - @Override - public Integer call(Integer t1, Integer t2) { - return t1 + t2; - } - }; - - @Test - public void testAggregateAsIntSum() { - - Observable result = Observable.just(1, 2, 3, 4, 5).reduce(0, sum).map(UtilityFunctions. identity()); - - result.subscribe(observer); - - verify(observer).onNext(1 + 2 + 3 + 4 + 5); - verify(observer).onCompleted(); - verify(observer, never()).onError(any(Throwable.class)); - } - - @Test - public void testAggregateAsIntSumSourceThrows() { - Observable result = Observable.concat(Observable.just(1, 2, 3, 4, 5), - Observable. error(new TestException())) - .reduce(0, sum).map(UtilityFunctions. identity()); - - result.subscribe(observer); - - verify(observer, never()).onNext(any()); - verify(observer, never()).onCompleted(); - verify(observer, times(1)).onError(any(TestException.class)); - } - - @Test - public void testAggregateAsIntSumAccumulatorThrows() { - Func2 sumErr = new Func2() { - @Override - public Integer call(Integer t1, Integer t2) { - throw new TestException(); - } - }; - - Observable result = Observable.just(1, 2, 3, 4, 5) - .reduce(0, sumErr).map(UtilityFunctions. identity()); - - result.subscribe(observer); - - verify(observer, never()).onNext(any()); - verify(observer, never()).onCompleted(); - verify(observer, times(1)).onError(any(TestException.class)); - } - - @Test - public void testAggregateAsIntSumResultSelectorThrows() { - - Func1 error = new Func1() { - - @Override - public Integer call(Integer t1) { - throw new TestException(); - } - }; - - Observable result = Observable.just(1, 2, 3, 4, 5) - .reduce(0, sum).map(error); - - result.subscribe(observer); - - verify(observer, never()).onNext(any()); - verify(observer, never()).onCompleted(); - verify(observer, times(1)).onError(any(TestException.class)); - } - - @Test - public void testBackpressureWithNoInitialValue() throws InterruptedException { - Observable source = Observable.just(1, 2, 3, 4, 5, 6); - Observable reduced = source.reduce(sum); - - Integer r = reduced.toBlocking().first(); - assertEquals(21, r.intValue()); - } - - @Test - public void testBackpressureWithInitialValue() throws InterruptedException { - Observable source = Observable.just(1, 2, 3, 4, 5, 6); - Observable reduced = source.reduce(0, sum); - - Integer r = reduced.toBlocking().first(); - assertEquals(21, r.intValue()); - } - - - -} diff --git a/src/test/java/rx/internal/operators/OperatorRepeatTest.java b/src/test/java/rx/internal/operators/OperatorRepeatTest.java index 44371867c5..1a37f23c05 100644 --- a/src/test/java/rx/internal/operators/OperatorRepeatTest.java +++ b/src/test/java/rx/internal/operators/OperatorRepeatTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -15,8 +15,7 @@ */ package rx.internal.operators; -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; +import static org.junit.Assert.*; import static org.mockito.Matchers.any; import static org.mockito.Mockito.*; @@ -41,7 +40,7 @@ public class OperatorRepeatTest { public void testRepetition() { int NUM = 10; final AtomicInteger count = new AtomicInteger(); - int value = Observable.create(new OnSubscribe() { + int value = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(final Subscriber o) { @@ -69,7 +68,7 @@ public void testNoStackOverFlow() { public void testRepeatTakeWithSubscribeOn() throws InterruptedException { final AtomicInteger counter = new AtomicInteger(); - Observable oi = Observable.create(new OnSubscribe() { + Observable oi = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber sub) { @@ -102,9 +101,9 @@ public Integer call(Integer t1) { public void testRepeatAndTake() { @SuppressWarnings("unchecked") Observer o = mock(Observer.class); - + Observable.just(1).repeat().take(10).subscribe(o); - + verify(o, times(10)).onNext(1); verify(o).onCompleted(); verify(o, never()).onError(any(Throwable.class)); @@ -114,9 +113,9 @@ public void testRepeatAndTake() { public void testRepeatLimited() { @SuppressWarnings("unchecked") Observer o = mock(Observer.class); - + Observable.just(1).repeat(10).subscribe(o); - + verify(o, times(10)).onNext(1); verify(o).onCompleted(); verify(o, never()).onError(any(Throwable.class)); @@ -126,22 +125,22 @@ public void testRepeatLimited() { public void testRepeatError() { @SuppressWarnings("unchecked") Observer o = mock(Observer.class); - + Observable.error(new TestException()).repeat(10).subscribe(o); - + verify(o).onError(any(TestException.class)); verify(o, never()).onNext(any()); verify(o, never()).onCompleted(); - + } @Test(timeout = 2000) public void testRepeatZero() { @SuppressWarnings("unchecked") Observer o = mock(Observer.class); - + Observable.just(1).repeat(0).subscribe(o); - + verify(o).onCompleted(); verify(o, never()).onNext(any()); verify(o, never()).onError(any(Throwable.class)); @@ -151,14 +150,14 @@ public void testRepeatZero() { public void testRepeatOne() { @SuppressWarnings("unchecked") Observer o = mock(Observer.class); - + Observable.just(1).repeat(1).subscribe(o); - + verify(o).onCompleted(); verify(o, times(1)).onNext(any()); verify(o, never()).onError(any(Throwable.class)); } - + /** Issue #2587. */ @Test public void testRepeatAndDistinctUnbounded() { @@ -166,11 +165,11 @@ public void testRepeatAndDistinctUnbounded() { .take(3) .repeat(3) .distinct(); - + TestSubscriber ts = new TestSubscriber(); - + src.subscribe(ts); - + ts.assertNoErrors(); ts.assertTerminalEvent(); ts.assertReceivedOnNext(Arrays.asList(1, 2, 3)); @@ -196,7 +195,55 @@ public Observable call(Integer x) { ts.awaitTerminalEvent(); ts.assertNoErrors(); ts.assertReceivedOnNext(Collections.emptyList()); - + assertEquals(Arrays.asList(1, 2, 1, 2, 1, 2, 1, 2, 1, 2), concatBase); } + + @Test + public void repeatScheduled() { + + TestSubscriber ts = TestSubscriber.create(); + + Observable.just(1).repeat(5, Schedulers.computation()).subscribe(ts); + + ts.awaitTerminalEventAndUnsubscribeOnTimeout(5, TimeUnit.SECONDS); + ts.assertValues(1, 1, 1, 1, 1); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Test + public void repeatWhenDefaultScheduler() { + TestSubscriber ts = TestSubscriber.create(); + + Observable.just(1).repeatWhen((Func1)new Func1() { + @Override + public Observable call(Observable o) { + return o.take(2); + } + }).subscribe(ts); + + ts.assertValues(1, 1); + ts.assertNoErrors(); + ts.assertCompleted(); + + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Test + public void repeatWhenTrampolineScheduler() { + TestSubscriber ts = TestSubscriber.create(); + + Observable.just(1).repeatWhen((Func1)new Func1() { + @Override + public Observable call(Observable o) { + return o.take(2); + } + }, Schedulers.trampoline()).subscribe(ts); + + ts.assertValues(1, 1); + ts.assertNoErrors(); + ts.assertCompleted(); + } } diff --git a/src/test/java/rx/internal/operators/OperatorReplayTest.java b/src/test/java/rx/internal/operators/OperatorReplayTest.java index fb741991d7..fbe256ef01 100644 --- a/src/test/java/rx/internal/operators/OperatorReplayTest.java +++ b/src/test/java/rx/internal/operators/OperatorReplayTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -16,41 +16,29 @@ package rx.internal.operators; import static org.junit.Assert.*; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.notNull; +import static org.mockito.Matchers.*; import static org.mockito.Mockito.*; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicLong; +import java.lang.management.*; +import java.util.*; +import java.util.concurrent.*; +import java.util.concurrent.atomic.*; -import org.junit.Assert; -import org.junit.Test; +import org.junit.*; import org.mockito.InOrder; +import rx.*; import rx.Observable; import rx.Observable.OnSubscribe; import rx.Observer; -import rx.Scheduler; import rx.Scheduler.Worker; -import rx.Subscriber; -import rx.Subscription; import rx.exceptions.TestException; -import rx.functions.Action0; -import rx.functions.Action1; -import rx.functions.Func1; -import rx.internal.operators.OperatorReplay.BoundedReplayBuffer; -import rx.internal.operators.OperatorReplay.Node; -import rx.internal.operators.OperatorReplay.SizeAndTimeBoundReplayBuffer; +import rx.functions.*; +import rx.internal.operators.OperatorReplay.*; import rx.internal.util.PlatformDependent; import rx.observables.ConnectableObservable; import rx.observers.TestSubscriber; -import rx.schedulers.Schedulers; -import rx.schedulers.TestScheduler; +import rx.schedulers.*; import rx.subjects.PublishSubject; public class OperatorReplayTest { @@ -499,7 +487,7 @@ public void call(Integer v) { System.out.println("Sideeffect #" + v); } }); - + Observable result = source.replay( new Func1, Observable>() { @Override @@ -507,7 +495,7 @@ public Observable call(Observable o) { return o.take(2); } }); - + for (int i = 1; i < 3; i++) { effectCounter.set(0); System.out.printf("- %d -%n", i); @@ -517,14 +505,14 @@ public Observable call(Observable o) { public void call(Integer t1) { System.out.println(t1); } - + }, new Action1() { @Override public void call(Throwable t1) { t1.printStackTrace(); } - }, + }, new Action0() { @Override public void call() { @@ -759,16 +747,16 @@ public void testBoundedReplayBuffer() { buf.addLast(new Node(3, 2)); buf.addLast(new Node(4, 3)); buf.addLast(new Node(5, 4)); - + List values = new ArrayList(); buf.collect(values); - + Assert.assertEquals(Arrays.asList(1, 2, 3, 4, 5), values); - + buf.removeSome(2); buf.removeFirst(); buf.removeSome(2); - + values.clear(); buf.collect(values); Assert.assertTrue(values.isEmpty()); @@ -776,17 +764,17 @@ public void testBoundedReplayBuffer() { buf.addLast(new Node(5, 5)); buf.addLast(new Node(6, 6)); buf.collect(values); - + Assert.assertEquals(Arrays.asList(5, 6), values); - + } - + @Test public void testTimedAndSizedTruncation() { TestScheduler test = Schedulers.test(); SizeAndTimeBoundReplayBuffer buf = new SizeAndTimeBoundReplayBuffer(2, 2000, test); List values = new ArrayList(); - + buf.next(1); test.advanceTimeBy(1, TimeUnit.SECONDS); buf.next(2); @@ -805,25 +793,25 @@ public void testTimedAndSizedTruncation() { values.clear(); buf.collect(values); Assert.assertEquals(Arrays.asList(3, 4), values); - + test.advanceTimeBy(2, TimeUnit.SECONDS); buf.next(5); - + values.clear(); buf.collect(values); Assert.assertEquals(Arrays.asList(5), values); - + test.advanceTimeBy(2, TimeUnit.SECONDS); buf.complete(); - + values.clear(); buf.collect(values); Assert.assertTrue(values.isEmpty()); - + Assert.assertEquals(1, buf.size); Assert.assertTrue(buf.hasCompleted()); } - + @Test public void testBackpressure() { final AtomicLong requested = new AtomicLong(); @@ -835,26 +823,26 @@ public void call(Long t) { } }); ConnectableObservable co = source.replay(); - + TestSubscriber ts1 = TestSubscriber.create(10); TestSubscriber ts2 = TestSubscriber.create(90); - + co.subscribe(ts1); co.subscribe(ts2); - + ts2.requestMore(10); - + co.connect(); - + ts1.assertValueCount(10); ts1.assertNoTerminalEvent(); - + ts2.assertValueCount(100); ts2.assertNoTerminalEvent(); - + Assert.assertEquals(100, requested.get()); } - + @Test public void testBackpressureBounded() { final AtomicLong requested = new AtomicLong(); @@ -866,32 +854,32 @@ public void call(Long t) { } }); ConnectableObservable co = source.replay(50); - + TestSubscriber ts1 = TestSubscriber.create(10); TestSubscriber ts2 = TestSubscriber.create(90); - + co.subscribe(ts1); co.subscribe(ts2); - + ts2.requestMore(10); - + co.connect(); - + ts1.assertValueCount(10); ts1.assertNoTerminalEvent(); - + ts2.assertValueCount(100); ts2.assertNoTerminalEvent(); - + Assert.assertEquals(100, requested.get()); } - + @Test public void testColdReplayNoBackpressure() { Observable source = Observable.range(0, 1000).replay().autoConnect(); - + TestSubscriber ts = new TestSubscriber(); - + source.subscribe(ts); ts.assertNoErrors(); @@ -906,10 +894,10 @@ public void testColdReplayNoBackpressure() { @Test public void testColdReplayBackpressure() { Observable source = Observable.range(0, 1000).replay().autoConnect(); - + TestSubscriber ts = new TestSubscriber(); ts.requestMore(10); - + source.subscribe(ts); ts.assertNoErrors(); @@ -920,14 +908,14 @@ public void testColdReplayBackpressure() { for (int i = 0; i < 10; i++) { assertEquals((Integer)i, onNextEvents.get(i)); } - + ts.unsubscribe(); } - + @Test public void testCache() throws InterruptedException { final AtomicInteger counter = new AtomicInteger(); - Observable o = Observable.create(new Observable.OnSubscribe() { + Observable o = Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(final Subscriber observer) { @@ -984,38 +972,38 @@ public void testUnsubscribeSource() { o.subscribe(); verify(unsubscribe, times(1)).call(); } - + @Test public void testTake() { TestSubscriber ts = new TestSubscriber(); Observable cached = Observable.range(1, 100).replay().autoConnect(); cached.take(10).subscribe(ts); - + ts.assertNoErrors(); ts.assertTerminalEvent(); ts.assertReceivedOnNext(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)); ts.assertUnsubscribed(); } - + @Test public void testAsync() { Observable source = Observable.range(1, 10000); for (int i = 0; i < 100; i++) { TestSubscriber ts1 = new TestSubscriber(); - + Observable cached = source.replay().autoConnect(); - + cached.observeOn(Schedulers.computation()).subscribe(ts1); - + ts1.awaitTerminalEvent(2, TimeUnit.SECONDS); ts1.assertNoErrors(); ts1.assertTerminalEvent(); assertEquals(10000, ts1.getOnNextEvents().size()); - + TestSubscriber ts2 = new TestSubscriber(); cached.observeOn(Schedulers.computation()).subscribe(ts2); - + ts2.awaitTerminalEvent(2, TimeUnit.SECONDS); ts2.assertNoErrors(); ts2.assertTerminalEvent(); @@ -1028,9 +1016,9 @@ public void testAsyncComeAndGo() { .take(1000) .subscribeOn(Schedulers.io()); Observable cached = source.replay().autoConnect(); - + Observable output = cached.observeOn(Schedulers.computation()); - + List> list = new ArrayList>(100); for (int i = 0; i < 100; i++) { TestSubscriber ts = new TestSubscriber(); @@ -1047,17 +1035,17 @@ public void testAsyncComeAndGo() { ts.awaitTerminalEvent(3, TimeUnit.SECONDS); ts.assertNoErrors(); ts.assertTerminalEvent(); - + for (int i = j * 10; i < j * 10 + 10; i++) { expected.set(i - j * 10, (long)i); } - + ts.assertReceivedOnNext(expected); - + j++; } } - + @Test public void testNoMissingBackpressureException() { final int m; @@ -1066,8 +1054,8 @@ public void testNoMissingBackpressureException() { } else { m = 4 * 1000 * 1000; } - - Observable firehose = Observable.create(new OnSubscribe() { + + Observable firehose = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber t) { for (int i = 0; i < m; i++) { @@ -1076,43 +1064,43 @@ public void call(Subscriber t) { t.onCompleted(); } }); - + TestSubscriber ts = new TestSubscriber(); firehose.replay().autoConnect().observeOn(Schedulers.computation()).takeLast(100).subscribe(ts); - + ts.awaitTerminalEvent(3, TimeUnit.SECONDS); ts.assertNoErrors(); ts.assertTerminalEvent(); - + assertEquals(100, ts.getOnNextEvents().size()); } - + @Test public void testValuesAndThenError() { Observable source = Observable.range(1, 10) .concatWith(Observable.error(new TestException())) .replay().autoConnect(); - - + + TestSubscriber ts = new TestSubscriber(); source.subscribe(ts); - + ts.assertReceivedOnNext(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)); Assert.assertEquals(0, ts.getCompletions()); Assert.assertEquals(1, ts.getOnErrorEvents().size()); - + TestSubscriber ts2 = new TestSubscriber(); source.subscribe(ts2); - + ts2.assertReceivedOnNext(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)); Assert.assertEquals(0, ts2.getCompletions()); Assert.assertEquals(1, ts2.getOnErrorEvents().size()); } - + @Test public void unsafeChildThrows() { final AtomicInteger count = new AtomicInteger(); - + Observable source = Observable.range(1, 100) .doOnNext(new Action1() { @Override @@ -1121,23 +1109,23 @@ public void call(Integer t) { } }) .replay().autoConnect(); - + TestSubscriber ts = new TestSubscriber() { @Override public void onNext(Integer t) { throw new TestException(); } }; - + source.unsafeSubscribe(ts); - + Assert.assertEquals(100, count.get()); ts.assertNoValues(); ts.assertNotCompleted(); ts.assertError(TestException.class); } - + @Test public void unboundedLeavesEarly() { PublishSubject source = PublishSubject.create(); @@ -1151,150 +1139,458 @@ public void call(Long t) { requests.add(t); } }).replay().autoConnect(); - + TestSubscriber ts1 = TestSubscriber.create(5); TestSubscriber ts2 = TestSubscriber.create(10); - + out.subscribe(ts1); out.subscribe(ts2); ts2.unsubscribe(); - + Assert.assertEquals(Arrays.asList(5L, 5L), requests); } - + @Test public void testSubscribersComeAndGoAtRequestBoundaries() { ConnectableObservable source = Observable.range(1, 10).replay(1); source.connect(); - + TestSubscriber ts1 = TestSubscriber.create(2); - + source.subscribe(ts1); - + ts1.assertValues(1, 2); ts1.assertNoErrors(); ts1.unsubscribe(); - + TestSubscriber ts2 = TestSubscriber.create(2); - + source.subscribe(ts2); - + ts2.assertValues(2, 3); ts2.assertNoErrors(); ts2.unsubscribe(); TestSubscriber ts21 = TestSubscriber.create(1); - + source.subscribe(ts21); - + ts21.assertValues(3); ts21.assertNoErrors(); ts21.unsubscribe(); TestSubscriber ts22 = TestSubscriber.create(1); - + source.subscribe(ts22); - + ts22.assertValues(3); ts22.assertNoErrors(); ts22.unsubscribe(); - + TestSubscriber ts3 = TestSubscriber.create(); - + source.subscribe(ts3); - + ts3.assertNoErrors(); System.out.println(ts3.getOnNextEvents()); ts3.assertValues(3, 4, 5, 6, 7, 8, 9, 10); ts3.assertCompleted(); } - + @Test public void testSubscribersComeAndGoAtRequestBoundaries2() { ConnectableObservable source = Observable.range(1, 10).replay(2); source.connect(); - + TestSubscriber ts1 = TestSubscriber.create(2); - + source.subscribe(ts1); - + ts1.assertValues(1, 2); ts1.assertNoErrors(); ts1.unsubscribe(); TestSubscriber ts11 = TestSubscriber.create(2); - + source.subscribe(ts11); - + ts11.assertValues(1, 2); ts11.assertNoErrors(); ts11.unsubscribe(); TestSubscriber ts2 = TestSubscriber.create(3); - + source.subscribe(ts2); - + ts2.assertValues(1, 2, 3); ts2.assertNoErrors(); ts2.unsubscribe(); TestSubscriber ts21 = TestSubscriber.create(1); - + source.subscribe(ts21); - + ts21.assertValues(2); ts21.assertNoErrors(); ts21.unsubscribe(); TestSubscriber ts22 = TestSubscriber.create(1); - + source.subscribe(ts22); - + ts22.assertValues(2); ts22.assertNoErrors(); ts22.unsubscribe(); - + TestSubscriber ts3 = TestSubscriber.create(); - + source.subscribe(ts3); - + ts3.assertNoErrors(); System.out.println(ts3.getOnNextEvents()); ts3.assertValues(2, 3, 4, 5, 6, 7, 8, 9, 10); ts3.assertCompleted(); } - + @Test public void dontReplayOldValues() { - + PublishSubject ps = PublishSubject.create(); - + TestScheduler scheduler = new TestScheduler(); - + ConnectableObservable co = ps.replay(1, TimeUnit.SECONDS, scheduler); - + co.subscribe(); // make sure replay runs in unbounded mode - + co.connect(); - + ps.onNext(1); - + scheduler.advanceTimeBy(1, TimeUnit.SECONDS); - + ps.onNext(2); scheduler.advanceTimeBy(500, TimeUnit.MILLISECONDS); - + ps.onNext(3); scheduler.advanceTimeBy(500, TimeUnit.MILLISECONDS); - + TestSubscriber ts = TestSubscriber.create(); - + co.subscribe(ts); - + ts.assertValue(3); } + + @Test + public void invalidBufferSize() { + try { + Observable.just(1).replay(-1, 1, TimeUnit.MILLISECONDS); + } catch (IllegalArgumentException ex) { + assertEquals("bufferSize < 0", ex.getMessage()); + } + } + + @Test + public void bufferScheduled() { + + TestScheduler test = new TestScheduler(); + + + ConnectableObservable co = Observable.range(1, 5).replay(2, test); + + TestSubscriber ts1 = TestSubscriber.create(); + + co.subscribe(ts1); + + co.connect(); + + ts1.assertNoValues(); + ts1.assertNotCompleted(); + + test.triggerActions(); + + ts1.assertValues(1, 2, 3, 4, 5); + ts1.assertNoErrors(); + ts1.assertCompleted(); + + TestSubscriber ts2 = TestSubscriber.create(); + + co.subscribe(ts2); + + ts2.assertNoValues(); + ts2.assertNotCompleted(); + + test.triggerActions(); + + ts2.assertValues(4, 5); + ts2.assertNoErrors(); + ts2.assertCompleted(); + } + + @Test + public void allScheduled() { + + TestScheduler test = new TestScheduler(); + + + ConnectableObservable co = Observable.range(1, 5).replay(test); + + TestSubscriber ts1 = TestSubscriber.create(); + + co.subscribe(ts1); + + co.connect(); + + ts1.assertNoValues(); + ts1.assertNotCompleted(); + + test.triggerActions(); + + ts1.assertValues(1, 2, 3, 4, 5); + ts1.assertNoErrors(); + ts1.assertCompleted(); + + TestSubscriber ts2 = TestSubscriber.create(); + + co.subscribe(ts2); + + ts2.assertNoValues(); + ts2.assertNotCompleted(); + + test.triggerActions(); + + ts2.assertValues(1, 2, 3, 4, 5); + ts2.assertNoErrors(); + ts2.assertCompleted(); + } + + @Test + public void replayTimedDefaultScheduler() { + ConnectableObservable co = Observable.range(1, 5).replay(2, TimeUnit.SECONDS); + + TestSubscriber ts = TestSubscriber.create(); + + co.subscribe(ts); + + co.connect(); + + ts.awaitTerminalEventAndUnsubscribeOnTimeout(5, TimeUnit.SECONDS); + ts.assertValues(1, 2, 3, 4, 5); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @Test + public void bufferTimedSelectorScheduler() { + Observable co = Observable.range(1, 5) + .replay(new Func1, Observable>() { + @Override + public Observable call(Observable t) { + return t; + } + }, 2, 2, TimeUnit.SECONDS, Schedulers.computation()); + + TestSubscriber ts = TestSubscriber.create(); + + co.subscribe(ts); + + ts.awaitTerminalEventAndUnsubscribeOnTimeout(5, TimeUnit.SECONDS); + ts.assertValues(1, 2, 3, 4, 5); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @Test + public void bufferTimedSelectorSchedulerBadBuffer() { + try { + Observable.range(1, 5) + .replay(new Func1, Observable>() { + @Override + public Observable call(Observable t) { + return t; + } + }, -99, 2, TimeUnit.SECONDS, Schedulers.computation()); + } catch (IllegalArgumentException ex) { + assertEquals("bufferSize < 0", ex.getMessage()); + } + } + + @Test + public void selectorSizeTimeDefaultScheduler() { + Observable co = Observable.range(1, 5) + .replay(new Func1, Observable>() { + @Override + public Observable call(Observable t) { + return t; + } + }, 2, 2, TimeUnit.SECONDS); + + TestSubscriber ts = TestSubscriber.create(); + + co.subscribe(ts); + + ts.awaitTerminalEventAndUnsubscribeOnTimeout(5, TimeUnit.SECONDS); + ts.assertValues(1, 2, 3, 4, 5); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @Test + public void selectorSizeScheduler() { + Observable co = Observable.range(1, 5) + .replay(new Func1, Observable>() { + @Override + public Observable call(Observable t) { + return t; + } + }, 2, Schedulers.computation()); + + TestSubscriber ts = TestSubscriber.create(); + + co.subscribe(ts); + + ts.awaitTerminalEventAndUnsubscribeOnTimeout(5, TimeUnit.SECONDS); + ts.assertValues(1, 2, 3, 4, 5); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @Test + public void selectorScheduler() { + Observable co = Observable.range(1, 5) + .replay(new Func1, Observable>() { + @Override + public Observable call(Observable t) { + return t; + } + }, Schedulers.computation()); + + TestSubscriber ts = TestSubscriber.create(); + + co.subscribe(ts); + + ts.awaitTerminalEventAndUnsubscribeOnTimeout(5, TimeUnit.SECONDS); + ts.assertValues(1, 2, 3, 4, 5); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @Test + public void timeSizeDefaultScheduler() { + ConnectableObservable co = Observable.range(1, 5) + .replay(2, 2, TimeUnit.SECONDS); + + TestSubscriber ts = TestSubscriber.create(); + + co.subscribe(ts); + + co.connect(); + + ts.awaitTerminalEventAndUnsubscribeOnTimeout(5, TimeUnit.SECONDS); + ts.assertValues(1, 2, 3, 4, 5); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + void replayNoRetention(Func1, ConnectableObservable> replayOp) throws InterruptedException { + System.gc(); + + Thread.sleep(500); + + MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean(); + MemoryUsage memHeap = memoryMXBean.getHeapMemoryUsage(); + long initial = memHeap.getUsed(); + + System.out.printf("Starting: %.3f MB%n", initial / 1024.0 / 1024.0); + + PublishSubject ps = PublishSubject.create(); + + ConnectableObservable co = replayOp.call(ps); + + Subscription s = co.subscribe(new Action1() { + int[] array = new int[1024 * 1024 * 32]; + + @Override + public void call(Integer t) { + System.out.println(array.length); + } + }); + + co.connect(); + ps.onNext(1); + + memHeap = memoryMXBean.getHeapMemoryUsage(); + long middle = memHeap.getUsed(); + + System.out.printf("Starting: %.3f MB%n", middle / 1024.0 / 1024.0); + + s.unsubscribe(); + s = null; + + System.gc(); + + Thread.sleep(500); + + 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("Leak: %.3f -> %.3f -> %.3f", initial / 1024 / 1024.0, middle / 1024 / 1024.0, finish / 1024 / 1024d)); + } + + } + + @Test + public void replayNoRetentionUnbounded() throws Exception { + replayNoRetention(new Func1, ConnectableObservable>() { + @Override + public ConnectableObservable call(Observable o) { + return o.replay(); + } + }); + } + + @Test + public void replayNoRetentionSizeBound() throws Exception { + replayNoRetention(new Func1, ConnectableObservable>() { + @Override + public ConnectableObservable call(Observable o) { + return o.replay(1); + } + }); + } + + @Test + public void replayNoRetentionTimebound() throws Exception { + replayNoRetention(new Func1, ConnectableObservable>() { + @Override + public ConnectableObservable call(Observable o) { + return o.replay(1, TimeUnit.DAYS); + } + }); + } + + @Test + public void noOldEntries() { + TestScheduler scheduler = new TestScheduler(); + + Observable source = Observable.just(1) + .replay(2, TimeUnit.SECONDS, scheduler) + .autoConnect(); + + source.test().assertResult(1); + + source.test().assertResult(1); + + scheduler.advanceTimeBy(3, TimeUnit.SECONDS); + + source.test().assertResult(); + } } \ No newline at end of file diff --git a/src/test/java/rx/internal/operators/OperatorRetryTest.java b/src/test/java/rx/internal/operators/OperatorRetryTest.java index b461289130..d4cf161dc5 100644 --- a/src/test/java/rx/internal/operators/OperatorRetryTest.java +++ b/src/test/java/rx/internal/operators/OperatorRetryTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -27,9 +27,10 @@ import org.mockito.*; import rx.*; -import rx.Observable.OnSubscribe; import rx.Observable; +import rx.Observable.OnSubscribe; import rx.Observer; +import rx.exceptions.TestException; import rx.functions.*; import rx.internal.util.RxRingBuffer; import rx.observables.GroupedObservable; @@ -44,7 +45,7 @@ public class OperatorRetryTest { public void iterativeBackoff() { @SuppressWarnings("unchecked") Observer consumer = mock(Observer.class); - Observable producer = Observable.create(new OnSubscribe() { + Observable producer = Observable.unsafeCreate(new OnSubscribe() { private AtomicInteger count = new AtomicInteger(4); long last = System.currentTimeMillis(); @@ -56,11 +57,11 @@ public void call(Subscriber t1) { if (count.getAndDecrement() == 0) { t1.onNext("hello"); t1.onCompleted(); - } - else + } else { t1.onError(new RuntimeException()); + } } - + }); TestSubscriber ts = new TestSubscriber(consumer); producer.retryWhen(new Func1, Observable>() { @@ -74,7 +75,7 @@ public Observable call(Observable attempts) { public Tuple call(Throwable n) { return new Tuple(new Long(1), n); }}) - .scan(new Func2(){ + .scan(new Func2() { @Override public Tuple call(Tuple t, Tuple n) { return new Tuple(t.count + n.count, n.n); @@ -82,10 +83,10 @@ public Tuple call(Tuple t, Tuple n) { .flatMap(new Func1>() { @Override public Observable call(Tuple t) { - System.out.println("Retry # "+t.count); - return t.count > 20 ? + System.out.println("Retry # " + t.count); + return t.count > 20 ? Observable.error(t.n) : - Observable.timer(t.count *1L, TimeUnit.MILLISECONDS); + Observable.timer(t.count * 1L, TimeUnit.MILLISECONDS); }}); } }).subscribe(ts); @@ -115,7 +116,7 @@ public void testRetryIndefinitely() { @SuppressWarnings("unchecked") Observer observer = mock(Observer.class); int NUM_RETRIES = 20; - Observable origin = Observable.create(new FuncWithErrors(NUM_RETRIES)); + Observable origin = Observable.unsafeCreate(new FuncWithErrors(NUM_RETRIES)); origin.retry().unsafeSubscribe(new TestSubscriber(observer)); InOrder inOrder = inOrder(observer); @@ -135,7 +136,7 @@ public void testSchedulingNotificationHandler() { @SuppressWarnings("unchecked") Observer observer = mock(Observer.class); int NUM_RETRIES = 2; - Observable origin = Observable.create(new FuncWithErrors(NUM_RETRIES)); + Observable origin = Observable.unsafeCreate(new FuncWithErrors(NUM_RETRIES)); TestSubscriber subscriber = new TestSubscriber(observer); origin.retryWhen(new Func1, Observable>() { @Override @@ -167,7 +168,7 @@ public void testOnNextFromNotificationHandler() { @SuppressWarnings("unchecked") Observer observer = mock(Observer.class); int NUM_RETRIES = 2; - Observable origin = Observable.create(new FuncWithErrors(NUM_RETRIES)); + Observable origin = Observable.unsafeCreate(new FuncWithErrors(NUM_RETRIES)); origin.retryWhen(new Func1, Observable>() { @Override public Observable call(Observable t1) { @@ -197,7 +198,7 @@ public Void call(Throwable t1) { public void testOnCompletedFromNotificationHandler() { @SuppressWarnings("unchecked") Observer observer = mock(Observer.class); - Observable origin = Observable.create(new FuncWithErrors(1)); + Observable origin = Observable.unsafeCreate(new FuncWithErrors(1)); TestSubscriber subscriber = new TestSubscriber(observer); origin.retryWhen(new Func1, Observable>() { @Override @@ -218,7 +219,7 @@ public Observable call(Observable t1) { public void testOnErrorFromNotificationHandler() { @SuppressWarnings("unchecked") Observer observer = mock(Observer.class); - Observable origin = Observable.create(new FuncWithErrors(2)); + Observable origin = Observable.unsafeCreate(new FuncWithErrors(2)); origin.retryWhen(new Func1, Observable>() { @Override public Observable call(Observable t1) { @@ -246,7 +247,7 @@ public void call(Subscriber subscriber) { } }; - int first = Observable.create(onSubscribe) + int first = Observable.unsafeCreate(onSubscribe) .retryWhen(new Func1, Observable>() { @Override public Observable call(Observable attempt) { @@ -269,7 +270,7 @@ public Void call(Throwable o, Integer integer) { public void testOriginFails() { @SuppressWarnings("unchecked") Observer observer = mock(Observer.class); - Observable origin = Observable.create(new FuncWithErrors(1)); + Observable origin = Observable.unsafeCreate(new FuncWithErrors(1)); origin.subscribe(observer); InOrder inOrder = inOrder(observer); @@ -285,7 +286,7 @@ public void testRetryFail() { int NUM_FAILURES = 2; @SuppressWarnings("unchecked") Observer observer = mock(Observer.class); - Observable origin = Observable.create(new FuncWithErrors(NUM_FAILURES)); + Observable origin = Observable.unsafeCreate(new FuncWithErrors(NUM_FAILURES)); origin.retry(NUM_RETRIES).subscribe(observer); InOrder inOrder = inOrder(observer); @@ -304,7 +305,7 @@ public void testRetrySuccess() { int NUM_FAILURES = 1; @SuppressWarnings("unchecked") Observer observer = mock(Observer.class); - Observable origin = Observable.create(new FuncWithErrors(NUM_FAILURES)); + Observable origin = Observable.unsafeCreate(new FuncWithErrors(NUM_FAILURES)); origin.retry(3).subscribe(observer); InOrder inOrder = inOrder(observer); @@ -324,7 +325,7 @@ public void testInfiniteRetry() { int NUM_FAILURES = 20; @SuppressWarnings("unchecked") Observer observer = mock(Observer.class); - Observable origin = Observable.create(new FuncWithErrors(NUM_FAILURES)); + Observable origin = Observable.unsafeCreate(new FuncWithErrors(NUM_FAILURES)); origin.retry().subscribe(observer); InOrder inOrder = inOrder(observer); @@ -397,8 +398,8 @@ public void call(final Subscriber o) { final AtomicLong req = new AtomicLong(); // 0 = not set, 1 = fast path, 2 = backpressure final AtomicInteger path = new AtomicInteger(0); - volatile boolean done = false; - + volatile boolean done; + @Override public void request(long n) { if (n == Long.MAX_VALUE && path.compareAndSet(0, 1)) { @@ -462,7 +463,7 @@ public void testRetryAllowsSubscriptionAfterAllSubscriptionsUnsubscribed() throw public void call(Subscriber s) { subsCount.incrementAndGet(); s.add(new Subscription() { - boolean unsubscribed = false; + boolean unsubscribed; @Override public void unsubscribe() { @@ -477,7 +478,7 @@ public boolean isUnsubscribed() { }); } }; - Observable stream = Observable.create(onSubscribe); + Observable stream = Observable.unsafeCreate(onSubscribe); Observable streamWithRetry = stream.retry(); Subscription sub = streamWithRetry.subscribe(); assertEquals(1, subsCount.get()); @@ -511,7 +512,7 @@ public void call(Subscriber s) { } }; - Observable.create(onSubscribe).retry(3).subscribe(ts); + Observable.unsafeCreate(onSubscribe).retry(3).subscribe(ts); assertEquals(4, subsCount.get()); // 1 + 3 retries } @@ -529,7 +530,7 @@ public void call(Subscriber s) { } }; - Observable.create(onSubscribe).retry(1).subscribe(ts); + Observable.unsafeCreate(onSubscribe).retry(1).subscribe(ts); assertEquals(2, subsCount.get()); } @@ -547,7 +548,7 @@ public void call(Subscriber s) { } }; - Observable.create(onSubscribe).retry(0).subscribe(ts); + Observable.unsafeCreate(onSubscribe).retry(0).subscribe(ts); assertEquals(1, subsCount.get()); } @@ -650,7 +651,7 @@ public void testUnsubscribeAfterError() { // Observable that always fails after 100ms SlowObservable so = new SlowObservable(100, 0); - Observable o = Observable.create(so).retry(5); + Observable o = Observable.unsafeCreate(so).retry(5); AsyncObserver async = new AsyncObserver(observer); @@ -675,7 +676,7 @@ public void testTimeoutWithRetry() { // Observable that sends every 100ms (timeout fails instead) SlowObservable so = new SlowObservable(100, 10); - Observable o = Observable.create(so).timeout(80, TimeUnit.MILLISECONDS).retry(5); + Observable o = Observable.unsafeCreate(so).timeout(80, TimeUnit.MILLISECONDS).retry(5); AsyncObserver async = new AsyncObserver(observer); @@ -690,20 +691,20 @@ public void testTimeoutWithRetry() { assertEquals("Start 6 threads, retry 5 then fail on 6", 6, so.efforts.get()); } - + @Test//(timeout = 15000) public void testRetryWithBackpressure() throws InterruptedException { final int NUM_LOOPS = 1; - for (int j=0;j observer = mock(Observer.class); - Observable origin = Observable.create(new FuncWithErrors(NUM_RETRIES)); + Observable origin = Observable.unsafeCreate(new FuncWithErrors(NUM_RETRIES)); TestSubscriber ts = new TestSubscriber(observer); origin.retry().observeOn(Schedulers.computation()).unsafeSubscribe(ts); ts.awaitTerminalEvent(5, TimeUnit.SECONDS); - + InOrder inOrder = inOrder(observer); // should have no errors verify(observer, never()).onError(any(Throwable.class)); @@ -717,7 +718,7 @@ public void testRetryWithBackpressure() throws InterruptedException { } } } - + @Test//(timeout = 15000) public void testRetryWithBackpressureParallel() throws InterruptedException { final int NUM_LOOPS = 1; @@ -742,7 +743,7 @@ public void testRetryWithBackpressureParallel() throws InterruptedException { public void run() { final AtomicInteger nexts = new AtomicInteger(); try { - Observable origin = Observable.create(new FuncWithErrors(NUM_RETRIES)); + Observable origin = Observable.unsafeCreate(new FuncWithErrors(NUM_RETRIES)); TestSubscriber ts = new TestSubscriber(); origin.retry() .observeOn(Schedulers.computation()).unsafeSubscribe(ts); @@ -831,7 +832,7 @@ public String call(Integer t1) { return "msg: " + count.incrementAndGet(); } }); - + origin.retry() .groupBy(new Func1() { @Override @@ -846,7 +847,7 @@ public Observable call(GroupedObservable t1) { } }) .unsafeSubscribe(new TestSubscriber(observer)); - + InOrder inOrder = inOrder(observer); // should show 3 attempts inOrder.verify(observer, times(NUM_MSG)).onNext(any(java.lang.String.class)); @@ -865,17 +866,17 @@ public void testIssue1900SourceNotSupportingBackpressure() { final int NUM_MSG = 1034; final AtomicInteger count = new AtomicInteger(); - Observable origin = Observable.create(new Observable.OnSubscribe() { + Observable origin = Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber o) { - for(int i=0; i() { @@ -891,7 +892,7 @@ public Observable call(GroupedObservable t1) { } }) .unsafeSubscribe(new TestSubscriber(observer)); - + InOrder inOrder = inOrder(observer); // should show 3 attempts inOrder.verify(observer, times(NUM_MSG)).onNext(any(java.lang.String.class)); @@ -904,4 +905,42 @@ public Observable call(GroupedObservable t1) { inOrder.verifyNoMoreInteractions(); } + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Test + public void retryWhenDefaultScheduler() { + TestSubscriber ts = TestSubscriber.create(); + + Observable.just(1) + .concatWith(Observable.error(new TestException())) + .retryWhen((Func1)new Func1() { + @Override + public Observable call(Observable o) { + return o.take(2); + } + }).subscribe(ts); + + ts.assertValues(1, 1); + ts.assertNoErrors(); + ts.assertCompleted(); + + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Test + public void retryWhenTrampolineScheduler() { + TestSubscriber ts = TestSubscriber.create(); + + Observable.just(1) + .concatWith(Observable.error(new TestException())) + .retryWhen((Func1)new Func1() { + @Override + public Observable call(Observable o) { + return o.take(2); + } + }, Schedulers.trampoline()).subscribe(ts); + + ts.assertValues(1, 1); + ts.assertNoErrors(); + ts.assertCompleted(); + } } diff --git a/src/test/java/rx/internal/operators/OperatorRetryWithPredicateTest.java b/src/test/java/rx/internal/operators/OperatorRetryWithPredicateTest.java index c3d438b200..dab59e9f0d 100644 --- a/src/test/java/rx/internal/operators/OperatorRetryWithPredicateTest.java +++ b/src/test/java/rx/internal/operators/OperatorRetryWithPredicateTest.java @@ -66,13 +66,13 @@ public Boolean call(Integer t1, Throwable t2) { @Test public void testWithNothingToRetry() { Observable source = Observable.range(0, 3); - + @SuppressWarnings("unchecked") Observer o = mock(Observer.class); InOrder inOrder = inOrder(o); - + source.retry(retryTwice).subscribe(o); - + inOrder.verify(o).onNext(0); inOrder.verify(o).onNext(1); inOrder.verify(o).onNext(2); @@ -81,7 +81,7 @@ public void testWithNothingToRetry() { } @Test public void testRetryTwice() { - Observable source = Observable.create(new OnSubscribe() { + Observable source = Observable.unsafeCreate(new OnSubscribe() { int count; @Override public void call(Subscriber t1) { @@ -97,11 +97,11 @@ public void call(Subscriber t1) { t1.onCompleted(); } }); - + @SuppressWarnings("unchecked") Observer o = mock(Observer.class); InOrder inOrder = inOrder(o); - + source.retry(retryTwice).subscribe(o); inOrder.verify(o).onNext(0); @@ -112,11 +112,11 @@ public void call(Subscriber t1) { inOrder.verify(o).onNext(3); inOrder.verify(o).onCompleted(); verify(o, never()).onError(any(Throwable.class)); - + } @Test public void testRetryTwiceAndGiveUp() { - Observable source = Observable.create(new OnSubscribe() { + Observable source = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber t1) { t1.onNext(0); @@ -124,11 +124,11 @@ public void call(Subscriber t1) { t1.onError(new TestException()); } }); - + @SuppressWarnings("unchecked") Observer o = mock(Observer.class); InOrder inOrder = inOrder(o); - + source.retry(retryTwice).subscribe(o); inOrder.verify(o).onNext(0); @@ -139,11 +139,11 @@ public void call(Subscriber t1) { inOrder.verify(o).onNext(1); inOrder.verify(o).onError(any(TestException.class)); verify(o, never()).onCompleted(); - + } @Test public void testRetryOnSpecificException() { - Observable source = Observable.create(new OnSubscribe() { + Observable source = Observable.unsafeCreate(new OnSubscribe() { int count; @Override public void call(Subscriber t1) { @@ -159,11 +159,11 @@ public void call(Subscriber t1) { t1.onCompleted(); } }); - + @SuppressWarnings("unchecked") Observer o = mock(Observer.class); InOrder inOrder = inOrder(o); - + source.retry(retryOnTestException).subscribe(o); inOrder.verify(o).onNext(0); @@ -179,7 +179,7 @@ public void call(Subscriber t1) { public void testRetryOnSpecificExceptionAndNotOther() { final IOException ioe = new IOException(); final TestException te = new TestException(); - Observable source = Observable.create(new OnSubscribe() { + Observable source = Observable.unsafeCreate(new OnSubscribe() { int count; @Override public void call(Subscriber t1) { @@ -195,11 +195,11 @@ public void call(Subscriber t1) { t1.onError(te); } }); - + @SuppressWarnings("unchecked") Observer o = mock(Observer.class); InOrder inOrder = inOrder(o); - + source.retry(retryOnTestException).subscribe(o); inOrder.verify(o).onNext(0); @@ -212,7 +212,7 @@ public void call(Subscriber t1) { verify(o, never()).onError(ioe); verify(o, never()).onCompleted(); } - + @Test public void testUnsubscribeFromRetry() { PublishSubject subject = PublishSubject.create(); @@ -228,7 +228,7 @@ public void call(Integer n) { subject.onNext(2); assertEquals(1, count.get()); } - + @Test(timeout = 10000) public void testUnsubscribeAfterError() { @@ -238,7 +238,7 @@ public void testUnsubscribeAfterError() { // Observable that always fails after 100ms OperatorRetryTest.SlowObservable so = new OperatorRetryTest.SlowObservable(100, 0); Observable o = Observable - .create(so) + .unsafeCreate(so) .retry(retry5); OperatorRetryTest.AsyncObserver async = new OperatorRetryTest.AsyncObserver(observer); @@ -265,7 +265,7 @@ public void testTimeoutWithRetry() { // Observable that sends every 100ms (timeout fails instead) OperatorRetryTest.SlowObservable so = new OperatorRetryTest.SlowObservable(100, 10); Observable o = Observable - .create(so) + .unsafeCreate(so) .timeout(80, TimeUnit.MILLISECONDS) .retry(retry5); @@ -282,7 +282,7 @@ public void testTimeoutWithRetry() { assertEquals("Start 6 threads, retry 5 then fail on 6", 6, so.efforts.get()); } - + @Test public void testIssue2826() { TestSubscriber ts = new TestSubscriber(); @@ -315,12 +315,12 @@ public Integer call(Integer t1) { assertEquals(1, value); } - + @Test public void testIssue3008RetryWithPredicate() { final List list = new CopyOnWriteArrayList(); final AtomicBoolean isFirst = new AtomicBoolean(true); - Observable. just(1L, 2L, 3L).map(new Func1(){ + Observable. just(1L, 2L, 3L).map(new Func1() { @Override public Long call(Long x) { System.out.println("map " + x); @@ -343,12 +343,12 @@ public void call(Long t) { }}); assertEquals(Arrays.asList(1L,1L,2L,3L), list); } - + @Test public void testIssue3008RetryInfinite() { final List list = new CopyOnWriteArrayList(); final AtomicBoolean isFirst = new AtomicBoolean(true); - Observable. just(1L, 2L, 3L).map(new Func1(){ + Observable. just(1L, 2L, 3L).map(new Func1() { @Override public Long call(Long x) { System.out.println("map " + x); @@ -370,7 +370,7 @@ public void call(Long t) { @Test public void testBackpressure() { final List requests = new ArrayList(); - + Observable source = Observable .just(1) .concatWith(Observable.error(new TestException())) @@ -380,7 +380,7 @@ public void call(Long t) { requests.add(t); } }); - + TestSubscriber ts = TestSubscriber.create(3); source .retry(new Func2() { @@ -389,7 +389,7 @@ public Boolean call(Integer t1, Throwable t2) { return t1 < 3; } }).subscribe(ts); - + assertEquals(Arrays.asList(3L, 2L, 1L), requests); ts.assertValues(1, 1, 1); ts.assertNotCompleted(); diff --git a/src/test/java/rx/internal/operators/OperatorSampleTest.java b/src/test/java/rx/internal/operators/OperatorSampleTest.java index c05abda5d2..20d59d8523 100644 --- a/src/test/java/rx/internal/operators/OperatorSampleTest.java +++ b/src/test/java/rx/internal/operators/OperatorSampleTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -50,7 +50,7 @@ public void before() { @Test public void testSample() { - Observable source = Observable.create(new OnSubscribe() { + Observable source = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(final Subscriber observer1) { innerScheduler.schedule(new Action0() { @@ -111,7 +111,7 @@ public void call() { @Test public void sampleWithTimeEmitAndTerminate() { - Observable source = Observable.create(new OnSubscribe() { + Observable source = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(final Subscriber observer1) { innerScheduler.schedule(new Action0() { @@ -303,7 +303,7 @@ public void sampleWithSamplerThrows() { @Test public void testSampleUnsubscribe() { final Subscription s = mock(Subscription.class); - Observable o = Observable.create( + Observable o = Observable.unsafeCreate( new OnSubscribe() { @Override public void call(Subscriber subscriber) { @@ -314,12 +314,12 @@ public void call(Subscriber subscriber) { o.throttleLast(1, TimeUnit.MILLISECONDS).subscribe().unsubscribe(); verify(s).unsubscribe(); } - + @Test public void testSampleOtherUnboundedIn() { - + final long[] requested = { -1 }; - + PublishSubject.create() .doOnRequest(new Action1() { @Override @@ -328,15 +328,15 @@ public void call(Long t) { } }) .sample(PublishSubject.create()).subscribe(); - + Assert.assertEquals(Long.MAX_VALUE, requested[0]); } - + @Test public void testSampleTimedUnboundedIn() { - + final long[] requested = { -1 }; - + PublishSubject.create() .doOnRequest(new Action1() { @Override @@ -345,49 +345,49 @@ public void call(Long t) { } }) .sample(1, TimeUnit.SECONDS).subscribe().unsubscribe(); - + Assert.assertEquals(Long.MAX_VALUE, requested[0]); } - + @Test public void dontUnsubscribeChild1() { TestSubscriber ts = new TestSubscriber(); - + PublishSubject source = PublishSubject.create(); - + PublishSubject sampler = PublishSubject.create(); - + source.sample(sampler).unsafeSubscribe(ts); - + source.onCompleted(); - + Assert.assertFalse("Source has subscribers?", source.hasObservers()); Assert.assertFalse("Sampler has subscribers?", sampler.hasObservers()); - + Assert.assertFalse("TS unsubscribed?", ts.isUnsubscribed()); } @Test public void dontUnsubscribeChild2() { TestSubscriber ts = new TestSubscriber(); - + PublishSubject source = PublishSubject.create(); - + PublishSubject sampler = PublishSubject.create(); - + source.sample(sampler).unsafeSubscribe(ts); - + sampler.onCompleted(); - + Assert.assertFalse("Source has subscribers?", source.hasObservers()); Assert.assertFalse("Sampler has subscribers?", sampler.hasObservers()); - + Assert.assertFalse("TS unsubscribed?", ts.isUnsubscribed()); } - + @Test public void neverSetProducer() { - Observable neverBackpressure = Observable.create(new OnSubscribe() { + Observable neverBackpressure = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber t) { t.setProducer(new Producer() { @@ -398,39 +398,39 @@ public void request(long n) { }); } }); - + final AtomicInteger count = new AtomicInteger(); - + neverBackpressure.sample(neverBackpressure).unsafeSubscribe(new Subscriber() { @Override public void onNext(Integer t) { // irrelevant } - + @Override public void onError(Throwable e) { // irrelevant } - + @Override public void onCompleted() { // irrelevant } - + @Override public void setProducer(Producer p) { count.incrementAndGet(); } }); - + Assert.assertEquals(0, count.get()); } - + @Test public void unsubscribeMainAfterCompleted() { final AtomicBoolean unsubscribed = new AtomicBoolean(); - - Observable source = Observable.create(new OnSubscribe() { + + Observable source = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber t) { t.add(Subscriptions.create(new Action0() { @@ -441,7 +441,7 @@ public void call() { })); } }); - + TestSubscriber ts = new TestSubscriber() { @Override public void onCompleted() { @@ -452,22 +452,22 @@ public void onCompleted() { } } }; - + PublishSubject sampler = PublishSubject.create(); - + source.sample(sampler).unsafeSubscribe(ts); - + sampler.onCompleted(); - + ts.assertNoErrors(); ts.assertCompleted(); } - + @Test public void unsubscribeSamplerAfterCompleted() { final AtomicBoolean unsubscribed = new AtomicBoolean(); - - Observable source = Observable.create(new OnSubscribe() { + + Observable source = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber t) { t.add(Subscriptions.create(new Action0() { @@ -478,7 +478,7 @@ public void call() { })); } }); - + TestSubscriber ts = new TestSubscriber() { @Override public void onCompleted() { @@ -489,13 +489,13 @@ public void onCompleted() { } } }; - + PublishSubject sampled = PublishSubject.create(); - + sampled.sample(source).unsafeSubscribe(ts); - + sampled.onCompleted(); - + ts.assertNoErrors(); ts.assertCompleted(); } diff --git a/src/test/java/rx/internal/operators/OperatorScanTest.java b/src/test/java/rx/internal/operators/OperatorScanTest.java index e45f32f92c..e20519ed17 100644 --- a/src/test/java/rx/internal/operators/OperatorScanTest.java +++ b/src/test/java/rx/internal/operators/OperatorScanTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -118,7 +118,7 @@ public Integer call(Integer t1, Integer t2) { verify(observer, times(1)).onCompleted(); verify(observer, never()).onError(any(Throwable.class)); } - + @Test public void shouldNotEmitUntilAfterSubscription() { TestSubscriber ts = new TestSubscriber(); @@ -136,12 +136,12 @@ public Boolean call(Integer t1) { // this will cause request(1) when 0 is emitted return t1 > 0; } - + }).subscribe(ts); - + assertEquals(100, ts.getOnNextEvents().size()); } - + @Test public void testBackpressureWithInitialValue() { final AtomicInteger count = new AtomicInteger(); @@ -182,7 +182,7 @@ public void onNext(Integer t) { // we only expect to receive 10 since we request(10) assertEquals(10, count.get()); } - + @Test public void testBackpressureWithoutInitialValue() { final AtomicInteger count = new AtomicInteger(); @@ -223,7 +223,7 @@ public void onNext(Integer t) { // we only expect to receive 10 since we request(10) assertEquals(10, count.get()); } - + @Test public void testNoBackpressureWithInitialValue() { final AtomicInteger count = new AtomicInteger(); @@ -272,7 +272,7 @@ public void testSeedFactory() { public List call() { return new ArrayList(); } - + }, new Action2, Integer>() { @Override @@ -306,7 +306,7 @@ public Integer call(Integer t1, Integer t2) { @Test public void testScanShouldNotRequestZero() { final AtomicReference producer = new AtomicReference(); - Observable o = Observable.create(new Observable.OnSubscribe() { + Observable o = Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(final Subscriber subscriber) { Producer p = spy(new Producer() { @@ -350,28 +350,28 @@ public void onNext(Integer integer) { verify(producer.get(), never()).request(0); verify(producer.get(), times(2)).request(1); } - + @Test public void testInitialValueEmittedNoProducer() { PublishSubject source = PublishSubject.create(); - + TestSubscriber ts = TestSubscriber.create(); - + source.scan(0, new Func2() { @Override public Integer call(Integer t1, Integer t2) { return t1 + t2; } }).subscribe(ts); - + ts.assertNoErrors(); ts.assertNotCompleted(); ts.assertValue(0); } - + @Test public void testInitialValueEmittedWithProducer() { - Observable source = Observable.create(new OnSubscribe() { + Observable source = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber t) { t.setProducer(new Producer() { @@ -382,25 +382,25 @@ public void request(long n) { }); } }); - + TestSubscriber ts = TestSubscriber.create(); - + source.scan(0, new Func2() { @Override public Integer call(Integer t1, Integer t2) { return t1 + t2; } }).subscribe(ts); - + ts.assertNoErrors(); ts.assertNotCompleted(); ts.assertValue(0); } - + @Test public void testInitialValueNull() { TestSubscriber ts = TestSubscriber.create(); - + Observable.range(1, 10).scan(null, new Func2() { @Override public Integer call(Integer t1, Integer t2) { @@ -410,28 +410,28 @@ public Integer call(Integer t1, Integer t2) { return t1 + t2; } }).subscribe(ts); - + ts.assertValues(null, 1, 3, 6, 10, 15, 21, 28, 36, 45, 55); ts.assertNoErrors(); ts.assertCompleted(); } - + @Test public void testEverythingIsNull() { TestSubscriber ts = TestSubscriber.create(); - + Observable.range(1, 6).scan(null, new Func2() { @Override public Integer call(Integer t1, Integer t2) { return null; } }).subscribe(ts); - + ts.assertValues(null, null, null, null, null, null, null); ts.assertNoErrors(); ts.assertCompleted(); } - + @Test(timeout = 1000) public void testUnboundedSource() { Observable.range(0, Integer.MAX_VALUE) @@ -451,7 +451,7 @@ public void onNext(Integer t) { } }); } - + @Test public void scanShouldPassUpstreamARequestForMaxValue() { final List requests = new ArrayList(); @@ -466,7 +466,7 @@ public void call(Long n) { public Integer call(Integer t1, Integer t2) { return 0; }}).count().subscribe(); - + assertEquals(Arrays.asList(Long.MAX_VALUE), requests); } } diff --git a/src/test/java/rx/internal/operators/OperatorSequenceEqualTest.java b/src/test/java/rx/internal/operators/OperatorSequenceEqualTest.java index e0fa617bb8..04ddf01afc 100644 --- a/src/test/java/rx/internal/operators/OperatorSequenceEqualTest.java +++ b/src/test/java/rx/internal/operators/OperatorSequenceEqualTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -16,19 +16,20 @@ package rx.internal.operators; import static org.mockito.Matchers.isA; -import static org.mockito.Mockito.inOrder; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; +import static org.mockito.Mockito.*; import org.junit.Test; import org.mockito.InOrder; -import rx.Observable; -import rx.Observer; +import rx.*; import rx.exceptions.TestException; import rx.functions.Func2; public class OperatorSequenceEqualTest { + @Test + public void constructorShouldBePrivate() { + TestUtil.checkUtilityClass(OperatorSequenceEqual.class); + } @Test public void test1() { diff --git a/src/test/java/rx/internal/operators/OperatorSerializeTest.java b/src/test/java/rx/internal/operators/OperatorSerializeTest.java index f126307af5..14d38dd76b 100644 --- a/src/test/java/rx/internal/operators/OperatorSerializeTest.java +++ b/src/test/java/rx/internal/operators/OperatorSerializeTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -51,7 +51,7 @@ public void before() { @Test public void testSingleThreadedBasic() { TestSingleThreadedObservable onSubscribe = new TestSingleThreadedObservable("one", "two", "three"); - Observable w = Observable.create(onSubscribe); + Observable w = Observable.unsafeCreate(onSubscribe); w.serialize().subscribe(observer); onSubscribe.waitToFinish(); @@ -69,7 +69,7 @@ public void testSingleThreadedBasic() { @Test public void testMultiThreadedBasic() { TestMultiThreadedObservable onSubscribe = new TestMultiThreadedObservable("one", "two", "three"); - Observable w = Observable.create(onSubscribe); + Observable w = Observable.unsafeCreate(onSubscribe); BusyObserver busyobserver = new BusyObserver(); @@ -92,7 +92,7 @@ public void testMultiThreadedBasic() { @Test public void testMultiThreadedWithNPE() { TestMultiThreadedObservable onSubscribe = new TestMultiThreadedObservable("one", "two", "three", null); - Observable w = Observable.create(onSubscribe); + Observable w = Observable.unsafeCreate(onSubscribe); BusyObserver busyobserver = new BusyObserver(); @@ -123,15 +123,15 @@ public void testMultiThreadedWithNPEinMiddle() { boolean lessThan9 = false; for (int i = 0; i < 3; i++) { TestMultiThreadedObservable onSubscribe = new TestMultiThreadedObservable("one", "two", "three", null, "four", "five", "six", "seven", "eight", "nine"); - Observable w = Observable.create(onSubscribe); - + Observable w = Observable.unsafeCreate(onSubscribe); + BusyObserver busyobserver = new BusyObserver(); - + w.serialize().subscribe(busyobserver); onSubscribe.waitToFinish(); - + System.out.println("maxConcurrentThreads: " + onSubscribe.maxConcurrentThreads.get()); - // this should not always be the full number of items since the error should (very often) + // this should not always be the full number of items since the error should (very often) // stop it before it completes all 9 System.out.println("onNext count: " + busyobserver.onNextCount.get()); if (busyobserver.onNextCount.get() < 9) { @@ -143,7 +143,7 @@ public void testMultiThreadedWithNPEinMiddle() { // 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(); - + // we can have concurrency ... assertTrue(onSubscribe.maxConcurrentThreads.get() > 1); // ... but the onNext execution should be single threaded @@ -151,7 +151,7 @@ public void testMultiThreadedWithNPEinMiddle() { } assertTrue(lessThan9); } - + /** * A thread that will pass data to onNext */ @@ -223,7 +223,7 @@ private enum TestConcurrencyobserverEvent { private static class TestSingleThreadedObservable implements Observable.OnSubscribe { final String[] values; - private Thread t = null; + private Thread t; public TestSingleThreadedObservable(final String... values) { this.values = values; @@ -270,7 +270,7 @@ public void waitToFinish() { */ private static class TestMultiThreadedObservable implements Observable.OnSubscribe { final String[] values; - Thread t = null; + Thread t; AtomicInteger threadsRunning = new AtomicInteger(); AtomicInteger maxConcurrentThreads = new AtomicInteger(); ExecutorService threadPool; @@ -302,8 +302,9 @@ public void run() { System.out.println("TestMultiThreadedObservable onNext: null"); // force an error throw npe; - } else + } else { System.out.println("TestMultiThreadedObservable onNext: " + s); + } observer.onNext(s); // capture 'maxThreads' int concurrentThreads = threadsRunning.get(); @@ -350,8 +351,8 @@ public void waitToFinish() { } private static class BusyObserver extends Subscriber { - volatile boolean onCompleted = false; - volatile boolean onError = false; + volatile boolean onCompleted; + volatile boolean onError; AtomicInteger onNextCount = new AtomicInteger(); AtomicInteger threadsRunning = new AtomicInteger(); AtomicInteger maxConcurrentThreads = new AtomicInteger(); diff --git a/src/test/java/rx/internal/operators/OperatorSingleTest.java b/src/test/java/rx/internal/operators/OperatorSingleTest.java index 9bdd630c5d..96459a01d3 100644 --- a/src/test/java/rx/internal/operators/OperatorSingleTest.java +++ b/src/test/java/rx/internal/operators/OperatorSingleTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -82,7 +82,7 @@ public void testSingleWithEmpty() { isA(NoSuchElementException.class)); inOrder.verifyNoMoreInteractions(); } - + @Test public void testSingleDoesNotRequestMoreThanItNeedsToEmitItem() { final AtomicLong request = new AtomicLong(); @@ -124,7 +124,7 @@ public void call(Long n) { assertEquals(2, request.get()); } } - + @Test public void testSingleDoesNotRequestMoreThanItNeedsIf1Then2Requested() { final List requests = new ArrayList(); @@ -163,7 +163,7 @@ public void onNext(Integer t) { }); assertEquals(Arrays.asList(2L), requests); } - + @Test public void testSingleDoesNotRequestMoreThanItNeedsIf3Requested() { final List requests = new ArrayList(); @@ -201,7 +201,7 @@ public void onNext(Integer t) { }); assertEquals(Arrays.asList(2L), requests); } - + @Test public void testSingleRequestsExactlyWhatItNeedsIf1Requested() { final List requests = new ArrayList(); @@ -457,17 +457,17 @@ public Integer call(Integer i1, Integer i2) { Integer r = reduced.toBlocking().first(); assertEquals(21, r.intValue()); } - + @Test public void defaultBackpressure() { TestSubscriber ts = TestSubscriber.create(0); - + Observable.empty().singleOrDefault(1).subscribe(ts); - + ts.assertNoValues(); - + ts.requestMore(1); - + ts.assertValue(1); ts.assertCompleted(); ts.assertNoErrors(); diff --git a/src/test/java/rx/internal/operators/OperatorSkipLastTest.java b/src/test/java/rx/internal/operators/OperatorSkipLastTest.java index fb9de98ab9..361e7bcd88 100644 --- a/src/test/java/rx/internal/operators/OperatorSkipLastTest.java +++ b/src/test/java/rx/internal/operators/OperatorSkipLastTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -24,15 +24,18 @@ import static org.mockito.Mockito.verify; import java.util.Arrays; +import java.util.concurrent.TimeUnit; import org.junit.Test; import org.mockito.InOrder; -import rx.Observable; -import rx.Observer; +import rx.*; +import rx.functions.Func1; import rx.internal.util.RxRingBuffer; import rx.observers.TestSubscriber; -import rx.schedulers.Schedulers; +import rx.plugins.RxJavaHooks; +import rx.schedulers.*; +import rx.subjects.PublishSubject; public class OperatorSkipLastTest { @@ -119,4 +122,41 @@ public void testSkipLastWithNegativeCount() { Observable.just("one").skipLast(-1); } + @Test + public void skipLastDefaultScheduler() { + final TestScheduler scheduler = new TestScheduler(); + + RxJavaHooks.setOnComputationScheduler(new Func1() { + @Override + public Scheduler call(Scheduler t) { + return scheduler; + } + }); + + try { + TestSubscriber ts = TestSubscriber.create(); + + PublishSubject ps = PublishSubject.create(); + + ps.skipLast(1, TimeUnit.SECONDS).subscribe(ts); + + ps.onNext(1); + ps.onNext(2); + ps.onNext(3); + + scheduler.advanceTimeBy(1, TimeUnit.SECONDS); + + ps.onNext(4); + ps.onNext(5); + + scheduler.advanceTimeBy(1, TimeUnit.SECONDS); + ps.onCompleted(); + + ts.assertValues(1, 2, 3); + ts.assertNoErrors(); + ts.assertCompleted(); + } finally { + RxJavaHooks.reset(); + } + } } diff --git a/src/test/java/rx/internal/operators/OperatorSkipLastTimedTest.java b/src/test/java/rx/internal/operators/OperatorSkipLastTimedTest.java index fce0a05f9b..f543f05744 100644 --- a/src/test/java/rx/internal/operators/OperatorSkipLastTimedTest.java +++ b/src/test/java/rx/internal/operators/OperatorSkipLastTimedTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. diff --git a/src/test/java/rx/internal/operators/OperatorSkipTest.java b/src/test/java/rx/internal/operators/OperatorSkipTest.java index 21ad07b251..fdfd12de1d 100644 --- a/src/test/java/rx/internal/operators/OperatorSkipTest.java +++ b/src/test/java/rx/internal/operators/OperatorSkipTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -29,10 +29,12 @@ import org.junit.Test; -import rx.Observable; -import rx.Observer; -import rx.functions.Action1; +import rx.*; +import rx.functions.*; import rx.observers.TestSubscriber; +import rx.plugins.RxJavaHooks; +import rx.schedulers.TestScheduler; +import rx.subjects.PublishSubject; public class OperatorSkipTest { @@ -146,7 +148,7 @@ public void testSkipError() { verify(observer, never()).onCompleted(); } - + @Test public void testBackpressureMultipleSmallAsyncRequests() throws InterruptedException { final AtomicLong requests = new AtomicLong(0); @@ -167,15 +169,47 @@ public void call(Long n) { ts.assertNoErrors(); assertEquals(6, requests.get()); } - + @Test public void testRequestOverflowDoesNotOccur() { - TestSubscriber ts = new TestSubscriber(Long.MAX_VALUE-1); + TestSubscriber ts = new TestSubscriber(Long.MAX_VALUE - 1); Observable.range(1, 10).skip(5).subscribe(ts); ts.assertTerminalEvent(); ts.assertCompleted(); ts.assertNoErrors(); assertEquals(Arrays.asList(6,7,8,9,10), ts.getOnNextEvents()); } - + + @Test + public void skipDefaultScheduler() { + final TestScheduler scheduler = new TestScheduler(); + + RxJavaHooks.setOnComputationScheduler(new Func1() { + @Override + public Scheduler call(Scheduler t) { + return scheduler; + } + }); + + try { + TestSubscriber ts = TestSubscriber.create(); + + PublishSubject ps = PublishSubject.create(); + + ps.skip(1, TimeUnit.SECONDS).subscribe(ts); + + ps.onNext(1); + + scheduler.advanceTimeBy(1, TimeUnit.SECONDS); + + ps.onNext(2); + ps.onCompleted(); + + ts.assertValue(2); + ts.assertNoErrors(); + ts.assertCompleted(); + } finally { + RxJavaHooks.reset(); + } + } } diff --git a/src/test/java/rx/internal/operators/OperatorSkipTimedTest.java b/src/test/java/rx/internal/operators/OperatorSkipTimedTest.java index b75c84eef8..1ccd7ec636 100644 --- a/src/test/java/rx/internal/operators/OperatorSkipTimedTest.java +++ b/src/test/java/rx/internal/operators/OperatorSkipTimedTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -15,6 +15,7 @@ */ package rx.internal.operators; +import static org.junit.Assert.assertTrue; import static org.mockito.Matchers.any; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; @@ -22,6 +23,7 @@ import static org.mockito.Mockito.verify; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; import org.junit.Test; import org.mockito.InOrder; @@ -29,6 +31,8 @@ import rx.Observable; import rx.Observer; import rx.exceptions.TestException; +import rx.functions.Action0; +import rx.observers.TestSubscriber; import rx.schedulers.TestScheduler; import rx.subjects.PublishSubject; @@ -166,4 +170,27 @@ public void testSkipTimedErrorAfterTime() { verify(o, never()).onCompleted(); } + + @Test + public void testSkipTimedUnsubscribePropagatesToUpstream() { + TestScheduler scheduler = new TestScheduler(); + + PublishSubject source = PublishSubject.create(); + + final AtomicBoolean unsub = new AtomicBoolean(); + Observable result = source.doOnUnsubscribe(new Action0() { + + @Override + public void call() { + unsub.set(true); + } + }).skip(1, TimeUnit.SECONDS, scheduler); + + TestSubscriber ts = TestSubscriber.create(); + + result.subscribe(ts); + source.onNext(1); + ts.unsubscribe(); + assertTrue(unsub.get()); + } } diff --git a/src/test/java/rx/internal/operators/OperatorSkipUntilTest.java b/src/test/java/rx/internal/operators/OperatorSkipUntilTest.java index 762798ace7..7754be3e6f 100644 --- a/src/test/java/rx/internal/operators/OperatorSkipUntilTest.java +++ b/src/test/java/rx/internal/operators/OperatorSkipUntilTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. diff --git a/src/test/java/rx/internal/operators/OperatorSkipWhileTest.java b/src/test/java/rx/internal/operators/OperatorSkipWhileTest.java index d0d8f6960b..205ed930bb 100644 --- a/src/test/java/rx/internal/operators/OperatorSkipWhileTest.java +++ b/src/test/java/rx/internal/operators/OperatorSkipWhileTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -44,27 +44,28 @@ public class OperatorSkipWhileTest { private static final Func1 LESS_THAN_FIVE = new Func1() { @Override public Boolean call(Integer v) { - if (v == 42) + if (v == 42) { throw new RuntimeException("that's not the answer to everything!"); + } return v < 5; } }; private static final Func1 INDEX_LESS_THAN_THREE = new Func1() { - int index = 0; + int index; @Override public Boolean call(Integer value) { return index++ < 3; } }; - + private static final Func1 THROWS_NON_FATAL = new Func1() { @Override public Boolean call(Integer values) { throw new RuntimeException(); } }; - + private static final Func1 THROWS_FATAL = new Func1() { @Override public Boolean call(Integer values) { @@ -139,7 +140,7 @@ public void testSkipError() { inOrder.verify(w, never()).onCompleted(); inOrder.verify(w, times(1)).onError(any(RuntimeException.class)); } - + @Test public void testPredicateRuntimeError() { Observable.just(1).skipWhile(THROWS_NON_FATAL).subscribe(w); @@ -148,12 +149,12 @@ public void testPredicateRuntimeError() { inOrder.verify(w, never()).onCompleted(); inOrder.verify(w, times(1)).onError(any(RuntimeException.class)); } - + @Test(expected = OutOfMemoryError.class) public void testPredicateFatalError() { Observable.just(1).skipWhile(THROWS_FATAL).unsafeSubscribe(Subscribers.empty()); } - + @Test public void testPredicateRuntimeErrorDoesNotGoUpstreamFirst() { final AtomicBoolean errorOccurred = new AtomicBoolean(false); @@ -166,7 +167,7 @@ public void call(Throwable t) { }).skipWhile(THROWS_NON_FATAL).subscribe(ts); assertFalse(errorOccurred.get()); } - + @Test public void testSkipManySubscribers() { Observable src = Observable.range(1, 10).skipWhile(LESS_THAN_FIVE); @@ -175,12 +176,12 @@ public void testSkipManySubscribers() { @SuppressWarnings("unchecked") Observer o = mock(Observer.class); InOrder inOrder = inOrder(o); - + src.subscribe(o); - + for (int j = 5; j < 10; j++) { inOrder.verify(o).onNext(j); - } + } inOrder.verify(o).onCompleted(); verify(o, never()).onError(any(Throwable.class)); } diff --git a/src/test/java/rx/internal/operators/OperatorSubscribeOnTest.java b/src/test/java/rx/internal/operators/OperatorSubscribeOnTest.java index 0c72ebaf7a..b28b3fc27b 100644 --- a/src/test/java/rx/internal/operators/OperatorSubscribeOnTest.java +++ b/src/test/java/rx/internal/operators/OperatorSubscribeOnTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -26,16 +26,12 @@ import org.junit.Test; -import rx.Observable; +import rx.*; import rx.Observable.OnSubscribe; import rx.Observable.Operator; -import rx.Observer; -import rx.Producer; -import rx.Scheduler; -import rx.Subscriber; -import rx.Subscription; -import rx.functions.Action0; -import rx.observers.TestSubscriber; +import rx.functions.*; +import rx.internal.util.*; +import rx.observers.*; import rx.schedulers.Schedulers; public class OperatorSubscribeOnTest { @@ -50,7 +46,7 @@ public void testIssue813() throws InterruptedException { TestSubscriber observer = new TestSubscriber(); final Subscription subscription = Observable - .create(new Observable.OnSubscribe() { + .unsafeCreate(new Observable.OnSubscribe() { @Override public void call( final Subscriber subscriber) { @@ -85,7 +81,7 @@ public void call( @Test public void testThrownErrorHandling() { TestSubscriber ts = new TestSubscriber(); - Observable.create(new OnSubscribe() { + Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber s) { @@ -100,7 +96,7 @@ public void call(Subscriber s) { @Test public void testOnError() { TestSubscriber ts = new TestSubscriber(); - Observable.create(new OnSubscribe() { + Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber s) { @@ -132,7 +128,7 @@ public Worker createWorker() { return new SlowInner(actual.createWorker()); } - private final class SlowInner extends Worker { + final class SlowInner extends Worker { private final Scheduler.Worker actualInner; @@ -170,7 +166,7 @@ public Subscription schedule(final Action0 action, final long delayTime, final T public void testUnsubscribeInfiniteStream() throws InterruptedException { TestSubscriber ts = new TestSubscriber(); final AtomicInteger count = new AtomicInteger(); - Observable.create(new OnSubscribe() { + Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber sub) { @@ -267,4 +263,85 @@ public void onNext(Integer t) { ts.assertNoErrors(); } + @Test + public void noSamepoolDeadlock() { + final int n = 4 * RxRingBuffer.SIZE; + + Observable.create(new Action1>() { + @Override + public void call(Emitter e) { + for (int i = 0; i < n; i++) { + e.onNext(i); + try { + Thread.sleep(1); + } catch (InterruptedException e1) { + e1.printStackTrace(); + } + } + e.onCompleted(); + } + }, Emitter.BackpressureMode.DROP) + .map(UtilityFunctions.identity()) + .subscribeOn(Schedulers.io(), false) + .observeOn(Schedulers.computation()) + .test() + .awaitTerminalEvent(5, TimeUnit.SECONDS) + .assertValueCount(n) + .assertNoErrors() + .assertCompleted(); + } + + @Test + public void noSamepoolDeadlockRequestOn() { + final int n = 4 * RxRingBuffer.SIZE; + + Observable.create(new Action1>() { + @Override + public void call(Emitter e) { + for (int i = 0; i < n; i++) { + e.onNext(i); + try { + Thread.sleep(1); + } catch (InterruptedException e1) { + e1.printStackTrace(); + } + } + e.onCompleted(); + } + }, Emitter.BackpressureMode.DROP) + .subscribeOn(Schedulers.io()) + .observeOn(Schedulers.computation()) + .test() + .awaitTerminalEvent(5, TimeUnit.SECONDS) + .assertValueCount(n) + .assertNoErrors() + .assertCompleted(); + } + + @Test + public void noSamepoolDeadlockRequestOn2() { + final int n = 4 * RxRingBuffer.SIZE; + + Observable.create(new Action1>() { + @Override + public void call(Emitter e) { + for (int i = 0; i < n; i++) { + e.onNext(i); + try { + Thread.sleep(1); + } catch (InterruptedException e1) { + e1.printStackTrace(); + } + } + e.onCompleted(); + } + }, Emitter.BackpressureMode.DROP) + .subscribeOn(Schedulers.io(), true) + .observeOn(Schedulers.computation()) + .test() + .awaitTerminalEvent(5, TimeUnit.SECONDS) + .assertValueCount(RxRingBuffer.SIZE) + .assertNoErrors() + .assertCompleted(); + } } diff --git a/src/test/java/rx/internal/operators/OperatorSwitchIfEmptyTest.java b/src/test/java/rx/internal/operators/OperatorSwitchIfEmptyTest.java index 332924ba68..235cac495b 100644 --- a/src/test/java/rx/internal/operators/OperatorSwitchIfEmptyTest.java +++ b/src/test/java/rx/internal/operators/OperatorSwitchIfEmptyTest.java @@ -17,16 +17,15 @@ import static org.junit.Assert.*; -import java.util.*; +import java.util.Arrays; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import org.junit.Test; import rx.*; -import rx.Observable; import rx.Observable.OnSubscribe; -import rx.functions.Action0; +import rx.functions.*; import rx.observers.TestSubscriber; import rx.schedulers.Schedulers; import rx.subscriptions.Subscriptions; @@ -58,7 +57,7 @@ public void testSwitchWhenEmpty() throws Exception { @Test public void testSwitchWithProducer() throws Exception { final AtomicBoolean emitted = new AtomicBoolean(false); - Observable withProducer = Observable.create(new Observable.OnSubscribe() { + Observable withProducer = Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(final Subscriber subscriber) { subscriber.setProducer(new Producer() { @@ -82,7 +81,7 @@ public void request(long n) { public void testSwitchTriggerUnsubscribe() throws Exception { final Subscription empty = Subscriptions.empty(); - Observable withProducer = Observable.create(new Observable.OnSubscribe() { + Observable withProducer = Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(final Subscriber subscriber) { subscriber.add(empty); @@ -121,7 +120,7 @@ public void onNext(Long aLong) { public void testSwitchShouldTriggerUnsubscribe() { final Subscription s = Subscriptions.empty(); - Observable.create(new Observable.OnSubscribe() { + Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(final Subscriber subscriber) { subscriber.add(s); @@ -142,7 +141,7 @@ public void onStart() { } }; Observable.empty().switchIfEmpty(Observable.just(1, 2, 3)).subscribe(ts); - + assertEquals(Arrays.asList(1), ts.getOnNextEvents()); ts.assertNoErrors(); ts.requestMore(1); @@ -163,7 +162,7 @@ public void onStart() { assertTrue(ts.getOnNextEvents().isEmpty()); ts.assertNoErrors(); } - + @Test public void testBackpressureOnFirstObservable() { TestSubscriber ts = new TestSubscriber(0); @@ -172,11 +171,11 @@ public void testBackpressureOnFirstObservable() { ts.assertNoErrors(); ts.assertNoValues(); } - + @Test(timeout = 10000) public void testRequestsNotLost() throws InterruptedException { final TestSubscriber ts = new TestSubscriber(0); - Observable.create(new OnSubscribe() { + Observable.unsafeCreate(new OnSubscribe() { @Override public void call(final Subscriber subscriber) { @@ -207,4 +206,34 @@ public void call() { ts.assertValueCount(2); ts.unsubscribe(); } + + @Test(expected = NullPointerException.class) + public void testAlternateNull() { + Observable.just(1).switchIfEmpty(null); + } + + Observable recursiveSwitch(final int level) { + if (level == 100) { + return Observable.just(Thread.currentThread().getStackTrace()); + } + return Observable.empty().switchIfEmpty(Observable.defer(new Func0>() { + @Override + public Observable call() { + return recursiveSwitch(level + 1); + } + })); + } + + @Test + public void stackDepth() { + StackTraceElement[] trace = recursiveSwitch(0) + .toBlocking().last(); + + if (trace.length > 1000 || trace.length < 100) { + for (StackTraceElement ste : trace) { + System.out.println(ste); + } + fail("Stack too deep: " + trace.length); + } + } } \ No newline at end of file diff --git a/src/test/java/rx/internal/operators/OperatorSwitchTest.java b/src/test/java/rx/internal/operators/OperatorSwitchTest.java index a908ec10cf..00887db5e1 100644 --- a/src/test/java/rx/internal/operators/OperatorSwitchTest.java +++ b/src/test/java/rx/internal/operators/OperatorSwitchTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -53,10 +53,10 @@ public void before() { @Test public void testSwitchWhenOuterCompleteBeforeInner() { - Observable> source = Observable.create(new Observable.OnSubscribe>() { + Observable> source = Observable.unsafeCreate(new Observable.OnSubscribe>() { @Override public void call(Subscriber> observer) { - publishNext(observer, 50, Observable.create(new Observable.OnSubscribe() { + publishNext(observer, 50, Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber observer) { publishNext(observer, 70, "one"); @@ -80,10 +80,10 @@ public void call(Subscriber observer) { @Test public void testSwitchWhenInnerCompleteBeforeOuter() { - Observable> source = Observable.create(new Observable.OnSubscribe>() { + Observable> source = Observable.unsafeCreate(new Observable.OnSubscribe>() { @Override public void call(Subscriber> observer) { - publishNext(observer, 10, Observable.create(new Observable.OnSubscribe() { + publishNext(observer, 10, Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber observer) { publishNext(observer, 0, "one"); @@ -92,7 +92,7 @@ public void call(Subscriber observer) { } })); - publishNext(observer, 100, Observable.create(new Observable.OnSubscribe() { + publishNext(observer, 100, Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber observer) { publishNext(observer, 0, "three"); @@ -123,10 +123,10 @@ public void call(Subscriber observer) { @Test public void testSwitchWithComplete() { - Observable> source = Observable.create(new Observable.OnSubscribe>() { + Observable> source = Observable.unsafeCreate(new Observable.OnSubscribe>() { @Override public void call(Subscriber> observer) { - publishNext(observer, 50, Observable.create(new Observable.OnSubscribe() { + publishNext(observer, 50, Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(final Subscriber observer) { publishNext(observer, 60, "one"); @@ -134,7 +134,7 @@ public void call(final Subscriber observer) { } })); - publishNext(observer, 200, Observable.create(new Observable.OnSubscribe() { + publishNext(observer, 200, Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(final Subscriber observer) { publishNext(observer, 0, "three"); @@ -179,10 +179,10 @@ public void call(final Subscriber observer) { @Test public void testSwitchWithError() { - Observable> source = Observable.create(new Observable.OnSubscribe>() { + Observable> source = Observable.unsafeCreate(new Observable.OnSubscribe>() { @Override public void call(Subscriber> observer) { - publishNext(observer, 50, Observable.create(new Observable.OnSubscribe() { + publishNext(observer, 50, Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(final Subscriber observer) { publishNext(observer, 50, "one"); @@ -190,7 +190,7 @@ public void call(final Subscriber observer) { } })); - publishNext(observer, 200, Observable.create(new Observable.OnSubscribe() { + publishNext(observer, 200, Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber observer) { publishNext(observer, 0, "three"); @@ -235,10 +235,10 @@ public void call(Subscriber observer) { @Test public void testSwitchWithSubsequenceComplete() { - Observable> source = Observable.create(new Observable.OnSubscribe>() { + Observable> source = Observable.unsafeCreate(new Observable.OnSubscribe>() { @Override public void call(Subscriber> observer) { - publishNext(observer, 50, Observable.create(new Observable.OnSubscribe() { + publishNext(observer, 50, Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber observer) { publishNext(observer, 50, "one"); @@ -246,14 +246,14 @@ public void call(Subscriber observer) { } })); - publishNext(observer, 130, Observable.create(new Observable.OnSubscribe() { + publishNext(observer, 130, Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber observer) { publishCompleted(observer, 0); } })); - publishNext(observer, 150, Observable.create(new Observable.OnSubscribe() { + publishNext(observer, 150, Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber observer) { publishNext(observer, 50, "three"); @@ -285,10 +285,10 @@ public void call(Subscriber observer) { @Test public void testSwitchWithSubsequenceError() { - Observable> source = Observable.create(new Observable.OnSubscribe>() { + Observable> source = Observable.unsafeCreate(new Observable.OnSubscribe>() { @Override public void call(Subscriber> observer) { - publishNext(observer, 50, Observable.create(new Observable.OnSubscribe() { + publishNext(observer, 50, Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber observer) { publishNext(observer, 50, "one"); @@ -296,14 +296,14 @@ public void call(Subscriber observer) { } })); - publishNext(observer, 130, Observable.create(new Observable.OnSubscribe() { + publishNext(observer, 130, Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber observer) { publishError(observer, 0, new TestException()); } })); - publishNext(observer, 150, Observable.create(new Observable.OnSubscribe() { + publishNext(observer, 150, Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber observer) { publishNext(observer, 50, "three"); @@ -364,10 +364,10 @@ public void call() { @Test public void testSwitchIssue737() { // https://github.com/ReactiveX/RxJava/issues/737 - Observable> source = Observable.create(new Observable.OnSubscribe>() { + Observable> source = Observable.unsafeCreate(new Observable.OnSubscribe>() { @Override public void call(Subscriber> observer) { - publishNext(observer, 0, Observable.create(new Observable.OnSubscribe() { + publishNext(observer, 0, Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber observer) { publishNext(observer, 10, "1-one"); @@ -377,7 +377,7 @@ public void call(Subscriber observer) { publishCompleted(observer, 40); } })); - publishNext(observer, 25, Observable.create(new Observable.OnSubscribe() { + publishNext(observer, 25, Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber observer) { publishNext(observer, 10, "2-one"); @@ -407,69 +407,69 @@ public void call(Subscriber observer) { @Test public void testBackpressure() { - final Observable o1 = Observable.create(new Observable.OnSubscribe() { + final Observable o1 = Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(final Subscriber observer) { observer.setProducer(new Producer() { - private int emitted = 0; + private int emitted; @Override public void request(long n) { - for(int i = 0; i < n && emitted < 10 && !observer.isUnsubscribed(); i++) { + for (int i = 0; i < n && emitted < 10 && !observer.isUnsubscribed(); i++) { scheduler.advanceTimeBy(5, TimeUnit.MILLISECONDS); emitted++; observer.onNext("a" + emitted); } - if(emitted == 10) { + if (emitted == 10) { observer.onCompleted(); } } }); } }); - final Observable o2 = Observable.create(new Observable.OnSubscribe() { + final Observable o2 = Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(final Subscriber observer) { observer.setProducer(new Producer() { - private int emitted = 0; + private int emitted; @Override public void request(long n) { - for(int i = 0; i < n && emitted < 10 && !observer.isUnsubscribed(); i++) { + for (int i = 0; i < n && emitted < 10 && !observer.isUnsubscribed(); i++) { scheduler.advanceTimeBy(5, TimeUnit.MILLISECONDS); emitted++; observer.onNext("b" + emitted); } - if(emitted == 10) { + if (emitted == 10) { observer.onCompleted(); } } }); } }); - final Observable o3 = Observable.create(new Observable.OnSubscribe() { + final Observable o3 = Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(final Subscriber observer) { observer.setProducer(new Producer() { - private int emitted = 0; + private int emitted; @Override public void request(long n) { - for(int i = 0; i < n && emitted < 10 && !observer.isUnsubscribed(); i++) { + for (int i = 0; i < n && emitted < 10 && !observer.isUnsubscribed(); i++) { emitted++; observer.onNext("c" + emitted); } - if(emitted == 10) { + if (emitted == 10) { observer.onCompleted(); } } }); } }); - Observable> o = Observable.create(new Observable.OnSubscribe>() { + Observable> o = Observable.unsafeCreate(new Observable.OnSubscribe>() { @Override public void call(Subscriber> observer) { publishNext(observer, 10, o1); @@ -481,7 +481,7 @@ public void call(Subscriber> observer) { final TestSubscriber testSubscriber = new TestSubscriber(); Observable.switchOnNext(o).subscribe(new Subscriber() { - private int requested = 0; + private int requested; @Override public void onStart() { @@ -503,7 +503,7 @@ public void onError(Throwable e) { public void onNext(String s) { testSubscriber.onNext(s); requested--; - if(requested == 0) { + if (requested == 0) { requested = 3; request(3); } @@ -519,7 +519,7 @@ public void onNext(String s) { public void testUnsubscribe() { final AtomicBoolean isUnsubscribed = new AtomicBoolean(); Observable.switchOnNext( - Observable.create(new Observable.OnSubscribe>() { + Observable.unsafeCreate(new Observable.OnSubscribe>() { @Override public void call(final Subscriber> subscriber) { subscriber.onNext(Observable.just(1)); @@ -533,7 +533,7 @@ public void call(final Subscriber> subscriber) { @Test public void testIssue2654() { Observable oneItem = Observable.just("Hello").mergeWith(Observable.never()); - + Observable src = oneItem.switchMap(new Func1>() { @Override public Observable call(final String s) { @@ -549,7 +549,7 @@ public String call(Long i) { }) .share() ; - + TestSubscriber ts = new TestSubscriber() { @Override public void onNext(String t) { @@ -561,17 +561,17 @@ public void onNext(String t) { } }; src.subscribe(ts); - + ts.awaitTerminalEvent(10, TimeUnit.SECONDS); - + System.out.println("> testIssue2654: " + ts.getOnNextEvents().size()); - + ts.assertTerminalEvent(); ts.assertNoErrors(); - + Assert.assertEquals(250, ts.getOnNextEvents().size()); } - + @Test(timeout = 10000) public void testInitialRequestsAreAdditive() { TestSubscriber ts = new TestSubscriber(0); @@ -590,7 +590,7 @@ public Observable call(Long t) { ts.requestMore(1); ts.awaitTerminalEvent(); } - + @Test(timeout = 10000) public void testInitialRequestsDontOverflow() { TestSubscriber ts = new TestSubscriber(0); @@ -607,8 +607,8 @@ public Observable call(Long t) { ts.awaitTerminalEvent(); assertTrue(ts.getOnNextEvents().size() > 0); } - - + + @Test(timeout = 10000) public void testSecondaryRequestsDontOverflow() throws InterruptedException { TestSubscriber ts = new TestSubscriber(0); @@ -628,7 +628,7 @@ public Observable call(Long t) { ts.awaitTerminalEvent(); ts.assertValueCount(7); } - + @Test(timeout = 10000) public void testSecondaryRequestsAdditivelyAreMoreThanLongMaxValueInducesMaxValueRequestFromUpstream() throws InterruptedException { @@ -657,26 +657,26 @@ public Observable call(Long t) { ts.awaitTerminalEvent(); assertTrue(ts.getOnNextEvents().size() > 0); assertEquals(4, requests.size()); // depends on the request pattern - assertEquals(Long.MAX_VALUE, (long) requests.get(requests.size()-1)); + assertEquals(Long.MAX_VALUE, (long) requests.get(requests.size() - 1)); } @Test public void mainError() { TestSubscriber ts = TestSubscriber.create(); - + PublishSubject source = PublishSubject.create(); - + source.switchMapDelayError(new Func1>() { @Override public Observable call(Integer v) { return Observable.range(v, 2); } }).subscribe(ts); - + source.onNext(1); source.onNext(2); source.onError(new TestException()); - + ts.assertValues(1, 2, 2, 3); ts.assertError(TestException.class); ts.assertNotCompleted(); @@ -685,14 +685,14 @@ public Observable call(Integer v) { @Test public void innerError() { TestSubscriber ts = TestSubscriber.create(); - + Observable.range(0, 3).switchMapDelayError(new Func1>() { @Override public Observable call(Integer v) { return v == 1 ? Observable.error(new TestException()) : Observable.range(v, 2); } }).subscribe(ts); - + ts.assertValues(0, 1, 2, 3); ts.assertError(TestException.class); ts.assertNotCompleted(); @@ -701,22 +701,22 @@ public Observable call(Integer v) { @Test public void innerAllError() { TestSubscriber ts = TestSubscriber.create(); - + Observable.range(0, 3).switchMapDelayError(new Func1>() { @Override public Observable call(Integer v) { return Observable.range(v, 2).concatWith(Observable.error(new TestException())); } }).subscribe(ts); - + ts.assertValues(0, 1, 1, 2, 2, 3); ts.assertError(CompositeException.class); ts.assertNotCompleted(); - + List exceptions = ((CompositeException)ts.getOnErrorEvents().get(0)).getExceptions(); - + assertEquals(3, exceptions.size()); - + for (Throwable ex : exceptions) { assertTrue(ex.toString(), ex instanceof TestException); } @@ -725,20 +725,20 @@ public Observable call(Integer v) { @Test public void backpressure() { TestSubscriber ts = TestSubscriber.create(0); - + Observable.range(0, 3).switchMapDelayError(new Func1>() { @Override public Observable call(Integer v) { return Observable.range(v, 2); } }).subscribe(ts); - + ts.assertNoValues(); ts.assertNoErrors(); ts.assertNotCompleted(); - + ts.requestMore(2); - + ts.assertValues(2, 3); ts.assertNoErrors(); ts.assertCompleted(); @@ -756,27 +756,27 @@ public Observable call(Integer v) { return Observable.range(v, 2); } }).subscribe(ts); - + ts.assertNoValues(); ts.assertNoErrors(); ts.assertNotCompleted(); - + ts.requestMore(1); - + source.onNext(0); - + ts.assertValues(0); ts.assertNoErrors(); ts.assertNotCompleted(); - + source.onNext(1); ts.assertValues(0); ts.assertNoErrors(); ts.assertNotCompleted(); - + ts.requestMore(1); - + ts.assertValues(0, 1); ts.assertNoErrors(); ts.assertNotCompleted(); @@ -786,22 +786,22 @@ public Observable call(Integer v) { ts.requestMore(2); source.onCompleted(); - + ts.assertValues(0, 1, 2, 3); ts.assertNoErrors(); ts.assertCompleted(); } Object ref; - + @Test public void producerIsNotRetained() throws Exception { ref = new Object(); - + WeakReference wr = new WeakReference(ref); - + PublishSubject> ps = PublishSubject.create(); - + Subscriber observer = new Subscriber() { @Override public void onCompleted() { @@ -815,17 +815,17 @@ public void onError(Throwable e) { public void onNext(Object t) { } }; - + Observable.switchOnNext(ps).subscribe(observer); - + ps.onNext(Observable.just(ref)); - + ref = null; - + System.gc(); - + Thread.sleep(500); - + Assert.assertNotNull(observer); // retain every other referenec in the pipeline Assert.assertNotNull(ps); Assert.assertNull("Object retained!", wr.get()); @@ -835,11 +835,11 @@ public void onNext(Object t) { public void switchAsyncHeavily() { for (int i = 1; i < 1024; i *= 2) { System.out.println("switchAsyncHeavily >> " + i); - + final Queue q = new ConcurrentLinkedQueue(); - + final long[] lastSeen = { 0L }; - + final int j = i; TestSubscriber ts = new TestSubscriber(i) { int count; @@ -853,7 +853,7 @@ public void onNext(Integer t) { } } }; - + Observable.range(1, 10000) .observeOn(Schedulers.computation(), i) .switchMap(new Func1>() { @@ -870,10 +870,12 @@ public void call(Throwable e) { }) .timeout(30, TimeUnit.SECONDS) .subscribe(ts); - + ts.awaitTerminalEvent(60, TimeUnit.SECONDS); if (!q.isEmpty()) { - throw new AssertionError("Dropped exceptions", new CompositeException(q)); + AssertionError ae = new AssertionError("Dropped exceptions"); + ae.initCause(new CompositeException(q)); + throw ae; } ts.assertNoErrors(); if (ts.getCompletions() == 0) { @@ -881,18 +883,18 @@ public void call(Throwable e) { } } } - + @Test public void asyncInner() throws Throwable { for (int i = 0; i < 100; i++) { - + final AtomicReference error = new AtomicReference(); - + Observable.just(Observable.range(1, 1000 * 1000).subscribeOn(Schedulers.computation())) .switchMap(UtilityFunctions.>identity()) .observeOn(Schedulers.computation()) .ignoreElements() - .timeout(5, TimeUnit.SECONDS) + .timeout(15, TimeUnit.SECONDS) .toBlocking() .subscribe(Actions.empty(), new Action1() { @Override @@ -900,7 +902,7 @@ public void call(Throwable e) { error.set(e); } }); - + Throwable ex = error.get(); if (ex != null) { throw ex; diff --git a/src/test/java/rx/internal/operators/OperatorTakeLastOneTest.java b/src/test/java/rx/internal/operators/OperatorTakeLastOneTest.java index 0ea5bc6be1..752d500895 100644 --- a/src/test/java/rx/internal/operators/OperatorTakeLastOneTest.java +++ b/src/test/java/rx/internal/operators/OperatorTakeLastOneTest.java @@ -88,7 +88,7 @@ public void testLastWithBackpressure() { s.requestMore(1); assertEquals(1, s.list.size()); } - + @Test public void testTakeLastZeroProcessesAllItemsButIgnoresThem() { final AtomicInteger upstreamCount = new AtomicInteger(); @@ -103,7 +103,7 @@ public void call(Integer t) { assertEquals(num, upstreamCount.get()); assertEquals(0, count); } - + private static class MySubscriber extends Subscriber { private long initialRequest; diff --git a/src/test/java/rx/internal/operators/OperatorTakeLastTest.java b/src/test/java/rx/internal/operators/OperatorTakeLastTest.java index 154b3067b0..1a2eddec64 100644 --- a/src/test/java/rx/internal/operators/OperatorTakeLastTest.java +++ b/src/test/java/rx/internal/operators/OperatorTakeLastTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -133,7 +133,7 @@ public void testBackpressure2() { private Func1 newSlowProcessor() { return new Func1() { - int c = 0; + int c; @Override public Integer call(Integer i) { @@ -263,7 +263,7 @@ public void onNext(Integer integer) { } }); } - + @Test public void testUnsubscribeTakesEffectEarlyOnFastPath() { final AtomicInteger count = new AtomicInteger(); @@ -291,8 +291,8 @@ public void onNext(Integer integer) { }); assertEquals(1,count.get()); } - - @Test(timeout=10000) + + @Test(timeout = 10000) public void testRequestOverflow() { final List list = new ArrayList(); Observable.range(1, 100).takeLast(50).subscribe(new Subscriber() { @@ -301,25 +301,25 @@ public void testRequestOverflow() { public void onStart() { request(2); } - + @Override public void onCompleted() { - + } @Override public void onError(Throwable e) { - + } @Override public void onNext(Integer t) { list.add(t); - request(Long.MAX_VALUE-1); + request(Long.MAX_VALUE - 1); }}); assertEquals(50, list.size()); } - + @Test(timeout = 30000) // original could get into an infinite loop public void completionRequestRace() { Worker w = Schedulers.computation().createWorker(); @@ -331,32 +331,32 @@ public void completionRequestRace() { } PublishSubject ps = PublishSubject.create(); final TestSubscriber ts = new TestSubscriber(0); - + ps.takeLast(n).subscribe(ts); - + for (int j = 0; j < n; j++) { ps.onNext(j); } final AtomicBoolean go = new AtomicBoolean(); - + w.schedule(new Action0() { @Override public void call() { - while (!go.get()); + while (!go.get()) { } ts.requestMore(n + 1); } }); - + go.set(true); ps.onCompleted(); - + ts.awaitTerminalEvent(1, TimeUnit.SECONDS); - + ts.assertValueCount(n); ts.assertNoErrors(); ts.assertCompleted(); - + List list = ts.getOnNextEvents(); for (int j = 0; j < n; j++) { Assert.assertEquals(j, list.get(j).intValue()); @@ -370,25 +370,84 @@ public void call() { @Test public void nullElements() { TestSubscriber ts = new TestSubscriber(0); - + Observable.from(new Integer[] { 1, null, 2}).takeLast(4) .subscribe(ts); - + ts.assertNoValues(); ts.assertNoErrors(); ts.assertNotCompleted(); - + ts.requestMore(1); - + ts.assertValue(1); ts.assertNoErrors(); ts.assertNotCompleted(); - + ts.requestMore(2); - + ts.assertValues(1, null, 2); ts.assertCompleted(); ts.assertNoErrors(); } + @Test + public void takeLastBuffer() { + TestSubscriber> ts = TestSubscriber.create(); + + Observable.range(1, 5).takeLastBuffer(1).subscribe(ts); + + ts.assertValue(Arrays.asList(5)); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @Test + public void takeLastBufferTimed() { + TestSubscriber> ts = TestSubscriber.create(); + + Observable.range(1, 5).takeLastBuffer(5, TimeUnit.SECONDS).subscribe(ts); + + ts.awaitTerminalEventAndUnsubscribeOnTimeout(5, TimeUnit.SECONDS); + ts.assertValue(Arrays.asList(1, 2, 3, 4, 5)); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @Test + public void takeLastBufferTimedSized() { + TestSubscriber> ts = TestSubscriber.create(); + + Observable.range(1, 5).takeLastBuffer(1, 5, TimeUnit.SECONDS).subscribe(ts); + + ts.awaitTerminalEventAndUnsubscribeOnTimeout(5, TimeUnit.SECONDS); + ts.assertValue(Arrays.asList(5)); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @Test + public void takeLastBufferTimedIO() { + TestSubscriber> ts = TestSubscriber.create(); + + Observable.range(1, 5).takeLastBuffer(5, TimeUnit.SECONDS, Schedulers.io()).subscribe(ts); + + ts.awaitTerminalEventAndUnsubscribeOnTimeout(5, TimeUnit.SECONDS); + ts.assertValue(Arrays.asList(1, 2, 3, 4, 5)); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @Test + public void takeLastBufferTimedSizedIO() { + TestSubscriber> ts = TestSubscriber.create(); + + Observable.range(1, 5).takeLastBuffer(1, 5, TimeUnit.SECONDS, Schedulers.io()).subscribe(ts); + + ts.awaitTerminalEventAndUnsubscribeOnTimeout(5, TimeUnit.SECONDS); + ts.assertValue(Arrays.asList(5)); + ts.assertNoErrors(); + ts.assertCompleted(); + } + } diff --git a/src/test/java/rx/internal/operators/OperatorTakeLastTimedTest.java b/src/test/java/rx/internal/operators/OperatorTakeLastTimedTest.java index c227339702..491825d3f9 100644 --- a/src/test/java/rx/internal/operators/OperatorTakeLastTimedTest.java +++ b/src/test/java/rx/internal/operators/OperatorTakeLastTimedTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -29,12 +29,12 @@ import org.junit.*; import org.mockito.InOrder; -import rx.Observable; -import rx.Observer; +import rx.*; import rx.Scheduler.Worker; import rx.exceptions.TestException; -import rx.functions.Action0; +import rx.functions.*; import rx.observers.TestSubscriber; +import rx.plugins.RxJavaHooks; import rx.schedulers.*; import rx.subjects.PublishSubject; @@ -225,32 +225,32 @@ public void completionRequestRace() { } PublishSubject ps = PublishSubject.create(); final TestSubscriber ts = new TestSubscriber(0); - + ps.takeLast(n, 1, TimeUnit.DAYS).subscribe(ts); - + for (int j = 0; j < n; j++) { ps.onNext(j); } final AtomicBoolean go = new AtomicBoolean(); - + w.schedule(new Action0() { @Override public void call() { - while (!go.get()); + while (!go.get()) { } ts.requestMore(n + 1); } }); - + go.set(true); ps.onCompleted(); - + ts.awaitTerminalEvent(1, TimeUnit.SECONDS); - + ts.assertValueCount(n); ts.assertNoErrors(); ts.assertCompleted(); - + List list = ts.getOnNextEvents(); for (int j = 0; j < n; j++) { Assert.assertEquals(j, list.get(j).intValue()); @@ -260,28 +260,64 @@ public void call() { w.unsubscribe(); } } - + @Test public void nullElements() { TestSubscriber ts = new TestSubscriber(0); - + Observable.from(new Integer[] { 1, null, 2}).takeLast(4, 1, TimeUnit.DAYS) .subscribe(ts); - + ts.assertNoValues(); ts.assertNoErrors(); ts.assertNotCompleted(); - + ts.requestMore(1); - + ts.assertValue(1); ts.assertNoErrors(); ts.assertNotCompleted(); - + ts.requestMore(2); - + ts.assertValues(1, null, 2); ts.assertCompleted(); ts.assertNoErrors(); } + + @Test + public void takeLastDefaultScheduler() { + final TestScheduler scheduler = new TestScheduler(); + + RxJavaHooks.setOnComputationScheduler(new Func1() { + @Override + public Scheduler call(Scheduler t) { + return scheduler; + } + }); + + try { + TestSubscriber ts = TestSubscriber.create(); + + PublishSubject ps = PublishSubject.create(); + + ps.takeLast(1, TimeUnit.SECONDS).subscribe(ts); + + ps.onNext(1); + ps.onNext(2); + ps.onNext(3); + + scheduler.advanceTimeBy(2, TimeUnit.SECONDS); + + ps.onNext(4); + ps.onNext(5); + ps.onCompleted(); + + ts.assertValues(4, 5); + ts.assertNoErrors(); + ts.assertCompleted(); + } finally { + RxJavaHooks.reset(); + } + } } diff --git a/src/test/java/rx/internal/operators/OperatorTakeTest.java b/src/test/java/rx/internal/operators/OperatorTakeTest.java index df23a64150..0885a8b343 100644 --- a/src/test/java/rx/internal/operators/OperatorTakeTest.java +++ b/src/test/java/rx/internal/operators/OperatorTakeTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -19,7 +19,7 @@ import static org.mockito.Matchers.*; import static org.mockito.Mockito.*; -import java.util.Arrays; +import java.util.*; import java.util.concurrent.*; import java.util.concurrent.atomic.*; @@ -27,10 +27,13 @@ import org.mockito.InOrder; import rx.*; +import rx.Observable; import rx.Observable.OnSubscribe; +import rx.Observer; import rx.exceptions.TestException; import rx.functions.*; import rx.observers.*; +import rx.plugins.RxJavaHooks; import rx.schedulers.Schedulers; import rx.subjects.PublishSubject; @@ -112,7 +115,7 @@ public Integer call(Integer t1) { @Test public void testTakeDoesntLeakErrors() { - Observable source = Observable.create(new Observable.OnSubscribe() { + Observable source = Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber observer) { observer.onNext("one"); @@ -136,7 +139,7 @@ public void call(Subscriber observer) { public void testTakeZeroDoesntLeakError() { final AtomicBoolean subscribed = new AtomicBoolean(false); final AtomicBoolean unSubscribed = new AtomicBoolean(false); - Observable source = Observable.create(new Observable.OnSubscribe() { + Observable source = Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber observer) { subscribed.set(true); @@ -173,14 +176,14 @@ public boolean isUnsubscribed() { public void testUnsubscribeAfterTake() { final Subscription s = mock(Subscription.class); TestObservableFunc f = new TestObservableFunc("one", "two", "three"); - Observable w = Observable.create(f); + Observable w = Observable.unsafeCreate(f); @SuppressWarnings("unchecked") Observer observer = mock(Observer.class); - + Subscriber subscriber = Subscribers.from(observer); subscriber.add(s); - + Observable take = w.lift(new OperatorTake(1)); take.subscribe(subscriber); @@ -218,7 +221,7 @@ public void call(Long l) { @Test(timeout = 2000) public void testMultiTake() { final AtomicInteger count = new AtomicInteger(); - Observable.create(new OnSubscribe() { + Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber s) { @@ -245,7 +248,7 @@ public void call(Integer t1) { private static class TestObservableFunc implements Observable.OnSubscribe { final String[] values; - Thread t = null; + Thread t; public TestObservableFunc(String... values) { this.values = values; @@ -277,7 +280,7 @@ public void run() { } } - private static Observable INFINITE_OBSERVABLE = Observable.create(new OnSubscribe() { + private static Observable INFINITE_OBSERVABLE = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber op) { @@ -289,29 +292,29 @@ public void call(Subscriber op) { } }); - + @Test(timeout = 2000) public void testTakeObserveOn() { @SuppressWarnings("unchecked") Observer o = mock(Observer.class); TestSubscriber ts = new TestSubscriber(o); - + 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).onCompleted(); verify(o, never()).onError(any(Throwable.class)); } - + @Test public void testProducerRequestThroughTake() { TestSubscriber ts = new TestSubscriber(); ts.requestMore(3); final AtomicLong requested = new AtomicLong(); - Observable.create(new OnSubscribe() { + Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber s) { @@ -328,13 +331,13 @@ public void request(long n) { }).take(3).subscribe(ts); assertEquals(3, requested.get()); } - + @Test public void testProducerRequestThroughTakeIsModified() { TestSubscriber ts = new TestSubscriber(); ts.requestMore(3); final AtomicLong requested = new AtomicLong(); - Observable.create(new OnSubscribe() { + Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber s) { @@ -351,7 +354,7 @@ public void request(long n) { }).take(1).subscribe(ts); assertEquals(1, requested.get()); } - + @Test public void testInterrupt() throws InterruptedException { final AtomicReference exception = new AtomicReference(); @@ -375,7 +378,7 @@ public void call(Integer t1) { latch.await(); assertNull(exception.get()); } - + @Test public void testDoesntRequestMoreThanNeededFromUpstream() throws InterruptedException { final AtomicLong requests = new AtomicLong(); @@ -400,40 +403,42 @@ public void call(Long n) { ts.assertNoErrors(); assertEquals(2,requests.get()); } - + @Test public void takeFinalValueThrows() { Observable source = Observable.just(1).take(1); - + TestSubscriber ts = new TestSubscriber() { @Override public void onNext(Integer t) { throw new TestException(); } }; - + source.subscribe(ts); - + ts.assertNoValues(); ts.assertError(TestException.class); ts.assertNotCompleted(); } - + @Test public void testReentrantTake() { final PublishSubject source = PublishSubject.create(); - + TestSubscriber ts = new TestSubscriber(); - - source.take(1).doOnNext(new Action1() { + + source + .rebatchRequests(2) // take(1) requests 1 + .take(1).doOnNext(new Action1() { @Override public void call(Integer v) { source.onNext(2); } }).subscribe(ts); - + source.onNext(1); - + ts.assertValue(1); ts.assertNoErrors(); ts.assertCompleted(); @@ -449,10 +454,45 @@ public void takeZero() { TestSubscriber ts = TestSubscriber.create(); Observable.range(1, 1000 * 1000 * 1000).take(0).subscribe(ts); - + ts.assertNoValues(); ts.assertNoErrors(); ts.assertCompleted(); } + @Test + public void crashReportedToHooks() { + final List errors = Collections.synchronizedList(new ArrayList()); + RxJavaHooks.setOnError(new Action1() { + @Override + public void call(Throwable error) { + errors.add(error); + } + }); + + try { + Observable.just("1") + .take(1) + .toSingle() + .subscribe( + new Action1() { + @Override + public void call(String it) { + throw new TestException("bla"); + } + }, + new Action1() { + @Override + public void call(Throwable error) { + errors.add(new AssertionError()); + } + } + ); + + assertEquals("" + errors, 1, errors.size()); + assertTrue("" + errors.get(0), errors.get(0).getMessage().equals("bla")); + } finally { + RxJavaHooks.setOnError(null); + } + } } diff --git a/src/test/java/rx/internal/operators/OperatorTakeTimedTest.java b/src/test/java/rx/internal/operators/OperatorTakeTimedTest.java index 214e295bd6..4b6f439515 100644 --- a/src/test/java/rx/internal/operators/OperatorTakeTimedTest.java +++ b/src/test/java/rx/internal/operators/OperatorTakeTimedTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -26,9 +26,11 @@ import org.junit.Test; import org.mockito.InOrder; -import rx.Observable; -import rx.Observer; +import rx.*; import rx.exceptions.TestException; +import rx.functions.Func1; +import rx.observers.TestSubscriber; +import rx.plugins.RxJavaHooks; import rx.schedulers.TestScheduler; import rx.subjects.PublishSubject; @@ -131,4 +133,42 @@ public void testTakeTimedErrorAfterTime() { verify(o, never()).onNext(4); verify(o, never()).onError(any(TestException.class)); } + + @Test + public void takeDefaultScheduler() { + final TestScheduler scheduler = new TestScheduler(); + + RxJavaHooks.setOnComputationScheduler(new Func1() { + @Override + public Scheduler call(Scheduler t) { + return scheduler; + } + }); + + try { + TestSubscriber ts = TestSubscriber.create(); + + PublishSubject ps = PublishSubject.create(); + + ps.take(1, TimeUnit.SECONDS).subscribe(ts); + + ps.onNext(1); + ps.onNext(2); + ps.onNext(3); + + scheduler.advanceTimeBy(1, TimeUnit.SECONDS); + + ps.onNext(4); + ps.onNext(5); + + scheduler.advanceTimeBy(1, TimeUnit.SECONDS); + ps.onCompleted(); + + ts.assertValues(1, 2, 3); + ts.assertNoErrors(); + ts.assertCompleted(); + } finally { + RxJavaHooks.reset(); + } + } } diff --git a/src/test/java/rx/internal/operators/OperatorTakeUntilPredicateTest.java b/src/test/java/rx/internal/operators/OperatorTakeUntilPredicateTest.java index 06b75c05f4..56df24c666 100644 --- a/src/test/java/rx/internal/operators/OperatorTakeUntilPredicateTest.java +++ b/src/test/java/rx/internal/operators/OperatorTakeUntilPredicateTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -37,9 +37,9 @@ public class OperatorTakeUntilPredicateTest { public void takeEmpty() { @SuppressWarnings("unchecked") Observer o = mock(Observer.class); - + Observable.empty().takeUntil(UtilityFunctions.alwaysTrue()).subscribe(o); - + verify(o, never()).onNext(any()); verify(o, never()).onError(any(Throwable.class)); verify(o).onCompleted(); @@ -48,9 +48,9 @@ public void takeEmpty() { public void takeAll() { @SuppressWarnings("unchecked") Observer o = mock(Observer.class); - + Observable.just(1, 2).takeUntil(UtilityFunctions.alwaysFalse()).subscribe(o); - + verify(o).onNext(1); verify(o).onNext(2); verify(o, never()).onError(any(Throwable.class)); @@ -60,9 +60,9 @@ public void takeAll() { public void takeFirst() { @SuppressWarnings("unchecked") Observer o = mock(Observer.class); - + Observable.just(1, 2).takeUntil(UtilityFunctions.alwaysTrue()).subscribe(o); - + verify(o).onNext(1); verify(o, never()).onNext(2); verify(o, never()).onError(any(Throwable.class)); @@ -72,14 +72,14 @@ public void takeFirst() { public void takeSome() { @SuppressWarnings("unchecked") Observer o = mock(Observer.class); - + Observable.just(1, 2, 3).takeUntil(new Func1() { @Override public Boolean call(Integer t1) { return t1 == 2; } }).subscribe(o); - + verify(o).onNext(1); verify(o).onNext(2); verify(o, never()).onNext(3); @@ -90,14 +90,14 @@ public Boolean call(Integer t1) { public void functionThrows() { @SuppressWarnings("unchecked") Observer o = mock(Observer.class); - + Observable.just(1, 2, 3).takeUntil(new Func1() { @Override public Boolean call(Integer t1) { throw new TestException("Forced failure"); } }).subscribe(o); - + verify(o).onNext(1); verify(o, never()).onNext(2); verify(o, never()).onNext(3); @@ -108,12 +108,12 @@ public Boolean call(Integer t1) { public void sourceThrows() { @SuppressWarnings("unchecked") Observer o = mock(Observer.class); - + Observable.just(1) .concatWith(Observable.error(new TestException())) .concatWith(Observable.just(2)) .takeUntil(UtilityFunctions.alwaysFalse()).subscribe(o); - + verify(o).onNext(1); verify(o, never()).onNext(2); verify(o).onError(any(TestException.class)); @@ -127,14 +127,14 @@ public void onStart() { request(5); } }; - + Observable.range(1, 1000).takeUntil(UtilityFunctions.alwaysFalse()).subscribe(ts); - + ts.assertNoErrors(); ts.assertReceivedOnNext(Arrays.asList(1, 2, 3, 4, 5)); Assert.assertEquals(0, ts.getCompletions()); } - + @Test public void testErrorIncludesLastValueAsCause() { TestSubscriber ts = new TestSubscriber(); diff --git a/src/test/java/rx/internal/operators/OperatorTakeUntilTest.java b/src/test/java/rx/internal/operators/OperatorTakeUntilTest.java index 5ffada913e..5c590c68c5 100644 --- a/src/test/java/rx/internal/operators/OperatorTakeUntilTest.java +++ b/src/test/java/rx/internal/operators/OperatorTakeUntilTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -40,7 +40,7 @@ public void testTakeUntil() { TestObservable other = new TestObservable(sOther); Observer result = mock(Observer.class); - Observable stringObservable = Observable.create(source).takeUntil(Observable.create(other)); + Observable stringObservable = Observable.unsafeCreate(source).takeUntil(Observable.unsafeCreate(other)); stringObservable.subscribe(result); source.sendOnNext("one"); source.sendOnNext("two"); @@ -67,7 +67,7 @@ public void testTakeUntilSourceCompleted() { TestObservable other = new TestObservable(sOther); Observer result = mock(Observer.class); - Observable stringObservable = Observable.create(source).takeUntil(Observable.create(other)); + Observable stringObservable = Observable.unsafeCreate(source).takeUntil(Observable.unsafeCreate(other)); stringObservable.subscribe(result); source.sendOnNext("one"); source.sendOnNext("two"); @@ -90,7 +90,7 @@ public void testTakeUntilSourceError() { Throwable error = new Throwable(); Observer result = mock(Observer.class); - Observable stringObservable = Observable.create(source).takeUntil(Observable.create(other)); + Observable stringObservable = Observable.unsafeCreate(source).takeUntil(Observable.unsafeCreate(other)); stringObservable.subscribe(result); source.sendOnNext("one"); source.sendOnNext("two"); @@ -116,7 +116,7 @@ public void testTakeUntilOtherError() { Throwable error = new Throwable(); Observer result = mock(Observer.class); - Observable stringObservable = Observable.create(source).takeUntil(Observable.create(other)); + Observable stringObservable = Observable.unsafeCreate(source).takeUntil(Observable.unsafeCreate(other)); stringObservable.subscribe(result); source.sendOnNext("one"); source.sendOnNext("two"); @@ -145,7 +145,7 @@ public void testTakeUntilOtherCompleted() { TestObservable other = new TestObservable(sOther); Observer result = mock(Observer.class); - Observable stringObservable = Observable.create(source).takeUntil(Observable.create(other)); + Observable stringObservable = Observable.unsafeCreate(source).takeUntil(Observable.unsafeCreate(other)); stringObservable.subscribe(result); source.sendOnNext("one"); source.sendOnNext("two"); @@ -163,7 +163,7 @@ public void testTakeUntilOtherCompleted() { private static class TestObservable implements Observable.OnSubscribe { - Observer observer = null; + Observer observer; Subscription s; public TestObservable(Subscription s) { @@ -191,28 +191,28 @@ public void call(Subscriber observer) { observer.add(s); } } - + @Test public void testUntilFires() { PublishSubject source = PublishSubject.create(); PublishSubject until = PublishSubject.create(); - + TestSubscriber ts = new TestSubscriber(); - + source.takeUntil(until).unsafeSubscribe(ts); assertTrue(source.hasObservers()); assertTrue(until.hasObservers()); source.onNext(1); - + ts.assertReceivedOnNext(Arrays.asList(1)); until.onNext(1); - + ts.assertReceivedOnNext(Arrays.asList(1)); ts.assertNoErrors(); ts.assertTerminalEvent(); - + assertFalse("Source still has observers", source.hasObservers()); assertFalse("Until still has observers", until.hasObservers()); assertFalse("TestSubscriber is unsubscribed", ts.isUnsubscribed()); @@ -221,9 +221,9 @@ public void testUntilFires() { public void testMainCompletes() { PublishSubject source = PublishSubject.create(); PublishSubject until = PublishSubject.create(); - + TestSubscriber ts = new TestSubscriber(); - + source.takeUntil(until).unsafeSubscribe(ts); assertTrue(source.hasObservers()); @@ -231,11 +231,11 @@ public void testMainCompletes() { source.onNext(1); source.onCompleted(); - + ts.assertReceivedOnNext(Arrays.asList(1)); ts.assertNoErrors(); ts.assertTerminalEvent(); - + assertFalse("Source still has observers", source.hasObservers()); assertFalse("Until still has observers", until.hasObservers()); assertFalse("TestSubscriber is unsubscribed", ts.isUnsubscribed()); @@ -244,44 +244,44 @@ public void testMainCompletes() { public void testDownstreamUnsubscribes() { PublishSubject source = PublishSubject.create(); PublishSubject until = PublishSubject.create(); - + TestSubscriber ts = new TestSubscriber(); - + source.takeUntil(until).take(1).unsafeSubscribe(ts); assertTrue(source.hasObservers()); assertTrue(until.hasObservers()); source.onNext(1); - + ts.assertReceivedOnNext(Arrays.asList(1)); ts.assertNoErrors(); ts.assertTerminalEvent(); - + assertFalse("Source still has observers", source.hasObservers()); assertFalse("Until still has observers", until.hasObservers()); assertFalse("TestSubscriber is unsubscribed", ts.isUnsubscribed()); } public void testBackpressure() { PublishSubject until = PublishSubject.create(); - + TestSubscriber ts = new TestSubscriber() { @Override public void onStart() { requestMore(0); } }; - + Observable.range(1, 10).takeUntil(until).unsafeSubscribe(ts); assertTrue(until.hasObservers()); ts.requestMore(1); - + ts.assertReceivedOnNext(Arrays.asList(1)); ts.assertNoErrors(); assertEquals("TestSubscriber completed", 0, ts.getCompletions()); - + assertFalse("Until still has observers", until.hasObservers()); assertFalse("TestSubscriber is unsubscribed", ts.isUnsubscribed()); } diff --git a/src/test/java/rx/internal/operators/OperatorTakeWhileTest.java b/src/test/java/rx/internal/operators/OperatorTakeWhileTest.java index 8317e41b65..2537e0e807 100644 --- a/src/test/java/rx/internal/operators/OperatorTakeWhileTest.java +++ b/src/test/java/rx/internal/operators/OperatorTakeWhileTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -87,7 +87,7 @@ public Boolean call(Integer input) { public void testTakeWhile2() { Observable w = Observable.just("one", "two", "three"); Observable take = w.takeWhile(new Func1() { - int index = 0; + int index; @Override public Boolean call(String input) { @@ -107,7 +107,7 @@ public Boolean call(String input) { @Test public void testTakeWhileDoesntLeakErrors() { - Observable source = Observable.create(new OnSubscribe() { + Observable source = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber observer) { observer.onNext("one"); @@ -130,7 +130,7 @@ public void testTakeWhileProtectsPredicateCall() { @SuppressWarnings("unchecked") Observer observer = mock(Observer.class); - Observable take = Observable.create(source).takeWhile(new Func1() { + Observable take = Observable.unsafeCreate(source).takeWhile(new Func1() { @Override public Boolean call(String s) { throw testException; @@ -157,8 +157,8 @@ public void testUnsubscribeAfterTake() { @SuppressWarnings("unchecked") Observer observer = mock(Observer.class); - Observable take = Observable.create(w).takeWhile(new Func1() { - int index = 0; + Observable take = Observable.unsafeCreate(w).takeWhile(new Func1() { + int index; @Override public Boolean call(String s) { @@ -186,7 +186,7 @@ private static class TestObservable implements Observable.OnSubscribe { final Subscription s; final String[] values; - Thread t = null; + Thread t; public TestObservable(Subscription s, String... values) { this.s = s; @@ -219,7 +219,7 @@ public void run() { System.out.println("done starting TestObservable thread"); } } - + @Test public void testBackpressure() { Observable source = Observable.range(1, 1000).takeWhile(new Func1() { @@ -234,18 +234,18 @@ public void onStart() { request(5); } }; - + source.subscribe(ts); - + ts.assertNoErrors(); ts.assertReceivedOnNext(Arrays.asList(1, 2, 3, 4, 5)); - + ts.requestMore(5); ts.assertNoErrors(); ts.assertReceivedOnNext(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)); } - + @Test public void testNoUnsubscribeDownstream() { Observable source = Observable.range(1, 1000).takeWhile(new Func1() { @@ -255,15 +255,15 @@ public Boolean call(Integer t1) { } }); TestSubscriber ts = new TestSubscriber(); - + source.unsafeSubscribe(ts); - + ts.assertNoErrors(); ts.assertReceivedOnNext(Arrays.asList(1)); - + Assert.assertFalse("Unsubscribed!", ts.isUnsubscribed()); } - + @Test public void testErrorCauseIncludesLastValue() { TestSubscriber ts = new TestSubscriber(); @@ -273,10 +273,10 @@ public Boolean call(String t1) { throw new TestException(); } }).subscribe(ts); - + ts.assertTerminalEvent(); ts.assertNoValues(); assertTrue(ts.getOnErrorEvents().get(0).getCause().getMessage().contains("abc")); } - + } diff --git a/src/test/java/rx/internal/operators/OperatorThrottleFirstTest.java b/src/test/java/rx/internal/operators/OperatorThrottleFirstTest.java index 0090d57546..f0fc6527a6 100644 --- a/src/test/java/rx/internal/operators/OperatorThrottleFirstTest.java +++ b/src/test/java/rx/internal/operators/OperatorThrottleFirstTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -16,23 +16,18 @@ package rx.internal.operators; import static org.mockito.Matchers.any; -import static org.mockito.Mockito.inOrder; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; +import static org.mockito.Mockito.*; import java.util.concurrent.TimeUnit; -import org.junit.Before; -import org.junit.Test; +import org.junit.*; import org.mockito.InOrder; -import rx.Observable; +import rx.*; import rx.Observable.OnSubscribe; -import rx.Observer; -import rx.Scheduler; -import rx.Subscriber; import rx.exceptions.TestException; import rx.functions.Action0; +import rx.observers.*; import rx.schedulers.TestScheduler; import rx.subjects.PublishSubject; @@ -52,7 +47,7 @@ public void before() { @Test public void testThrottlingWithCompleted() { - Observable source = Observable.create(new OnSubscribe() { + Observable source = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber observer) { publishNext(observer, 100, "one"); // publish as it's first @@ -79,7 +74,7 @@ public void call(Subscriber observer) { @Test public void testThrottlingWithError() { - Observable source = Observable.create(new OnSubscribe() { + Observable source = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber observer) { Exception error = new TestException(); @@ -158,4 +153,81 @@ public void testThrottle() { inOrder.verify(observer).onCompleted(); inOrder.verifyNoMoreInteractions(); } + + @Test + public void timed() { + + TestSubscriber ts = TestSubscriber.create(); + + Observable.range(1, 2).throttleFirst(1, TimeUnit.SECONDS).subscribe(ts); + + ts.awaitTerminalEventAndUnsubscribeOnTimeout(5, TimeUnit.SECONDS); + ts.assertValue(1); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @Test + public void throttleWithoutAdvancingTimeOfTestScheduler() { + @SuppressWarnings("unchecked") + Observer observer = mock(Observer.class); + TestScheduler s = new TestScheduler(); + PublishSubject o = PublishSubject.create(); + o.throttleFirst(500, TimeUnit.MILLISECONDS, s).subscribe(observer); + + // send events without calling advanceTimeBy/To + o.onNext(1); // deliver + o.onNext(2); // skip + o.onNext(3); // skip + o.onCompleted(); + + verify(observer).onNext(1); + verify(observer).onCompleted(); + verifyNoMoreInteractions(observer); + } + + @Test + public void throttleWithTestSchedulerTimeOfZero() { + @SuppressWarnings("unchecked") + Observer observer = mock(Observer.class); + TestScheduler s = new TestScheduler(); + PublishSubject o = PublishSubject.create(); + o.throttleFirst(500, TimeUnit.MILLISECONDS, s).subscribe(observer); + + s.advanceTimeBy(0, TimeUnit.MILLISECONDS); + + // send events while TestScheduler's time is 0 + o.onNext(1); // deliver + o.onNext(2); // skip + o.onNext(3); // skip + o.onCompleted(); + + verify(observer).onNext(1); + verify(observer).onCompleted(); + verifyNoMoreInteractions(observer); + } + + @Test + public void nowDrift() { + TestScheduler s = new TestScheduler(); + s.advanceTimeBy(2, TimeUnit.SECONDS); + + PublishSubject o = PublishSubject.create(); + + AssertableSubscriber as = o.throttleFirst(500, TimeUnit.MILLISECONDS, s) + .test(); + + o.onNext(1); + s.advanceTimeBy(100, TimeUnit.MILLISECONDS); + o.onNext(2); + s.advanceTimeBy(100, TimeUnit.MILLISECONDS); + o.onNext(3); + s.advanceTimeBy(-1000, TimeUnit.MILLISECONDS); + o.onNext(4); + s.advanceTimeBy(100, TimeUnit.MILLISECONDS); + o.onNext(5); + o.onCompleted(); + + as.assertResult(1, 4); + } } diff --git a/src/test/java/rx/internal/operators/OperatorTimeIntervalTest.java b/src/test/java/rx/internal/operators/OperatorTimeIntervalTest.java index 68c91bbd07..bf4fd4735a 100644 --- a/src/test/java/rx/internal/operators/OperatorTimeIntervalTest.java +++ b/src/test/java/rx/internal/operators/OperatorTimeIntervalTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -26,8 +26,9 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import rx.Observable; -import rx.Observer; +import rx.*; +import rx.functions.Func1; +import rx.plugins.RxJavaHooks; import rx.schedulers.TestScheduler; import rx.schedulers.TimeInterval; import rx.subjects.PublishSubject; @@ -73,4 +74,41 @@ public void testTimeInterval() { inOrder.verify(observer, times(1)).onCompleted(); inOrder.verifyNoMoreInteractions(); } + + @Test + public void withDefaultScheduler() { + + final TestScheduler scheduler = new TestScheduler(); + + RxJavaHooks.setOnComputationScheduler(new Func1() { + @Override + public Scheduler call(Scheduler t) { + return scheduler; + } + }); + + try { + InOrder inOrder = inOrder(observer); + subject.timeInterval().subscribe(observer); + + scheduler.advanceTimeBy(1000, TIME_UNIT); + subject.onNext(1); + scheduler.advanceTimeBy(2000, TIME_UNIT); + subject.onNext(2); + scheduler.advanceTimeBy(3000, TIME_UNIT); + subject.onNext(3); + subject.onCompleted(); + + inOrder.verify(observer, times(1)).onNext( + new TimeInterval(1000, 1)); + inOrder.verify(observer, times(1)).onNext( + new TimeInterval(2000, 2)); + inOrder.verify(observer, times(1)).onNext( + new TimeInterval(3000, 3)); + inOrder.verify(observer, times(1)).onCompleted(); + inOrder.verifyNoMoreInteractions(); + } finally { + RxJavaHooks.reset(); + } + } } diff --git a/src/test/java/rx/internal/operators/OperatorTimeoutTests.java b/src/test/java/rx/internal/operators/OperatorTimeoutTests.java index 4c2ae8c75b..6be7706d0f 100644 --- a/src/test/java/rx/internal/operators/OperatorTimeoutTests.java +++ b/src/test/java/rx/internal/operators/OperatorTimeoutTests.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -15,31 +15,26 @@ */ package rx.internal.operators; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.isA; -import static org.mockito.Mockito.inOrder; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; +import static org.junit.Assert.assertEquals; +import static org.mockito.Matchers.*; +import static org.mockito.Mockito.*; import java.io.IOException; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; +import java.util.*; +import java.util.concurrent.*; -import org.junit.Before; -import org.junit.Test; -import org.mockito.InOrder; -import org.mockito.MockitoAnnotations; +import org.junit.*; +import org.mockito.*; +import rx.*; import rx.Observable; import rx.Observable.OnSubscribe; import rx.Observer; -import rx.Subscriber; -import rx.Subscription; +import rx.exceptions.TestException; +import rx.functions.*; +import rx.observers.*; import rx.schedulers.TestScheduler; -import rx.subjects.PublishSubject; +import rx.subjects.*; public class OperatorTimeoutTests { private PublishSubject underlyingSubject; @@ -244,7 +239,7 @@ public void shouldTimeoutIfSynchronizedObservableEmitFirstOnNextNotWithinTimeout @Override public void run() { - Observable.create(new OnSubscribe() { + Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber subscriber) { @@ -278,7 +273,7 @@ public void shouldUnsubscribeFromUnderlyingSubscriptionOnTimeout() throws Interr // From https://github.com/ReactiveX/RxJava/pull/951 final Subscription s = mock(Subscription.class); - Observable never = Observable.create(new OnSubscribe() { + Observable never = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber subscriber) { subscriber.add(s); @@ -306,7 +301,7 @@ public void shouldUnsubscribeFromUnderlyingSubscriptionOnImmediatelyComplete() { // From https://github.com/ReactiveX/RxJava/pull/951 final Subscription s = mock(Subscription.class); - Observable immediatelyComplete = Observable.create(new OnSubscribe() { + Observable immediatelyComplete = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber subscriber) { subscriber.add(s); @@ -336,7 +331,7 @@ public void shouldUnsubscribeFromUnderlyingSubscriptionOnImmediatelyErrored() th // From https://github.com/ReactiveX/RxJava/pull/951 final Subscription s = mock(Subscription.class); - Observable immediatelyError = Observable.create(new OnSubscribe() { + Observable immediatelyError = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber subscriber) { subscriber.add(s); @@ -360,4 +355,148 @@ public void call(Subscriber subscriber) { verify(s, times(1)).unsubscribe(); } + + @Test + public void withDefaultScheduler() { + + TestSubscriber ts = TestSubscriber.create(); + + Observable.just(1).timeout(5, TimeUnit.SECONDS).subscribe(ts); + + ts.assertValue(1); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @Test + public void withSelector() { + + TestSubscriber ts = TestSubscriber.create(); + + Observable.just(1).timeout(new Func1>() { + @Override + public Observable call(Integer t) { + return Observable.never(); + } + }).subscribe(ts); + + ts.assertValue(1); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @Test + public void withSelectorAndDefault() { + + TestSubscriber ts = TestSubscriber.create(); + + Observable.just(1).timeout(new Func1>() { + @Override + public Observable call(Integer t) { + return Observable.never(); + } + }, Observable.just(2)).subscribe(ts); + + ts.assertValue(1); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @Test + public void withSelectorAndDefault2() { + + TestSubscriber ts = TestSubscriber.create(); + + Observable.just(1).concatWith( + Observable.never()) + .timeout(new Func1>() { + @Override + public Observable call(Integer t) { + return Observable.just((Object)1); + } + }, Observable.just(2)).subscribe(ts); + + ts.assertValues(1, 2); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @Test + public void withDefaultSchedulerAndOther() { + + TestSubscriber ts = TestSubscriber.create(); + + Observable.just(1).timeout(5, TimeUnit.SECONDS, Observable.just(2)).subscribe(ts); + + ts.assertValue(1); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @Test + public void disconnectOnTimeout() { + final List list = Collections.synchronizedList(new ArrayList()); + + TestScheduler sch = new TestScheduler(); + + Subject subject = PublishSubject.create(); + Observable initialObservable = subject.share() + .map(new Func1() { + @Override + public Long call(Long value) { + list.add("Received value " + value); + return value; + } + }); + + Observable timeoutObservable = initialObservable + .map(new Func1() { + @Override + public Long call(Long value) { + list.add("Timeout received value " + value); + return value; + } + }); + + TestSubscriber subscriber = new TestSubscriber(); + initialObservable + .doOnUnsubscribe(new Action0() { + @Override + public void call() { + list.add("Unsubscribed"); + } + }) + .timeout(1, TimeUnit.SECONDS, timeoutObservable, sch).subscribe(subscriber); + + subject.onNext(5L); + + sch.advanceTimeBy(2, TimeUnit.SECONDS); + + subject.onNext(10L); + subject.onCompleted(); + + subscriber.awaitTerminalEvent(); + subscriber.assertNoErrors(); + subscriber.assertValues(5L, 10L); + + assertEquals(Arrays.asList( + "Received value 5", + "Unsubscribed", + "Received value 10", + "Timeout received value 10" + ), list); + } + + @Test + public void fallbackIsError() { + TestScheduler sch = new TestScheduler(); + + AssertableSubscriber as = Observable.never() + .timeout(1, TimeUnit.SECONDS, Observable.error(new TestException()), sch) + .test(); + + sch.advanceTimeBy(1, TimeUnit.SECONDS); + + as.assertFailure(TestException.class); + } } diff --git a/src/test/java/rx/internal/operators/OperatorTimeoutWithSelectorTest.java b/src/test/java/rx/internal/operators/OperatorTimeoutWithSelectorTest.java index 51670b1b7a..628d1eca55 100644 --- a/src/test/java/rx/internal/operators/OperatorTimeoutWithSelectorTest.java +++ b/src/test/java/rx/internal/operators/OperatorTimeoutWithSelectorTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -15,19 +15,13 @@ */ package rx.internal.operators; -import static org.junit.Assert.assertFalse; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.isA; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.inOrder; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; - -import java.util.Arrays; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; +import static org.junit.Assert.*; +import static org.mockito.Matchers.*; +import static org.mockito.Mockito.*; + +import java.io.IOException; +import java.util.*; +import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicBoolean; import org.junit.Test; @@ -40,11 +34,10 @@ import rx.Observer; import rx.Subscriber; import rx.exceptions.TestException; -import rx.functions.Func0; -import rx.functions.Func1; -import rx.observers.TestSubscriber; -import rx.schedulers.Schedulers; -import rx.subjects.PublishSubject; +import rx.functions.*; +import rx.observers.*; +import rx.schedulers.*; +import rx.subjects.*; public class OperatorTimeoutWithSelectorTest { @Test(timeout = 2000) @@ -114,9 +107,9 @@ public Observable call() { InOrder inOrder = inOrder(o); source.timeout(firstTimeoutFunc, timeoutFunc, other).subscribe(o); - + timeout.onNext(1); - + inOrder.verify(o).onNext(100); inOrder.verify(o).onCompleted(); verify(o, never()).onError(any(Throwable.class)); @@ -344,7 +337,7 @@ public void testTimeoutSelectorWithTimeoutAndOnNextRaceCondition() throws Interr public Observable call(Integer t1) { if (t1 == 1) { // Force "unsubscribe" run on another thread - return Observable.create(new OnSubscribe() { + return Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber subscriber) { enteredTimeoutOne.countDown(); @@ -403,7 +396,7 @@ public void run() { source.timeout(timeoutFunc, Observable.just(3)).subscribe(ts); source.onNext(1); // start timeout try { - if(!enteredTimeoutOne.await(30, TimeUnit.SECONDS)) { + if (!enteredTimeoutOne.await(30, TimeUnit.SECONDS)) { latchTimeout.set(true); } } catch (InterruptedException e) { @@ -411,7 +404,7 @@ public void run() { } source.onNext(2); // disable timeout try { - if(!timeoutEmittedOne.await(30, TimeUnit.SECONDS)) { + if (!timeoutEmittedOne.await(30, TimeUnit.SECONDS)) { latchTimeout.set(true); } } catch (InterruptedException e) { @@ -422,7 +415,7 @@ public void run() { }).start(); - if(!observerCompleted.await(30, TimeUnit.SECONDS)) { + if (!observerCompleted.await(30, TimeUnit.SECONDS)) { latchTimeout.set(true); } @@ -435,4 +428,183 @@ public void run() { inOrder.verify(o).onCompleted(); inOrder.verifyNoMoreInteractions(); } + + @Test + public void selectorNull() { + try { + Observable.never().timeout(new Func0>() { + @Override + public Observable call() { + return Observable.never(); + } + }, null, Observable.empty()); + } catch (NullPointerException ex) { + assertEquals("timeoutSelector is null", ex.getMessage()); + } + } + + @Test + public void disconnectOnTimeout() { + final List list = Collections.synchronizedList(new ArrayList()); + + final TestScheduler sch = new TestScheduler(); + + Subject subject = PublishSubject.create(); + Observable initialObservable = subject.share() + .map(new Func1() { + @Override + public Long call(Long value) { + list.add("Received value " + value); + return value; + } + }); + + Observable timeoutObservable = initialObservable + .map(new Func1() { + @Override + public Long call(Long value) { + list.add("Timeout received value " + value); + return value; + } + }); + + TestSubscriber subscriber = new TestSubscriber(); + initialObservable + .doOnUnsubscribe(new Action0() { + @Override + public void call() { + list.add("Unsubscribed"); + } + }) + .timeout( + new Func0>() { + @Override + public Observable call() { + return Observable.timer(1, TimeUnit.SECONDS, sch); + } + }, + new Func1>() { + @Override + public Observable call(Long v) { + return Observable.timer(1, TimeUnit.SECONDS, sch); + } + }, + timeoutObservable).subscribe(subscriber); + + subject.onNext(5L); + + sch.advanceTimeBy(2, TimeUnit.SECONDS); + + subject.onNext(10L); + subject.onCompleted(); + + subscriber.awaitTerminalEvent(); + subscriber.assertNoErrors(); + subscriber.assertValues(5L, 10L); + + assertEquals(Arrays.asList( + "Received value 5", + "Unsubscribed", + "Received value 10", + "Timeout received value 10" + ), list); + } + + @Test + public void fallbackIsError() { + final TestScheduler sch = new TestScheduler(); + + AssertableSubscriber as = Observable.never() + .timeout(new Func0>() { + @Override + public Observable call() { + return Observable.timer(1, TimeUnit.SECONDS, sch); + } + }, + new Func1>() { + @Override + public Observable call(Object v) { + return Observable.timer(1, TimeUnit.SECONDS, sch); + } + }, Observable.error(new TestException())) + .test(); + + sch.advanceTimeBy(1, TimeUnit.SECONDS); + + as.assertFailure(TestException.class); + } + + @Test + public void mainErrors() { + final TestScheduler sch = new TestScheduler(); + + AssertableSubscriber as = Observable.error(new IOException()) + .timeout(new Func0>() { + @Override + public Observable call() { + return Observable.timer(1, TimeUnit.SECONDS, sch); + } + }, + new Func1>() { + @Override + public Observable call(Object v) { + return Observable.timer(1, TimeUnit.SECONDS, sch); + } + }, Observable.error(new TestException())) + .test(); + + as.assertFailure(IOException.class); + + sch.advanceTimeBy(1, TimeUnit.SECONDS); + + as.assertFailure(IOException.class); + } + + @Test + public void timeoutCompletesWithFallback() { + final TestScheduler sch = new TestScheduler(); + + AssertableSubscriber as = Observable.never() + .timeout(new Func0>() { + @Override + public Observable call() { + return Observable.timer(1, TimeUnit.SECONDS, sch).ignoreElements(); + } + }, + new Func1>() { + @Override + public Observable call(Object v) { + return Observable.timer(1, TimeUnit.SECONDS, sch); + } + }, Observable.just(1)) + .test(); + + sch.advanceTimeBy(1, TimeUnit.SECONDS); + + as.assertResult(1); + } + + @Test + public void nullItemTimeout() { + final TestScheduler sch = new TestScheduler(); + + AssertableSubscriber as = Observable.just(1).concatWith(Observable.never()) + .timeout(new Func0>() { + @Override + public Observable call() { + return Observable.timer(1, TimeUnit.SECONDS, sch).ignoreElements(); + } + }, + new Func1>() { + @Override + public Observable call(Object v) { + return null; + } + }, Observable.just(1)) + .test(); + + sch.advanceTimeBy(1, TimeUnit.SECONDS); + + as.assertFailureAndMessage(NullPointerException.class, "The itemTimeoutIndicator returned a null Observable", 1); + } } diff --git a/src/test/java/rx/internal/operators/OperatorTimestampTest.java b/src/test/java/rx/internal/operators/OperatorTimestampTest.java index bf39ae299a..11f3fb934f 100644 --- a/src/test/java/rx/internal/operators/OperatorTimestampTest.java +++ b/src/test/java/rx/internal/operators/OperatorTimestampTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -16,23 +16,17 @@ package rx.internal.operators; import static org.mockito.Matchers.any; -import static org.mockito.Mockito.inOrder; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.*; import java.util.concurrent.TimeUnit; -import org.junit.Before; -import org.junit.Test; -import org.mockito.InOrder; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; +import org.junit.*; +import org.mockito.*; -import rx.Observable; -import rx.Observer; -import rx.schedulers.TestScheduler; -import rx.schedulers.Timestamped; +import rx.*; +import rx.functions.Func1; +import rx.plugins.RxJavaHooks; +import rx.schedulers.*; import rx.subjects.PublishSubject; public class OperatorTimestampTest { @@ -91,4 +85,39 @@ public void timestampWithScheduler2() { verify(observer, never()).onError(any(Throwable.class)); verify(observer, never()).onCompleted(); } + + @Test + public void withDefaultScheduler() { + final TestScheduler scheduler = new TestScheduler(); + + RxJavaHooks.setOnComputationScheduler(new Func1() { + @Override + public Scheduler call(Scheduler t) { + return scheduler; + } + }); + + try { + PublishSubject source = PublishSubject.create(); + Observable> m = source.timestamp(); + m.subscribe(observer); + + source.onNext(1); + scheduler.advanceTimeBy(100, TimeUnit.MILLISECONDS); + source.onNext(2); + scheduler.advanceTimeBy(100, TimeUnit.MILLISECONDS); + source.onNext(3); + + InOrder inOrder = inOrder(observer); + + inOrder.verify(observer, times(1)).onNext(new Timestamped(0, 1)); + inOrder.verify(observer, times(1)).onNext(new Timestamped(100, 2)); + inOrder.verify(observer, times(1)).onNext(new Timestamped(200, 3)); + + verify(observer, never()).onError(any(Throwable.class)); + verify(observer, never()).onCompleted(); + } finally { + RxJavaHooks.reset(); + } + } } diff --git a/src/test/java/rx/internal/operators/OperatorToObservableListTest.java b/src/test/java/rx/internal/operators/OperatorToObservableListTest.java index 1bf0041d57..0f7c05d4f3 100644 --- a/src/test/java/rx/internal/operators/OperatorToObservableListTest.java +++ b/src/test/java/rx/internal/operators/OperatorToObservableListTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -50,7 +50,7 @@ public void testList() { verify(observer, Mockito.never()).onError(any(Throwable.class)); verify(observer, times(1)).onCompleted(); } - + @Test public void testListViaObservable() { Observable w = Observable.from(Arrays.asList("one", "two", "three")); @@ -116,15 +116,15 @@ public void onStart() { requestMore(0); } }; - + w.subscribe(ts); - + assertTrue(ts.getOnNextEvents().isEmpty()); assertTrue(ts.getOnErrorEvents().isEmpty()); assertEquals(0, ts.getCompletions()); - + ts.requestMore(1); - + ts.assertReceivedOnNext(Collections.singletonList(Arrays.asList(1, 2, 3, 4, 5))); assertTrue(ts.getOnErrorEvents().isEmpty()); assertEquals(1, ts.getCompletions()); diff --git a/src/test/java/rx/internal/operators/OperatorToObservableSortedListTest.java b/src/test/java/rx/internal/operators/OperatorToObservableSortedListTest.java index f687fb2b28..5081c2cb63 100644 --- a/src/test/java/rx/internal/operators/OperatorToObservableSortedListTest.java +++ b/src/test/java/rx/internal/operators/OperatorToObservableSortedListTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -40,12 +40,11 @@ public void testSortedList() { Observable w = Observable.just(1, 3, 2, 5, 4); Observable> observable = w.toSortedList(); - @SuppressWarnings("unchecked") - Observer> observer = mock(Observer.class); - 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)).onCompleted(); + TestSubscriber> testSubscriber = new TestSubscriber>(); + observable.subscribe(testSubscriber); + testSubscriber.assertValue(Arrays.asList(1,2,3,4,5)); + testSubscriber.assertNoErrors(); + testSubscriber.assertCompleted(); } @Test @@ -82,15 +81,15 @@ public void onStart() { requestMore(0); } }; - + w.subscribe(ts); - + assertTrue(ts.getOnNextEvents().isEmpty()); assertTrue(ts.getOnErrorEvents().isEmpty()); assertEquals(0, ts.getCompletions()); - + ts.requestMore(1); - + ts.assertReceivedOnNext(Collections.singletonList(Arrays.asList(1, 2, 3, 4, 5))); assertTrue(ts.getOnErrorEvents().isEmpty()); assertEquals(1, ts.getCompletions()); @@ -148,4 +147,125 @@ static void await(CyclicBarrier cb) { ex.printStackTrace(); } } + + @Test + public void testSortedListCapacity() { + Observable w = Observable.just(1, 3, 2, 5, 4); + Observable> observable = w.toSortedList(4); + + TestSubscriber> testSubscriber = new TestSubscriber>(); + observable.subscribe(testSubscriber); + testSubscriber.assertValue(Arrays.asList(1,2,3,4,5)); + testSubscriber.assertNoErrors(); + testSubscriber.assertCompleted(); + } + + @Test + public void testSortedCustomComparer() { + Observable w = Observable.just(1, 3, 2, 5, 4); + Observable> observable = w.toSortedList(new Func2() { + @Override + public Integer call(Integer t1, Integer t2) { + return t2.compareTo(t1); + } + }); + + TestSubscriber> testSubscriber = new TestSubscriber>(); + observable.subscribe(testSubscriber); + testSubscriber.assertValue(Arrays.asList(5, 4, 3, 2, 1)); + testSubscriber.assertNoErrors(); + testSubscriber.assertCompleted(); + } + + @Test + public void testSortedCustomComparerHinted() { + Observable w = Observable.just(1, 3, 2, 5, 4); + Observable> observable = w.toSortedList(new Func2() { + @Override + public Integer call(Integer t1, Integer t2) { + return t2.compareTo(t1); + } + }, 4); + + TestSubscriber> testSubscriber = new TestSubscriber>(); + observable.subscribe(testSubscriber); + testSubscriber.assertValue(Arrays.asList(5, 4, 3, 2, 1)); + testSubscriber.assertNoErrors(); + testSubscriber.assertCompleted(); + } + + @Test + public void testSorted() { + Observable w = Observable.just(1, 3, 2, 5, 4); + Observable observable = w.sorted(); + + TestSubscriber testSubscriber = new TestSubscriber(); + observable.subscribe(testSubscriber); + testSubscriber.assertValues(1,2,3,4,5); + testSubscriber.assertNoErrors(); + testSubscriber.assertCompleted(); + } + + @Test + public void testSortedWithCustomFunction() { + Observable w = Observable.just(1, 3, 2, 5, 4); + Observable observable = w.sorted(new Func2() { + + @Override + public Integer call(Integer t1, Integer t2) { + return t2 - t1; + } + + }); + + TestSubscriber testSubscriber = new TestSubscriber(); + observable.subscribe(testSubscriber); + testSubscriber.assertValues(5,4,3,2,1); + testSubscriber.assertNoErrors(); + testSubscriber.assertCompleted(); + } + + @Test + public void testSortedCustomComparator() { + Observable w = Observable.just(1, 3, 2, 5, 4); + Observable observable = w.sorted(new Func2() { + @Override + public Integer call(Integer t1, Integer t2) { + return t1.compareTo(t2); + } + + }); + + TestSubscriber testSubscriber = new TestSubscriber(); + observable.subscribe(testSubscriber); + testSubscriber.assertValues(1,2,3,4,5); + testSubscriber.assertNoErrors(); + testSubscriber.assertCompleted(); + } + + @Test + public void testSortedWithNonComparable() { + NonComparable n1 = new NonComparable(1,"a"); + NonComparable n2 = new NonComparable(2,"b"); + NonComparable n3 = new NonComparable(3,"c"); + Observable w = Observable.just(n1,n2,n3); + + Observable observable = w.sorted(); + + TestSubscriber testSubscriber = new TestSubscriber(); + observable.subscribe(testSubscriber); + testSubscriber.assertNoValues(); + testSubscriber.assertError(ClassCastException.class); + testSubscriber.assertNotCompleted(); + } + + final static class NonComparable { + public int i; + public String s; + + NonComparable(int i, String s) { + this.i = i; + this.s = s; + } + } } diff --git a/src/test/java/rx/internal/operators/OperatorUnsubscribeOnTest.java b/src/test/java/rx/internal/operators/OperatorUnsubscribeOnTest.java index 9e9b946f28..c409a467bf 100644 --- a/src/test/java/rx/internal/operators/OperatorUnsubscribeOnTest.java +++ b/src/test/java/rx/internal/operators/OperatorUnsubscribeOnTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -39,7 +39,7 @@ public void testUnsubscribeWhenSubscribeOnAndUnsubscribeOnAreOnSameThread() thro try { final ThreadSubscription subscription = new ThreadSubscription(); final AtomicReference subscribeThread = new AtomicReference(); - Observable w = Observable.create(new OnSubscribe() { + Observable w = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber t1) { @@ -84,7 +84,7 @@ public void testUnsubscribeWhenSubscribeOnAndUnsubscribeOnAreOnDifferentThreads( try { final ThreadSubscription subscription = new ThreadSubscription(); final AtomicReference subscribeThread = new AtomicReference(); - Observable w = Observable.create(new OnSubscribe() { + Observable w = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber t1) { @@ -169,7 +169,7 @@ public UIEventLoopScheduler() { eventLoop = Executors.newSingleThreadExecutor(new RxThreadFactory("Test-EventLoop")); single = Schedulers.from(eventLoop); - + /* * DON'T DO THIS IN PRODUCTION CODE */ @@ -189,7 +189,7 @@ public void run() { throw new RuntimeException("failed to initialize and get inner thread"); } } - + @Override public Worker createWorker() { return single.createWorker(); @@ -204,4 +204,31 @@ public Thread getThread() { } } + + @Test + public void backpressure() { + AssertableSubscriber as = Observable.range(1, 10) + .unsubscribeOn(Schedulers.trampoline()) + .test(0); + + as.assertNoValues() + .assertNoErrors() + .assertNotCompleted(); + + as.requestMore(1); + + as.assertValue(1) + .assertNoErrors() + .assertNotCompleted(); + + as.requestMore(3); + + as.assertValues(1, 2, 3, 4) + .assertNoErrors() + .assertNotCompleted(); + + as.requestMore(10); + + as.assertResult(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + } } \ No newline at end of file diff --git a/src/test/java/rx/internal/operators/OperatorWindowWithObservableTest.java b/src/test/java/rx/internal/operators/OperatorWindowWithObservableTest.java index 9d6ee60baa..3d7f0c181d 100644 --- a/src/test/java/rx/internal/operators/OperatorWindowWithObservableTest.java +++ b/src/test/java/rx/internal/operators/OperatorWindowWithObservableTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -287,7 +287,7 @@ public Observable call() { assertEquals(1, ts.getOnNextEvents().size()); assertEquals(Arrays.asList(1, 2), tsw.getOnNextEvents()); } - + @Test public void testWindowViaObservableNoUnsubscribe() { Observable source = Observable.range(1, 10); @@ -297,13 +297,13 @@ public Observable call() { return Observable.empty(); } }; - + TestSubscriber> ts = TestSubscriber.create(); source.window(boundary).unsafeSubscribe(ts); - + assertFalse(ts.isUnsubscribed()); } - + @Test public void testBoundaryUnsubscribedOnMainCompletion() { PublishSubject source = PublishSubject.create(); @@ -314,18 +314,18 @@ public Observable call() { return boundary; } }; - + TestSubscriber> ts = TestSubscriber.create(); source.window(boundaryFunc).subscribe(ts); - + assertTrue(source.hasObservers()); assertTrue(boundary.hasObservers()); - + source.onCompleted(); assertFalse(source.hasObservers()); assertFalse(boundary.hasObservers()); - + ts.assertCompleted(); ts.assertNoErrors(); ts.assertValueCount(1); @@ -340,18 +340,18 @@ public Observable call() { return boundary; } }; - + TestSubscriber> ts = TestSubscriber.create(); source.window(boundaryFunc).subscribe(ts); - + assertTrue(source.hasObservers()); assertTrue(boundary.hasObservers()); - + boundary.onCompleted(); assertFalse(source.hasObservers()); assertFalse(boundary.hasObservers()); - + ts.assertCompleted(); ts.assertNoErrors(); ts.assertValueCount(1); @@ -366,10 +366,10 @@ public Observable call() { return boundary; } }; - + TestSubscriber> ts = TestSubscriber.create(); source.window(boundaryFunc).subscribe(ts); - + assertTrue(source.hasObservers()); assertTrue(boundary.hasObservers()); @@ -377,7 +377,7 @@ public Observable call() { assertFalse(source.hasObservers()); assertFalse(boundary.hasObservers()); - + ts.assertNotCompleted(); ts.assertNoErrors(); ts.assertValueCount(1); @@ -392,7 +392,7 @@ public Observable call() { return boundary; } }; - + final TestSubscriber ts = TestSubscriber.create(1); final TestSubscriber> ts1 = new TestSubscriber>(1) { @Override @@ -403,17 +403,17 @@ public void onNext(Observable t) { }; source.window(boundaryFunc) .subscribe(ts1); - + ts1.assertNoErrors(); ts1.assertCompleted(); ts1.assertValueCount(1); - + ts.assertNoErrors(); ts.assertNotCompleted(); ts.assertValues(1); - + ts.requestMore(11); - + ts.assertValues(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); ts.assertNoErrors(); ts.assertCompleted(); @@ -430,10 +430,10 @@ public Observable call() { return boundary; } }; - + TestSubscriber> ts = TestSubscriber.create(); source.window(boundaryFunc).subscribe(ts); - + source.onNext(1); boundary.onNext(1); assertTrue(boundary.hasObservers()); @@ -445,10 +445,10 @@ public Observable call() { source.onNext(3); boundary.onNext(3); assertTrue(boundary.hasObservers()); - + source.onNext(4); source.onCompleted(); - + ts.assertNoErrors(); ts.assertValueCount(4); ts.assertCompleted(); diff --git a/src/test/java/rx/internal/operators/OperatorWindowWithSizeTest.java b/src/test/java/rx/internal/operators/OperatorWindowWithSizeTest.java index b44f149588..2e9b718e67 100644 --- a/src/test/java/rx/internal/operators/OperatorWindowWithSizeTest.java +++ b/src/test/java/rx/internal/operators/OperatorWindowWithSizeTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -200,16 +200,16 @@ private List list(String... args) { } return list; } - + @Test public void testBackpressureOuter() { Observable> source = Observable.range(1, 10).window(3); - + final List list = new ArrayList(); - + @SuppressWarnings("unchecked") final Observer o = mock(Observer.class); - + source.subscribe(new Subscriber>() { @Override public void onStart() { @@ -241,15 +241,15 @@ public void onCompleted() { o.onCompleted(); } }); - + assertEquals(Arrays.asList(1, 2, 3), list); - + verify(o, never()).onError(any(Throwable.class)); verify(o, times(1)).onCompleted(); // 1 inner } public static Observable hotStream() { - return Observable.create(new OnSubscribe() { + return Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber s) { while (!s.isUnsubscribed()) { @@ -269,13 +269,13 @@ public void call(Subscriber s) { } }).subscribeOn(Schedulers.newThread()); // use newThread since we are using sleep to block } - + @Test public void testTakeFlatMapCompletes() { TestSubscriber ts = new TestSubscriber(); - + final int indicator = 999999999; - + hotStream() .window(10) .take(2) @@ -285,18 +285,18 @@ public Observable call(Observable w) { return w.startWith(indicator); } }).subscribe(ts); - + ts.awaitTerminalEvent(2, TimeUnit.SECONDS); ts.assertCompleted(); Assert.assertFalse(ts.getOnNextEvents().isEmpty()); } - + @Ignore("Requires #3678") @Test @SuppressWarnings("unchecked") public void testBackpressureOuterInexact() { TestSubscriber> ts = new TestSubscriber>(0); - + Observable.range(1, 5).window(2, 1) .map(new Func1, Observable>>() { @Override @@ -305,11 +305,11 @@ public Observable> call(Observable t) { } }).concatMap(UtilityFunctions.>>identity()) .subscribe(ts); - + ts.assertNoErrors(); ts.assertNoValues(); ts.assertNotCompleted(); - + ts.requestMore(2); ts.assertValues(Arrays.asList(1, 2), Arrays.asList(2, 3)); @@ -319,44 +319,44 @@ public Observable> call(Observable t) { ts.requestMore(5); System.out.println(ts.getOnNextEvents()); - + ts.assertValues(Arrays.asList(1, 2), Arrays.asList(2, 3), Arrays.asList(3, 4), Arrays.asList(4, 5), Arrays.asList(5)); ts.assertNoErrors(); ts.assertCompleted(); } - + @Test public void testBackpressureOuterOverlap() { Observable> source = Observable.range(1, 10).window(3, 1); - + TestSubscriber> ts = TestSubscriber.create(0L); - + source.subscribe(ts); - + ts.assertNoValues(); ts.assertNoErrors(); ts.assertNotCompleted(); - + ts.requestMore(1); - + ts.assertValueCount(1); ts.assertNoErrors(); ts.assertNotCompleted(); ts.requestMore(7); - + ts.assertValueCount(8); ts.assertNoErrors(); ts.assertNotCompleted(); - + ts.requestMore(3); ts.assertValueCount(10); ts.assertCompleted(); ts.assertNoErrors(); } - + @Test(expected = IllegalArgumentException.class) public void testCountInvalid() { Observable.range(1, 10).window(0, 1); @@ -369,25 +369,25 @@ public void testSkipInvalid() { @Test public void testTake1Overlapping() { Observable> source = Observable.range(1, 10).window(3, 1).take(1); - + TestSubscriber> ts = TestSubscriber.create(0L); source.subscribe(ts); - + ts.assertNoValues(); ts.assertNoErrors(); ts.assertNotCompleted(); - + ts.requestMore(2); - + ts.assertValueCount(1); ts.assertCompleted(); ts.assertNoErrors(); TestSubscriber ts1 = TestSubscriber.create(); - + ts.getOnNextEvents().get(0).subscribe(ts1); - + ts1.assertValues(1, 2, 3); ts1.assertCompleted(); ts1.assertNoErrors(); diff --git a/src/test/java/rx/internal/operators/OperatorWindowWithStartEndObservableTest.java b/src/test/java/rx/internal/operators/OperatorWindowWithStartEndObservableTest.java index be3c16e660..eeb47b89be 100644 --- a/src/test/java/rx/internal/operators/OperatorWindowWithStartEndObservableTest.java +++ b/src/test/java/rx/internal/operators/OperatorWindowWithStartEndObservableTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -46,7 +46,7 @@ public void testObservableBasedOpenerAndCloser() { final List list = new ArrayList(); final List> lists = new ArrayList>(); - Observable source = Observable.create(new Observable.OnSubscribe() { + Observable source = Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber observer) { push(observer, "one", 10); @@ -58,7 +58,7 @@ public void call(Subscriber observer) { } }); - Observable openings = Observable.create(new Observable.OnSubscribe() { + Observable openings = Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber observer) { push(observer, new Object(), 50); @@ -70,7 +70,7 @@ public void call(Subscriber observer) { Func1> closer = new Func1>() { @Override public Observable call(Object opening) { - return Observable.create(new Observable.OnSubscribe() { + return Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber observer) { push(observer, new Object(), 100); @@ -94,7 +94,7 @@ public void testObservableBasedCloser() { final List list = new ArrayList(); final List> lists = new ArrayList>(); - Observable source = Observable.create(new Observable.OnSubscribe() { + Observable source = Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber observer) { push(observer, "one", 10); @@ -110,7 +110,7 @@ public void call(Subscriber observer) { int calls; @Override public Observable call() { - return Observable.create(new Observable.OnSubscribe() { + return Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber observer) { int c = calls++; @@ -187,67 +187,67 @@ public void onNext(String args) { } }; } - + @Test public void testNoUnsubscribeAndNoLeak() { PublishSubject source = PublishSubject.create(); - + PublishSubject open = PublishSubject.create(); final PublishSubject close = PublishSubject.create(); - + TestSubscriber> ts = TestSubscriber.create(); - + source.window(open, new Func1>() { @Override public Observable call(Integer t) { return close; } }).unsafeSubscribe(ts); - + open.onNext(1); source.onNext(1); - + assertTrue(open.hasObservers()); assertTrue(close.hasObservers()); close.onNext(1); - + assertFalse(close.hasObservers()); - + source.onCompleted(); - + ts.assertCompleted(); ts.assertNoErrors(); ts.assertValueCount(1); - + assertFalse(ts.isUnsubscribed()); assertFalse(open.hasObservers()); assertFalse(close.hasObservers()); } - + @Test public void testUnsubscribeAll() { PublishSubject source = PublishSubject.create(); - + PublishSubject open = PublishSubject.create(); final PublishSubject close = PublishSubject.create(); - + TestSubscriber> ts = TestSubscriber.create(); - + source.window(open, new Func1>() { @Override public Observable call(Integer t) { return close; } }).unsafeSubscribe(ts); - + open.onNext(1); - + assertTrue(open.hasObservers()); assertTrue(close.hasObservers()); ts.unsubscribe(); - + assertFalse(open.hasObservers()); assertFalse(close.hasObservers()); } diff --git a/src/test/java/rx/internal/operators/OperatorWindowWithTimeTest.java b/src/test/java/rx/internal/operators/OperatorWindowWithTimeTest.java index 34c3739c88..78500db578 100644 --- a/src/test/java/rx/internal/operators/OperatorWindowWithTimeTest.java +++ b/src/test/java/rx/internal/operators/OperatorWindowWithTimeTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -45,7 +45,7 @@ public void testTimedAndCount() { final List list = new ArrayList(); final List> lists = new ArrayList>(); - Observable source = Observable.create(new Observable.OnSubscribe() { + Observable source = Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber observer) { push(observer, "one", 10); @@ -78,7 +78,7 @@ public void testTimed() { final List list = new ArrayList(); final List> lists = new ArrayList>(); - Observable source = Observable.create(new Observable.OnSubscribe() { + Observable source = Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber observer) { push(observer, "one", 98); @@ -155,12 +155,12 @@ public void onNext(T args) { @Test public void testExactWindowSize() { Observable> source = Observable.range(1, 10).window(1, TimeUnit.MINUTES, 3, scheduler); - + final List list = new ArrayList(); final List> lists = new ArrayList>(); - + source.subscribe(observeWindow(list, lists)); - + assertEquals(4, lists.size()); assertEquals(3, lists.get(0).size()); assertEquals(Arrays.asList(1, 2, 3), lists.get(0)); @@ -171,13 +171,13 @@ public void testExactWindowSize() { assertEquals(1, lists.get(3).size()); assertEquals(Arrays.asList(10), lists.get(3)); } - + @Test public void testTakeFlatMapCompletes() { TestSubscriber ts = new TestSubscriber(); - + final int indicator = 999999999; - + OperatorWindowWithSizeTest.hotStream() .window(300, TimeUnit.MILLISECONDS) .take(10) @@ -187,10 +187,47 @@ public Observable call(Observable w) { return w.startWith(indicator); } }).subscribe(ts); - + ts.awaitTerminalEvent(5, TimeUnit.SECONDS); ts.assertCompleted(); Assert.assertFalse(ts.getOnNextEvents().isEmpty()); } - + + @Test + public void timeCountDefaultScheduler() { + + TestSubscriber ts = TestSubscriber.create(); + + Observable.range(1, 10).window(5, TimeUnit.SECONDS, 5) + .flatMap(new Func1, Observable>() { + @Override + public Observable call(Observable w) { + return w; + } + }).subscribe(ts); + + ts.awaitTerminalEventAndUnsubscribeOnTimeout(5, TimeUnit.SECONDS); + ts.assertValueCount(10); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @Test + public void spanSkipDefaultScheduler() { + + TestSubscriber ts = TestSubscriber.create(); + + Observable.range(1, 10).window(5, 5, TimeUnit.SECONDS) + .flatMap(new Func1, Observable>() { + @Override + public Observable call(Observable w) { + return w; + } + }).subscribe(ts); + + ts.awaitTerminalEventAndUnsubscribeOnTimeout(5, TimeUnit.SECONDS); + ts.assertValueCount(10); + ts.assertNoErrors(); + ts.assertCompleted(); + } } diff --git a/src/test/java/rx/internal/operators/OperatorWithLatestFromTest.java b/src/test/java/rx/internal/operators/OperatorWithLatestFromTest.java index 69506a5d66..126e9e52b6 100644 --- a/src/test/java/rx/internal/operators/OperatorWithLatestFromTest.java +++ b/src/test/java/rx/internal/operators/OperatorWithLatestFromTest.java @@ -48,101 +48,101 @@ public Integer call(Integer t1, Integer t2) { public void testSimple() { PublishSubject source = PublishSubject.create(); PublishSubject other = PublishSubject.create(); - + @SuppressWarnings("unchecked") Observer o = mock(Observer.class); InOrder inOrder = inOrder(o); - + Observable result = source.withLatestFrom(other, COMBINER); - + result.subscribe(o); - + source.onNext(1); inOrder.verify(o, never()).onNext(anyInt()); - + other.onNext(1); inOrder.verify(o, never()).onNext(anyInt()); - + source.onNext(2); inOrder.verify(o).onNext((2 << 8) + 1); - + other.onNext(2); inOrder.verify(o, never()).onNext(anyInt()); - + other.onCompleted(); inOrder.verify(o, never()).onCompleted(); - + source.onNext(3); inOrder.verify(o).onNext((3 << 8) + 2); - + source.onCompleted(); inOrder.verify(o).onCompleted(); - + verify(o, never()).onError(any(Throwable.class)); } - + @Test public void testEmptySource() { PublishSubject source = PublishSubject.create(); PublishSubject other = PublishSubject.create(); - + Observable result = source.withLatestFrom(other, COMBINER); - + TestSubscriber ts = new TestSubscriber(); - + result.subscribe(ts); assertTrue(source.hasObservers()); assertTrue(other.hasObservers()); other.onNext(1); - + source.onCompleted(); - + ts.assertNoErrors(); ts.assertTerminalEvent(); assertEquals(0, ts.getOnNextEvents().size()); - + assertFalse(source.hasObservers()); assertFalse(other.hasObservers()); } - + @Test public void testEmptyOther() { PublishSubject source = PublishSubject.create(); PublishSubject other = PublishSubject.create(); - + Observable result = source.withLatestFrom(other, COMBINER); - + TestSubscriber ts = new TestSubscriber(); - + result.subscribe(ts); assertTrue(source.hasObservers()); assertTrue(other.hasObservers()); source.onNext(1); - + source.onCompleted(); - + ts.assertNoErrors(); ts.assertTerminalEvent(); assertEquals(0, ts.getOnNextEvents().size()); - + assertFalse(source.hasObservers()); assertFalse(other.hasObservers()); } - - + + @Test public void testUnsubscription() { PublishSubject source = PublishSubject.create(); PublishSubject other = PublishSubject.create(); - + Observable result = source.withLatestFrom(other, COMBINER); - + TestSubscriber ts = new TestSubscriber(); - + result.subscribe(ts); assertTrue(source.hasObservers()); @@ -150,13 +150,13 @@ public void testUnsubscription() { other.onNext(1); source.onNext(1); - + ts.unsubscribe(); - + ts.assertReceivedOnNext(Arrays.asList((1 << 8) + 1)); ts.assertNoErrors(); assertEquals(0, ts.getCompletions()); - + assertFalse(source.hasObservers()); assertFalse(other.hasObservers()); } @@ -165,11 +165,11 @@ public void testUnsubscription() { public void testSourceThrows() { PublishSubject source = PublishSubject.create(); PublishSubject other = PublishSubject.create(); - + Observable result = source.withLatestFrom(other, COMBINER); - + TestSubscriber ts = new TestSubscriber(); - + result.subscribe(ts); assertTrue(source.hasObservers()); @@ -177,14 +177,14 @@ public void testSourceThrows() { other.onNext(1); source.onNext(1); - + source.onError(new TestException()); - + ts.assertTerminalEvent(); ts.assertReceivedOnNext(Arrays.asList((1 << 8) + 1)); assertEquals(1, ts.getOnErrorEvents().size()); assertTrue(ts.getOnErrorEvents().get(0) instanceof TestException); - + assertFalse(source.hasObservers()); assertFalse(other.hasObservers()); } @@ -192,11 +192,11 @@ public void testSourceThrows() { public void testOtherThrows() { PublishSubject source = PublishSubject.create(); PublishSubject other = PublishSubject.create(); - + Observable result = source.withLatestFrom(other, COMBINER); - + TestSubscriber ts = new TestSubscriber(); - + result.subscribe(ts); assertTrue(source.hasObservers()); @@ -204,27 +204,27 @@ public void testOtherThrows() { other.onNext(1); source.onNext(1); - + other.onError(new TestException()); - + ts.assertTerminalEvent(); ts.assertReceivedOnNext(Arrays.asList((1 << 8) + 1)); assertEquals(1, ts.getOnErrorEvents().size()); assertTrue(ts.getOnErrorEvents().get(0) instanceof TestException); - + assertFalse(source.hasObservers()); assertFalse(other.hasObservers()); } - + @Test public void testFunctionThrows() { PublishSubject source = PublishSubject.create(); PublishSubject other = PublishSubject.create(); - + Observable result = source.withLatestFrom(other, COMBINER_ERROR); - + TestSubscriber ts = new TestSubscriber(); - + result.subscribe(ts); assertTrue(source.hasObservers()); @@ -232,65 +232,65 @@ public void testFunctionThrows() { other.onNext(1); source.onNext(1); - + ts.assertTerminalEvent(); assertEquals(0, ts.getOnNextEvents().size()); assertEquals(1, ts.getOnErrorEvents().size()); assertTrue(ts.getOnErrorEvents().get(0) instanceof TestException); - + assertFalse(source.hasObservers()); assertFalse(other.hasObservers()); } - + @Test public void testNoDownstreamUnsubscribe() { PublishSubject source = PublishSubject.create(); PublishSubject other = PublishSubject.create(); - + Observable result = source.withLatestFrom(other, COMBINER); - + TestSubscriber ts = new TestSubscriber(); - + result.unsafeSubscribe(ts); - + source.onCompleted(); - + assertFalse(ts.isUnsubscribed()); } @Test public void testBackpressure() { Observable source = Observable.range(1, 10); PublishSubject other = PublishSubject.create(); - + Observable result = source.withLatestFrom(other, COMBINER); - + TestSubscriber ts = new TestSubscriber() { @Override public void onStart() { request(0); } }; - + result.subscribe(ts); - + ts.requestMore(1); - + ts.assertReceivedOnNext(Collections.emptyList()); - + other.onNext(1); - + ts.requestMore(1); - + ts.assertReceivedOnNext(Arrays.asList((2 << 8) + 1)); - + ts.requestMore(5); ts.assertReceivedOnNext(Arrays.asList( - (2 << 8) + 1, (3 << 8) + 1, (4 << 8) + 1, (5 << 8) + 1, - (6 << 8) + 1, (7 << 8) + 1 + (2 << 8) + 1, (3 << 8) + 1, (4 << 8) + 1, (5 << 8) + 1, + (6 << 8) + 1, (7 << 8) + 1 )); - + ts.unsubscribe(); - + assertFalse("Other has observers!", other.hasObservers()); ts.assertNoErrors(); @@ -309,12 +309,12 @@ public void manySources() { PublishSubject ps2 = PublishSubject.create(); PublishSubject ps3 = PublishSubject.create(); PublishSubject main = PublishSubject.create(); - + TestSubscriber ts = new TestSubscriber(); - + main.withLatestFrom(new Observable[] { ps1, ps2, ps3 }, toArray) .subscribe(ts); - + main.onNext("1"); ts.assertNoValues(); ps1.onNext("a"); @@ -323,45 +323,45 @@ public void manySources() { ts.assertNoValues(); ps3.onNext("="); ts.assertNoValues(); - + main.onNext("2"); ts.assertValues("[2, a, A, =]"); - + ps2.onNext("B"); - + ts.assertValues("[2, a, A, =]"); - + ps3.onCompleted(); ts.assertValues("[2, a, A, =]"); - + ps1.onNext("b"); - + main.onNext("3"); - + ts.assertValues("[2, a, A, =]", "[3, b, B, =]"); - + main.onCompleted(); ts.assertValues("[2, a, A, =]", "[3, b, B, =]"); ts.assertNoErrors(); ts.assertCompleted(); - + Assert.assertFalse("ps1 has subscribers?", ps1.hasObservers()); Assert.assertFalse("ps2 has subscribers?", ps2.hasObservers()); Assert.assertFalse("ps3 has subscribers?", ps3.hasObservers()); } - + @Test public void manySourcesIterable() { PublishSubject ps1 = PublishSubject.create(); PublishSubject ps2 = PublishSubject.create(); PublishSubject ps3 = PublishSubject.create(); PublishSubject main = PublishSubject.create(); - + TestSubscriber ts = new TestSubscriber(); - + main.withLatestFrom(Arrays.>asList(ps1, ps2, ps3), toArray) .subscribe(ts); - + main.onNext("1"); ts.assertNoValues(); ps1.onNext("a"); @@ -370,33 +370,33 @@ public void manySourcesIterable() { ts.assertNoValues(); ps3.onNext("="); ts.assertNoValues(); - + main.onNext("2"); ts.assertValues("[2, a, A, =]"); - + ps2.onNext("B"); - + ts.assertValues("[2, a, A, =]"); - + ps3.onCompleted(); ts.assertValues("[2, a, A, =]"); - + ps1.onNext("b"); - + main.onNext("3"); - + ts.assertValues("[2, a, A, =]", "[3, b, B, =]"); - + main.onCompleted(); ts.assertValues("[2, a, A, =]", "[3, b, B, =]"); ts.assertNoErrors(); ts.assertCompleted(); - + Assert.assertFalse("ps1 has subscribers?", ps1.hasObservers()); Assert.assertFalse("ps2 has subscribers?", ps2.hasObservers()); Assert.assertFalse("ps3 has subscribers?", ps3.hasObservers()); } - + @Test public void manySourcesIterableSweep() { for (String val : new String[] { "1", null }) { @@ -405,93 +405,93 @@ public void manySourcesIterableSweep() { List> sources = new ArrayList>(); List expected = new ArrayList(); expected.add(val); - + for (int j = 0; j < i; j++) { sources.add(Observable.just(val)); expected.add(String.valueOf(val)); } - + TestSubscriber ts = new TestSubscriber(); - + PublishSubject main = PublishSubject.create(); - + main.withLatestFrom(sources, toArray).subscribe(ts); - + ts.assertNoValues(); - + main.onNext(val); main.onCompleted(); - + ts.assertValue(expected.toString()); ts.assertNoErrors(); ts.assertCompleted(); } } } - + @Test public void backpressureNoSignal() { PublishSubject ps1 = PublishSubject.create(); PublishSubject ps2 = PublishSubject.create(); - + TestSubscriber ts = new TestSubscriber(0); - + Observable.range(1, 10).withLatestFrom(new Observable[] { ps1, ps2 }, toArray) .subscribe(ts); - + ts.assertNoValues(); - + ts.requestMore(1); - + ts.assertNoValues(); ts.assertNoErrors(); ts.assertCompleted(); - + Assert.assertFalse("ps1 has subscribers?", ps1.hasObservers()); Assert.assertFalse("ps2 has subscribers?", ps2.hasObservers()); } - + @Test public void backpressureWithSignal() { PublishSubject ps1 = PublishSubject.create(); PublishSubject ps2 = PublishSubject.create(); - + TestSubscriber ts = new TestSubscriber(0); - + Observable.range(1, 3).withLatestFrom(new Observable[] { ps1, ps2 }, toArray) .subscribe(ts); - + ts.assertNoValues(); - + ps1.onNext("1"); ps2.onNext("1"); - + ts.requestMore(1); - + ts.assertValue("[1, 1, 1]"); - + ts.requestMore(1); ts.assertValues("[1, 1, 1]", "[2, 1, 1]"); ts.requestMore(1); - + ts.assertValues("[1, 1, 1]", "[2, 1, 1]", "[3, 1, 1]"); ts.assertNoErrors(); ts.assertCompleted(); - + Assert.assertFalse("ps1 has subscribers?", ps1.hasObservers()); Assert.assertFalse("ps2 has subscribers?", ps2.hasObservers()); } - + @Test public void withEmpty() { TestSubscriber ts = new TestSubscriber(0); - + Observable.range(1, 3).withLatestFrom( new Observable[] { Observable.just(1), Observable.empty() }, toArray) .subscribe(ts); - + ts.assertNoValues(); ts.assertNoErrors(); ts.assertCompleted(); @@ -500,11 +500,11 @@ public void withEmpty() { @Test public void withError() { TestSubscriber ts = new TestSubscriber(0); - + Observable.range(1, 3).withLatestFrom( new Observable[] { Observable.just(1), Observable.error(new TestException()) }, toArray) .subscribe(ts); - + ts.assertNoValues(); ts.assertError(TestException.class); ts.assertNotCompleted(); @@ -513,11 +513,11 @@ public void withError() { @Test public void withMainError() { TestSubscriber ts = new TestSubscriber(0); - + Observable.error(new TestException()).withLatestFrom( new Observable[] { Observable.just(1), Observable.just(1) }, toArray) .subscribe(ts); - + ts.assertNoValues(); ts.assertError(TestException.class); ts.assertNotCompleted(); @@ -526,9 +526,9 @@ public void withMainError() { @Test public void with2Others() { Observable just = Observable.just(1); - + TestSubscriber> ts = new TestSubscriber>(); - + just.withLatestFrom(just, just, new Func3>() { @Override public List call(Integer a, Integer b, Integer c) { @@ -536,18 +536,18 @@ public List call(Integer a, Integer b, Integer c) { } }) .subscribe(ts); - + ts.assertValue(Arrays.asList(1, 1, 1)); ts.assertNoErrors(); ts.assertCompleted(); } - + @Test public void with3Others() { Observable just = Observable.just(1); - + TestSubscriber> ts = new TestSubscriber>(); - + just.withLatestFrom(just, just, just, new Func4>() { @Override public List call(Integer a, Integer b, Integer c, Integer d) { @@ -555,18 +555,18 @@ public List call(Integer a, Integer b, Integer c, Integer d) { } }) .subscribe(ts); - + ts.assertValue(Arrays.asList(1, 1, 1, 1)); ts.assertNoErrors(); ts.assertCompleted(); } - + @Test public void with4Others() { Observable just = Observable.just(1); - + TestSubscriber> ts = new TestSubscriber>(); - + just.withLatestFrom(just, just, just, just, new Func5>() { @Override public List call(Integer a, Integer b, Integer c, Integer d, Integer e) { @@ -574,18 +574,18 @@ public List call(Integer a, Integer b, Integer c, Integer d, Integer e) } }) .subscribe(ts); - + ts.assertValue(Arrays.asList(1, 1, 1, 1, 1)); ts.assertNoErrors(); ts.assertCompleted(); } - + @Test public void with5Others() { Observable just = Observable.just(1); - + TestSubscriber> ts = new TestSubscriber>(); - + just.withLatestFrom(just, just, just, just, just, new Func6>() { @Override public List call(Integer a, Integer b, Integer c, Integer d, Integer e, Integer f) { @@ -593,18 +593,18 @@ public List call(Integer a, Integer b, Integer c, Integer d, Integer e, } }) .subscribe(ts); - + ts.assertValue(Arrays.asList(1, 1, 1, 1, 1, 1)); ts.assertNoErrors(); ts.assertCompleted(); } - + @Test public void with6Others() { Observable just = Observable.just(1); - + TestSubscriber> ts = new TestSubscriber>(); - + just.withLatestFrom(just, just, just, just, just, just, new Func7>() { @Override public List call(Integer a, Integer b, Integer c, Integer d, Integer e, Integer f, Integer g) { @@ -612,18 +612,18 @@ public List call(Integer a, Integer b, Integer c, Integer d, Integer e, } }) .subscribe(ts); - + ts.assertValue(Arrays.asList(1, 1, 1, 1, 1, 1, 1)); ts.assertNoErrors(); ts.assertCompleted(); } - + @Test public void with7Others() { Observable just = Observable.just(1); - + TestSubscriber> ts = new TestSubscriber>(); - + just.withLatestFrom(just, just, just, just, just, just, just, new Func8>() { @Override public List call(Integer a, Integer b, Integer c, Integer d, Integer e, Integer f, Integer g, Integer i) { @@ -631,7 +631,7 @@ public List call(Integer a, Integer b, Integer c, Integer d, Integer e, } }) .subscribe(ts); - + ts.assertValue(Arrays.asList(1, 1, 1, 1, 1, 1, 1, 1)); ts.assertNoErrors(); ts.assertCompleted(); @@ -640,9 +640,9 @@ public List call(Integer a, Integer b, Integer c, Integer d, Integer e, @Test public void with8Others() { Observable just = Observable.just(1); - + TestSubscriber> ts = new TestSubscriber>(); - + just.withLatestFrom(just, just, just, just, just, just, just, just, new Func9>() { @Override public List call(Integer a, Integer b, Integer c, Integer d, Integer e, Integer f, Integer g, Integer i, Integer j) { @@ -650,7 +650,7 @@ public List call(Integer a, Integer b, Integer c, Integer d, Integer e, } }) .subscribe(ts); - + ts.assertValue(Arrays.asList(1, 1, 1, 1, 1, 1, 1, 1, 1)); ts.assertNoErrors(); ts.assertCompleted(); diff --git a/src/test/java/rx/internal/operators/OperatorZipCompletionTest.java b/src/test/java/rx/internal/operators/OperatorZipCompletionTest.java index ef5ddb6847..33bc5c9ab1 100644 --- a/src/test/java/rx/internal/operators/OperatorZipCompletionTest.java +++ b/src/test/java/rx/internal/operators/OperatorZipCompletionTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -31,7 +31,7 @@ /** * Systematically tests that when zipping an infinite and a finite Observable, * the resulting Observable is finite. - * + * */ public class OperatorZipCompletionTest { Func2 concat2Strings; diff --git a/src/test/java/rx/internal/operators/OperatorZipIterableTest.java b/src/test/java/rx/internal/operators/OperatorZipIterableTest.java index 2aa2ed9a9d..bfcd20c09d 100644 --- a/src/test/java/rx/internal/operators/OperatorZipIterableTest.java +++ b/src/test/java/rx/internal/operators/OperatorZipIterableTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -353,7 +353,7 @@ public void remove() { verify(o, never()).onCompleted(); } - + Action1 printer = new Action1() { @Override public void call(String t1) { @@ -366,19 +366,19 @@ static final class SquareStr implements Func1 { @Override public String call(Integer t1) { counter.incrementAndGet(); - System.out.println("Omg I'm calculating so hard: " + t1 + "*" + t1 + "=" + (t1*t1)); - return " " + (t1*t1); + System.out.println("Omg I'm calculating so hard: " + t1 + "*" + t1 + "=" + (t1 * t1)); + return " " + (t1 * t1); } } @Test public void testTake2() { Observable o = Observable.just(1, 2, 3, 4, 5); Iterable it = Arrays.asList("a", "b", "c", "d", "e"); - + SquareStr squareStr = new SquareStr(); - + o.map(squareStr).zipWith(it, concat2Strings).take(2).subscribe(printer); - + assertEquals(2, squareStr.counter.get()); } diff --git a/src/test/java/rx/internal/operators/OperatorZipTest.java b/src/test/java/rx/internal/operators/OperatorZipTest.java index 7dd255b1f9..5f4b06bcbe 100644 --- a/src/test/java/rx/internal/operators/OperatorZipTest.java +++ b/src/test/java/rx/internal/operators/OperatorZipTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -15,44 +15,22 @@ */ package rx.internal.operators; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyString; -import static org.mockito.Mockito.inOrder; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; -import java.util.NoSuchElementException; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; +import static org.junit.Assert.*; +import static org.mockito.Matchers.*; +import static org.mockito.Mockito.*; -import org.junit.Assert; +import java.util.*; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicInteger; -import org.junit.Before; -import org.junit.Test; +import org.junit.*; import org.mockito.InOrder; -import rx.Notification; +import rx.*; import rx.Observable; import rx.Observable.OnSubscribe; import rx.Observer; -import rx.Subscriber; -import rx.Subscription; -import rx.functions.Action1; -import rx.functions.Func2; -import rx.functions.Func3; -import rx.functions.FuncN; -import rx.functions.Functions; +import rx.functions.*; import rx.internal.util.RxRingBuffer; import rx.observers.TestSubscriber; import rx.schedulers.Schedulers; @@ -116,7 +94,7 @@ public void testStartpingDifferentLengthObservableSequences1() { TestObservable w2 = new TestObservable(); TestObservable w3 = new TestObservable(); - Observable zipW = Observable.zip(Observable.create(w1), Observable.create(w2), Observable.create(w3), getConcat3StringsZipr()); + Observable zipW = Observable.zip(Observable.unsafeCreate(w1), Observable.unsafeCreate(w2), Observable.unsafeCreate(w3), getConcat3StringsZipr()); zipW.subscribe(w); /* simulate sending data */ @@ -150,7 +128,7 @@ public void testStartpingDifferentLengthObservableSequences2() { TestObservable w2 = new TestObservable(); TestObservable w3 = new TestObservable(); - Observable zipW = Observable.zip(Observable.create(w1), Observable.create(w2), Observable.create(w3), getConcat3StringsZipr()); + Observable zipW = Observable.zip(Observable.unsafeCreate(w1), Observable.unsafeCreate(w2), Observable.unsafeCreate(w3), getConcat3StringsZipr()); zipW.subscribe(w); /* simulate sending data */ @@ -1234,7 +1212,7 @@ public boolean hasNext() { Observable OBSERVABLE_OF_5_INTEGERS = OBSERVABLE_OF_5_INTEGERS(new AtomicInteger()); Observable OBSERVABLE_OF_5_INTEGERS(final AtomicInteger numEmitted) { - return Observable.create(new OnSubscribe() { + return Observable.unsafeCreate(new OnSubscribe() { @Override public void call(final Subscriber o) { @@ -1253,7 +1231,7 @@ public void call(final Subscriber o) { } Observable ASYNC_OBSERVABLE_OF_INFINITE_INTEGERS(final CountDownLatch latch) { - return Observable.create(new OnSubscribe() { + return Observable.unsafeCreate(new OnSubscribe() { @Override public void call(final Subscriber o) { @@ -1313,7 +1291,7 @@ public Integer call(Integer t1, Integer t2) { return t1 + 10 * t2; } }); - + TestSubscriber ts = new TestSubscriber() { @Override public void onNext(Integer t) { @@ -1321,9 +1299,9 @@ public void onNext(Integer t) { requestMore(5); } }; - + source.subscribe(ts); - + ts.assertNoErrors(); ts.assertTerminalEvent(); ts.assertReceivedOnNext(Arrays.asList(11, 22)); @@ -1332,23 +1310,23 @@ public void onNext(Integer t) { public void testZipRace() { long startTime = System.currentTimeMillis(); Observable src = Observable.just(1).subscribeOn(Schedulers.computation()); - + // now try and generate a hang by zipping src with itself repeatedly. A // time limit of 9 seconds ( 1 second less than the test timeout) is // used so that this test will not timeout on slow machines. int i = 0; - while (System.currentTimeMillis()-startTime < 9000 && i++ < 100000) { + while (System.currentTimeMillis() - startTime < 9000 && i++ < 100000) { int value = Observable.zip(src, src, new Func2() { @Override public Integer call(Integer t1, Integer t2) { return t1 + t2 * 10; } }).toBlocking().singleOrDefault(0); - + Assert.assertEquals(11, value); } } - /** + /** * Request only a single value and don't wait for another request just * to emit an onCompleted. */ @@ -1361,28 +1339,28 @@ public void onStart() { requestMore(1); } }; - + Observable.zip(src, src, new Func2() { @Override public Integer call(Integer t1, Integer t2) { return t1 + t2 * 10; } }).subscribe(ts); - + ts.awaitTerminalEvent(1, TimeUnit.SECONDS); ts.assertNoErrors(); ts.assertReceivedOnNext(Arrays.asList(11)); } - + @SuppressWarnings("cast") @Test public void testZipObservableObservableBackpressure() { @SuppressWarnings("unchecked") - Observable[] osArray = new Observable[] { - Observable.range(0, 10), - Observable.range(0, 10) + Observable[] osArray = new Observable[] { + Observable.range(0, 10), + Observable.range(0, 10) }; - + Observable> os = (Observable>) Observable.from(osArray); Observable o1 = Observable.zip(os, new FuncN() { @Override @@ -1390,9 +1368,9 @@ public Integer call(Object... a) { return 0; } }); - + TestSubscriber sub1 = TestSubscriber.create(5); - + o1.subscribe(sub1); sub1.requestMore(5); @@ -1401,4 +1379,119 @@ public Integer call(Object... a) { sub1.assertNoErrors(); sub1.assertCompleted(); } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Test + public void zip4() { + TestSubscriber ts = TestSubscriber.create(); + Observable source = Observable.just(1); + + Observable.zip(source, source, source, source, new Func4() { + @Override + public Object call(Object t1, Object t2, Object t3, Object t4) { + return "" + t1 + t2 + t3 + t4; + } + }) + .subscribe(ts); + + ts.assertValue("1111"); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Test + public void zip5() { + TestSubscriber ts = TestSubscriber.create(); + Observable source = Observable.just(1); + + Observable.zip(source, source, source, source, source, new Func5() { + @Override + public Object call(Object t1, Object t2, Object t3, Object t4, Object t5) { + return "" + t1 + t2 + t3 + t4 + t5; + } + }) + .subscribe(ts); + + ts.assertValue("11111"); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Test + public void zip6() { + TestSubscriber ts = TestSubscriber.create(); + Observable source = Observable.just(1); + + Observable.zip(source, source, source, source, source, source, new Func6() { + @Override + public Object call(Object t1, Object t2, Object t3, Object t4, Object t5, Object t6) { + return "" + t1 + t2 + t3 + t4 + t5 + t6; + } + }) + .subscribe(ts); + + ts.assertValue("111111"); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Test + public void zip7() { + TestSubscriber ts = TestSubscriber.create(); + Observable source = Observable.just(1); + + Observable.zip(source, source, source, source, source, source, source, new Func7() { + @Override + public Object call(Object t1, Object t2, Object t3, Object t4, Object t5, Object t6, Object t7) { + return "" + t1 + t2 + t3 + t4 + t5 + t6 + t7; + } + }) + .subscribe(ts); + + ts.assertValue("1111111"); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Test + public void zip8() { + TestSubscriber ts = TestSubscriber.create(); + Observable source = Observable.just(1); + + Observable.zip(source, source, source, source, source, source, source, source, new Func8() { + @Override + public Object call(Object t1, Object t2, Object t3, Object t4, Object t5, Object t6, Object t7, Object t8) { + return "" + t1 + t2 + t3 + t4 + t5 + t6 + t7 + t8; + } + }) + .subscribe(ts); + + ts.assertValue("11111111"); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Test + public void zip9() { + TestSubscriber ts = TestSubscriber.create(); + Observable source = Observable.just(1); + + Observable.zip(source, source, source, source, source, source, source, source, source, new Func9() { + @Override + public Object call(Object t1, Object t2, Object t3, Object t4, Object t5, + Object t6, Object t7, Object t8, Object t9) { + return "" + t1 + t2 + t3 + t4 + t5 + t6 + t7 + t8 + t9; + } + }) + .subscribe(ts); + + ts.assertValue("111111111"); + ts.assertNoErrors(); + ts.assertCompleted(); + } } diff --git a/src/test/java/rx/internal/operators/SafeSubscriberTest.java b/src/test/java/rx/internal/operators/SafeSubscriberTest.java index 43ad503f79..57fc3446bc 100644 --- a/src/test/java/rx/internal/operators/SafeSubscriberTest.java +++ b/src/test/java/rx/internal/operators/SafeSubscriberTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -38,7 +38,7 @@ public class SafeSubscriberTest { @Test public void testOnNextAfterOnError() { TestObservable t = new TestObservable(); - Observable st = Observable.create(t); + Observable st = Observable.unsafeCreate(t); @SuppressWarnings("unchecked") Observer w = mock(Observer.class); @@ -60,7 +60,7 @@ public void testOnNextAfterOnError() { @Test public void testOnCompletedAfterOnError() { TestObservable t = new TestObservable(); - Observable st = Observable.create(t); + Observable st = Observable.unsafeCreate(t); @SuppressWarnings("unchecked") Observer w = mock(Observer.class); @@ -82,7 +82,7 @@ public void testOnCompletedAfterOnError() { @Test public void testOnNextAfterOnCompleted() { TestObservable t = new TestObservable(); - Observable st = Observable.create(t); + Observable st = Observable.unsafeCreate(t); @SuppressWarnings("unchecked") Observer w = mock(Observer.class); @@ -105,7 +105,7 @@ public void testOnNextAfterOnCompleted() { @Test public void testOnErrorAfterOnCompleted() { TestObservable t = new TestObservable(); - Observable st = Observable.create(t); + Observable st = Observable.unsafeCreate(t); @SuppressWarnings("unchecked") Observer w = mock(Observer.class); @@ -126,7 +126,7 @@ public void testOnErrorAfterOnCompleted() { */ private static class TestObservable implements Observable.OnSubscribe { - Observer observer = null; + Observer observer; /* used to simulate subscription */ public void sendOnCompleted() { diff --git a/src/test/java/rx/internal/operators/SingleDoAfterTerminateTest.java b/src/test/java/rx/internal/operators/SingleDoAfterTerminateTest.java index be2ab6fb5d..e1df07579a 100644 --- a/src/test/java/rx/internal/operators/SingleDoAfterTerminateTest.java +++ b/src/test/java/rx/internal/operators/SingleDoAfterTerminateTest.java @@ -1,12 +1,12 @@ /** * Copyright 2015 Netflix, Inc. - * + * * 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. @@ -29,7 +29,7 @@ public class SingleDoAfterTerminateTest { public void chainedCallsOuter() { for (int i = 2; i <= 5; i++) { final AtomicInteger counter = new AtomicInteger(); - + Single source = Single.just("Test") .flatMap(new Func1>() { @Override @@ -54,21 +54,21 @@ public void call() { } }); } - + result .subscribe(new TestSubscriber()); - + Assert.assertEquals(i, counter.get()); } } - + @Test public void chainedCallsInner() { for (int i = 2; i <= 5; i++) { final AtomicInteger counter = new AtomicInteger(); - + final int fi = i; - + Single.just("Test") .flatMap(new Func1>() { @Override @@ -92,7 +92,7 @@ public void call() { } }) .subscribe(new TestSubscriber()); - + Assert.assertEquals(i, counter.get()); } } diff --git a/src/test/java/rx/internal/operators/SingleFromEmitterTest.java b/src/test/java/rx/internal/operators/SingleFromEmitterTest.java new file mode 100644 index 0000000000..a9f8dfd5b1 --- /dev/null +++ b/src/test/java/rx/internal/operators/SingleFromEmitterTest.java @@ -0,0 +1,221 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * 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 rx.internal.operators; + +import static org.junit.Assert.*; + +import java.io.IOException; +import java.util.*; + +import org.junit.*; + +import rx.*; +import rx.exceptions.TestException; +import rx.functions.*; +import rx.observers.AssertableSubscriber; +import rx.plugins.RxJavaHooks; + +public class SingleFromEmitterTest implements Cancellable, Action1 { + + int calls; + + final List errors = Collections.synchronizedList(new ArrayList()); + + @Before + public void before() { + RxJavaHooks.setOnError(this); + } + + @After + public void after() { + RxJavaHooks.reset(); + } + + @Override + public void cancel() throws Exception { + calls++; + } + + @Override + public void call(Throwable t) { + errors.add(t); + } + + @Test + public void normal() { + Single.fromEmitter(new Action1>() { + @Override + public void call(SingleEmitter e) { + e.setCancellation(SingleFromEmitterTest.this); + e.onSuccess(1); + } + }) + .test() + .assertResult(1); + + assertEquals(1, calls); + } + + @Test + public void nullSuccess() { + Single.fromEmitter(new Action1>() { + @Override + public void call(SingleEmitter e) { + e.setCancellation(SingleFromEmitterTest.this); + e.onSuccess(null); + } + }) + .test() + .assertResult((Object)null); + + assertEquals(1, calls); + } + + @Test + public void error() { + Single.fromEmitter(new Action1>() { + @Override + public void call(SingleEmitter e) { + e.setCancellation(SingleFromEmitterTest.this); + e.onError(new TestException()); + } + }) + .test() + .assertFailure(TestException.class); + + assertEquals(1, calls); + } + + @Test + public void nullError() { + Single.fromEmitter(new Action1>() { + @Override + public void call(SingleEmitter e) { + e.setCancellation(SingleFromEmitterTest.this); + e.onError(null); + } + }) + .test() + .assertFailure(NullPointerException.class); + + assertEquals(1, calls); + } + + @Test + public void crash() { + Single.fromEmitter(new Action1>() { + @Override + public void call(SingleEmitter e) { + e.setCancellation(SingleFromEmitterTest.this); + throw new TestException(); + } + }) + .test() + .assertFailure(TestException.class); + + assertEquals(1, calls); + } + + @Test + public void crash2() { + Single.fromEmitter(new Action1>() { + @Override + public void call(SingleEmitter e) { + e.setCancellation(SingleFromEmitterTest.this); + e.onError(null); + throw new TestException(); + } + }) + .test() + .assertFailure(NullPointerException.class); + + assertEquals(1, calls); + + assertTrue(errors.get(0).toString(), errors.get(0) instanceof TestException); + } + + @Test + public void unsubscribe() { + @SuppressWarnings("unchecked") + final SingleEmitter[] emitter = new SingleEmitter[1]; + + AssertableSubscriber ts = Single.fromEmitter(new Action1>() { + @Override + public void call(SingleEmitter e) { + emitter[0] = e; + } + }) + .test(); + + ts.unsubscribe(); + + emitter[0].onSuccess(1); + emitter[0].onError(new TestException()); + + ts.assertNoErrors().assertNotCompleted().assertNoValues(); + + assertTrue(errors.get(0).toString(), errors.get(0) instanceof TestException); + } + + @Test + public void onSuccessThrows() { + Single.fromEmitter(new Action1>() { + @Override + public void call(SingleEmitter e) { + e.setCancellation(SingleFromEmitterTest.this); + e.onSuccess(1); + } + }) + .subscribe(new SingleSubscriber() { + @Override + public void onSuccess(Object value) { + throw new TestException(); + } + @Override + public void onError(Throwable error) { + } + }); + + assertEquals(1, calls); + + assertTrue(errors.get(0).toString(), errors.get(0) instanceof TestException); + } + + @Test + public void onErrorThrows() { + Single.fromEmitter(new Action1>() { + @Override + public void call(SingleEmitter e) { + e.setCancellation(SingleFromEmitterTest.this); + e.onError(new IOException()); + } + }) + .subscribe(new SingleSubscriber() { + @Override + public void onSuccess(Object value) { + } + @Override + public void onError(Throwable error) { + throw new TestException(); + } + }); + + assertEquals(1, calls); + + assertTrue(errors.get(0).toString(), errors.get(0) instanceof TestException); + } +} diff --git a/src/test/java/rx/internal/operators/SingleOnSubscribeUsingTest.java b/src/test/java/rx/internal/operators/SingleOnSubscribeUsingTest.java index 96fd9ab05e..e8fc60ead5 100644 --- a/src/test/java/rx/internal/operators/SingleOnSubscribeUsingTest.java +++ b/src/test/java/rx/internal/operators/SingleOnSubscribeUsingTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -33,7 +33,7 @@ public class SingleOnSubscribeUsingTest { private interface Resource { String getTextFromWeb(); - + void dispose(); } @@ -273,7 +273,7 @@ public void disposesEagerlyBeforeCompletion() { final List events = new ArrayList(); Func0 resourceFactory = createResourceFactory(events); final Action1 completion = createOnSuccessAction(events); - final Action0 unsub =createUnsubAction(events); + final Action0 unsub = createUnsubAction(events); Func1> observableFactory = new Func1>() { @Override @@ -289,7 +289,7 @@ public Single call(Resource resource) { .doOnSuccess(completion); observable.subscribe(observer); - assertEquals(Arrays.asList("disposed", "completed", "unsub"), events); + assertEquals(Arrays.asList("disposed", "completed"/*, "unsub"*/), events); } @@ -298,7 +298,7 @@ public void doesNotDisposesEagerlyBeforeCompletion() { final List events = new ArrayList(); Func0 resourceFactory = createResourceFactory(events); final Action1 completion = createOnSuccessAction(events); - final Action0 unsub =createUnsubAction(events); + final Action0 unsub = createUnsubAction(events); Func1> observableFactory = new Func1>() { @Override @@ -314,19 +314,19 @@ public Single call(Resource resource) { .doOnSuccess(completion); observable.subscribe(observer); - assertEquals(Arrays.asList("completed", "unsub", "disposed"), events); + assertEquals(Arrays.asList("completed", /* "unsub",*/ "disposed"), events); } - - + + @Test public void disposesEagerlyBeforeError() { final List events = new ArrayList(); Func0 resourceFactory = createResourceFactory(events); final Action1 onError = createOnErrorAction(events); final Action0 unsub = createUnsubAction(events); - + Func1> observableFactory = new Func1>() { @Override public Single call(Resource resource) { @@ -341,17 +341,17 @@ public Single call(Resource resource) { .doOnError(onError); observable.subscribe(observer); - assertEquals(Arrays.asList("disposed", "error", "unsub"), events); + assertEquals(Arrays.asList("disposed", "error"/*, "unsub"*/), events); } - + @Test public void doesNotDisposesEagerlyBeforeError() { final List events = new ArrayList(); Func0 resourceFactory = createResourceFactory(events); final Action1 onError = createOnErrorAction(events); final Action0 unsub = createUnsubAction(events); - + Func1> observableFactory = new Func1>() { @Override public Single call(Resource resource) { @@ -366,7 +366,7 @@ public Single call(Resource resource) { .doOnError(onError); observable.subscribe(observer); - assertEquals(Arrays.asList("error", "unsub", "disposed"), events); + assertEquals(Arrays.asList("error", /* "unsub",*/ "disposed"), events); } private static Action0 createUnsubAction(final List events) { @@ -406,7 +406,7 @@ public void dispose() { } }; } - + private static Action1 createOnSuccessAction(final List events) { return new Action1() { @Override @@ -431,7 +431,7 @@ public Single call(Resource resource) { Single.using(null, observableFactory, new DisposeAction(), false); - + fail("Failed to throw NullPointerException"); } catch (NullPointerException ex) { assertEquals("resourceFactory is null", ex.getMessage()); @@ -453,7 +453,7 @@ public Resource call() { Single.using(resourceFactory, null, new DisposeAction(), false); - + fail("Failed to throw NullPointerException"); } catch (NullPointerException ex) { assertEquals("singleFactory is null", ex.getMessage()); @@ -482,7 +482,7 @@ public Single call(Resource resource) { Single.using(resourceFactory, observableFactory, null, false); - + fail("Failed to throw NullPointerException"); } catch (NullPointerException ex) { assertEquals("disposeAction is null", ex.getMessage()); diff --git a/src/test/java/rx/internal/operators/SingleOperatorCastTest.java b/src/test/java/rx/internal/operators/SingleOperatorCastTest.java new file mode 100644 index 0000000000..6d60c8b411 --- /dev/null +++ b/src/test/java/rx/internal/operators/SingleOperatorCastTest.java @@ -0,0 +1,51 @@ +/** + * Copyright 2014 Netflix, Inc. + * + * 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 rx.internal.operators; + +import org.junit.Test; +import rx.Observer; +import rx.Single; + +import static org.mockito.Mockito.*; + +public class SingleOperatorCastTest { + + @Test + public void testSingleCast() { + Single source = Single.just(1); + Single single = source.cast(Integer.class); + + @SuppressWarnings("unchecked") + Observer observer = mock(Observer.class); + single.subscribe(observer); + verify(observer, times(1)).onNext(1); + verify(observer, never()).onError( + org.mockito.Matchers.any(Throwable.class)); + verify(observer, times(1)).onCompleted(); + } + + @Test + public void testSingleCastWithWrongType() { + Single source = Single.just(1); + Single single = source.cast(Boolean.class); + + @SuppressWarnings("unchecked") + Observer observer = mock(Observer.class); + single.subscribe(observer); + verify(observer, times(1)).onError( + org.mockito.Matchers.any(ClassCastException.class)); + } +} diff --git a/src/test/java/rx/internal/operators/SingleOperatorZipTest.java b/src/test/java/rx/internal/operators/SingleOperatorZipTest.java new file mode 100644 index 0000000000..56e93071e3 --- /dev/null +++ b/src/test/java/rx/internal/operators/SingleOperatorZipTest.java @@ -0,0 +1,27 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * 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 rx.internal.operators; + +import org.junit.Test; + +import rx.TestUtil; + +public class SingleOperatorZipTest { + @Test + public void constructorShouldBePrivate() { + TestUtil.checkUtilityClass(SingleOperatorZip.class); + } +} diff --git a/src/test/java/rx/internal/operators/SingleTakeUntilTest.java b/src/test/java/rx/internal/operators/SingleTakeUntilTest.java new file mode 100644 index 0000000000..45a9bb23db --- /dev/null +++ b/src/test/java/rx/internal/operators/SingleTakeUntilTest.java @@ -0,0 +1,61 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * 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 rx.internal.operators; + +import java.util.concurrent.CancellationException; + +import static org.junit.Assert.*; +import org.junit.Test; + +import rx.*; +import rx.observers.AssertableSubscriber; + +public class SingleTakeUntilTest { + + @Test + public void withSingleMessage() { + AssertableSubscriber ts = Single.just(1).takeUntil(Single.just(2)) + .test() + .assertFailure(CancellationException.class); + + String message = ts.getOnErrorEvents().get(0).getMessage(); + + assertTrue(message, message.startsWith("Single::takeUntil(Single)")); + } + + @Test + public void withCompletableMessage() { + AssertableSubscriber ts = Single.just(1).takeUntil(Completable.complete()) + .test() + .assertFailure(CancellationException.class); + + String message = ts.getOnErrorEvents().get(0).getMessage(); + + assertTrue(message, message.startsWith("Single::takeUntil(Completable)")); + } + + @Test + public void withObservableMessage() { + AssertableSubscriber ts = Single.just(1).takeUntil(Observable.just(1)) + .test() + .assertFailure(CancellationException.class); + + String message = ts.getOnErrorEvents().get(0).getMessage(); + + assertTrue(message, message.startsWith("Single::takeUntil(Observable)")); + } +} diff --git a/src/test/java/rx/internal/producers/ProducerArbiterTest.java b/src/test/java/rx/internal/producers/ProducerArbiterTest.java new file mode 100644 index 0000000000..61b49ae380 --- /dev/null +++ b/src/test/java/rx/internal/producers/ProducerArbiterTest.java @@ -0,0 +1,125 @@ +/** + * Copyright 2015 Netflix, Inc. + * + * 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 rx.internal.producers; + +import org.junit.*; + +import rx.Producer; +import rx.exceptions.TestException; + +public class ProducerArbiterTest { + + @Test + public void negativeRequestThrows() { + ProducerArbiter pa = new ProducerArbiter(); + try { + pa.request(-99); + Assert.fail("Failed to throw on invalid request amount"); + } catch (IllegalArgumentException ex) { + Assert.assertEquals("n >= 0 required", ex.getMessage()); + } + } + + @Test + public void negativeProducedThrows() { + ProducerArbiter pa = new ProducerArbiter(); + try { + pa.produced(-99); + Assert.fail("Failed to throw on invalid produced amount"); + } catch (IllegalArgumentException ex) { + Assert.assertEquals("n > 0 required", ex.getMessage()); + } + } + + @Test + public void overproductionThrows() { + ProducerArbiter pa = new ProducerArbiter(); + try { + pa.produced(1); + Assert.fail("Failed to throw on overproduction amount"); + } catch (IllegalStateException ex) { + Assert.assertEquals("more items arrived than were requested", ex.getMessage()); + } + } + + @Test + public void nullProducerAccepted() { + ProducerArbiter pa = new ProducerArbiter(); + pa.setProducer(null); + } + + @Test + public void failedRequestUnlocksEmitting() { + ProducerArbiter pa = new ProducerArbiter(); + pa.setProducer(new Producer() { + @Override + public void request(long n) { + if (n != 0) { + throw new TestException("Forced failure"); + } + } + }); + try { + pa.request(1); + Assert.fail("Failed to throw on overproduction amount"); + } catch (TestException ex) { + Assert.assertEquals("Forced failure", ex.getMessage()); + Assert.assertFalse("Still emitting?!", pa.emitting); + } + } + + @Test + public void reentrantSetProducerNull() { + final ProducerArbiter pa = new ProducerArbiter(); + pa.setProducer(new Producer() { + @Override + public void request(long n) { + pa.setProducer(null); + } + }); + } + + @Test + public void reentrantSetProducer() { + final ProducerArbiter pa = new ProducerArbiter(); + pa.setProducer(new Producer() { + @Override + public void request(long n) { + pa.setProducer(new ProducerArbiter()); + } + }); + } + + @Test + public void overproductionReentrantThrows() { + final ProducerArbiter pa = new ProducerArbiter(); + try { + pa.setProducer(new Producer() { + @Override + public void request(long n) { + if (n != 0) { + pa.produced(2); + } + } + }); + pa.request(1); + Assert.fail("Failed to throw on overproduction amount"); + } catch (IllegalStateException ex) { + Assert.assertEquals("more produced than requested", ex.getMessage()); + } + } + +} diff --git a/src/test/java/rx/internal/producers/ProducerObserverArbiterTest.java b/src/test/java/rx/internal/producers/ProducerObserverArbiterTest.java new file mode 100644 index 0000000000..e1008befe8 --- /dev/null +++ b/src/test/java/rx/internal/producers/ProducerObserverArbiterTest.java @@ -0,0 +1,239 @@ +/** + * Copyright 2015 Netflix, Inc. + * + * 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 rx.internal.producers; + +import org.junit.*; +import static org.junit.Assert.*; + +import rx.*; +import rx.exceptions.TestException; +import rx.observers.*; + +public class ProducerObserverArbiterTest { + + @Test + public void negativeRequestThrows() { + ProducerObserverArbiter pa = new ProducerObserverArbiter(Subscribers.empty()); + try { + pa.request(-99); + Assert.fail("Failed to throw on invalid request amount"); + } catch (IllegalArgumentException ex) { + Assert.assertEquals("n >= 0 required", ex.getMessage()); + } + } + + @Test + public void nullProducerAccepted() { + ProducerObserverArbiter pa = new ProducerObserverArbiter(Subscribers.empty()); + pa.setProducer(null); + pa.request(5); + } + + public void failedRequestUnlocksEmitting() { + ProducerObserverArbiter pa = new ProducerObserverArbiter(Subscribers.empty()); + pa.setProducer(new Producer() { + @Override + public void request(long n) { + throw new TestException("Forced failure"); + } + }); + try { + pa.request(1); + Assert.fail("Failed to throw on overproduction amount"); + } catch (TestException ex) { + Assert.assertEquals("Forced failure", ex.getMessage()); + Assert.assertFalse("Still emitting?!", pa.emitting); + } + } + + @Test + public void reentrantSetProducerNull() { + final ProducerObserverArbiter pa = new ProducerObserverArbiter(Subscribers.empty()); + pa.setProducer(new Producer() { + @Override + public void request(long n) { + pa.setProducer(null); + } + }); + } + + @Test + public void reentrantSetProducer() { + final ProducerObserverArbiter pa = new ProducerObserverArbiter(Subscribers.empty()); + pa.setProducer(new Producer() { + @Override + public void request(long n) { + pa.setProducer(new ProducerArbiter()); + } + }); + } + + @Test + public void reentrantOnNext() { + @SuppressWarnings("rawtypes") + final Observer[] o = { null }; + TestSubscriber ts = new TestSubscriber() { + @SuppressWarnings("unchecked") + @Override + public void onNext(Integer t) { + if (t == 1) { + o[0].onNext(2); + } + super.onNext(t); + } + }; + ProducerObserverArbiter poa = new ProducerObserverArbiter(ts); + o[0] = poa; + poa.request(2); + poa.onNext(1); + ts.assertValues(1, 2); + } + + @Test + public void reentrantOnError() { + @SuppressWarnings("rawtypes") + final Observer[] o = { null }; + TestSubscriber ts = new TestSubscriber() { + @Override + public void onNext(Integer t) { + if (t == 1) { + o[0].onError(new TestException()); + } + super.onNext(t); + } + }; + ProducerObserverArbiter poa = new ProducerObserverArbiter(ts); + o[0] = poa; + poa.onNext(1); + ts.assertValue(1); + ts.assertError(TestException.class); + } + + @Test + public void reentrantOnCompleted() { + @SuppressWarnings("rawtypes") + final Observer[] o = { null }; + TestSubscriber ts = new TestSubscriber() { + @Override + public void onNext(Integer t) { + if (t == 1) { + o[0].onCompleted(); + } + super.onNext(t); + } + }; + ProducerObserverArbiter poa = new ProducerObserverArbiter(ts); + o[0] = poa; + poa.onNext(1); + ts.assertValue(1); + ts.assertCompleted(); + } + + @Test + public void onNextThrows() { + @SuppressWarnings("rawtypes") + final Observer[] o = { null }; + TestSubscriber ts = new TestSubscriber() { + @Override + public void onNext(Integer t) { + throw new TestException(); + } + }; + ProducerObserverArbiter poa = new ProducerObserverArbiter(ts); + o[0] = poa; + try { + poa.onNext(1); + Assert.fail("Arbiter didn't throw"); + } catch (TestException ex) { + // expected + } + } + + @Test + public void onNextRequests() { + @SuppressWarnings("rawtypes") + final ProducerObserverArbiter[] o = { null }; + TestSubscriber ts = new TestSubscriber() { + @Override + public void onNext(Integer t) { + o[0].request(1); + } + }; + ProducerObserverArbiter poa = new ProducerObserverArbiter(ts); + poa.request(1); + o[0] = poa; + try { + poa.onNext(1); + } catch (TestException ex) { + // expected + } + assertEquals(1, poa.requested); + } + + @Test + public void requestIsCapped() { + ProducerObserverArbiter poa = new ProducerObserverArbiter(new TestSubscriber()); + + poa.request(Long.MAX_VALUE - 1); + poa.request(2); + + assertEquals(Long.MAX_VALUE, Long.MAX_VALUE); + } + + @Test + public void onNextChangesProducerNull() { + @SuppressWarnings("rawtypes") + final ProducerObserverArbiter[] o = { null }; + TestSubscriber ts = new TestSubscriber() { + @Override + public void onNext(Integer t) { + o[0].setProducer(null); + } + }; + ProducerObserverArbiter poa = new ProducerObserverArbiter(ts); + poa.request(1); + o[0] = poa; + try { + poa.onNext(1); + } catch (TestException ex) { + // expected + } + assertNull(poa.currentProducer); + } + + @Test + public void onNextChangesProducerNotNull() { + @SuppressWarnings("rawtypes") + final ProducerObserverArbiter[] o = { null }; + TestSubscriber ts = new TestSubscriber() { + @SuppressWarnings("unchecked") + @Override + public void onNext(Integer t) { + o[0].setProducer(new SingleProducer(o[0].child, 2)); + } + }; + ProducerObserverArbiter poa = new ProducerObserverArbiter(ts); + poa.request(1); + o[0] = poa; + try { + poa.onNext(1); + } catch (TestException ex) { + // expected + } + assertNotNull(poa.currentProducer); + } + +} diff --git a/src/test/java/rx/internal/producers/ProducersTest.java b/src/test/java/rx/internal/producers/ProducersTest.java index cda5a17d32..46533b8ada 100644 --- a/src/test/java/rx/internal/producers/ProducersTest.java +++ b/src/test/java/rx/internal/producers/ProducersTest.java @@ -15,6 +15,7 @@ */ package rx.internal.producers; +import static org.junit.Assert.*; import static org.mockito.Mockito.*; import java.util.*; @@ -25,8 +26,10 @@ import rx.*; import rx.Observable; import rx.Observable.*; +import rx.exceptions.MissingBackpressureException; import rx.Observer; import rx.functions.*; +import rx.internal.util.unsafe.SpscArrayQueue; import rx.observers.TestSubscriber; import rx.schedulers.*; import rx.subscriptions.SerialSubscription; @@ -37,7 +40,7 @@ public void testSingleNoBackpressure() { TestSubscriber ts = new TestSubscriber(); SingleProducer sp = new SingleProducer(ts, 1); ts.setProducer(sp); - + ts.assertNoErrors(); ts.assertTerminalEvent(); ts.assertReceivedOnNext(Arrays.asList(1)); @@ -48,18 +51,18 @@ public void testSingleWithBackpressure() { ts.requestMore(0); SingleProducer sp = new SingleProducer(ts, 1); ts.setProducer(sp); - + ts.assertNoErrors(); ts.assertReceivedOnNext(Collections.emptyList()); Assert.assertEquals(0, ts.getCompletions()); - + ts.requestMore(2); - + ts.assertNoErrors(); ts.assertTerminalEvent(); ts.assertReceivedOnNext(Arrays.asList(1)); } - + @Test public void testSingleDelayedNoBackpressure() { TestSubscriber ts = new TestSubscriber(); @@ -69,15 +72,15 @@ public void testSingleDelayedNoBackpressure() { ts.assertNoErrors(); ts.assertReceivedOnNext(Collections.emptyList()); Assert.assertEquals(0, ts.getCompletions()); - + sdp.setValue(1); - + ts.assertNoErrors(); ts.assertTerminalEvent(); ts.assertReceivedOnNext(Arrays.asList(1)); sdp.setValue(2); - + ts.assertReceivedOnNext(Arrays.asList(1)); } @Test @@ -90,32 +93,32 @@ public void testSingleDelayedWithBackpressure() { ts.assertNoErrors(); ts.assertReceivedOnNext(Collections.emptyList()); Assert.assertEquals(0, ts.getCompletions()); - + sdp.setValue(1); ts.assertNoErrors(); ts.assertReceivedOnNext(Collections.emptyList()); Assert.assertEquals(0, ts.getCompletions()); - + ts.requestMore(2); - + ts.assertNoErrors(); ts.assertTerminalEvent(); ts.assertReceivedOnNext(Arrays.asList(1)); sdp.setValue(2); - + ts.assertReceivedOnNext(Arrays.asList(1)); } - + @Test public void testQueuedValueNoBackpressure() { TestSubscriber ts = new TestSubscriber(); QueuedValueProducer qvp = new QueuedValueProducer(ts); ts.setProducer(qvp); - + qvp.offer(1); - + ts.assertNoErrors(); ts.assertReceivedOnNext(Arrays.asList(1)); @@ -134,35 +137,35 @@ public void testQueuedValueWithBackpressure() { ts.setProducer(qvp); qvp.offer(1); - + ts.assertNoErrors(); ts.assertReceivedOnNext(Collections.emptyList()); - + qvp.offer(2); ts.requestMore(2); - + ts.assertNoErrors(); ts.assertReceivedOnNext(Arrays.asList(1, 2)); - + ts.requestMore(2); ts.assertNoErrors(); ts.assertReceivedOnNext(Arrays.asList(1, 2)); - + qvp.offer(3); qvp.offer(4); ts.assertNoErrors(); ts.assertReceivedOnNext(Arrays.asList(1, 2, 3, 4)); } - + @Test public void testQueuedNoBackpressure() { TestSubscriber ts = new TestSubscriber(); QueuedProducer qp = new QueuedProducer(ts); ts.setProducer(qp); - + qp.offer(1); - + ts.assertNoErrors(); ts.assertReceivedOnNext(Arrays.asList(1)); @@ -172,9 +175,9 @@ public void testQueuedNoBackpressure() { ts.assertNoErrors(); ts.assertReceivedOnNext(Arrays.asList(1, 2, 3, 4)); - + qp.onCompleted(); - + ts.assertTerminalEvent(); ts.assertNoErrors(); ts.assertReceivedOnNext(Arrays.asList(1, 2, 3, 4)); @@ -187,52 +190,52 @@ public void testQueuedWithBackpressure() { ts.setProducer(qp); qp.offer(1); - + ts.assertNoErrors(); ts.assertReceivedOnNext(Collections.emptyList()); - + qp.offer(2); ts.requestMore(2); - + ts.assertNoErrors(); ts.assertReceivedOnNext(Arrays.asList(1, 2)); - + ts.requestMore(2); ts.assertNoErrors(); ts.assertReceivedOnNext(Arrays.asList(1, 2)); - + qp.offer(3); qp.offer(4); ts.assertNoErrors(); ts.assertReceivedOnNext(Arrays.asList(1, 2, 3, 4)); - + qp.onCompleted(); ts.assertTerminalEvent(); ts.assertNoErrors(); ts.assertReceivedOnNext(Arrays.asList(1, 2, 3, 4)); } - + @Test public void testArbiter() { Producer p1 = mock(Producer.class); Producer p2 = mock(Producer.class); - + ProducerArbiter pa = new ProducerArbiter(); - + pa.request(100); - + pa.setProducer(p1); - + verify(p1).request(100); - + pa.produced(50); - + pa.setProducer(p2); - + verify(p2).request(50); } - + static final class TestProducer implements Producer { final Observer child; public TestProducer(Observer child) { @@ -243,7 +246,7 @@ public void request(long n) { child.onNext((int)n); } } - + @Test public void testObserverArbiterWithBackpressure() { final TestSubscriber ts = new TestSubscriber(); @@ -251,11 +254,11 @@ public void testObserverArbiterWithBackpressure() { final ProducerObserverArbiter poa = new ProducerObserverArbiter(ts); ts.setProducer(poa); - + poa.setProducer(new TestProducer(poa)); ts.requestMore(1); - + ts.assertNoErrors(); ts.assertReceivedOnNext(Arrays.asList(1)); @@ -269,20 +272,20 @@ public void testObserverArbiterWithBackpressure() { ts.assertNoErrors(); ts.assertReceivedOnNext(Arrays.asList(1, 5)); - + poa.onCompleted(); ts.assertNoErrors(); ts.assertTerminalEvent(); ts.assertReceivedOnNext(Arrays.asList(1, 5)); } - static final class SwitchTimer + static final class SwitchTimer implements OnSubscribe { final List> sources; final long time; final TimeUnit unit; final Scheduler scheduler; public SwitchTimer( - Iterable> sources, + Iterable> sources, long time, TimeUnit unit, Scheduler scheduler) { this.scheduler = scheduler; this.sources = new ArrayList>(); @@ -294,19 +297,19 @@ public SwitchTimer( } @Override public void call(Subscriber child) { - final ProducerObserverArbiter poa = + final ProducerObserverArbiter poa = new ProducerObserverArbiter(child); - + Scheduler.Worker w = scheduler.createWorker(); child.add(w); - + child.setProducer(poa); - + final SerialSubscription ssub = new SerialSubscription(); child.add(ssub); - + final int[] index = new int[1]; - + w.schedulePeriodically(new Action0() { @Override public void call() { @@ -335,7 +338,7 @@ public void setProducer(Producer producer) { poa.setProducer(producer); } }; - + ssub.set(s); sources.get(idx).unsafeSubscribe(s); } @@ -361,24 +364,24 @@ public void testObserverArbiterAsync() { Observable.interval(100, 100, TimeUnit.MILLISECONDS, test) .map(plus(40)) ); - - Observable source = Observable.create( - new SwitchTimer(timers, 550, + + Observable source = Observable.unsafeCreate( + new SwitchTimer(timers, 550, TimeUnit.MILLISECONDS, test)); - + TestSubscriber ts = new TestSubscriber(); ts.requestMore(100); source.subscribe(ts); - + test.advanceTimeBy(1, TimeUnit.MINUTES); - + ts.assertTerminalEvent(); ts.assertNoErrors(); ts.assertReceivedOnNext(Arrays.asList(0L, 1L, 2L, 3L, 4L, 20L, 21L, 22L, 23L, 24L, 40L, 41L, 42L, 43L, 44L)); } - + @Test(timeout = 1000) public void testProducerObserverArbiterUnbounded() { Observable.range(0, Integer.MAX_VALUE) @@ -386,7 +389,7 @@ public void testProducerObserverArbiterUnbounded() { @Override public Subscriber call(Subscriber t) { final ProducerObserverArbiter poa = new ProducerObserverArbiter(t); - + Subscriber parent = new Subscriber() { @Override @@ -403,17 +406,17 @@ public void onError(Throwable e) { public void onNext(Integer t) { poa.onNext(t); } - - + + @Override public void setProducer(Producer p) { poa.setProducer(p); } }; - + t.add(parent); t.setProducer(poa); - + return parent; } }).subscribe(new TestSubscriber() { @@ -426,4 +429,95 @@ public void onNext(Integer t) { } }); } + + @Test + public void queuedProducerRequestNegative() { + QueuedProducer qp = new QueuedProducer(new TestSubscriber()); + try { + qp.request(-99); + } catch (IllegalArgumentException ex) { + assertEquals("n >= 0 required", ex.getMessage()); + } + } + + @Test + public void queuedProducerOfferNull() { + TestSubscriber ts = TestSubscriber.create(); + QueuedProducer qp = new QueuedProducer(ts); + qp.offer(null); + + qp.request(1); + + ts.assertValue(null); + } + + @Test + public void queuedProducerFull() { + TestSubscriber ts = TestSubscriber.create(); + QueuedProducer qp = new QueuedProducer(ts, new SpscArrayQueue(1)); + assertTrue(qp.offer(1)); + assertFalse(qp.offer(2)); + + qp.request(1); + + ts.assertValue(1); + } + + @Test + public void queuedProducerOnNextFull() { + TestSubscriber ts = TestSubscriber.create(); + QueuedProducer qp = new QueuedProducer(ts, new SpscArrayQueue(1)); + + qp.onNext(1); + qp.onNext(2); + + qp.request(1); + + ts.assertError(MissingBackpressureException.class); + } + + @Test + public void queuedProducerOnNextFullWithNull() { + TestSubscriber ts = TestSubscriber.create(); + QueuedProducer qp = new QueuedProducer(ts, new SpscArrayQueue(1)); + + qp.onNext(1); + qp.onNext(null); + + qp.request(1); + + ts.assertError(MissingBackpressureException.class); + } + + @Test + public void queuedProducerRequestCompletes() { + TestSubscriber ts = TestSubscriber.create(); + QueuedProducer qp = new QueuedProducer(ts, new SpscArrayQueue(1)); + + qp.onNext(1); + qp.onCompleted(); + + qp.request(2); + + ts.assertValue(1); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @Test + public void queuedProducerUnsubscribed() { + TestSubscriber ts = TestSubscriber.create(); + ts.unsubscribe(); + QueuedProducer qp = new QueuedProducer(ts, new SpscArrayQueue(1)); + + qp.onNext(1); + qp.onCompleted(); + + qp.request(2); + + ts.assertNoValues(); + ts.assertNoErrors(); + ts.assertNotCompleted(); + } + } diff --git a/src/test/java/rx/internal/producers/SingleDelayedProducerTest.java b/src/test/java/rx/internal/producers/SingleDelayedProducerTest.java new file mode 100644 index 0000000000..f21753f4a3 --- /dev/null +++ b/src/test/java/rx/internal/producers/SingleDelayedProducerTest.java @@ -0,0 +1,80 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * 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 rx.internal.producers; + +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.*; + +import rx.Scheduler; +import rx.functions.Action0; +import rx.observers.*; +import rx.schedulers.Schedulers; + +public class SingleDelayedProducerTest { + + @Test + public void negativeRequestThrows() { + SingleDelayedProducer pa = new SingleDelayedProducer(Subscribers.empty()); + try { + pa.request(-99); + Assert.fail("Failed to throw on invalid request amount"); + } catch (IllegalArgumentException ex) { + Assert.assertEquals("n >= 0 required", ex.getMessage()); + } + } + + @Test + public void requestCompleteRace() throws Exception { + Scheduler.Worker w = Schedulers.computation().createWorker(); + try { + for (int i = 0; i < 10000; i++) { + final AtomicInteger waiter = new AtomicInteger(2); + + TestSubscriber ts = TestSubscriber.create(); + + final SingleDelayedProducer pa = new SingleDelayedProducer(ts); + + final CountDownLatch cdl = new CountDownLatch(1); + + w.schedule(new Action0() { + @Override + public void call() { + waiter.decrementAndGet(); + while (waiter.get() != 0) { } + pa.request(1); + cdl.countDown(); + } + }); + + waiter.decrementAndGet(); + while (waiter.get() != 0) { } + pa.setValue(1); + if (!cdl.await(5, TimeUnit.SECONDS)) { + Assert.fail("The wait for completion timed out"); + } + + ts.assertValue(1); + ts.assertCompleted(); + } + } finally { + w.unsubscribe(); + } + } + +} diff --git a/src/test/java/rx/internal/producers/SingleProducerTest.java b/src/test/java/rx/internal/producers/SingleProducerTest.java new file mode 100644 index 0000000000..1f63e289ec --- /dev/null +++ b/src/test/java/rx/internal/producers/SingleProducerTest.java @@ -0,0 +1,79 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * 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 rx.internal.producers; + +import org.junit.*; + +import rx.Observable; +import rx.exceptions.TestException; +import rx.observers.*; + +public class SingleProducerTest { + + @Test + public void negativeRequestThrows() { + SingleProducer pa = new SingleProducer(Subscribers.empty(), 1); + try { + pa.request(-99); + Assert.fail("Failed to throw on invalid request amount"); + } catch (IllegalArgumentException ex) { + Assert.assertEquals("n >= 0 required", ex.getMessage()); + } + } + + @Test + public void cancelBeforeOnNext() { + TestSubscriber ts = new TestSubscriber(); + SingleProducer pa = new SingleProducer(ts, 1); + + ts.unsubscribe(); + + pa.request(1); + + ts.assertNoValues(); + ts.assertNoErrors(); + ts.assertNotCompleted(); + } + + @Test + public void cancelAfterOnNext() { + TestSubscriber ts = new TestSubscriber(); + Observable.just(1).take(1).subscribe(ts); + + ts.assertValue(1); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @Test + public void onNextThrows() { + TestSubscriber ts = new TestSubscriber(0) { + @Override + public void onNext(Integer t) { + throw new TestException(); + } + }; + SingleProducer sp = new SingleProducer(ts, 1); + + sp.request(1); + + ts.assertNoValues(); + ts.assertError(TestException.class); + ts.assertNotCompleted(); + } + +} diff --git a/src/test/java/rx/internal/schedulers/ExecutorSchedulerTest.java b/src/test/java/rx/internal/schedulers/ExecutorSchedulerTest.java index f80a32b041..3c6b85255a 100644 --- a/src/test/java/rx/internal/schedulers/ExecutorSchedulerTest.java +++ b/src/test/java/rx/internal/schedulers/ExecutorSchedulerTest.java @@ -19,20 +19,21 @@ import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; -import org.junit.Test; + +import org.junit.*; + import rx.*; import rx.Scheduler.Worker; import rx.functions.*; import rx.internal.schedulers.ExecutorScheduler.ExecutorSchedulerWorker; import rx.internal.util.RxThreadFactory; -import rx.schedulers.AbstractSchedulerConcurrencyTests; -import rx.schedulers.SchedulerTests; -import rx.schedulers.Schedulers; +import rx.plugins.RxJavaHooks; +import rx.schedulers.*; public class ExecutorSchedulerTest extends AbstractSchedulerConcurrencyTests { final static Executor executor = Executors.newFixedThreadPool(2, new RxThreadFactory("TestCustomPool-")); - + @Override protected Scheduler getScheduler() { return Schedulers.from(executor); @@ -47,7 +48,7 @@ public final void testUnhandledErrorIsDeliveredToThreadHandler() throws Interrup public final void testHandledErrorIsNotDeliveredToThreadHandler() throws InterruptedException { SchedulerTests.testHandledErrorIsNotDeliveredToThreadHandler(getScheduler()); } - + @Test(timeout = 60000) public void testCancelledTaskRetention() throws InterruptedException { ExecutorService exec = Executors.newSingleThreadExecutor(); @@ -59,7 +60,7 @@ public void testCancelledTaskRetention() throws InterruptedException { } finally { w.unsubscribe(); } - + w = s.createWorker(); try { SchedulerTests.testCancelledRetention(w, true); @@ -70,7 +71,7 @@ public void testCancelledTaskRetention() throws InterruptedException { 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 queue = new ConcurrentLinkedQueue(); @@ -91,7 +92,7 @@ public void executeAll() { } } } - + @Test public void testCancelledTasksDontRun() { final AtomicInteger calls = new AtomicInteger(); @@ -108,13 +109,13 @@ public void call() { Subscription s1 = w.schedule(task); Subscription s2 = w.schedule(task); Subscription s3 = w.schedule(task); - + s1.unsubscribe(); s2.unsubscribe(); s3.unsubscribe(); - + exec.executeAll(); - + assertEquals(0, calls.get()); } finally { w.unsubscribe(); @@ -151,35 +152,35 @@ public void execute(Runnable command) { } }; ExecutorSchedulerWorker w = (ExecutorSchedulerWorker)Schedulers.from(e).createWorker(); - + w.schedule(Actions.empty(), 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) { - + } }; ExecutorSchedulerWorker w = (ExecutorSchedulerWorker)Schedulers.from(e).createWorker(); - + Subscription s = w.schedule(Actions.empty(), 1, TimeUnit.DAYS); - + assertTrue(w.tasks.hasSubscriptions()); - + s.unsubscribe(); - + assertFalse(w.tasks.hasSubscriptions()); } - + @Test public void testNoPeriodicTimedTaskPartRetention() throws InterruptedException { Executor e = new Executor() { @@ -189,7 +190,7 @@ public void execute(Runnable command) { } }; ExecutorSchedulerWorker w = (ExecutorSchedulerWorker)Schedulers.from(e).createWorker(); - + final CountDownLatch cdl = new CountDownLatch(1); final Action0 action = new Action0() { @Override @@ -197,15 +198,55 @@ public void call() { cdl.countDown(); } }; - + Subscription s = w.schedulePeriodically(action, 0, 1, TimeUnit.DAYS); - + assertTrue(w.tasks.hasSubscriptions()); - + cdl.await(); - + s.unsubscribe(); - + assertFalse(w.tasks.hasSubscriptions()); } + + @Test + public void actionHookCalled() throws Exception { + ExecutorService exec = Executors.newSingleThreadExecutor(); + try { + final int[] call = { 0 }; + + RxJavaHooks.setOnScheduleAction(new Func1() { + @Override + public Action0 call(Action0 t) { + call[0]++; + return t; + } + }); + + Scheduler s = Schedulers.from(exec); + + Worker w = s.createWorker(); + + final CountDownLatch cdl = new CountDownLatch(1); + + try { + w.schedule(new Action0() { + @Override + public void call() { + cdl.countDown(); + } + }); + + Assert.assertTrue("Action timed out", cdl.await(5, TimeUnit.SECONDS)); + } finally { + w.unsubscribe(); + } + + Assert.assertEquals("Hook not called!", 1, call[0]); + } finally { + RxJavaHooks.reset(); + exec.shutdown(); + } + } } diff --git a/src/test/java/rx/internal/schedulers/GenericScheduledExecutorServiceTest.java b/src/test/java/rx/internal/schedulers/GenericScheduledExecutorServiceTest.java index 6bb8800c97..eef6d293d9 100644 --- a/src/test/java/rx/internal/schedulers/GenericScheduledExecutorServiceTest.java +++ b/src/test/java/rx/internal/schedulers/GenericScheduledExecutorServiceTest.java @@ -25,12 +25,12 @@ public class GenericScheduledExecutorServiceTest { @Test public void verifyInstanceIsSingleThreaded() throws Exception { ScheduledExecutorService exec = GenericScheduledExecutorService.getInstance(); - + final AtomicInteger state = new AtomicInteger(); final AtomicInteger found1 = new AtomicInteger(); final AtomicInteger found2 = new AtomicInteger(); - + Future f1 = exec.schedule(new Runnable() { @Override public void run() { @@ -48,10 +48,10 @@ public void run() { found2.set(state.getAndSet(2)); } }, 250, TimeUnit.MILLISECONDS); - + f1.get(); f2.get(); - + Assert.assertEquals(2, state.get()); Assert.assertEquals(0, found1.get()); Assert.assertEquals(1, found2.get()); diff --git a/src/test/java/rx/internal/schedulers/NewThreadWorkerTest.java b/src/test/java/rx/internal/schedulers/NewThreadWorkerTest.java index 4368953241..f162ad958b 100644 --- a/src/test/java/rx/internal/schedulers/NewThreadWorkerTest.java +++ b/src/test/java/rx/internal/schedulers/NewThreadWorkerTest.java @@ -49,7 +49,7 @@ public void findSetRemoveOnCancelPolicyMethodShouldNotFindMethod() { private static abstract class ScheduledExecutorServiceWithSetRemoveOnCancelPolicy implements ScheduledExecutorService { // Just declaration of required method to allow run tests on JDK 6 - public void setRemoveOnCancelPolicy(boolean value) {} + public void setRemoveOnCancelPolicy(boolean value) { } } @Test diff --git a/src/test/java/rx/internal/util/BlockingUtilsTest.java b/src/test/java/rx/internal/util/BlockingUtilsTest.java index 02047b9d2f..007a2eb737 100644 --- a/src/test/java/rx/internal/util/BlockingUtilsTest.java +++ b/src/test/java/rx/internal/util/BlockingUtilsTest.java @@ -31,14 +31,14 @@ * Test suite for {@link BlockingUtils}. */ public class BlockingUtilsTest { - + @Before @After public void before() { // make sure the interrupted flag is cleared Thread.interrupted(); } - + @Test public void awaitCompleteShouldReturnIfCountIsZero() { Subscription subscription = mock(Subscription.class); diff --git a/src/test/java/rx/internal/util/ExceptionUtilsTest.java b/src/test/java/rx/internal/util/ExceptionUtilsTest.java new file mode 100644 index 0000000000..6f46325158 --- /dev/null +++ b/src/test/java/rx/internal/util/ExceptionUtilsTest.java @@ -0,0 +1,67 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * 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 rx.internal.util; + +import static org.junit.Assert.*; + +import java.util.concurrent.atomic.AtomicReference; + +import org.junit.*; + +import rx.exceptions.TestException; + +public class ExceptionUtilsTest { + AtomicReference error = new AtomicReference(); + + @Test + public void addToTerminatedFalse() { + ExceptionsUtils.terminate(error); + Assert.assertFalse(ExceptionsUtils.addThrowable(error, new TestException())); + } + + @Test + public void doubleTerminate() { + Assert.assertNull(ExceptionsUtils.terminate(error)); + Assert.assertNotNull(ExceptionsUtils.terminate(error)); + } + + @Test + public void isTerminated() { + Assert.assertFalse(ExceptionsUtils.isTerminated(error)); + Assert.assertFalse(ExceptionsUtils.isTerminated(error.get())); + + ExceptionsUtils.addThrowable(error, new TestException()); + + Assert.assertFalse(ExceptionsUtils.isTerminated(error)); + Assert.assertFalse(ExceptionsUtils.isTerminated(error.get())); + + ExceptionsUtils.terminate(error); + + Assert.assertTrue(ExceptionsUtils.isTerminated(error)); + Assert.assertTrue(ExceptionsUtils.isTerminated(error.get())); + } + + @Test + public void utilityEnum() { + assertEquals(0, ExceptionsUtils.values().length); + try { + ExceptionsUtils.valueOf("INSTANCE"); + fail("Failed to throw IllegalArgumentException"); + } catch (IllegalArgumentException ex) { + // expected + } + } +} diff --git a/src/test/java/rx/internal/util/IndexedRingBufferTest.java b/src/test/java/rx/internal/util/IndexedRingBufferTest.java index 5417693b85..233557ac08 100644 --- a/src/test/java/rx/internal/util/IndexedRingBufferTest.java +++ b/src/test/java/rx/internal/util/IndexedRingBufferTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -186,7 +186,7 @@ public Boolean call(String t1) { list.clear(); nextIndex = buffer.forEach(new Func1() { - int i = 0; + int i; @Override public Boolean call(String t1) { diff --git a/src/test/java/rx/internal/util/JCToolsQueueTests.java b/src/test/java/rx/internal/util/JCToolsQueueTests.java index a9f845b772..eb89381d94 100644 --- a/src/test/java/rx/internal/util/JCToolsQueueTests.java +++ b/src/test/java/rx/internal/util/JCToolsQueueTests.java @@ -47,17 +47,17 @@ public void casBasedUnsafe() { } long offset = UnsafeAccess.addressOf(IntField.class, "value"); IntField f = new IntField(); - + assertTrue(UnsafeAccess.compareAndSwapInt(f, offset, 0, 1)); assertFalse(UnsafeAccess.compareAndSwapInt(f, offset, 0, 2)); - + assertEquals(1, UnsafeAccess.getAndAddInt(f, offset, 2)); - + assertEquals(3, UnsafeAccess.getAndIncrementInt(f, offset)); - + assertEquals(4, UnsafeAccess.getAndSetInt(f, offset, 0)); } - + @Test public void powerOfTwo() { assertTrue(Pow2.isPowerOfTwo(1)); @@ -72,7 +72,7 @@ public void powerOfTwo() { assertFalse(Pow2.isPowerOfTwo(31)); assertTrue(Pow2.isPowerOfTwo(32)); } - + @Test(expected = NullPointerException.class) public void testMpmcArrayQueueNull() { if (!UnsafeAccess.isUnsafeAvailable()) { @@ -81,7 +81,7 @@ public void testMpmcArrayQueueNull() { MpmcArrayQueue q = new MpmcArrayQueue(16); q.offer(null); } - + @Test(expected = UnsupportedOperationException.class) public void testMpmcArrayQueueIterator() { if (!UnsafeAccess.isUnsafeAvailable()) { @@ -90,17 +90,17 @@ public void testMpmcArrayQueueIterator() { MpmcArrayQueue q = new MpmcArrayQueue(16); q.iterator(); } - + @Test public void testMpmcArrayQueueOfferPoll() { if (!UnsafeAccess.isUnsafeAvailable()) { return; } Queue q = new MpmcArrayQueue(128); - + testOfferPoll(q); } - + @Test public void testMpmcOfferUpToCapacity() { if (!UnsafeAccess.isUnsafeAvailable()) { @@ -118,31 +118,31 @@ public void testMpscLinkedAtomicQueueIterator() { MpscLinkedAtomicQueue q = new MpscLinkedAtomicQueue(); q.iterator(); } - + @Test(expected = NullPointerException.class) public void testMpscLinkedAtomicQueueNull() { MpscLinkedAtomicQueue q = new MpscLinkedAtomicQueue(); q.offer(null); } - + @Test public void testMpscLinkedAtomicQueueOfferPoll() { MpscLinkedAtomicQueue q = new MpscLinkedAtomicQueue(); - + testOfferPoll(q); } - - @Test(timeout = 2000) + + @Test(timeout = 20000) public void testMpscLinkedAtomicQueuePipelined() throws InterruptedException { final MpscLinkedAtomicQueue q = new MpscLinkedAtomicQueue(); - + Set set = new HashSet(); for (int i = 0; i < 1000 * 1000; i++) { set.add(i); } - + final CyclicBarrier cb = new CyclicBarrier(3); - + Thread t1 = new Thread(new Runnable() { @Override public void run() { @@ -161,20 +161,20 @@ public void run() { } } }); - + t1.start(); t2.start(); - + await(cb); - + Integer j; for (int i = 0; i < 1000 * 1000; i++) { - while ((j = q.poll()) == null); + while ((j = q.poll()) == null) { } assertTrue("Value " + j + " already removed", set.remove(j)); } assertTrue("Set is not empty", set.isEmpty()); } - + @Test(expected = UnsupportedOperationException.class) public void testMpscLinkedQueueIterator() { if (!UnsafeAccess.isUnsafeAvailable()) { @@ -183,7 +183,7 @@ public void testMpscLinkedQueueIterator() { MpscLinkedQueue q = new MpscLinkedQueue(); q.iterator(); } - + @Test(expected = NullPointerException.class) public void testMpscLinkedQueueNull() { if (!UnsafeAccess.isUnsafeAvailable()) { @@ -192,30 +192,30 @@ public void testMpscLinkedQueueNull() { MpscLinkedQueue q = new MpscLinkedQueue(); q.offer(null); } - + @Test public void testMpscLinkedQueueOfferPoll() { if (!UnsafeAccess.isUnsafeAvailable()) { return; } MpscLinkedQueue q = new MpscLinkedQueue(); - + testOfferPoll(q); } - @Test(timeout = 2000) + @Test(timeout = 20000) public void testMpscLinkedQueuePipelined() throws InterruptedException { if (!UnsafeAccess.isUnsafeAvailable()) { return; } final MpscLinkedQueue q = new MpscLinkedQueue(); - + Set set = new HashSet(); for (int i = 0; i < 1000 * 1000; i++) { set.add(i); } - + final CyclicBarrier cb = new CyclicBarrier(3); - + Thread t1 = new Thread(new Runnable() { @Override public void run() { @@ -234,20 +234,20 @@ public void run() { } } }); - + t1.start(); t2.start(); - + await(cb); - + Integer j; for (int i = 0; i < 1000 * 1000; i++) { - while ((j = q.poll()) == null); + while ((j = q.poll()) == null) { } assertTrue("Value " + j + " already removed", set.remove(j)); } assertTrue("Set is not empty", set.isEmpty()); } - + protected void testOfferPoll(Queue q) { for (int i = 0; i < 64; i++) { assertTrue(q.offer(i)); @@ -255,23 +255,23 @@ protected void testOfferPoll(Queue q) { assertFalse(q.isEmpty()); for (int i = 0; i < 64; i++) { assertEquals((Integer)i, q.peek()); - + assertEquals(64 - i, q.size()); - + assertEquals((Integer)i, q.poll()); } assertTrue(q.isEmpty()); - + for (int i = 0; i < 64; i++) { assertTrue(q.offer(i)); assertEquals((Integer)i, q.poll()); } - + assertTrue(q.isEmpty()); assertNull(q.peek()); assertNull(q.poll()); } - + @Test(expected = NullPointerException.class) public void testSpmcArrayQueueNull() { if (!UnsafeAccess.isUnsafeAvailable()) { @@ -280,14 +280,14 @@ public void testSpmcArrayQueueNull() { SpmcArrayQueue q = new SpmcArrayQueue(16); q.offer(null); } - + @Test public void testSpmcArrayQueueOfferPoll() { if (!UnsafeAccess.isUnsafeAvailable()) { return; } Queue q = new SpmcArrayQueue(128); - + testOfferPoll(q); } @Test(expected = UnsupportedOperationException.class) @@ -298,7 +298,7 @@ public void testSpmcArrayQueueIterator() { SpmcArrayQueue q = new SpmcArrayQueue(16); q.iterator(); } - + @Test public void testSpmcOfferUpToCapacity() { if (!UnsafeAccess.isUnsafeAvailable()) { @@ -311,7 +311,7 @@ public void testSpmcOfferUpToCapacity() { } assertFalse(queue.offer(n)); } - + @Test(expected = NullPointerException.class) public void testSpscArrayQueueNull() { if (!UnsafeAccess.isUnsafeAvailable()) { @@ -320,14 +320,14 @@ public void testSpscArrayQueueNull() { SpscArrayQueue q = new SpscArrayQueue(16); q.offer(null); } - + @Test public void testSpscArrayQueueOfferPoll() { if (!UnsafeAccess.isUnsafeAvailable()) { return; } Queue q = new SpscArrayQueue(128); - + testOfferPoll(q); } @Test(expected = UnsupportedOperationException.class) @@ -348,25 +348,25 @@ public void testSpscLinkedAtomicQueueNull() { SpscLinkedAtomicQueue q = new SpscLinkedAtomicQueue(); q.offer(null); } - + @Test public void testSpscLinkedAtomicQueueOfferPoll() { SpscLinkedAtomicQueue q = new SpscLinkedAtomicQueue(); - + testOfferPoll(q); } - - @Test(timeout = 2000) + + @Test(timeout = 20000) public void testSpscLinkedAtomicQueuePipelined() throws InterruptedException { final SpscLinkedAtomicQueue q = new SpscLinkedAtomicQueue(); final AtomicInteger count = new AtomicInteger(); - + Thread t = new Thread(new Runnable() { @Override public void run() { Integer j; for (int i = 0; i < 1000 * 1000; i++) { - while ((j = q.poll()) == null); + while ((j = q.poll()) == null) { } if (j == i) { count.getAndIncrement(); } @@ -374,15 +374,15 @@ public void run() { } }); t.start(); - + for (int i = 0; i < 1000 * 1000; i++) { assertTrue(q.offer(i)); } t.join(); - + assertEquals(1000 * 1000, count.get()); } - + @Test(expected = UnsupportedOperationException.class) public void testSpscLinkedQueueIterator() { if (!UnsafeAccess.isUnsafeAvailable()) { @@ -391,7 +391,7 @@ public void testSpscLinkedQueueIterator() { SpscLinkedQueue q = new SpscLinkedQueue(); q.iterator(); } - + @Test(expected = NullPointerException.class) public void testSpscLinkedQueueNull() { if (!UnsafeAccess.isUnsafeAvailable()) { @@ -400,31 +400,31 @@ public void testSpscLinkedQueueNull() { SpscLinkedQueue q = new SpscLinkedQueue(); q.offer(null); } - + @Test public void testSpscLinkedQueueOfferPoll() { if (!UnsafeAccess.isUnsafeAvailable()) { return; } SpscLinkedQueue q = new SpscLinkedQueue(); - + testOfferPoll(q); } - - @Test(timeout = 2000) + + @Test(timeout = 20000) public void testSpscLinkedQueuePipelined() throws InterruptedException { if (!UnsafeAccess.isUnsafeAvailable()) { return; } final SpscLinkedQueue q = new SpscLinkedQueue(); final AtomicInteger count = new AtomicInteger(); - + Thread t = new Thread(new Runnable() { @Override public void run() { Integer j; for (int i = 0; i < 1000 * 1000; i++) { - while ((j = q.poll()) == null); + while ((j = q.poll()) == null) { } if (j == i) { count.getAndIncrement(); } @@ -432,12 +432,12 @@ public void run() { } }); t.start(); - + for (int i = 0; i < 1000 * 1000; i++) { assertTrue(q.offer(i)); } t.join(); - + assertEquals(1000 * 1000, count.get()); } @@ -461,23 +461,23 @@ public void testUnsafeAccessAddressOf() { } UnsafeAccess.addressOf(Object.class, "field"); } - + @Test public void testSpscExactAtomicArrayQueue() { for (int i = 1; i <= RxRingBuffer.SIZE * 2; i++) { SpscExactAtomicArrayQueue q = new SpscExactAtomicArrayQueue(i); - + for (int j = 0; j < i; j++) { assertTrue(q.offer(j)); } - + assertFalse(q.offer(i)); - + for (int j = 0; j < i; j++) { assertEquals((Integer)j, q.peek()); assertEquals((Integer)j, q.poll()); } - + for (int j = 0; j < RxRingBuffer.SIZE * 4; j++) { assertTrue(q.offer(j)); assertEquals((Integer)j, q.peek()); @@ -485,46 +485,46 @@ public void testSpscExactAtomicArrayQueue() { } } } - + @Test public void testUnboundedAtomicArrayQueue() { for (int i = 1; i <= RxRingBuffer.SIZE * 2; i *= 2) { SpscUnboundedAtomicArrayQueue q = new SpscUnboundedAtomicArrayQueue(i); - + for (int j = 0; j < i; j++) { assertTrue(q.offer(j)); } - + assertTrue(q.offer(i)); - + for (int j = 0; j < i; j++) { assertEquals((Integer)j, q.peek()); assertEquals((Integer)j, q.poll()); } - + assertEquals((Integer)i, q.peek()); assertEquals((Integer)i, q.poll()); - + for (int j = 0; j < RxRingBuffer.SIZE * 4; j++) { assertTrue(q.offer(j)); assertEquals((Integer)j, q.peek()); assertEquals((Integer)j, q.poll()); } } - + } - + @Test(expected = NullPointerException.class) public void testSpscAtomicArrayQueueNull() { SpscAtomicArrayQueue q = new SpscAtomicArrayQueue(16); q.offer(null); } - + @Test public void testSpscAtomicArrayQueueOfferPoll() { Queue q = new SpscAtomicArrayQueue(128); - + testOfferPoll(q); } @Test(expected = UnsupportedOperationException.class) @@ -538,11 +538,11 @@ public void testSpscExactAtomicArrayQueueNull() { SpscExactAtomicArrayQueue q = new SpscExactAtomicArrayQueue(10); q.offer(null); } - + @Test public void testSpscExactAtomicArrayQueueOfferPoll() { Queue q = new SpscAtomicArrayQueue(120); - + testOfferPoll(q); } @Test(expected = UnsupportedOperationException.class) @@ -556,11 +556,11 @@ public void testSpscUnboundedAtomicArrayQueueNull() { SpscUnboundedAtomicArrayQueue q = new SpscUnboundedAtomicArrayQueue(16); q.offer(null); } - + @Test public void testSpscUnboundedAtomicArrayQueueOfferPoll() { Queue q = new SpscUnboundedAtomicArrayQueue(128); - + testOfferPoll(q); } @Test(expected = UnsupportedOperationException.class) diff --git a/src/test/java/rx/internal/util/LinkedArrayListTest.java b/src/test/java/rx/internal/util/LinkedArrayListTest.java index 1b7d34fa0b..6f865cf707 100644 --- a/src/test/java/rx/internal/util/LinkedArrayListTest.java +++ b/src/test/java/rx/internal/util/LinkedArrayListTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -24,13 +24,13 @@ public class LinkedArrayListTest { @Test public void testAdd() { LinkedArrayList list = new LinkedArrayList(16); - + List expected = new ArrayList(32); for (int i = 0; i < 32; i++) { list.add(i); expected.add(i); } - + assertEquals(expected, list.toList()); assertEquals(32, list.size()); } diff --git a/src/test/java/rx/internal/util/OpenHashSetTest.java b/src/test/java/rx/internal/util/OpenHashSetTest.java index b777bdbb50..9de6d0909b 100644 --- a/src/test/java/rx/internal/util/OpenHashSetTest.java +++ b/src/test/java/rx/internal/util/OpenHashSetTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -23,20 +23,20 @@ public class OpenHashSetTest { @Test public void addRemove() { OpenHashSet set = new OpenHashSet(); - + for (int i = 0; i < 1000; i++) { assertTrue(set.add(i)); assertFalse(set.add(i)); assertTrue(set.remove(i)); assertFalse(set.remove(i)); } - + Object[] values = set.values(); for (Object i : values) { assertNull(i); } } - + @Test public void addAllRemoveAll() { for (int i = 16; i < 128 * 1024; i *= 2) { diff --git a/src/main/java/rx/internal/util/FrontPadding.java b/src/test/java/rx/internal/util/PlatformDependentTest.java similarity index 53% rename from src/main/java/rx/internal/util/FrontPadding.java rename to src/test/java/rx/internal/util/PlatformDependentTest.java index 972315440d..b868145a83 100644 --- a/src/main/java/rx/internal/util/FrontPadding.java +++ b/src/test/java/rx/internal/util/PlatformDependentTest.java @@ -1,5 +1,5 @@ /** - * Copyright 2014 Netflix, Inc. + * Copyright 2016 Netflix, Inc. * * 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 @@ -15,17 +15,22 @@ */ package rx.internal.util; -import java.io.Serializable; +import org.junit.Test; -/** - * Padding up to 128 bytes at the front. - * Based on netty's implementation - */ -abstract class FrontPadding implements Serializable { - /** */ - private static final long serialVersionUID = -596356687591714352L; - /** Padding. */ - public transient long p1, p2, p3, p4, p5, p6; // 48 bytes (header is 16 bytes) - /** Padding. */ - public transient long p8, p9, p10, p11, p12, p13, p14, p15; // 64 bytes -} \ No newline at end of file +import rx.TestUtil; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +public class PlatformDependentTest { + @Test + public void constructorShouldBePrivate() { + TestUtil.checkUtilityClass(PlatformDependent.class); + } + + @Test + public void platformShouldNotBeAndroid() { + assertFalse(PlatformDependent.isAndroid()); + assertEquals(0, PlatformDependent.getAndroidApiVersion()); + } +} diff --git a/src/test/java/rx/internal/util/RxRingBufferBase.java b/src/test/java/rx/internal/util/RxRingBufferBase.java index 01a7b35ca5..c13dadb9af 100644 --- a/src/test/java/rx/internal/util/RxRingBufferBase.java +++ b/src/test/java/rx/internal/util/RxRingBufferBase.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. diff --git a/src/test/java/rx/internal/util/RxRingBufferSpmcTest.java b/src/test/java/rx/internal/util/RxRingBufferSpmcTest.java index 6129022c54..d9d2f34373 100644 --- a/src/test/java/rx/internal/util/RxRingBufferSpmcTest.java +++ b/src/test/java/rx/internal/util/RxRingBufferSpmcTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -57,7 +57,7 @@ public void testConcurrency() throws InterruptedException { final Producer p = new Producer() { // AtomicInteger c = new AtomicInteger(); - + @Override public void request(final long n) { // System.out.println("request[" + c.incrementAndGet() + "]: " + n + " Thread: " + Thread.currentThread()); diff --git a/src/test/java/rx/internal/util/RxRingBufferSpscTest.java b/src/test/java/rx/internal/util/RxRingBufferSpscTest.java index b1d1962eb0..4e12b1987f 100644 --- a/src/test/java/rx/internal/util/RxRingBufferSpscTest.java +++ b/src/test/java/rx/internal/util/RxRingBufferSpscTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. diff --git a/src/test/java/rx/internal/util/RxRingBufferWithoutUnsafeTest.java b/src/test/java/rx/internal/util/RxRingBufferWithoutUnsafeTest.java index c7d5931887..691de7c295 100644 --- a/src/test/java/rx/internal/util/RxRingBufferWithoutUnsafeTest.java +++ b/src/test/java/rx/internal/util/RxRingBufferWithoutUnsafeTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -42,7 +42,7 @@ public void testConcurrencyLoop() throws InterruptedException { testConcurrency(); } } - + /** * Single producer, 2 consumers. The request() ensures it gets scheduled back on the same Producer thread. * @throws InterruptedException if the wait is interrupted diff --git a/src/test/java/rx/internal/util/ScalarSynchronousObservableTest.java b/src/test/java/rx/internal/util/ScalarSynchronousObservableTest.java index 6c2386812b..ef0acf2fe2 100644 --- a/src/test/java/rx/internal/util/ScalarSynchronousObservableTest.java +++ b/src/test/java/rx/internal/util/ScalarSynchronousObservableTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -234,15 +234,15 @@ public void onNext(Integer t) { ts.assertError(TestException.class); ts.assertNotCompleted(); } - + @SuppressWarnings("rawtypes") @Test public void hookCalled() { Func1 save = RxJavaHooks.getOnObservableCreate(); try { final AtomicInteger c = new AtomicInteger(); - - + + RxJavaHooks.setOnObservableCreate(new Func1() { @Override public OnSubscribe call(OnSubscribe t) { @@ -250,13 +250,13 @@ public OnSubscribe call(OnSubscribe t) { return t; } }); - + int n = 10; - + for (int i = 0; i < n; i++) { Observable.just(1).subscribe(); } - + Assert.assertEquals(n, c.get()); } finally { RxJavaHooks.setOnObservableCreate(save); @@ -285,15 +285,15 @@ public void call(Subscriber t) { return f; } }); - + TestSubscriber ts = new TestSubscriber(); - + Observable.just(1).subscribe(ts); - + ts.assertValues(1, 1); ts.assertNoErrors(); ts.assertCompleted(); - + } finally { RxJavaHooks.setOnObservableCreate(save); } diff --git a/src/test/java/rx/internal/util/SubscriptionListTest.java b/src/test/java/rx/internal/util/SubscriptionListTest.java index 614b55445d..43b45c8da3 100644 --- a/src/test/java/rx/internal/util/SubscriptionListTest.java +++ b/src/test/java/rx/internal/util/SubscriptionListTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -23,11 +23,12 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicInteger; -import org.junit.Test; +import org.junit.*; import rx.Subscription; import rx.exceptions.CompositeException; import rx.internal.util.SubscriptionList; +import rx.subscriptions.Subscriptions; public class SubscriptionListTest { @@ -282,4 +283,54 @@ public void run() { // we should have only unsubscribed once assertEquals(1, counter.get()); } + + @Test + public void removeWhenEmpty() { + SubscriptionList slist = new SubscriptionList(); + Subscription s = Subscriptions.empty(); + + slist.remove(s); + + Assert.assertFalse(s.isUnsubscribed()); + } + + @Test + public void removeNotIn() { + SubscriptionList slist = new SubscriptionList(); + Subscription s0 = Subscriptions.empty(); + slist.add(s0); + + Assert.assertTrue(slist.hasSubscriptions()); + + Subscription s = Subscriptions.empty(); + + slist.remove(s); + + Assert.assertFalse(s.isUnsubscribed()); + + slist.clear(); + + Assert.assertTrue(s0.isUnsubscribed()); + + Assert.assertFalse(slist.hasSubscriptions()); + } + + @Test + public void unsubscribeClear() { + SubscriptionList slist = new SubscriptionList(); + + Assert.assertFalse(slist.hasSubscriptions()); + + Subscription s0 = Subscriptions.empty(); + slist.add(s0); + + slist.unsubscribe(); + + Assert.assertTrue(s0.isUnsubscribed()); + + Assert.assertFalse(slist.hasSubscriptions()); + + slist.clear(); + } + } diff --git a/src/test/java/rx/internal/util/UtilityFunctionsTest.java b/src/test/java/rx/internal/util/UtilityFunctionsTest.java new file mode 100644 index 0000000000..a47b71ffaa --- /dev/null +++ b/src/test/java/rx/internal/util/UtilityFunctionsTest.java @@ -0,0 +1,43 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * 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 rx.internal.util; + +import org.junit.Test; +import static org.junit.Assert.*; + +import rx.TestUtil; + +public class UtilityFunctionsTest { + @Test + public void constructorShouldBePrivate() { + TestUtil.checkUtilityClass(UtilityFunctions.class); + } + + @Test + public void alwaysFalse() { + assertEquals(1, UtilityFunctions.AlwaysFalse.values().length); + assertNotNull(UtilityFunctions.AlwaysFalse.valueOf("INSTANCE")); + assertFalse(UtilityFunctions.alwaysFalse().call(1)); + } + + @Test + public void alwaysTrue() { + assertEquals(1, UtilityFunctions.AlwaysTrue.values().length); + assertNotNull(UtilityFunctions.AlwaysTrue.valueOf("INSTANCE")); + assertTrue(UtilityFunctions.alwaysTrue().call(1)); + } + +} diff --git a/src/test/java/rx/internal/util/unsafe/Pow2Test.java b/src/test/java/rx/internal/util/unsafe/Pow2Test.java new file mode 100644 index 0000000000..3e8c527726 --- /dev/null +++ b/src/test/java/rx/internal/util/unsafe/Pow2Test.java @@ -0,0 +1,27 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * 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 rx.internal.util.unsafe; + +import org.junit.Test; + +import rx.TestUtil; + +public class Pow2Test { + @Test + public void constructorShouldBePrivate() { + TestUtil.checkUtilityClass(Pow2.class); + } +} diff --git a/src/test/java/rx/internal/util/unsafe/UnsafeAccessTest.java b/src/test/java/rx/internal/util/unsafe/UnsafeAccessTest.java new file mode 100644 index 0000000000..dbcdb44bf5 --- /dev/null +++ b/src/test/java/rx/internal/util/unsafe/UnsafeAccessTest.java @@ -0,0 +1,27 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * 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 rx.internal.util.unsafe; + +import org.junit.Test; + +import rx.TestUtil; + +public class UnsafeAccessTest { + @Test + public void constructorShouldBePrivate() { + TestUtil.checkUtilityClass(UnsafeAccess.class); + } +} diff --git a/src/test/java/rx/observables/AsyncOnSubscribeTest.java b/src/test/java/rx/observables/AsyncOnSubscribeTest.java index 71370e84f3..72afaad285 100644 --- a/src/test/java/rx/observables/AsyncOnSubscribeTest.java +++ b/src/test/java/rx/observables/AsyncOnSubscribeTest.java @@ -51,21 +51,21 @@ public class AsyncOnSubscribeTest { public Observer o; TestSubscriber subscriber; - + @Before public void setup() { subscriber = new TestSubscriber(o, 0L); } - + @Test public void testSerializesConcurrentObservables() throws InterruptedException { final TestScheduler scheduler = new TestScheduler(); - OnSubscribe os = AsyncOnSubscribe.createStateful(new Func0(){ + AsyncOnSubscribe os = AsyncOnSubscribe.createStateful(new Func0() { @Override public Integer call() { return 1; - }}, - new Func3>, Integer>(){ + }}, + new Func3>, Integer>() { @Override public Integer call(Integer state, Long requested, Observer> observer) { if (state == 1) { @@ -76,9 +76,9 @@ public Integer call(Integer state, Long requested, Observer o = Observable.just(5, 6, 7, 8); observer.onNext(o); - } - else + } else { observer.onCompleted(); + } return state + 1; }}); // initial request emits [[1, 2, 3, 4]] on delay @@ -90,58 +90,58 @@ else if (state == 2) { subscriber.assertNoErrors(); subscriber.assertValues(1, 2); // final request completes - subscriber.requestMore(3); + subscriber.requestMore(3); subscriber.assertNoErrors(); subscriber.assertValues(1, 2, 3, 4, 5); - + subscriber.requestMore(3); - + subscriber.assertNoErrors(); subscriber.assertValues(1, 2, 3, 4, 5, 6, 7, 8); subscriber.assertCompleted(); } - + @Test public void testSubscribedByBufferingOperator() { final TestScheduler scheduler = new TestScheduler(); OnSubscribe os = AsyncOnSubscribe.createStateless( - new Action2>>(){ + new Action2>>() { @Override public void call(Long requested, Observer> observer) { observer.onNext(Observable.range(1, requested.intValue())); }}); - Observable.create(os).observeOn(scheduler).subscribe(subscriber); + Observable.unsafeCreate(os).observeOn(scheduler).subscribe(subscriber); subscriber.requestMore(RxRingBuffer.SIZE); scheduler.advanceTimeBy(10, TimeUnit.DAYS); subscriber.assertNoErrors(); subscriber.assertValueCount(RxRingBuffer.SIZE); subscriber.assertNotCompleted(); } - + @Test public void testOnUnsubscribeHasCorrectState() throws InterruptedException { final AtomicInteger lastState = new AtomicInteger(-1); - OnSubscribe os = AsyncOnSubscribe.createStateful(new Func0(){ + OnSubscribe os = AsyncOnSubscribe.createStateful(new Func0() { @Override public Integer call() { return 1; - }}, - new Func3>, Integer>(){ + }}, + new Func3>, Integer>() { @Override public Integer call(Integer state, Long requested, Observer> observer) { if (state < 3) { observer.onNext(Observable.just(state)); - } - else + } else { observer.onCompleted(); + } return state + 1; - }}, + }}, new Action1() { @Override public void call(Integer t) { lastState.set(t); }}); - Observable.create(os).subscribe(subscriber); + Observable.unsafeCreate(os).subscribe(subscriber); subscriber.requestMore(1); // [[1]], state = 1 subscriber.requestMore(2); // [[1]], state = 2 subscriber.requestMore(3); // onComplete, state = 3 @@ -153,12 +153,12 @@ public void call(Integer t) { @Test public void testOnCompleteOuter() throws InterruptedException { - OnSubscribe os = AsyncOnSubscribe.createStateless(new Action2>>(){ + OnSubscribe os = AsyncOnSubscribe.createStateless(new Action2>>() { @Override public void call(Long requested, Observer> observer) { observer.onCompleted(); }}); - Observable.create(os).subscribe(subscriber); + Observable.unsafeCreate(os).subscribe(subscriber); subscriber.requestMore(1); subscriber.assertNoErrors(); subscriber.assertCompleted(); @@ -167,14 +167,14 @@ public void call(Long requested, Observer> observe @Test public void testTryOnNextTwice() throws InterruptedException { - OnSubscribe os = AsyncOnSubscribe.createStateless(new Action2>>(){ + OnSubscribe os = AsyncOnSubscribe.createStateless(new Action2>>() { @Override public void call(Long requested, Observer> observer) { observer.onNext(Observable.just(1)); observer.onNext(Observable.just(2)); } }); - Observable.create(os).subscribe(subscriber); + Observable.unsafeCreate(os).subscribe(subscriber); subscriber.requestMore(1); subscriber.assertError(IllegalStateException.class); subscriber.assertNotCompleted(); @@ -184,12 +184,12 @@ public void call(Long requested, Observer> observe @Test public void testThrowException() throws InterruptedException { OnSubscribe os = AsyncOnSubscribe.createStateless( - new Action2>>(){ + new Action2>>() { @Override public void call(Long requested, Observer> observer) { throw new TestException(); }}); - Observable.create(os).subscribe(subscriber); + Observable.unsafeCreate(os).subscribe(subscriber); subscriber.requestMore(1); subscriber.assertError(TestException.class); subscriber.assertNotCompleted(); @@ -198,18 +198,18 @@ public void call(Long requested, Observer> observe @Test public void testThrowExceptionAfterTerminal() throws InterruptedException { - OnSubscribe os = AsyncOnSubscribe.createStateful(new Func0(){ + OnSubscribe os = AsyncOnSubscribe.createStateful(new Func0() { @Override public Integer call() { return 1; - }}, - new Func3>, Integer>(){ + }}, + new Func3>, Integer>() { @Override public Integer call(Integer state, Long requested, Observer> observer) { observer.onCompleted(); throw new TestException(); }}); - Observable.create(os).subscribe(subscriber); + Observable.unsafeCreate(os).subscribe(subscriber); subscriber.requestMore(1); subscriber.assertNoErrors(); subscriber.assertCompleted(); @@ -218,19 +218,19 @@ public Integer call(Integer state, Long requested, Observer os = AsyncOnSubscribe.createStateful(new Func0(){ + OnSubscribe os = AsyncOnSubscribe.createStateful(new Func0() { @Override public Integer call() { return 1; - }}, - new Func3>, Integer>(){ + }}, + new Func3>, Integer>() { @Override public Integer call(Integer state, Long requested, Observer> observer) { observer.onCompleted(); observer.onNext(Observable.just(1)); return 1; }}); - Observable.create(os).subscribe(subscriber); + Observable.unsafeCreate(os).subscribe(subscriber); subscriber.requestMore(1); subscriber.assertNoErrors(); subscriber.assertCompleted(); @@ -239,19 +239,19 @@ public Integer call(Integer state, Long requested, Observer os = AsyncOnSubscribe.createStateful(new Func0(){ + OnSubscribe os = AsyncOnSubscribe.createStateful(new Func0() { @Override public Integer call() { return 1; - }}, - new Func3>, Integer>(){ + }}, + new Func3>, Integer>() { @Override public Integer call(Integer state, Long requested, Observer> observer) { observer.onError(new TestException()); observer.onNext(Observable.just(1)); return 1; }}); - Observable.create(os).subscribe(subscriber); + Observable.unsafeCreate(os).subscribe(subscriber); subscriber.requestMore(1); subscriber.assertError(TestException.class); subscriber.assertNotCompleted(); @@ -260,19 +260,19 @@ public Integer call(Integer state, Long requested, Observer os = AsyncOnSubscribe.createStateful(new Func0(){ + OnSubscribe os = AsyncOnSubscribe.createStateful(new Func0() { @Override public Integer call() { return 1; - }}, - new Func3>, Integer>(){ + }}, + new Func3>, Integer>() { @Override public Integer call(Integer state, Long requested, Observer> observer) { observer.onNext(Observable.empty()); observer.onCompleted(); return state; }}); - Observable.create(os).subscribe(subscriber); + Observable.unsafeCreate(os).subscribe(subscriber); subscriber.requestMore(1); subscriber.assertNoErrors(); subscriber.assertCompleted(); @@ -281,33 +281,33 @@ public Integer call(Integer state, Long requested, Observer os = AsyncOnSubscribe.createStateful(new Func0(){ + OnSubscribe os = AsyncOnSubscribe.createStateful(new Func0() { @Override public Integer call() { return 1; - }}, - new Func3>, Integer>(){ + }}, + new Func3>, Integer>() { @Override public Integer call(Integer state, Long requested, Observer> observer) { observer.onError(new TestException()); return state; } }); - Observable.create(os).subscribe(subscriber); + Observable.unsafeCreate(os).subscribe(subscriber); subscriber.requestMore(1); subscriber.assertError(TestException.class); subscriber.assertNotCompleted(); subscriber.assertNoValues(); } - + @Test public void testOnCompleteFollowedByOnErrorOuter() throws InterruptedException { - OnSubscribe os = AsyncOnSubscribe.createStateful(new Func0(){ + OnSubscribe os = AsyncOnSubscribe.createStateful(new Func0() { @Override public Integer call() { return 1; - }}, - new Func3>, Integer>(){ + }}, + new Func3>, Integer>() { @Override public Integer call(Integer state, Long requested, Observer> observer) { observer.onCompleted(); @@ -315,7 +315,7 @@ public Integer call(Integer state, Long requested, Observer os = AsyncOnSubscribe.createStateful(new Func0(){ + OnSubscribe os = AsyncOnSubscribe.createStateful(new Func0() { @Override public Integer call() { return 1; - }}, - new Func3>, Integer>(){ + }}, + new Func3>, Integer>() { @Override public Integer call(Integer state, Long requested, Observer> observer) { Observable o1; switch (state) { case 1: o1 = Observable.just(1) - .doOnUnsubscribe(new Action0(){ + .doOnUnsubscribe(new Action0() { @Override public void call() { l1.incrementAndGet(); @@ -346,7 +346,7 @@ public void call() { break; case 2: o1 = Observable.just(2) - .doOnUnsubscribe(new Action0(){ + .doOnUnsubscribe(new Action0() { @Override public void call() { l2.incrementAndGet(); @@ -359,7 +359,7 @@ public void call() { observer.onNext(o1); return state + 1; }}); - Observable.create(os).subscribe(subscriber); + Observable.unsafeCreate(os).subscribe(subscriber); subscriber.requestMore(1); // [[1]] subscriber.requestMore(2); // [[2]] subscriber.requestMore(2); // onCompleted @@ -373,26 +373,26 @@ public void call() { subscriber.assertNoErrors(); subscriber.assertCompleted(); } - + @Test public void testUnsubscribesFromAllNonTerminatedObservables() throws InterruptedException { final AtomicInteger l1 = new AtomicInteger(); final AtomicInteger l2 = new AtomicInteger(); final TestScheduler scheduler = new TestScheduler(); final AtomicReference sub = new AtomicReference(); - OnSubscribe os = AsyncOnSubscribe.createStateful(new Func0(){ + OnSubscribe os = AsyncOnSubscribe.createStateful(new Func0() { @Override public Integer call() { return 1; - }}, - new Func3>, Integer>(){ + }}, + new Func3>, Integer>() { @Override public Integer call(Integer state, Long requested, Observer> observer) { switch (state) { case 1: observer.onNext(Observable.range(1, requested.intValue()) .subscribeOn(scheduler) - .doOnUnsubscribe(new Action0(){ + .doOnUnsubscribe(new Action0() { @Override public void call() { l1.incrementAndGet(); @@ -402,7 +402,7 @@ public void call() { observer.onNext(Observable.just(1) .concatWith(Observable.never()) .subscribeOn(scheduler) - .doOnUnsubscribe(new Action0(){ + .doOnUnsubscribe(new Action0() { @Override public void call() { l2.incrementAndGet(); @@ -413,7 +413,7 @@ public void call() { } return state + 1; }}); - Subscription subscription = Observable.create(os) + Subscription subscription = Observable.unsafeCreate(os) .observeOn(scheduler, 1) .subscribe(subscriber); sub.set(subscription); @@ -428,30 +428,31 @@ public void call() { assertEquals("did not unsub from 1st observable after terminal", 1, l1.get()); assertEquals("did not unsub from Observable.never() inner obs", 1, l2.get()); } - - private static class Foo {} - private static class Bar extends Foo {} - + + private static class Foo { } + private static class Bar extends Foo { } + @Test public void testGenerics() { - AsyncOnSubscribe.createStateless(new Action2>>(){ + AsyncOnSubscribe.createStateless(new Action2>>() { @Override public void call(Long state, Observer> observer) { - if (state == null) + if (state == null) { observer.onNext(Observable.just(new Foo())); - else + } else { observer.onNext(Observable.just(new Bar())); + } }}); } - + @Test public void testUnderdeliveryCorrection() { - OnSubscribe os = AsyncOnSubscribe.createStateful(new Func0(){ + OnSubscribe os = AsyncOnSubscribe.createStateful(new Func0() { @Override public Integer call() { return 1; - }}, - new Func3>, Integer>(){ + }}, + new Func3>, Integer>() { @Override public Integer call(Integer state, Long requested, Observer> observer) { switch (state) { @@ -464,22 +465,56 @@ public Integer call(Integer state, Long requested, Observer os = Observable.create(AsyncOnSubscribe. createStateful( + new Func0() { + + @Override + public Integer call() { + return 0; + } + + }, + new Func3>, Integer>() { + + @Override + public Integer call(Integer state, Long requested, Observer> emitter) { + if (state == 0) { + emitter.onNext(Observable.range(0,100).delay(1, TimeUnit.SECONDS, scheduler)); + } else { + emitter.onCompleted(); + } + return state + 1; + } + + })); + + TestSubscriber ts = new TestSubscriber(); + os.mergeWith(Observable.just(0)).subscribe(ts); + scheduler.advanceTimeBy(1, TimeUnit.HOURS); + ts.assertCompleted(); + ts.assertValueCount(101); + } + } \ No newline at end of file diff --git a/src/test/java/rx/observables/BlockingObservableTest.java b/src/test/java/rx/observables/BlockingObservableTest.java index c20eabd01d..fc802f978a 100644 --- a/src/test/java/rx/observables/BlockingObservableTest.java +++ b/src/test/java/rx/observables/BlockingObservableTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -259,7 +259,7 @@ public void testToIterableManyTimes() { @Test(expected = TestException.class) public void testToIterableWithException() { - BlockingObservable obs = BlockingObservable.from(Observable.create(new Observable.OnSubscribe() { + BlockingObservable obs = BlockingObservable.from(Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber observer) { @@ -281,7 +281,7 @@ public void call(Subscriber observer) { @Test public void testForEachWithError() { try { - BlockingObservable.from(Observable.create(new Observable.OnSubscribe() { + BlockingObservable.from(Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(final Subscriber observer) { @@ -382,7 +382,7 @@ public Boolean call(String args) { @Test public void testSingleOrDefaultUnsubscribe() throws InterruptedException { final CountDownLatch unsubscribe = new CountDownLatch(1); - Observable o = Observable.create(new OnSubscribe() { + Observable o = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber subscriber) { subscriber.add(Subscriptions.create(new Action0() { @@ -565,7 +565,7 @@ private Observable createSynchronousObservable() { @Override public Iterator iterator() { return new Iterator() { - private boolean nextCalled = false; + private boolean nextCalled; @Override public boolean hasNext() { @@ -636,12 +636,12 @@ private InterruptedException getInterruptedExceptionOrNull() { public void testRun() { Observable.just(1).observeOn(Schedulers.computation()).toBlocking().subscribe(); } - + @Test(expected = TestException.class) public void testRunException() { Observable.error(new TestException()).observeOn(Schedulers.computation()).toBlocking().subscribe(); } - + @Test public void testRunIOException() { try { @@ -654,7 +654,7 @@ public void testRunIOException() { fail("Bad exception type: " + ex + ", " + ex.getCause()); } } - + @Test public void testSubscriberBackpressure() { TestSubscriber ts = new TestSubscriber() { @@ -662,26 +662,26 @@ public void testSubscriberBackpressure() { public void onStart() { request(2); } - + @Override public void onNext(Integer t) { super.onNext(t); unsubscribe(); } }; - + Observable.range(1, 10).observeOn(Schedulers.computation()).toBlocking().subscribe(ts); - + ts.assertNoErrors(); ts.assertNotCompleted(); ts.assertValue(1); } - + @Test(expected = OnErrorNotImplementedException.class) public void testOnErrorNotImplemented() { Observable.error(new TestException()).observeOn(Schedulers.computation()).toBlocking().subscribe(Actions.empty()); } - + @Test public void testSubscribeCallback1() { final boolean[] valueReceived = { false }; @@ -692,10 +692,10 @@ public void call(Integer t) { assertEquals((Integer)1, t); } }); - + assertTrue(valueReceived[0]); } - + @Test public void testSubscribeCallback2() { final boolean[] received = { false }; @@ -712,10 +712,10 @@ public void call(Throwable t) { assertEquals(TestException.class, t.getClass()); } }); - + assertTrue(received[0]); } - + @Test public void testSubscribeCallback3() { final boolean[] received = { false, false }; @@ -737,7 +737,7 @@ public void call() { received[1] = true; } }); - + assertTrue(received[0]); assertTrue(received[1]); } @@ -760,7 +760,7 @@ public void call() { ts.onCompleted(); } }); - + ts.assertNoValues(); ts.assertNotCompleted(); ts.assertError(TestException.class); diff --git a/src/test/java/rx/observables/ConnectableObservableTest.java b/src/test/java/rx/observables/ConnectableObservableTest.java index 419c694bb0..ab260e097c 100644 --- a/src/test/java/rx/observables/ConnectableObservableTest.java +++ b/src/test/java/rx/observables/ConnectableObservableTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -27,25 +27,25 @@ public class ConnectableObservableTest { @Test public void testAutoConnect() { final AtomicInteger run = new AtomicInteger(); - + ConnectableObservable co = Observable.defer(new Func0>() { @Override public Observable call() { return Observable.just(run.incrementAndGet()); } }).publish(); - + Observable source = co.autoConnect(); - + Assert.assertEquals(0, run.get()); - + TestSubscriber ts1 = TestSubscriber.create(); source.subscribe(ts1); - + ts1.assertCompleted(); ts1.assertNoErrors(); ts1.assertValue(1); - + Assert.assertEquals(1, run.get()); TestSubscriber ts2 = TestSubscriber.create(); @@ -54,31 +54,31 @@ public Observable call() { ts2.assertNotCompleted(); ts2.assertNoErrors(); ts2.assertNoValues(); - + Assert.assertEquals(1, run.get()); } @Test public void testAutoConnect0() { final AtomicInteger run = new AtomicInteger(); - + ConnectableObservable co = Observable.defer(new Func0>() { @Override public Observable call() { return Observable.just(run.incrementAndGet()); } }).publish(); - + Observable source = co.autoConnect(0); - + Assert.assertEquals(1, run.get()); - + TestSubscriber ts1 = TestSubscriber.create(); source.subscribe(ts1); - + ts1.assertNotCompleted(); ts1.assertNoErrors(); ts1.assertNoValues(); - + Assert.assertEquals(1, run.get()); TestSubscriber ts2 = TestSubscriber.create(); @@ -87,31 +87,31 @@ public Observable call() { ts2.assertNotCompleted(); ts2.assertNoErrors(); ts2.assertNoValues(); - + Assert.assertEquals(1, run.get()); } @Test public void testAutoConnect2() { final AtomicInteger run = new AtomicInteger(); - + ConnectableObservable co = Observable.defer(new Func0>() { @Override public Observable call() { return Observable.just(run.incrementAndGet()); } }).publish(); - + Observable source = co.autoConnect(2); - + Assert.assertEquals(0, run.get()); - + TestSubscriber ts1 = TestSubscriber.create(); source.subscribe(ts1); - + ts1.assertNotCompleted(); ts1.assertNoErrors(); ts1.assertNoValues(); - + Assert.assertEquals(0, run.get()); TestSubscriber ts2 = TestSubscriber.create(); @@ -126,31 +126,31 @@ public Observable call() { ts2.assertCompleted(); ts2.assertNoErrors(); ts2.assertValue(1); - + } - + @Test public void testAutoConnectUnsubscribe() { final AtomicInteger run = new AtomicInteger(); - + ConnectableObservable co = Observable.defer(new Func0>() { @Override public Observable call() { return Observable.range(run.incrementAndGet(), 10); } }).publish(); - + final AtomicReference conn = new AtomicReference(); - + Observable source = co.autoConnect(1, new Action1() { @Override public void call(Subscription t) { conn.set(t); } }); - + Assert.assertEquals(0, run.get()); - + TestSubscriber ts = new TestSubscriber() { @Override public void onNext(Integer t) { @@ -163,13 +163,13 @@ public void onNext(Integer t) { } } }; - + source.subscribe(ts); - + ts.assertNotCompleted(); ts.assertNoErrors(); ts.assertValue(1); - + Assert.assertTrue("Connection not unsubscribed?", conn.get().isUnsubscribed()); } } diff --git a/src/test/java/rx/observables/SyncOnSubscribeTest.java b/src/test/java/rx/observables/SyncOnSubscribeTest.java index 71fc0ac8e9..a219e1d7b7 100644 --- a/src/test/java/rx/observables/SyncOnSubscribeTest.java +++ b/src/test/java/rx/observables/SyncOnSubscribeTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -40,7 +40,7 @@ * Test if SyncOnSubscribe adheres to the usual unsubscription and backpressure contracts. */ public class SyncOnSubscribeTest { - + @Test public void testObservableJustEquivalent() { OnSubscribe os = SyncOnSubscribe.createStateless(new Action1>() { @@ -49,10 +49,10 @@ public void call(Observer subscriber) { subscriber.onNext(1); subscriber.onCompleted(); }}); - + TestSubscriber ts = new TestSubscriber(); - Observable.create(os).subscribe(ts); + Observable.unsafeCreate(os).subscribe(ts); ts.assertNoErrors(); ts.assertTerminalEvent(); @@ -62,27 +62,27 @@ public void call(Observer subscriber) { @Test public void testStateAfterTerminal() { final AtomicInteger finalStateValue = new AtomicInteger(-1); - OnSubscribe os = SyncOnSubscribe.createStateful(new Func0(){ + OnSubscribe os = SyncOnSubscribe.createStateful(new Func0() { @Override public Integer call() { return 1; - }}, + }}, new Func2, Integer>() { @Override public Integer call(Integer state, Observer subscriber) { subscriber.onNext(state); subscriber.onCompleted(); return state + 1; - }}, + }}, new Action1() { @Override public void call(Integer t) { finalStateValue.set(t); }}); - + TestSubscriber ts = new TestSubscriber(); - Observable.create(os).subscribe(ts); + Observable.unsafeCreate(os).subscribe(ts); ts.assertNoErrors(); ts.assertTerminalEvent(); @@ -99,11 +99,11 @@ public void call(Observer subscriber) { subscriber.onNext(2); subscriber.onCompleted(); }}); - + @SuppressWarnings("unchecked") Observer o = mock(Observer.class); - Observable.create(os).subscribe(o); + Observable.unsafeCreate(os).subscribe(o); verify(o, times(1)).onNext(1); verify(o, never()).onNext(2); @@ -120,11 +120,11 @@ public void call(Observer subscriber) { subscriber.onCompleted(); subscriber.onCompleted(); }}); - + @SuppressWarnings("unchecked") Observer o = mock(Observer.class); - Observable.create(os).subscribe(o); + Observable.unsafeCreate(os).subscribe(o); verify(o, times(1)).onNext(1); verify(o, times(1)).onCompleted(); @@ -140,11 +140,11 @@ public void call(Observer subscriber) { subscriber.onCompleted(); subscriber.onNext(1); }}); - + @SuppressWarnings("unchecked") Observer o = mock(Observer.class); - Observable.create(os).subscribe(o); + Observable.unsafeCreate(os).subscribe(o); verify(o, times(1)).onNext(1); verify(o, times(1)).onCompleted(); @@ -167,11 +167,11 @@ public void call(Observer subscriber) { subscriber.onError(new TestException("Forced failure 1")); subscriber.onError(new FooException("Should not see this error.")); }}); - + @SuppressWarnings("unchecked") Observer o = mock(Observer.class); - Observable.create(os).subscribe(o); + Observable.unsafeCreate(os).subscribe(o); verify(o, times(1)).onNext(1); verify(o, never()).onCompleted(); @@ -190,7 +190,7 @@ public void call(Observer subscriber) { @SuppressWarnings("unchecked") Observer o = mock(Observer.class); - Observable.create(os).subscribe(o); + Observable.unsafeCreate(os).subscribe(o); verify(o, never()).onNext(any(Integer.class)); verify(o, never()).onError(any(Throwable.class)); @@ -202,11 +202,11 @@ public void testNever() { OnSubscribe os = SyncOnSubscribe.createStateless(new Action1>() { @Override public void call(Observer subscriber) { - + }}); - Observable neverObservable = Observable.create(os).subscribeOn(Schedulers.newThread()); + Observable neverObservable = Observable.unsafeCreate(os).subscribeOn(Schedulers.newThread()); Observable merged = Observable.amb(neverObservable, Observable.timer(100, TimeUnit.MILLISECONDS).subscribeOn(Schedulers.newThread())); Iterator values = merged.toBlocking().toIterable().iterator(); @@ -225,7 +225,7 @@ public void call(Observer subscriber) { @SuppressWarnings("unchecked") Observer o = mock(Observer.class); - Observable.create(os).subscribe(o); + Observable.unsafeCreate(os).subscribe(o); verify(o, never()).onNext(any(Integer.class)); verify(o, never()).onCompleted(); @@ -243,7 +243,7 @@ public void call(Observer subscriber) { @SuppressWarnings("unchecked") Observer o = mock(Observer.class); - Observable.create(os).subscribe(o); + Observable.unsafeCreate(os).subscribe(o); verify(o, never()).onNext(any(Integer.class)); verify(o, times(1)).onCompleted(); @@ -268,7 +268,7 @@ public void onStart() { } }; - Observable.create(os).subscribe(ts); + Observable.unsafeCreate(os).subscribe(ts); ts.requestMore(1); @@ -288,7 +288,7 @@ public void call(Observer subscriber) { @SuppressWarnings("unchecked") Observer o = mock(Observer.class); - Observable.create(os).subscribe(o); + Observable.unsafeCreate(os).subscribe(o); verify(o, never()).onNext(any(Integer.class)); verify(o).onError(any(TestException.class)); @@ -299,11 +299,11 @@ public void call(Observer subscriber) { public void testRange() { final int start = 1; final int count = 4000; - OnSubscribe os = SyncOnSubscribe.createStateful(new Func0(){ + OnSubscribe os = SyncOnSubscribe.createStateful(new Func0() { @Override public Integer call() { return start; - }}, + }}, new Func2, Integer>() { @Override public Integer call(Integer state, Observer subscriber) { @@ -319,7 +319,7 @@ public Integer call(Integer state, Observer subscriber) { Observer o = mock(Observer.class); InOrder inOrder = inOrder(o); - Observable.create(os).subscribe(o); + Observable.unsafeCreate(os).subscribe(o); verify(o, never()).onError(any(TestException.class)); inOrder.verify(o, times(count)).onNext(any(Integer.class)); @@ -339,7 +339,7 @@ public void testFromIterable() { @Override public Iterator call() { return source.iterator(); - }}, + }}, new Func2, Observer, Iterator>() { @Override public Iterator call(Iterator it, Observer observer) { @@ -357,7 +357,7 @@ public Iterator call(Iterator it, Observer ob Observer o = mock(Observer.class); InOrder inOrder = inOrder(o); - Observable.create(os).subscribe(o); + Observable.unsafeCreate(os).subscribe(o); verify(o, never()).onError(any(TestException.class)); inOrder.verify(o, times(n)).onNext(any()); @@ -374,7 +374,7 @@ public void testInfiniteTake() { @Override public Integer call() { return start; - }}, + }}, new Func2, Integer>() { @Override public Integer call(Integer state, Observer observer) { @@ -385,7 +385,7 @@ public Integer call(Integer state, Observer observer) { Observer o = mock(Observer.class); InOrder inOrder = inOrder(o); - Observable.create(os).take(finalCount).subscribe(o); + Observable.unsafeCreate(os).take(finalCount).subscribe(o); verify(o, never()).onError(any(Throwable.class)); inOrder.verify(o, times(finalCount)).onNext(any()); @@ -397,16 +397,16 @@ public Integer call(Integer state, Observer observer) { public void testInfiniteRequestSome() { final int finalCount = 4000; final int start = 0; - + @SuppressWarnings("unchecked") Action1 onUnSubscribe = mock(Action1.class); - + OnSubscribe os = SyncOnSubscribe.createStateful( new Func0() { @Override public Integer call() { return start; - }}, + }}, new Func2, Integer>() { @Override public Integer call(Integer state, Observer observer) { @@ -416,7 +416,7 @@ public Integer call(Integer state, Observer observer) { onUnSubscribe); TestSubscriber ts = new TestSubscriber(0); - Observable.create(os).subscribe(ts); + Observable.unsafeCreate(os).subscribe(ts); ts.requestMore(finalCount); @@ -431,13 +431,13 @@ public Integer call(Integer state, Observer observer) { public void testUnsubscribeDownstream() { @SuppressWarnings("unchecked") Action1 onUnSubscribe = mock(Action1.class); - + OnSubscribe os = SyncOnSubscribe.createStateful( new Func0() { @Override public Integer call() { return null; - }}, + }}, new Func2, Integer>() { @Override public Integer call(Integer state, Observer observer) { @@ -451,7 +451,7 @@ public Integer call(Integer state, Observer observer) { TestSubscriber ts = new TestSubscriber(o); - Observable.create(os).take(1).subscribe(ts); + Observable.unsafeCreate(os).take(1).subscribe(ts); verify(o, never()).onError(any(Throwable.class)); verify(onUnSubscribe, times(1)).call(any(Integer.class)); @@ -466,7 +466,7 @@ public void testConcurrentRequestsLoop() throws InterruptedException { testConcurrentRequests(); } } - + @Test public void testConcurrentRequests() throws InterruptedException { final int count1 = 1000; @@ -475,7 +475,7 @@ public void testConcurrentRequests() throws InterruptedException { final int start = 1; final CountDownLatch l1 = new CountDownLatch(1); final CountDownLatch l2 = new CountDownLatch(1); - + final CountDownLatch l3 = new CountDownLatch(1); final Action1 onUnSubscribe = new Action1() { @@ -484,13 +484,13 @@ public void call(Object t) { l3.countDown(); } }; - + OnSubscribe os = SyncOnSubscribe.createStateful( new Func0() { @Override public Integer call() { return start; - }}, + }}, new Func2, Integer>() { @Override public Integer call(Integer state, Observer observer) { @@ -507,11 +507,11 @@ public Integer call(Integer state, Observer observer) { return state + 1; } observer.onNext(state); - + if (state == finalCount) { observer.onCompleted(); } - + return state + 1; }}, onUnSubscribe); @@ -521,7 +521,7 @@ public Integer call(Integer state, Observer observer) { InOrder inOrder = inOrder(o); final TestSubscriber ts = new TestSubscriber(o); - Observable.create(os).subscribeOn(Schedulers.newThread()).subscribe(ts); + Observable.unsafeCreate(os).subscribeOn(Schedulers.newThread()).subscribe(ts); // wait until the first request has started processing if (!l2.await(2, TimeUnit.SECONDS)) { @@ -538,7 +538,7 @@ public Integer call(Integer state, Observer observer) { inOrder.verify(o, times(finalCount)).onNext(any()); inOrder.verify(o, times(1)).onCompleted(); inOrder.verifyNoMoreInteractions(); - + if (!l3.await(2, TimeUnit.SECONDS)) { fail("SyncOnSubscribe failed to countDown onUnSubscribe latch"); } @@ -548,7 +548,7 @@ public Integer call(Integer state, Observer observer) { public void testUnsubscribeOutsideOfLoop() throws InterruptedException { final AtomicInteger calledUnsubscribe = new AtomicInteger(0); final AtomicBoolean currentlyEvaluating = new AtomicBoolean(false); - + OnSubscribe os = SyncOnSubscribe.createStateless( new Action1>() { @Override @@ -557,11 +557,11 @@ public void call(Observer observer) { observer.onNext(null); currentlyEvaluating.set(false); }}, - new Action0(){ + new Action0() { @Override public void call() { calledUnsubscribe.incrementAndGet(); - assertFalse(currentlyEvaluating.get()); + assertFalse(currentlyEvaluating.get()); }}); @SuppressWarnings("unchecked") @@ -569,10 +569,10 @@ public void call() { final CountDownLatch latch = new CountDownLatch(1); final TestSubscriber ts = new TestSubscriber(o); - Observable.create(os).lift(new Operator(){ + Observable.unsafeCreate(os).lift(new Operator() { @Override public Subscriber call(final Subscriber subscriber) { - return new Subscriber(subscriber){ + return new Subscriber(subscriber) { @Override public void setProducer(Producer p) { p.request(1); @@ -590,7 +590,7 @@ public void onError(Throwable e) { @Override public void onNext(final Void t) { subscriber.onNext(t); - new Thread(new Runnable(){ + new Thread(new Runnable() { @Override public void run() { try { @@ -610,21 +610,21 @@ public void run() { ts.assertUnsubscribed(); assertEquals(1, calledUnsubscribe.get()); } - + @Test public void testIndependentStates() { int count = 100; final ConcurrentHashMap subscribers = new ConcurrentHashMap(); - + @SuppressWarnings("unchecked") Action1> onUnSubscribe = mock(Action1.class); - + OnSubscribe os = SyncOnSubscribe.createStateful( new Func0>() { @Override public Map call() { return subscribers; - }}, + }}, new Func2, Observer, Map>() { @Override public Map call(Map state, Observer observer) { @@ -633,8 +633,8 @@ public Map call(Map state, Observer source = Observable.create(os); + + Observable source = Observable.unsafeCreate(os); for (int i = 0; i < count; i++) { source.subscribe(); } @@ -649,13 +649,13 @@ public void testSubscribeOn() { final int count = 400; final AtomicInteger countUnsubscribe = new AtomicInteger(0); final int numSubscribers = 4; - + OnSubscribe os = SyncOnSubscribe.createStateful( new Func0() { @Override public Integer call() { return start; - }}, + }}, new Func2, Integer>() { @Override public Integer call(Integer calls, Observer observer) { @@ -678,7 +678,7 @@ public void call(Integer t) { subs.add(ts); } TestScheduler scheduler = new TestScheduler(); - Observable o2 = Observable.create(os).subscribeOn(scheduler); + Observable o2 = Observable.unsafeCreate(os).subscribeOn(scheduler); for (Subscriber ts : subs) { o2.subscribe(ts); } @@ -689,7 +689,7 @@ public void call(Integer t) { ts.assertValueCount(count); ts.assertCompleted(); } - + assertEquals(numSubscribers, countUnsubscribe.get()); } @@ -697,20 +697,21 @@ public void call(Integer t) { public void testObserveOn() { final int start = 1; final int count = 4000; - + @SuppressWarnings("unchecked") Action1 onUnSubscribe = mock(Action1.class); @SuppressWarnings("unchecked") Func0 generator = mock(Func0.class); Mockito.when(generator.call()).thenReturn(start); - - OnSubscribe os = SyncOnSubscribe.createStateful(generator, + + OnSubscribe os = SyncOnSubscribe.createStateful(generator, new Func2, Integer>() { @Override public Integer call(Integer calls, Observer observer) { observer.onNext(calls); - if (calls == count) + if (calls == count) { observer.onCompleted(); + } return calls + 1; }}, onUnSubscribe); @@ -718,7 +719,7 @@ public Integer call(Integer calls, Observer observer) { TestSubscriber ts = new TestSubscriber(); TestScheduler scheduler = new TestScheduler(); - Observable.create(os).observeOn(scheduler).subscribe(ts); + Observable.unsafeCreate(os).observeOn(scheduler).subscribe(ts); scheduler.triggerActions(); ts.awaitTerminalEvent(); @@ -737,7 +738,7 @@ public Integer call(Integer calls, Observer observer) { @Test public void testCanRequestInOnNext() { Action0 onUnSubscribe = mock(Action0.class); - + OnSubscribe os = SyncOnSubscribe.createStateless( new Action1>() { @Override @@ -747,7 +748,7 @@ public void call(Observer observer) { }}, onUnSubscribe); final AtomicReference exception = new AtomicReference(); - Observable.create(os).subscribe(new Subscriber() { + Observable.unsafeCreate(os).subscribe(new Subscriber() { @Override public void onCompleted() { @@ -781,7 +782,7 @@ protected Object generateState() { lastState.set(o); return o; } - + @Override protected Object next(Object state, Observer observer) { observer.onNext(lastState.get()); @@ -790,14 +791,14 @@ protected Object next(Object state, Observer observer) { lastState.set(o); return o; } - + @Override protected void onUnsubscribe(Object state) { countUnsubs.incrementAndGet(); assertEquals(lastState.get(), state); } }; - + @SuppressWarnings("unchecked") Observer o = mock(Observer.class); @@ -811,12 +812,12 @@ protected void onUnsubscribe(Object state) { verify(o).onCompleted(); assertEquals(1, countUnsubs.get()); } - - private interface FooQux {} - private static class Foo implements FooQux {} - private interface BarQux extends FooQux {} - private static class Bar extends Foo implements BarQux {} - + + private interface FooQux { } + private static class Foo implements FooQux { } + private interface BarQux extends FooQux { } + private static class Bar extends Foo implements BarQux { } + @Test public void testGenericsCreateSingleState() { Func0 generator = new Func0() { @@ -849,7 +850,7 @@ public void call(BarQux state, Observer observer) { Action1 unsub = new Action1() { @Override public void call(FooQux t) { - + }}; assertJustBehavior(SyncOnSubscribe.createSingleState(generator, next, unsub)); } @@ -888,12 +889,12 @@ public BarQux call(BarQux state, Observer observer) { Action1 unsub = new Action1() { @Override public void call(FooQux t) { - + }}; OnSubscribe os = SyncOnSubscribe.createStateful(generator, next, unsub); assertJustBehavior(os); } - + @Test public void testGenericsCreateStateless() { Action1> next = new Action1>() { @@ -917,7 +918,7 @@ public void call(Observer observer) { Action0 unsub = new Action0() { @Override public void call() { - + }}; OnSubscribe os = SyncOnSubscribe.createStateless(next, unsub); assertJustBehavior(os); @@ -930,25 +931,25 @@ private void assertJustBehavior(OnSubscribe os) { ts.assertNoErrors(); ts.assertValueCount(1); } - + @Test - public void testConcurrentUnsubscribe3000Iterations() throws InterruptedException, BrokenBarrierException, ExecutionException{ + public void testConcurrentUnsubscribe3000Iterations() throws InterruptedException, BrokenBarrierException, ExecutionException { ExecutorService exec = null; try { - exec = Executors.newSingleThreadExecutor(); + exec = Executors.newSingleThreadExecutor(); for (int i = 0; i < 3000; i++) { final AtomicInteger wip = new AtomicInteger(); - + Func0 func0 = new Func0() { @Override public AtomicInteger call() { return wip; } }; - Func2, AtomicInteger> func2 = + Func2, AtomicInteger> func2 = new Func2, AtomicInteger>() { @Override - public AtomicInteger call(AtomicInteger s, Observer o) { + public AtomicInteger call(AtomicInteger s, Observer o) { o.onNext(1); return s; } @@ -960,17 +961,17 @@ public void call(AtomicInteger s) { } }; Observable source = Observable.create( - SyncOnSubscribe.createStateful( - func0, + SyncOnSubscribe.createStateful( + func0, func2, action1 )); - - + + final TestSubscriber ts = TestSubscriber.create(0); source.subscribe(ts); - + final CyclicBarrier cb = new CyclicBarrier(2); - + Future f = exec.submit(new Callable() { @Override public Object call() throws Exception { @@ -979,21 +980,23 @@ public Object call() throws Exception { return null; } }); - + cb.await(); ts.unsubscribe(); f.get(); assertEquals("Unsubscribe supposed to be called once", 1, wip.get()); } } finally { - if (exec != null) exec.shutdownNow(); + if (exec != null) { + exec.shutdownNow(); + } } } - + @Test public void testStateThrows() { TestSubscriber ts = new TestSubscriber(); - + SyncOnSubscribe.createSingleState( new Func0() { @Override @@ -1003,11 +1006,11 @@ public Object call() { } , new Action2>() { @Override - public void call(Object s, Observer o) { - + public void call(Object s, Observer o) { + } }).call(ts); - + ts.assertNoValues(); ts.assertError(TestException.class); ts.assertNotCompleted(); diff --git a/src/test/java/rx/observers/AssertableSubscriberTest.java b/src/test/java/rx/observers/AssertableSubscriberTest.java new file mode 100644 index 0000000000..4c6adbb06d --- /dev/null +++ b/src/test/java/rx/observers/AssertableSubscriberTest.java @@ -0,0 +1,338 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * 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 rx.observers; + +import static org.junit.Assert.*; + +import java.io.IOException; +import java.util.*; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.junit.Test; + +import rx.*; +import rx.Observable; +import rx.exceptions.TestException; +import rx.functions.*; + +public class AssertableSubscriberTest { + + @Test + public void testManyMaxValue() { + AssertableSubscriber ts = Observable.just(1, 2, 3) + .test() + .assertValues(1, 2, 3) + .awaitTerminalEvent() + .awaitTerminalEvent(5, TimeUnit.SECONDS) + .awaitTerminalEventAndUnsubscribeOnTimeout(5, TimeUnit.SECONDS) + .assertCompleted() + .assertTerminalEvent() + .assertNoErrors() + .assertUnsubscribed() + .assertReceivedOnNext(Arrays.asList(1, 2, 3)) + .assertValueCount(3); + assertEquals(3, ts.getValueCount()); + assertEquals(1, ts.getCompletions()); + assertEquals(Thread.currentThread().getName(), ts.getLastSeenThread().getName()); + assertEquals(Arrays.asList(1,2,3), ts.getOnNextEvents()); + ts.awaitValueCount(3, 5, TimeUnit.SECONDS); + } + + @Test + public void testManyWithInitialRequest() { + AssertableSubscriber ts = Observable.just(1, 2, 3) + .test(1) + .assertValue(1) + .assertValues(1) + .assertValuesAndClear(1) + .assertNotCompleted() + .assertNoErrors() + .assertNoTerminalEvent() + .requestMore(1) + .assertValuesAndClear(2); + ts.unsubscribe(); + ts.assertUnsubscribed(); + assertTrue(ts.isUnsubscribed()); + } + + @Test + public void testEmpty() { + Observable.empty() + .test() + .assertNoValues(); + } + + @Test + public void testError() { + IOException e = new IOException(); + AssertableSubscriber ts = Observable.error(e) + .test() + .assertError(e) + .assertError(IOException.class); + assertEquals(Arrays.asList(e), ts.getOnErrorEvents()); + } + + @Test + public void toStringIsNotNull() { + AssertableSubscriber ts = Observable.empty().test(); + assertNotNull(ts.toString()); + } + + @Test + public void testPerform() { + final AtomicBoolean performed = new AtomicBoolean(false); + Observable.empty() + .test() + .perform(new Action0() { + @Override + public void call() { + performed.set(true); + } + }); + assertTrue(performed.get()); + } + + @Test + public void testCompletable() { + AssertableSubscriber ts = Completable + .fromAction(Actions.empty()) + .test() + .assertNoValues() + .assertNoErrors() + .assertValueCount(0) + .assertValues() + .assertTerminalEvent() + .assertReceivedOnNext(Collections.emptyList()) + .awaitTerminalEvent() + .awaitTerminalEvent(5, TimeUnit.SECONDS) + .awaitTerminalEventAndUnsubscribeOnTimeout(5, TimeUnit.SECONDS) + .assertCompleted() + .assertUnsubscribed(); + assertEquals(1, ts.getCompletions()); + assertEquals(Thread.currentThread().getName(), ts.getLastSeenThread().getName()); + assertTrue(ts.getOnErrorEvents().isEmpty()); + assertTrue(ts.getOnNextEvents().isEmpty()); + assertEquals(0, ts.getValueCount()); + } + + @Test + public void testSingle() { + AssertableSubscriber ts = Single + .just(10) + .test() + .assertValue(10) + .assertValues(10) + .assertValueCount(1) + .assertReceivedOnNext(Arrays.asList(10)) + .assertValuesAndClear(10) + .assertNoValues() + .assertTerminalEvent() + .assertNoErrors() + .assertCompleted() + .assertUnsubscribed() + .awaitTerminalEvent() + .awaitTerminalEvent(5, TimeUnit.SECONDS) + .awaitTerminalEventAndUnsubscribeOnTimeout(5, TimeUnit.SECONDS); + assertEquals(1, ts.getCompletions()); + assertEquals(Thread.currentThread().getName(), ts.getLastSeenThread().getName()); + assertTrue(ts.getOnErrorEvents().isEmpty()); + assertTrue(ts.getOnNextEvents().isEmpty()); + assertEquals(0, ts.getValueCount()); + } + + @Test + public void assertResult() { + Observable.just(1) + .test() + .assertResult(1); + } + + @Test + public void assertResultFail() { + try { + Observable.just(1) + .test() + .assertResult(2); + throw new RuntimeException("Should have thrown!"); // fail() doesn't work here because it also throws AssertionError and may look like the test passed + } catch (AssertionError ex) { + // expected + } + + try { + Observable.just(1) + .test() + .assertResult(); + throw new RuntimeException("Should have thrown!"); // fail() doesn't work here because it also throws AssertionError and may look like the test passed + } catch (AssertionError ex) { + // expected + } + + try { + Observable.never() + .test() + .assertResult(1); + throw new RuntimeException("Should have thrown!"); // fail() doesn't work here because it also throws AssertionError and may look like the test passed + } catch (AssertionError ex) { + // expected + } + + try { + Observable.error(new TestException()) + .test() + .assertResult(2); + throw new RuntimeException("Should have thrown!"); // fail() doesn't work here because it also throws AssertionError and may look like the test passed + } catch (AssertionError ex) { + // expected + } + } + + @Test + public void assertFailure() { + Observable.error(new TestException()) + .test() + .assertFailure(TestException.class); + + Observable.just(1).concatWith(Observable.error(new TestException())) + .test() + .assertFailure(TestException.class, 1); + } + + @Test + public void assertFailureFail() { + try { + Observable.error(new TestException()) + .test() + .assertFailure(IOException.class); + throw new RuntimeException("Should have thrown!"); // fail() doesn't work here because it also throws AssertionError and may look like the test passed + } catch (AssertionError ex) { + // expected + } + + try { + Observable.just(1) + .test() + .assertFailure(IOException.class); + throw new RuntimeException("Should have thrown!"); // fail() doesn't work here because it also throws AssertionError and may look like the test passed + } catch (AssertionError ex) { + // expected + } + + try { + Observable.just(1) + .test() + .assertFailure(IOException.class, 1); + throw new RuntimeException("Should have thrown!"); // fail() doesn't work here because it also throws AssertionError and may look like the test passed + } catch (AssertionError ex) { + // expected + } + + try { + Observable.empty() + .test() + .assertFailure(IOException.class, 1); + throw new RuntimeException("Should have thrown!"); // fail() doesn't work here because it also throws AssertionError and may look like the test passed + } catch (AssertionError ex) { + // expected + } + } + + @Test + public void assertFailureAndMessage() { + Observable.error(new TestException("forced failure")) + .test() + .assertFailureAndMessage(TestException.class, "forced failure"); + + Observable.just(1).concatWith(Observable.error(new TestException("forced failure 2"))) + .test() + .assertFailureAndMessage(TestException.class, "forced failure 2", 1); + } + + @Test + public void assertFailureAndMessageFail() { + try { + Observable.error(new TestException()) + .test() + .assertFailureAndMessage(IOException.class, "forced failure"); + throw new RuntimeException("Should have thrown!"); // fail() doesn't work here because it also throws AssertionError and may look like the test passed + } catch (AssertionError ex) { + // expected + } + try { + Observable.error(new TestException("forced failure")) + .test() + .assertFailureAndMessage(IOException.class, "forced failure"); + throw new RuntimeException("Should have thrown!"); // fail() doesn't work here because it also throws AssertionError and may look like the test passed + } catch (AssertionError ex) { + // expected + } + + try { + Observable.just(1) + .test() + .assertFailureAndMessage(IOException.class, "forced failure"); + throw new RuntimeException("Should have thrown!"); // fail() doesn't work here because it also throws AssertionError and may look like the test passed + } catch (AssertionError ex) { + // expected + } + + try { + Observable.just(1) + .test() + .assertFailureAndMessage(IOException.class, "forced failure", 1); + throw new RuntimeException("Should have thrown!"); // fail() doesn't work here because it also throws AssertionError and may look like the test passed + } catch (AssertionError ex) { + // expected + } + + try { + Observable.empty() + .test() + .assertFailureAndMessage(IOException.class, "forced failure", 1); + throw new RuntimeException("Should have thrown!"); // fail() doesn't work here because it also throws AssertionError and may look like the test passed + } catch (AssertionError ex) { + // expected + } + + try { + Observable.error(new TestException("failure forced")) + .test() + .assertFailureAndMessage(TestException.class, "forced failure"); + throw new RuntimeException("Should have thrown!"); // fail() doesn't work here because it also throws AssertionError and may look like the test passed + } catch (AssertionError ex) { + // expected + } + + try { + Observable.just(1).concatWith(Observable.error(new TestException("failure forced"))) + .test() + .assertFailureAndMessage(TestException.class, "forced failure", 1); + throw new RuntimeException("Should have thrown!"); // fail() doesn't work here because it also throws AssertionError and may look like the test passed + } catch (AssertionError ex) { + // expected + } + + try { + Observable.just(1).concatWith(Observable.error(new TestException())) + .test() + .assertFailureAndMessage(TestException.class, "forced failure", 1); + throw new RuntimeException("Should have thrown!"); // fail() doesn't work here because it also throws AssertionError and may look like the test passed + } catch (AssertionError ex) { + // expected + } + } +} + diff --git a/src/test/java/rx/observers/AsyncCompletableSubscriberTest.java b/src/test/java/rx/observers/AsyncCompletableSubscriberTest.java index 7ac34f1628..95782b687a 100644 --- a/src/test/java/rx/observers/AsyncCompletableSubscriberTest.java +++ b/src/test/java/rx/observers/AsyncCompletableSubscriberTest.java @@ -1,12 +1,12 @@ /** * Copyright 2016 Netflix, Inc. - * + * * 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. @@ -27,37 +27,37 @@ public class AsyncCompletableSubscriberTest { static final class TestCS extends AsyncCompletableSubscriber { int started; - + int completions; - + final List errors = new ArrayList(); - + @Override protected void onStart() { started++; } - + @Override public void onCompleted() { completions++; clear(); } - + @Override public void onError(Throwable e) { errors.add(e); clear(); } } - + @Test public void normal() { TestCS ts = new TestCS(); - + Assert.assertFalse(ts.isUnsubscribed()); - + Completable.complete().subscribe(ts); - + Assert.assertEquals(1, ts.started); Assert.assertEquals(1, ts.completions); Assert.assertEquals(ts.errors.toString(), 0, ts.errors.size()); @@ -67,11 +67,11 @@ public void normal() { @Test public void error() { TestCS ts = new TestCS(); - + Assert.assertFalse(ts.isUnsubscribed()); - + Completable.error(new TestException("Forced failure")).subscribe(ts); - + Assert.assertEquals(1, ts.started); Assert.assertEquals(0, ts.completions); Assert.assertEquals(ts.errors.toString(), 1, ts.errors.size()); @@ -80,16 +80,16 @@ public void error() { Assert.assertTrue(ts.isUnsubscribed()); } - + @Test public void unsubscribed() { TestCS ts = new TestCS(); ts.unsubscribe(); - + Assert.assertTrue(ts.isUnsubscribed()); - + Observable.range(1, 10).toCompletable().subscribe(ts); - + Assert.assertEquals(0, ts.started); Assert.assertEquals(0, ts.completions); Assert.assertEquals(ts.errors.toString(), 0, ts.errors.size()); diff --git a/src/test/java/rx/observers/CompletableSubscriberTest.java b/src/test/java/rx/observers/CompletableSubscriberTest.java index be598dc04c..5ae2351960 100644 --- a/src/test/java/rx/observers/CompletableSubscriberTest.java +++ b/src/test/java/rx/observers/CompletableSubscriberTest.java @@ -1,12 +1,12 @@ /** * Copyright 2016 Netflix, Inc. - * + * * 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. @@ -21,7 +21,7 @@ import org.junit.*; -import rx.Completable.CompletableSubscriber; +import rx.CompletableSubscriber; import rx.exceptions.*; import rx.Subscription; import rx.subscriptions.Subscriptions; @@ -30,161 +30,161 @@ public class CompletableSubscriberTest { @Test public void childOnSubscribeThrows() { - + final AtomicReference error = new AtomicReference(); - + SafeCompletableSubscriber safe = new SafeCompletableSubscriber(new CompletableSubscriber() { - + @Override public void onSubscribe(Subscription d) { throw new TestException(); } - + @Override public void onError(Throwable e) { error.set(e); - + } - + @Override public void onCompleted() { - + } }); - + safe.onSubscribe(Subscriptions.empty()); - + Assert.assertTrue("" + error.get(), error.get() instanceof TestException); - + Assert.assertTrue(safe.isUnsubscribed()); } - + @Test public void unsubscribeComposes() { - + SafeCompletableSubscriber safe = new SafeCompletableSubscriber(new CompletableSubscriber() { - + @Override public void onSubscribe(Subscription d) { } - + @Override public void onError(Throwable e) { } - + @Override public void onCompleted() { - + } }); - + Subscription empty = Subscriptions.empty(); safe.onSubscribe(empty); - + Assert.assertFalse(empty.isUnsubscribed()); Assert.assertFalse(safe.isUnsubscribed()); - + safe.unsubscribe(); - + Assert.assertTrue(empty.isUnsubscribed()); Assert.assertTrue(safe.isUnsubscribed()); } - + @Test public void childOnErrorThrows() { - + SafeCompletableSubscriber safe = new SafeCompletableSubscriber(new CompletableSubscriber() { - + @Override public void onSubscribe(Subscription d) { } - + @Override public void onError(Throwable e) { throw new TestException(); } - + @Override public void onCompleted() { - + } }); - + safe.onSubscribe(Subscriptions.empty()); - + try { safe.onError(new IOException()); Assert.fail("Didn't throw a fatal exception"); } catch (OnErrorFailedException ex) { CompositeException ce = (CompositeException)ex.getCause(); - + List list = ce.getExceptions(); Assert.assertEquals(2, list.size()); - + Assert.assertTrue("" + list.get(0), list.get(0) instanceof IOException); Assert.assertTrue("" + list.get(1), list.get(1) instanceof TestException); } } - + @Test public void preventsCompleteError() { final boolean[] calls = { false, false }; - + SafeCompletableSubscriber safe = new SafeCompletableSubscriber(new CompletableSubscriber() { - + @Override public void onSubscribe(Subscription d) { } - + @Override public void onError(Throwable e) { calls[0] = true; } - + @Override public void onCompleted() { calls[1] = true; } }); - + safe.onSubscribe(Subscriptions.empty()); safe.onCompleted(); safe.onError(new TestException()); - + Assert.assertTrue(safe.isUnsubscribed()); Assert.assertFalse(calls[0]); Assert.assertTrue(calls[1]); } - + @Test public void preventsErrorComplete() { final boolean[] calls = { false, false }; - + SafeCompletableSubscriber safe = new SafeCompletableSubscriber(new CompletableSubscriber() { - + @Override public void onSubscribe(Subscription d) { } - + @Override public void onError(Throwable e) { calls[0] = true; } - + @Override public void onCompleted() { calls[1] = true; } }); - + safe.onSubscribe(Subscriptions.empty()); safe.onError(new TestException()); safe.onCompleted(); - + Assert.assertTrue(safe.isUnsubscribed()); Assert.assertTrue(calls[0]); Assert.assertFalse(calls[1]); diff --git a/src/test/java/rx/observers/ObserversTest.java b/src/test/java/rx/observers/ObserversTest.java index 4507306f51..fb7caa4264 100644 --- a/src/test/java/rx/observers/ObserversTest.java +++ b/src/test/java/rx/observers/ObserversTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -19,36 +19,22 @@ import static org.mockito.Mockito.*; import java.io.IOException; -import java.lang.reflect.*; import java.util.Queue; import java.util.concurrent.*; import java.util.concurrent.atomic.*; import org.junit.*; -import rx.Observer; +import rx.*; import rx.exceptions.*; import rx.functions.*; public class ObserversTest { @Test - public void testNotInstantiable() { - try { - Constructor c = Observers.class.getDeclaredConstructor(); - c.setAccessible(true); - Object instance = c.newInstance(); - fail("Could instantiate Actions! " + instance); - } catch (NoSuchMethodException ex) { - ex.printStackTrace(); - } catch (InvocationTargetException ex) { - ex.printStackTrace(); - } catch (InstantiationException ex) { - ex.printStackTrace(); - } catch (IllegalAccessException ex) { - ex.printStackTrace(); - } + public void constructorShouldBePrivate() { + TestUtil.checkUtilityClass(Observers.class); } - + @Test public void testEmptyOnErrorNotImplemented() { try { @@ -84,7 +70,7 @@ public void testCreate2Null() { public void testCreate3Null() { Observers.create(Actions.empty(), null); } - + @Test(expected = IllegalArgumentException.class) public void testCreate4Null() { Action1 throwAction = Actions.empty(); @@ -99,7 +85,7 @@ public void testCreate6Null() { Action1 throwAction = Actions.empty(); Observers.create(Actions.empty(), throwAction, null); } - + @Test public void testCreate1Value() { final AtomicInteger value = new AtomicInteger(); @@ -110,7 +96,7 @@ public void call(Integer t) { } }; Observers.create(action).onNext(1); - + assertEquals(1, value.get()); } @Test @@ -124,10 +110,10 @@ public void call(Integer t) { }; Action1 throwAction = Actions.empty(); Observers.create(action, throwAction).onNext(1); - + assertEquals(1, value.get()); } - + @Test public void testCreate3Value() { final AtomicInteger value = new AtomicInteger(); @@ -139,10 +125,10 @@ public void call(Integer t) { }; Action1 throwAction = Actions.empty(); Observers.create(action, throwAction, Actions.empty()).onNext(1); - + assertEquals(1, value.get()); } - + @Test public void testError2() { final AtomicReference value = new AtomicReference(); @@ -154,10 +140,10 @@ public void call(Throwable t) { }; TestException exception = new TestException(); Observers.create(Actions.empty(), action).onError(exception); - + assertEquals(exception, value.get()); } - + @Test public void testError3() { final AtomicReference value = new AtomicReference(); @@ -169,28 +155,28 @@ public void call(Throwable t) { }; TestException exception = new TestException(); Observers.create(Actions.empty(), action, Actions.empty()).onError(exception); - + assertEquals(exception, value.get()); } - + @Test public void testCompleted() { Action0 action = mock(Action0.class); - + Action1 throwAction = Actions.empty(); Observers.create(Actions.empty(), throwAction, action).onCompleted(); verify(action).call(); } - + @Test public void testEmptyCompleted() { Observers.create(Actions.empty()).onCompleted(); - + Action1 throwAction = Actions.empty(); Observers.create(Actions.empty(), throwAction).onCompleted(); } - + @Test public void onCompleteQueues() { @SuppressWarnings("unchecked") @@ -202,45 +188,45 @@ public void onNext(Integer t) { observer[0].onNext(1); observer[0].onCompleted(); } - + @Override public void onError(Throwable e) { - + } - + @Override public void onCompleted() { completeCalled[0] = true; } }); observer[0] = so; - + so.onNext(1); - + Assert.assertTrue(completeCalled[0]); } - + @Test public void concurrentOnError() throws Exception { final Queue queue = new ConcurrentLinkedQueue(); - + final SerializedObserver so = new SerializedObserver(new Observer() { @Override public void onNext(Integer t) { } - + @Override public void onError(Throwable e) { queue.offer(e); } - + @Override public void onCompleted() { } }); final CountDownLatch cdl = new CountDownLatch(1); - + synchronized (so) { Thread t = new Thread(new Runnable() { @Override @@ -252,19 +238,19 @@ public void run() { t.start(); Thread.sleep(200); - + so.onError(new TestException()); } - + if (!cdl.await(5, TimeUnit.SECONDS)) { fail("The wait timed out"); } - + Assert.assertEquals(1, queue.size()); Throwable ex = queue.poll(); Assert.assertTrue("" + ex, ex instanceof TestException); } - + @Test public void concurrentOnComplete() throws Exception { final int[] completed = { 0 }; @@ -272,11 +258,11 @@ public void concurrentOnComplete() throws Exception { @Override public void onNext(Integer t) { } - + @Override public void onError(Throwable e) { } - + @Override public void onCompleted() { completed[0]++; @@ -284,7 +270,7 @@ public void onCompleted() { }); final CountDownLatch cdl = new CountDownLatch(1); - + synchronized (so) { Thread t = new Thread(new Runnable() { @Override @@ -296,14 +282,14 @@ public void run() { t.start(); Thread.sleep(200); - + so.onCompleted(); } - + if (!cdl.await(5, TimeUnit.SECONDS)) { fail("The wait timed out"); } - + Assert.assertEquals(1, completed[0]); } } diff --git a/src/test/java/rx/observers/SafeObserverTest.java b/src/test/java/rx/observers/SafeObserverTest.java index 0863ff9e50..088b465d3d 100644 --- a/src/test/java/rx/observers/SafeObserverTest.java +++ b/src/test/java/rx/observers/SafeObserverTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -444,14 +444,14 @@ public SafeObserverTestException(String message) { super(message); } } - + @Test public void testOnCompletedThrows() { final AtomicReference error = new AtomicReference(); SafeSubscriber s = new SafeSubscriber(new Subscriber() { @Override public void onNext(Integer t) { - + } @Override public void onError(Throwable e) { @@ -462,7 +462,7 @@ public void onCompleted() { throw new TestException(); } }); - + try { s.onCompleted(); Assert.fail(); @@ -470,7 +470,7 @@ public void onCompleted() { assertNull(error.get()); } } - + @Test public void testActual() { Subscriber actual = new Subscriber() { @@ -485,7 +485,7 @@ public void onCompleted() { } }; SafeSubscriber s = new SafeSubscriber(actual); - + assertSame(actual, s.getActual()); } } diff --git a/src/test/java/rx/observers/SafeSubscriberTest.java b/src/test/java/rx/observers/SafeSubscriberTest.java index 30d4ee905b..3fdd8bbf66 100644 --- a/src/test/java/rx/observers/SafeSubscriberTest.java +++ b/src/test/java/rx/observers/SafeSubscriberTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -37,7 +37,7 @@ @SuppressWarnings("deprecation") public class SafeSubscriberTest { - + @Before @After public void resetBefore() { @@ -60,7 +60,7 @@ public void onCompleted() { assertTrue(safe.isUnsubscribed()); } } - + @Test public void testOnCompletedThrows2() { TestSubscriber ts = new TestSubscriber() { @@ -70,17 +70,17 @@ public void onCompleted() { } }; SafeSubscriber safe = new SafeSubscriber(ts); - + try { safe.onCompleted(); } catch (OnErrorNotImplementedException ex) { // expected } - + assertTrue(safe.isUnsubscribed()); } - - @Test(expected=OnCompletedFailedException.class) + + @Test(expected = OnCompletedFailedException.class) public void testPluginException() { RxJavaPlugins.getInstance().registerErrorHandler(new RxJavaErrorHandler() { @Override @@ -88,7 +88,7 @@ public void handleError(Throwable e) { throw new RuntimeException(); } }); - + TestSubscriber ts = new TestSubscriber() { @Override public void onCompleted() { @@ -96,10 +96,10 @@ public void onCompleted() { } }; SafeSubscriber safe = new SafeSubscriber(ts); - + safe.onCompleted(); } - + @Test(expected = OnErrorFailedException.class) public void testPluginExceptionWhileOnErrorUnsubscribeThrows() { RxJavaPlugins.getInstance().registerErrorHandler(new RxJavaErrorHandler() { @@ -111,7 +111,7 @@ public void handleError(Throwable e) { } } }); - + TestSubscriber ts = new TestSubscriber(); SafeSubscriber safe = new SafeSubscriber(ts); safe.add(Subscriptions.create(new Action0() { @@ -120,10 +120,10 @@ public void call() { throw new RuntimeException(); } })); - + safe.onError(new TestException()); } - + @Test(expected = RuntimeException.class) public void testPluginExceptionWhileOnErrorThrowsNotImplAndUnsubscribeThrows() { RxJavaPlugins.getInstance().registerErrorHandler(new RxJavaErrorHandler() { @@ -135,7 +135,7 @@ public void handleError(Throwable e) { } } }); - + TestSubscriber ts = new TestSubscriber() { @Override public void onError(Throwable e) { @@ -149,10 +149,10 @@ public void call() { throw new RuntimeException(); } })); - + safe.onError(new TestException()); } - + @Test(expected = OnErrorFailedException.class) public void testPluginExceptionWhileOnErrorThrows() { RxJavaPlugins.getInstance().registerErrorHandler(new RxJavaErrorHandler() { @@ -164,7 +164,7 @@ public void handleError(Throwable e) { } } }); - + TestSubscriber ts = new TestSubscriber() { @Override public void onError(Throwable e) { @@ -172,7 +172,7 @@ public void onError(Throwable e) { } }; SafeSubscriber safe = new SafeSubscriber(ts); - + safe.onError(new TestException()); } @Test(expected = OnErrorFailedException.class) @@ -186,7 +186,7 @@ public void handleError(Throwable e) { } } }); - + TestSubscriber ts = new TestSubscriber() { @Override public void onError(Throwable e) { @@ -200,7 +200,7 @@ public void call() { throw new RuntimeException(); } })); - + safe.onError(new TestException()); } @Test(expected = OnErrorFailedException.class) @@ -214,7 +214,7 @@ public void handleError(Throwable e) { } } }); - + TestSubscriber ts = new TestSubscriber() { @Override public void onError(Throwable e) { @@ -228,10 +228,10 @@ public void call() { throw new RuntimeException(); } })); - + safe.onError(new TestException()); } - + @Test public void testPluginErrorHandlerReceivesExceptionWhenUnsubscribeAfterCompletionThrows() { final AtomicInteger calls = new AtomicInteger(); @@ -241,7 +241,7 @@ public void handleError(Throwable e) { calls.incrementAndGet(); } }); - + final AtomicInteger errors = new AtomicInteger(); TestSubscriber ts = new TestSubscriber() { @Override @@ -257,11 +257,11 @@ public void call() { throw ex; } })); - + try { safe.onCompleted(); Assert.fail(); - } catch(UnsubscribeFailedException e) { + } catch (UnsubscribeFailedException e) { assertEquals(1, calls.get()); assertEquals(0, errors.get()); } @@ -276,15 +276,15 @@ public void handleError(Throwable e) { calls.incrementAndGet(); } }); - + final AtomicInteger errors = new AtomicInteger(); TestSubscriber ts = new TestSubscriber() { - - @Override + + @Override public void onCompleted() { throw new RuntimeException(); } - + @Override public void onError(Throwable e) { errors.incrementAndGet(); @@ -297,15 +297,15 @@ public void call() { throw new RuntimeException(); } })); - + try { safe.onCompleted(); Assert.fail(); - } catch(UnsubscribeFailedException e) { + } catch (UnsubscribeFailedException e) { assertEquals(2, calls.get()); assertEquals(0, errors.get()); } } - + } diff --git a/src/test/java/rx/observers/SerializedObserverTest.java b/src/test/java/rx/observers/SerializedObserverTest.java index 775017b1dd..83f49ecd52 100644 --- a/src/test/java/rx/observers/SerializedObserverTest.java +++ b/src/test/java/rx/observers/SerializedObserverTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -50,7 +50,7 @@ private Observer serializedObserver(Observer o) { @Test public void testSingleThreadedBasic() { TestSingleThreadedObservable onSubscribe = new TestSingleThreadedObservable("one", "two", "three"); - Observable w = Observable.create(onSubscribe); + Observable w = Observable.unsafeCreate(onSubscribe); Observer aw = serializedObserver(observer); @@ -70,7 +70,7 @@ public void testSingleThreadedBasic() { @Test public void testMultiThreadedBasic() { TestMultiThreadedObservable onSubscribe = new TestMultiThreadedObservable("one", "two", "three"); - Observable w = Observable.create(onSubscribe); + Observable w = Observable.unsafeCreate(onSubscribe); BusyObserver busyObserver = new BusyObserver(); Observer aw = serializedObserver(busyObserver); @@ -94,7 +94,7 @@ public void testMultiThreadedBasic() { @Test(timeout = 1000) public void testMultiThreadedWithNPE() throws InterruptedException { TestMultiThreadedObservable onSubscribe = new TestMultiThreadedObservable("one", "two", "three", null); - Observable w = Observable.create(onSubscribe); + Observable w = Observable.unsafeCreate(onSubscribe); BusyObserver busyObserver = new BusyObserver(); Observer aw = serializedObserver(busyObserver); @@ -126,9 +126,9 @@ public void testMultiThreadedWithNPE() throws InterruptedException { public void testMultiThreadedWithNPEinMiddle() { int n = 10; for (int i = 0; i < n; i++) { - TestMultiThreadedObservable onSubscribe = new TestMultiThreadedObservable("one", "two", "three", null, + TestMultiThreadedObservable onSubscribe = new TestMultiThreadedObservable("one", "two", "three", null, "four", "five", "six", "seven", "eight", "nine"); - Observable w = Observable.create(onSubscribe); + Observable w = Observable.unsafeCreate(onSubscribe); BusyObserver busyObserver = new BusyObserver(); Observer aw = serializedObserver(busyObserver); @@ -254,7 +254,7 @@ public void runConcurrencyTest() { /** * Test that a notification does not get delayed in the queue waiting for the next event to push it through. - * + * * @throws InterruptedException */ @Test @@ -263,12 +263,12 @@ public void testNotificationDelay() throws InterruptedException { try { int n = 10000; for (int i = 0; i < n; i++) { - + @SuppressWarnings("unchecked") final Observer[] os = new Observer[1]; - + final List threads = new ArrayList(); - + final Observer o = new SerializedObserver(new Observer() { boolean first; @Override @@ -290,25 +290,25 @@ public void run() { } } } - + @Override - public void onError(Throwable e) { + public void onError(Throwable e) { e.printStackTrace(); } - + @Override - public void onCompleted() { - + public void onCompleted() { + } }); - + os[0] = o; - + o.onNext(1); - + System.out.println(threads); assertEquals(2, threads.size()); - + assertSame(threads.get(0), threads.get(1)); } } finally { @@ -318,25 +318,25 @@ public void onCompleted() { /** * Demonstrates thread starvation problem. - * + * * No solution on this for now. Trade-off in this direction as per https://github.com/ReactiveX/RxJava/issues/998#issuecomment-38959474 * Probably need backpressure for this to work - * + * * When using SynchronizedObserver we get this output: - * + * * p1: 18 p2: 68 => should be close to each other unless we have thread starvation - * + * * When using SerializedObserver we get: - * + * * p1: 1 p2: 2445261 => should be close to each other unless we have thread starvation - * + * * This demonstrates how SynchronizedObserver balances back and forth better, and blocks emission. * The real issue in this example is the async buffer-bloat, so we need backpressure. - * - * + * + * * @throws InterruptedException */ - @Ignore + @Ignore("Demonstrates thread starvation problem. Read JavaDoc") @Test public void testThreadStarvation() throws InterruptedException { @@ -391,7 +391,7 @@ private static void waitOnThreads(Future... futures) { } private static Observable infinite(final AtomicInteger produced) { - return Observable.create(new OnSubscribe() { + return Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber s) { @@ -541,7 +541,7 @@ public void onNext(String args) { /** * Assert the order of events is correct and return the number of onNext executions. - * + * * @param expectedEndingEvent * @return int count of onNext calls * @throws IllegalStateException @@ -589,7 +589,7 @@ public int assertEvents(TestConcurrencyObserverEvent expectedEndingEvent) throws private static class TestSingleThreadedObservable implements Observable.OnSubscribe { final String[] values; - private Thread t = null; + private Thread t; public TestSingleThreadedObservable(final String... values) { this.values = values; @@ -637,7 +637,7 @@ public void waitToFinish() { private static class TestMultiThreadedObservable implements Observable.OnSubscribe { final String[] values; - Thread t = null; + Thread t; AtomicInteger threadsRunning = new AtomicInteger(); AtomicInteger maxConcurrentThreads = new AtomicInteger(); ExecutorService threadPool; @@ -726,8 +726,8 @@ public void waitToFinish() { } private static class BusyObserver extends Subscriber { - volatile boolean onCompleted = false; - volatile boolean onError = false; + volatile boolean onCompleted; + volatile boolean onError; AtomicInteger onNextCount = new AtomicInteger(); AtomicInteger threadsRunning = new AtomicInteger(); AtomicInteger maxConcurrentThreads = new AtomicInteger(); @@ -788,7 +788,7 @@ protected void captureMaxThreads() { } } - + @Test public void testSerializeNull() { final AtomicReference> serial = new AtomicReference>(); @@ -801,15 +801,15 @@ public void onNext(Integer t) { super.onNext(t); } }; - + SerializedObserver sobs = new SerializedObserver(to); serial.set(sobs); - + sobs.onNext(0); - + to.assertReceivedOnNext(Arrays.asList(0, null)); } - + @Test public void testSerializeAllowsOnError() { TestSubscriber to = new TestSubscriber() { @@ -818,19 +818,19 @@ public void onNext(Integer t) { throw new TestException(); } }; - + SerializedObserver sobs = new SerializedObserver(to); - + try { sobs.onNext(0); } catch (TestException ex) { sobs.onError(ex); } - + assertEquals(1, to.getOnErrorEvents().size()); assertTrue(to.getOnErrorEvents().get(0) instanceof TestException); } - + @Test public void testSerializeReentrantNullAndComplete() { final AtomicReference> serial = new AtomicReference>(); @@ -841,21 +841,21 @@ public void onNext(Integer t) { throw new TestException(); } }; - + SerializedObserver sobs = new SerializedObserver(to); serial.set(sobs); - + try { sobs.onNext(0); } catch (TestException ex) { sobs.onError(ex); } - + assertEquals(1, to.getOnErrorEvents().size()); assertTrue(to.getOnErrorEvents().get(0) instanceof TestException); assertEquals(0, to.getCompletions()); } - + @Test public void testSerializeReentrantNullAndError() { final AtomicReference> serial = new AtomicReference>(); @@ -866,21 +866,21 @@ public void onNext(Integer t) { throw new TestException(); } }; - + SerializedObserver sobs = new SerializedObserver(to); serial.set(sobs); - + try { sobs.onNext(0); } catch (TestException ex) { sobs.onError(ex); } - + assertEquals(1, to.getOnErrorEvents().size()); assertTrue(to.getOnErrorEvents().get(0) instanceof TestException); assertEquals(0, to.getCompletions()); } - + @Test public void testSerializeDrainPhaseThrows() { final AtomicReference> serial = new AtomicReference>(); @@ -896,21 +896,21 @@ public void onNext(Integer t) { super.onNext(t); } }; - + SerializedObserver sobs = new SerializedObserver(to); serial.set(sobs); - + sobs.onNext(0); - + to.assertReceivedOnNext(Arrays.asList(0)); assertEquals(1, to.getOnErrorEvents().size()); assertTrue(to.getOnErrorEvents().get(0) instanceof TestException); } - + @Test public void testErrorReentry() { final AtomicReference> serial = new AtomicReference>(); - + TestSubscriber ts = new TestSubscriber() { @Override public void onNext(Integer v) { @@ -921,16 +921,16 @@ public void onNext(Integer v) { }; SerializedObserver sobs = new SerializedObserver(ts); serial.set(sobs); - + sobs.onNext(1); - + ts.assertValue(1); ts.assertError(TestException.class); } @Test public void testCompleteReentry() { final AtomicReference> serial = new AtomicReference>(); - + TestSubscriber ts = new TestSubscriber() { @Override public void onNext(Integer v) { @@ -941,9 +941,9 @@ public void onNext(Integer v) { }; SerializedObserver sobs = new SerializedObserver(ts); serial.set(sobs); - + sobs.onNext(1); - + ts.assertValue(1); ts.assertCompleted(); ts.assertNoErrors(); diff --git a/src/test/java/rx/observers/SubscribersTest.java b/src/test/java/rx/observers/SubscribersTest.java index e0e91d84b0..a55d250f48 100644 --- a/src/test/java/rx/observers/SubscribersTest.java +++ b/src/test/java/rx/observers/SubscribersTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -18,35 +18,21 @@ import static org.junit.Assert.*; import static org.mockito.Mockito.*; -import java.lang.reflect.*; import java.util.concurrent.atomic.*; import org.junit.Test; -import rx.Subscriber; +import rx.*; import rx.exceptions.*; import rx.functions.*; import rx.subscriptions.Subscriptions; public class SubscribersTest { @Test - public void testNotInstantiable() { - try { - Constructor c = Subscribers.class.getDeclaredConstructor(); - c.setAccessible(true); - Object instance = c.newInstance(); - fail("Could instantiate Actions! " + instance); - } catch (NoSuchMethodException ex) { - ex.printStackTrace(); - } catch (InvocationTargetException ex) { - ex.printStackTrace(); - } catch (InstantiationException ex) { - ex.printStackTrace(); - } catch (IllegalAccessException ex) { - ex.printStackTrace(); - } + public void constructorShouldBePrivate() { + TestUtil.checkUtilityClass(Subscribers.class); } - + @Test public void testEmptyOnErrorNotImplemented() { try { @@ -82,7 +68,7 @@ public void testCreate2Null() { public void testCreate3Null() { Subscribers.create(Actions.empty(), null); } - + @Test(expected = IllegalArgumentException.class) public void testCreate4Null() { Action1 throwAction = Actions.empty(); @@ -97,7 +83,7 @@ public void testCreate6Null() { Action1 throwAction = Actions.empty(); Subscribers.create(Actions.empty(), throwAction, null); } - + @Test public void testCreate1Value() { final AtomicInteger value = new AtomicInteger(); @@ -108,7 +94,7 @@ public void call(Integer t) { } }; Subscribers.create(action).onNext(1); - + assertEquals(1, value.get()); } @Test @@ -122,10 +108,10 @@ public void call(Integer t) { }; Action1 throwAction = Actions.empty(); Subscribers.create(action, throwAction).onNext(1); - + assertEquals(1, value.get()); } - + @Test public void testCreate3Value() { final AtomicInteger value = new AtomicInteger(); @@ -137,10 +123,10 @@ public void call(Integer t) { }; Action1 throwAction = Actions.empty(); Subscribers.create(action, throwAction, Actions.empty()).onNext(1); - + assertEquals(1, value.get()); } - + @Test public void testError2() { final AtomicReference value = new AtomicReference(); @@ -152,10 +138,10 @@ public void call(Throwable t) { }; TestException exception = new TestException(); Subscribers.create(Actions.empty(), action).onError(exception); - + assertEquals(exception, value.get()); } - + @Test public void testError3() { final AtomicReference value = new AtomicReference(); @@ -167,14 +153,14 @@ public void call(Throwable t) { }; TestException exception = new TestException(); Subscribers.create(Actions.empty(), action, Actions.empty()).onError(exception); - + assertEquals(exception, value.get()); } - + @Test public void testCompleted() { Action0 action = mock(Action0.class); - + Action1 throwAction = Actions.empty(); Subscribers.create(Actions.empty(), throwAction, action).onCompleted(); @@ -183,30 +169,30 @@ public void testCompleted() { @Test public void testEmptyCompleted() { Subscribers.create(Actions.empty()).onCompleted(); - + Action1 throwAction = Actions.empty(); Subscribers.create(Actions.empty(), throwAction).onCompleted(); } - + @Test public void shareSubscriptionButNullSubscriber() { Subscriber s = new Subscriber(null, true) { @Override public void onNext(Integer t) { - + } - + @Override public void onError(Throwable e) { - + } - + @Override public void onCompleted() { - + } }; - + s.add(Subscriptions.empty()); } } diff --git a/src/test/java/rx/observers/TestObserverTest.java b/src/test/java/rx/observers/TestObserverTest.java index f1b26f1e56..cdf1d005ef 100644 --- a/src/test/java/rx/observers/TestObserverTest.java +++ b/src/test/java/rx/observers/TestObserverTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -121,42 +121,42 @@ public void testWrappingMockWhenUnsubscribeInvolved() { inOrder.verify(mockObserver, times(1)).onCompleted(); inOrder.verifyNoMoreInteractions(); } - + @Test public void testErrorSwallowed() { Observable.error(new RuntimeException()).subscribe(new TestObserver()); } - + @Test public void testGetEvents() { TestObserver to = new TestObserver(); to.onNext(1); to.onNext(2); - - assertEquals(Arrays.asList(Arrays.asList(1, 2), - Collections.emptyList(), + + assertEquals(Arrays.asList(Arrays.asList(1, 2), + Collections.emptyList(), Collections.emptyList()), to.getEvents()); - + to.onCompleted(); - + assertEquals(Arrays.asList(Arrays.asList(1, 2), Collections.emptyList(), Collections.singletonList(Notification.createOnCompleted())), to.getEvents()); - + TestException ex = new TestException(); TestObserver to2 = new TestObserver(); to2.onNext(1); to2.onNext(2); - - assertEquals(Arrays.asList(Arrays.asList(1, 2), - Collections.emptyList(), + + assertEquals(Arrays.asList(Arrays.asList(1, 2), + Collections.emptyList(), Collections.emptyList()), to2.getEvents()); - + to2.onError(ex); - + assertEquals(Arrays.asList( Arrays.asList(1, 2), Collections.singletonList(ex), - Collections.emptyList()), + Collections.emptyList()), to2.getEvents()); } @@ -173,7 +173,7 @@ public void testNullExpected() { } fail("Null element check assertion didn't happen!"); } - + @Test public void testNullActual() { TestObserver to = new TestObserver(); @@ -187,13 +187,13 @@ public void testNullActual() { } fail("Null element check assertion didn't happen!"); } - + @Test public void testTerminalErrorOnce() { TestObserver to = new TestObserver(); to.onError(new TestException()); to.onError(new TestException()); - + try { to.assertTerminalEvent(); } catch (AssertionError ex) { @@ -207,7 +207,7 @@ public void testTerminalCompletedOnce() { TestObserver to = new TestObserver(); to.onCompleted(); to.onCompleted(); - + try { to.assertTerminalEvent(); } catch (AssertionError ex) { @@ -216,13 +216,13 @@ public void testTerminalCompletedOnce() { } fail("Failed to report multiple onError terminal events!"); } - + @Test public void testTerminalOneKind() { TestObserver to = new TestObserver(); to.onError(new TestException()); to.onCompleted(); - + try { to.assertTerminalEvent(); } catch (AssertionError ex) { diff --git a/src/test/java/rx/observers/TestSubscriberTest.java b/src/test/java/rx/observers/TestSubscriberTest.java index 776f48367c..a78890aa74 100644 --- a/src/test/java/rx/observers/TestSubscriberTest.java +++ b/src/test/java/rx/observers/TestSubscriberTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -72,7 +72,7 @@ public void testAssertNotMatchValue() { oi.subscribe(o); thrown.expect(AssertionError.class); - thrown.expectMessage("Value at index: 1 expected to be [3] (Integer) but was: [2] (Integer)"); + thrown.expectMessage("Value at index: 1 expected: [3] (Integer) but was: [2] (Integer)"); o.assertReceivedOnNext(Arrays.asList(1, 3)); @@ -132,7 +132,7 @@ public void testAssertError() { Observable.error(e).subscribe(subscriber); subscriber.assertError(e); } - + @Test public void testAwaitTerminalEventWithDuration() { TestSubscriber ts = new TestSubscriber(); @@ -140,7 +140,7 @@ public void testAwaitTerminalEventWithDuration() { ts.awaitTerminalEvent(1, TimeUnit.SECONDS); ts.assertTerminalEvent(); } - + @Test public void testAwaitTerminalEventWithDurationAndUnsubscribeOnTimeout() { TestSubscriber ts = new TestSubscriber(); @@ -164,38 +164,38 @@ public void testNullDelegate1() { TestSubscriber ts = new TestSubscriber((Observer)null); ts.onCompleted(); } - + @Test(expected = NullPointerException.class) public void testNullDelegate2() { TestSubscriber ts = new TestSubscriber((Subscriber)null); ts.onCompleted(); } - + @Test(expected = NullPointerException.class) public void testNullDelegate3() { TestSubscriber ts = new TestSubscriber((Subscriber)null, 0); ts.onCompleted(); } - + @Test public void testDelegate1() { @SuppressWarnings("unchecked") Observer to = mock(Observer.class); TestSubscriber ts = TestSubscriber.create(to); ts.onCompleted(); - + verify(to).onCompleted(); } - + @Test public void testDelegate2() { TestSubscriber ts1 = TestSubscriber.create(); TestSubscriber ts2 = TestSubscriber.create(ts1); ts2.onCompleted(); - + ts1.assertCompleted(); } - + @Test public void testDelegate3() { TestSubscriber ts1 = TestSubscriber.create(); @@ -203,7 +203,7 @@ public void testDelegate3() { ts2.onCompleted(); ts1.assertCompleted(); } - + @Test public void testUnsubscribed() { TestSubscriber ts = new TestSubscriber(); @@ -215,7 +215,7 @@ public void testUnsubscribed() { } fail("Not unsubscribed but not reported!"); } - + @Test public void testNoErrors() { TestSubscriber ts = new TestSubscriber(); @@ -228,7 +228,7 @@ public void testNoErrors() { } fail("Error present but no assertion error!"); } - + @Test public void testNotCompleted() { TestSubscriber ts = new TestSubscriber(); @@ -240,7 +240,7 @@ public void testNotCompleted() { } fail("Not completed and no assertion error!"); } - + @Test public void testMultipleCompletions() { TestSubscriber ts = new TestSubscriber(); @@ -254,7 +254,7 @@ public void testMultipleCompletions() { } fail("Multiple completions and no assertion error!"); } - + @Test public void testCompleted() { TestSubscriber ts = new TestSubscriber(); @@ -267,7 +267,7 @@ public void testCompleted() { } fail("Completed and no assertion error!"); } - + @Test public void testMultipleCompletions2() { TestSubscriber ts = new TestSubscriber(); @@ -281,7 +281,7 @@ public void testMultipleCompletions2() { } fail("Multiple completions and no assertion error!"); } - + @Test public void testMultipleErrors() { TestSubscriber ts = new TestSubscriber(); @@ -298,7 +298,7 @@ public void testMultipleErrors() { } fail("Multiple Error present but no assertion error!"); } - + @Test public void testMultipleErrors2() { TestSubscriber ts = new TestSubscriber(); @@ -315,7 +315,7 @@ public void testMultipleErrors2() { } fail("Multiple Error present but no assertion error!"); } - + @Test public void testMultipleErrors3() { TestSubscriber ts = new TestSubscriber(); @@ -332,7 +332,7 @@ public void testMultipleErrors3() { } fail("Multiple Error present but no assertion error!"); } - + @Test public void testDifferentError() { TestSubscriber ts = new TestSubscriber(); @@ -345,7 +345,7 @@ public void testDifferentError() { } fail("Different Error present but no assertion error!"); } - + @Test public void testDifferentError2() { TestSubscriber ts = new TestSubscriber(); @@ -358,7 +358,7 @@ public void testDifferentError2() { } fail("Different Error present but no assertion error!"); } - + @Test public void testDifferentError3() { TestSubscriber ts = new TestSubscriber(); @@ -371,7 +371,7 @@ public void testDifferentError3() { } fail("Different Error present but no assertion error!"); } - + @Test public void testNoError() { TestSubscriber ts = new TestSubscriber(); @@ -395,7 +395,7 @@ public void testNoError2() { } fail("No present but no assertion error!"); } - + @Test public void testInterruptTerminalEventAwait() { TestSubscriber ts = TestSubscriber.create(); @@ -409,7 +409,7 @@ public void call() { t0.interrupt(); } }, 200, TimeUnit.MILLISECONDS); - + try { ts.awaitTerminalEvent(); fail("Did not interrupt wait!"); @@ -422,7 +422,7 @@ public void call() { w.unsubscribe(); } } - + @Test public void testInterruptTerminalEventAwaitTimed() { TestSubscriber ts = TestSubscriber.create(); @@ -436,7 +436,7 @@ public void call() { t0.interrupt(); } }, 200, TimeUnit.MILLISECONDS); - + try { ts.awaitTerminalEvent(5, TimeUnit.SECONDS); fail("Did not interrupt wait!"); @@ -449,7 +449,7 @@ public void call() { w.unsubscribe(); } } - + @Test public void testInterruptTerminalEventAwaitAndUnsubscribe() { TestSubscriber ts = TestSubscriber.create(); @@ -463,7 +463,7 @@ public void call() { t0.interrupt(); } }, 200, TimeUnit.MILLISECONDS); - + ts.awaitTerminalEventAndUnsubscribeOnTimeout(5, TimeUnit.SECONDS); if (!ts.isUnsubscribed()) { fail("Did not unsubscribe!"); @@ -472,13 +472,13 @@ public void call() { w.unsubscribe(); } } - + @Test public void testNoTerminalEventBut1Completed() { TestSubscriber ts = TestSubscriber.create(); - + ts.onCompleted(); - + try { ts.assertNoTerminalEvent(); fail("Failed to report there were terminal event(s)!"); @@ -486,13 +486,13 @@ public void testNoTerminalEventBut1Completed() { // expected } } - + @Test public void testNoTerminalEventBut1Error() { TestSubscriber ts = TestSubscriber.create(); - + ts.onError(new TestException()); - + try { ts.assertNoTerminalEvent(); fail("Failed to report there were terminal event(s)!"); @@ -500,14 +500,14 @@ public void testNoTerminalEventBut1Error() { // expected } } - + @Test public void testNoTerminalEventBut1Error1Completed() { TestSubscriber ts = TestSubscriber.create(); - + ts.onCompleted(); ts.onError(new TestException()); - + try { ts.assertNoTerminalEvent(); fail("Failed to report there were terminal event(s)!"); @@ -515,14 +515,14 @@ public void testNoTerminalEventBut1Error1Completed() { // expected } } - + @Test public void testNoTerminalEventBut2Errors() { TestSubscriber ts = TestSubscriber.create(); - + ts.onError(new TestException()); ts.onError(new TestException()); - + try { ts.assertNoTerminalEvent(); fail("Failed to report there were terminal event(s)!"); @@ -533,12 +533,12 @@ public void testNoTerminalEventBut2Errors() { } } } - + @Test public void testNoValues() { TestSubscriber ts = TestSubscriber.create(); ts.onNext(1); - + try { ts.assertNoValues(); fail("Failed to report there were values!"); @@ -546,13 +546,13 @@ public void testNoValues() { // expected } } - + @Test public void testValueCount() { TestSubscriber ts = TestSubscriber.create(); ts.onNext(1); ts.onNext(2); - + try { ts.assertValueCount(3); fail("Failed to report there were values!"); @@ -560,7 +560,7 @@ public void testValueCount() { // expected } } - + @Test(timeout = 1000) public void testOnCompletedCrashCountsDownLatch() { Observer to = new Observer() { @@ -571,26 +571,26 @@ public void onCompleted() { @Override public void onError(Throwable e) { - + } @Override public void onNext(Integer t) { - + } }; TestSubscriber ts = TestSubscriber.create(to); - + try { ts.onCompleted(); } catch (TestException ex) { // expected } - + ts.awaitTerminalEvent(); } - - @Test(timeout = 1000) + + @Test(timeout = 5000) public void testOnErrorCrashCountsDownLatch() { Observer to = new Observer() { @Override @@ -600,22 +600,22 @@ public void onError(Throwable e) { @Override public void onCompleted() { - + } @Override public void onNext(Integer t) { - + } }; TestSubscriber ts = TestSubscriber.create(to); - + try { ts.onError(new RuntimeException()); } catch (TestException ex) { // expected } - + ts.awaitTerminalEvent(); } @@ -638,7 +638,7 @@ public void assertValuesShouldThrowIfNumberOfItemsDoesNotMatch() { ); } } - + @Test public void assertionFailureGivesActiveDetails() { TestSubscriber ts = new TestSubscriber(); @@ -662,7 +662,7 @@ public void assertionFailureGivesActiveDetails() { assertEquals("forced failure", ex.getMessage()); } } - + @Test public void assertionFailureShowsMultipleErrors() { TestSubscriber ts = new TestSubscriber(); @@ -741,35 +741,89 @@ public void completionCount() { Assert.assertEquals(0, ts.getCompletions()); ts.onCompleted(); - + Assert.assertEquals(1, ts.getCompletions()); ts.onCompleted(); - + Assert.assertEquals(2, ts.getCompletions()); } - + @Test - public void awaitValueCount() throws Exception { + public void awaitValueCount() { TestSubscriber ts = TestSubscriber.create(); - + Observable.range(1, 5).delay(100, TimeUnit.MILLISECONDS) .subscribe(ts); - + Assert.assertTrue(ts.awaitValueCount(2, 5, TimeUnit.SECONDS)); - + Assert.assertEquals(1, ts.getOnNextEvents().get(0).intValue()); Assert.assertEquals(2, ts.getOnNextEvents().get(1).intValue()); } - + @Test - public void awaitValueCountFails() throws Exception { + public void awaitValueCountFails() { TestSubscriber ts = TestSubscriber.create(); - + Observable.range(1, 2).delay(100, TimeUnit.MILLISECONDS) .subscribe(ts); - + Assert.assertFalse(ts.awaitValueCount(5, 1, TimeUnit.SECONDS)); - + + } + + @Test + public void assertAndConsume() { + TestSubscriber ts = TestSubscriber.create(); + + ts.assertNoValues(); + + ts.onNext(1); + + ts.assertValuesAndClear(1); + + ts.assertNoValues(); + + ts.onNext(2); + ts.onNext(3); + + ts.assertValueCount(2); + + ts.assertValuesAndClear(2, 3); + + ts.onNext(4); + ts.onNext(5); + + try { + ts.assertValuesAndClear(4); + Assert.fail("Should have thrown AssertionError"); + } catch (AssertionError ex) { + // expected + } + + ts.assertValueCount(2); + + try { + ts.assertValuesAndClear(4, 5, 6); + Assert.fail("Should have thrown AssertionError"); + } catch (AssertionError ex) { + // expected + } + + ts.assertValuesAndClear(4, 5); + + ts.assertNoValues(); + } + + @Test + public void assertAndClearResetsValueCount() { + TestSubscriber ts = TestSubscriber.create(); + + ts.onNext(1); + ts.assertValuesAndClear(1); + + ts.assertNoValues(); + Assert.assertEquals(0, ts.getValueCount()); } } diff --git a/src/test/java/rx/plugins/RxJavaHooksTest.java b/src/test/java/rx/plugins/RxJavaHooksTest.java index 2072e72d49..a4b3999f73 100644 --- a/src/test/java/rx/plugins/RxJavaHooksTest.java +++ b/src/test/java/rx/plugins/RxJavaHooksTest.java @@ -1,12 +1,12 @@ /** * Copyright 2016 Netflix, Inc. - * + * * 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. @@ -15,18 +15,38 @@ */ package rx.plugins; +import static org.junit.Assert.*; + +import java.lang.Thread.UncaughtExceptionHandler; import java.lang.reflect.Method; +import java.util.*; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.*; -import org.junit.*; +import org.junit.Test; import rx.*; +import rx.Observable; +import rx.Scheduler.Worker; import rx.exceptions.*; import rx.functions.*; +import rx.internal.operators.OnSubscribeRange; import rx.internal.util.UtilityFunctions; import rx.observers.TestSubscriber; +import rx.schedulers.Schedulers; +import rx.subscriptions.Subscriptions; + public class RxJavaHooksTest { + public static class TestExceptionWithUnknownCause extends RuntimeException { + private static final long serialVersionUID = 6771158999860253299L; + + TestExceptionWithUnknownCause() { + super((Throwable) null); + } + } + static Observable createObservable() { return Observable.range(1, 5).map(new Func1() { @Override @@ -35,29 +55,82 @@ public Integer call(Integer t) { } }); } - + + static Observable createObservableThrowingUnknownCause() { + return Observable.range(1, 5).map(new Func1() { + @Override + public Integer call(Integer t) { + throw new TestExceptionWithUnknownCause(); + } + }); + } + + @Test + public void constructorShouldBePrivate() { + TestUtil.checkUtilityClass(RxJavaHooks.class); + } + @Test public void assemblyTrackingObservable() { RxJavaHooks.enableAssemblyTracking(); try { TestSubscriber ts = TestSubscriber.create(); - + createObservable().subscribe(ts); - - ts.assertError(AssemblyStackTraceException.class); - + + ts.assertError(TestException.class); + Throwable ex = ts.getOnErrorEvents().get(0); - - Assert.assertTrue("" + ex.getCause(), ex.getCause() instanceof TestException); - - Assert.assertTrue("" + ex, ex instanceof AssemblyStackTraceException); - - Assert.assertTrue(ex.getMessage(), ex.getMessage().contains("createObservable")); + + AssemblyStackTraceException aste = AssemblyStackTraceException.find(ex); + + assertNotNull(aste); + + assertTrue(aste.getMessage(), aste.getMessage().contains("createObservable")); + + RxJavaHooks.clearAssemblyTracking(); + + ts = TestSubscriber.create(); + + createObservable().subscribe(ts); + + ts.assertError(TestException.class); + + ex = ts.getOnErrorEvents().get(0); + + aste = AssemblyStackTraceException.find(ex); + + assertNull(aste); } finally { RxJavaHooks.resetAssemblyTracking(); } } - + + @Test + public void assemblyTrackingObservableUnknownCause() { + RxJavaHooks.enableAssemblyTracking(); + try { + final AtomicReference onErrorThrowableRef = new AtomicReference(); + RxJavaHooks.setOnError(new Action1() { + @Override + public void call(Throwable throwable) { + onErrorThrowableRef.set(throwable); + } + }); + TestSubscriber ts = TestSubscriber.create(); + + createObservableThrowingUnknownCause().subscribe(ts); + + ts.assertError(TestExceptionWithUnknownCause.class); + + Throwable receivedError = onErrorThrowableRef.get(); + assertNotNull(receivedError); + assertTrue(receivedError.getMessage().contains("cause set to null")); + } finally { + RxJavaHooks.reset(); + } + } + static Single createSingle() { return Single.just(1).map(new Func1() { @Override @@ -66,29 +139,43 @@ public Integer call(Integer t) { } }); } - + @Test public void assemblyTrackingSingle() { RxJavaHooks.enableAssemblyTracking(); try { TestSubscriber ts = TestSubscriber.create(); - + createSingle().subscribe(ts); - - ts.assertError(AssemblyStackTraceException.class); - + + ts.assertError(TestException.class); + Throwable ex = ts.getOnErrorEvents().get(0); - Assert.assertTrue("" + ex, ex instanceof AssemblyStackTraceException); + AssemblyStackTraceException aste = AssemblyStackTraceException.find(ex); + + assertNotNull(aste); + + assertTrue(aste.getMessage(), aste.getMessage().contains("createSingle")); + + RxJavaHooks.clearAssemblyTracking(); + + ts = TestSubscriber.create(); + + createSingle().subscribe(ts); + + ts.assertError(TestException.class); + + ex = ts.getOnErrorEvents().get(0); - Assert.assertTrue("" + ex.getCause(), ex.getCause() instanceof TestException); + aste = AssemblyStackTraceException.find(ex); - Assert.assertTrue(ex.getMessage(), ex.getMessage().contains("createSingle")); + assertNull(aste); } finally { RxJavaHooks.resetAssemblyTracking(); } } - + static Completable createCompletable() { return Completable.error(new Func0() { @Override @@ -97,36 +184,58 @@ public Throwable call() { } }); } - + @Test public void assemblyTrackingCompletable() { RxJavaHooks.enableAssemblyTracking(); try { TestSubscriber ts = TestSubscriber.create(); - + createCompletable().subscribe(ts); - - ts.assertError(AssemblyStackTraceException.class); - + + ts.assertError(TestException.class); + Throwable ex = ts.getOnErrorEvents().get(0); - Assert.assertTrue("" + ex, ex instanceof AssemblyStackTraceException); + AssemblyStackTraceException aste = AssemblyStackTraceException.find(ex); + + assertNotNull(aste); - Assert.assertTrue("" + ex.getCause(), ex.getCause() instanceof TestException); + assertTrue(aste.getMessage(), aste.getMessage().contains("createCompletable")); + + RxJavaHooks.clearAssemblyTracking(); + + ts = TestSubscriber.create(); + + createCompletable().subscribe(ts); + + ts.assertError(TestException.class); + + ex = ts.getOnErrorEvents().get(0); + + aste = AssemblyStackTraceException.find(ex); + + assertNull(aste); - Assert.assertTrue(ex.getMessage(), ex.getMessage().contains("createCompletable")); } finally { RxJavaHooks.resetAssemblyTracking(); } } - + @SuppressWarnings("rawtypes") @Test public void lockdown() throws Exception { RxJavaHooks.reset(); RxJavaHooks.lockdown(); try { + assertTrue(RxJavaHooks.isLockdown()); Action1 a1 = Actions.empty(); + Func0 f0 = new Func0() { + @Override + public Object call() { + return null; + } + }; Func1 f1 = UtilityFunctions.identity(); Func2 f2 = new Func2() { @Override @@ -134,14 +243,17 @@ public Object call(Object t1, Object t2) { return t2; } }; - + for (Method m : RxJavaHooks.class.getMethods()) { if (m.getName().startsWith("setOn")) { - + Method getter = RxJavaHooks.class.getMethod("get" + m.getName().substring(3)); - + Object before = getter.invoke(null); - + + if (m.getParameterTypes()[0].isAssignableFrom(Func0.class)) { + m.invoke(null, f0); + } else if (m.getParameterTypes()[0].isAssignableFrom(Func1.class)) { m.invoke(null, f1); } else @@ -150,16 +262,888 @@ public Object call(Object t1, Object t2) { } else { m.invoke(null, f2); } - + Object after = getter.invoke(null); - - Assert.assertSame(m.toString(), before, after); + + assertSame(m.toString(), before, after); + + if (before != null) { + RxJavaHooks.clear(); + RxJavaHooks.reset(); + assertSame(m.toString(), before, getter.invoke(null)); + } } } - + + + Object o1 = RxJavaHooks.getOnObservableCreate(); + Object o2 = RxJavaHooks.getOnSingleCreate(); + Object o3 = RxJavaHooks.getOnCompletableCreate(); + + RxJavaHooks.enableAssemblyTracking(); + RxJavaHooks.clearAssemblyTracking(); + RxJavaHooks.resetAssemblyTracking(); + + + assertSame(o1, RxJavaHooks.getOnObservableCreate()); + assertSame(o2, RxJavaHooks.getOnSingleCreate()); + assertSame(o3, RxJavaHooks.getOnCompletableCreate()); + } finally { RxJavaHooks.lockdown = false; RxJavaHooks.reset(); + assertFalse(RxJavaHooks.isLockdown()); + } + } + + Func1 replaceWithImmediate = new Func1() { + @Override + public Scheduler call(Scheduler t) { + return Schedulers.immediate(); + } + }; + + @Test + public void overrideComputationScheduler() { + try { + RxJavaHooks.setOnComputationScheduler(replaceWithImmediate); + + assertSame(Schedulers.immediate(), Schedulers.computation()); + } finally { + RxJavaHooks.reset(); + } + // make sure the reset worked + assertNotSame(Schedulers.immediate(), Schedulers.computation()); + } + + @Test + public void overrideIoScheduler() { + try { + RxJavaHooks.setOnIOScheduler(replaceWithImmediate); + + assertSame(Schedulers.immediate(), Schedulers.io()); + } finally { + RxJavaHooks.reset(); + } + // make sure the reset worked + assertNotSame(Schedulers.immediate(), Schedulers.io()); + } + + @Test + public void overrideNewThreadScheduler() { + try { + RxJavaHooks.setOnNewThreadScheduler(replaceWithImmediate); + + assertSame(Schedulers.immediate(), Schedulers.newThread()); + } finally { + RxJavaHooks.reset(); + } + // make sure the reset worked + assertNotSame(Schedulers.immediate(), Schedulers.newThread()); + } + + @SuppressWarnings("rawtypes") + @Test + public void observableCreate() { + try { + RxJavaHooks.setOnObservableCreate(new Func1() { + @Override + public Observable.OnSubscribe call(Observable.OnSubscribe t) { + return new OnSubscribeRange(1, 2); + } + }); + + TestSubscriber ts = TestSubscriber.create(); + + Observable.range(10, 3).subscribe(ts); + + ts.assertValues(1, 2); + ts.assertNoErrors(); + ts.assertCompleted(); + } finally { + RxJavaHooks.reset(); + } + // make sure the reset worked + TestSubscriber ts = TestSubscriber.create(); + + Observable.range(10, 3).subscribe(ts); + + ts.assertValues(10, 11, 12); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @SuppressWarnings("rawtypes") + @Test + public void observableStart() { + try { + RxJavaHooks.setOnObservableStart(new Func2() { + @Override + public Observable.OnSubscribe call(Observable o, Observable.OnSubscribe t) { + return new OnSubscribeRange(1, 2); + } + }); + + TestSubscriber ts = TestSubscriber.create(); + + Observable.range(10, 3).subscribe(ts); + + ts.assertValues(1, 2); + ts.assertNoErrors(); + ts.assertCompleted(); + } finally { + RxJavaHooks.reset(); + } + // make sure the reset worked + TestSubscriber ts = TestSubscriber.create(); + + Observable.range(10, 3).subscribe(ts); + + ts.assertValues(10, 11, 12); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @Test + public void observableReturn() { + try { + final Subscription s = Subscriptions.empty(); + + RxJavaHooks.setOnObservableReturn(new Func1() { + @Override + public Subscription call(Subscription t) { + return s; + } + }); + + TestSubscriber ts = TestSubscriber.create(); + + Subscription u = Observable.range(10, 3).subscribe(ts); + + ts.assertValues(10, 11, 12); + ts.assertNoErrors(); + ts.assertCompleted(); + + assertSame(s, u); + } finally { + RxJavaHooks.reset(); + } + } + + @SuppressWarnings("rawtypes") + @Test + public void singleCreate() { + try { + RxJavaHooks.setOnSingleCreate(new Func1() { + @Override + public Single.OnSubscribe call(Single.OnSubscribe t) { + return new Single.OnSubscribe() { + @Override + public void call(SingleSubscriber t) { + t.onSuccess(10); + } + }; + } + }); + + TestSubscriber ts = TestSubscriber.create(); + + Single.just(1).subscribe(ts); + + ts.assertValue(10); + ts.assertNoErrors(); + ts.assertCompleted(); + } finally { + RxJavaHooks.reset(); + } + // make sure the reset worked + TestSubscriber ts = TestSubscriber.create(); + + Single.just(1).subscribe(ts); + + ts.assertValue(1); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @SuppressWarnings("rawtypes") + @Test + public void singleStart() { + try { + RxJavaHooks.setOnSingleStart(new Func2() { + @Override + public Single.OnSubscribe call(Single o, Single.OnSubscribe t) { + return new Single.OnSubscribe() { + @Override + public void call(SingleSubscriber t) { + t.onSuccess(10); + } + }; + } + }); + + TestSubscriber ts = TestSubscriber.create(); + + Single.just(1).subscribe(ts); + + ts.assertValue(10); + ts.assertNoErrors(); + ts.assertCompleted(); + } finally { + RxJavaHooks.reset(); + } + // make sure the reset worked + TestSubscriber ts = TestSubscriber.create(); + + Single.just(1).subscribe(ts); + + ts.assertValue(1); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @Test + public void singleReturn() { + try { + final Subscription s = Subscriptions.empty(); + + RxJavaHooks.setOnSingleReturn(new Func1() { + @Override + public Subscription call(Subscription t) { + return s; + } + }); + + TestSubscriber ts = TestSubscriber.create(); + + Subscription u = Single.just(1).subscribe(ts); + + ts.assertValue(1); + ts.assertNoErrors(); + ts.assertCompleted(); + + assertSame(s, u); + } finally { + RxJavaHooks.reset(); + } + } + + @Test + public void completableCreate() { + try { + RxJavaHooks.setOnCompletableCreate(new Func1() { + @Override + public Completable.OnSubscribe call(Completable.OnSubscribe t) { + return new Completable.OnSubscribe() { + @Override + public void call(CompletableSubscriber t) { + t.onError(new TestException()); + } + }; + } + }); + + TestSubscriber ts = TestSubscriber.create(); + + Completable.complete().subscribe(ts); + + ts.assertNoValues(); + ts.assertNotCompleted(); + ts.assertError(TestException.class); + } finally { + RxJavaHooks.reset(); + } + // make sure the reset worked + TestSubscriber ts = TestSubscriber.create(); + + Completable.complete().subscribe(ts); + + ts.assertNoValues(); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @Test + public void completableStart() { + try { + RxJavaHooks.setOnCompletableStart(new Func2() { + @Override + public Completable.OnSubscribe call(Completable o, Completable.OnSubscribe t) { + return new Completable.OnSubscribe() { + @Override + public void call(CompletableSubscriber t) { + t.onError(new TestException()); + } + }; + } + }); + + TestSubscriber ts = TestSubscriber.create(); + + Completable.complete().subscribe(ts); + + ts.assertNoValues(); + ts.assertNotCompleted(); + ts.assertError(TestException.class); + } finally { + RxJavaHooks.reset(); + } + // make sure the reset worked + TestSubscriber ts = TestSubscriber.create(); + + Completable.complete().subscribe(ts); + + ts.assertNoValues(); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + void onSchedule(Worker w) throws InterruptedException { + try { + try { + final AtomicInteger value = new AtomicInteger(); + final CountDownLatch cdl = new CountDownLatch(1); + + RxJavaHooks.setOnScheduleAction(new Func1() { + @Override + public Action0 call(Action0 t) { + return new Action0() { + @Override + public void call() { + value.set(10); + cdl.countDown(); + } + }; + } + }); + + w.schedule(new Action0() { + @Override + public void call() { + value.set(1); + cdl.countDown(); + } + }); + + cdl.await(); + + assertEquals(10, value.get()); + + } finally { + + RxJavaHooks.reset(); + } + + // make sure the reset worked + final AtomicInteger value = new AtomicInteger(); + final CountDownLatch cdl = new CountDownLatch(1); + + w.schedule(new Action0() { + @Override + public void call() { + value.set(1); + cdl.countDown(); + } + }); + + cdl.await(); + + assertEquals(1, value.get()); + } finally { + w.unsubscribe(); + } + } + + @Test + public void onScheduleComputation() throws InterruptedException { + onSchedule(Schedulers.computation().createWorker()); + } + + @Test + public void onScheduleIO() throws InterruptedException { + onSchedule(Schedulers.io().createWorker()); + } + + @Test + public void onScheduleNewThread() throws InterruptedException { + onSchedule(Schedulers.newThread().createWorker()); + } + + @Test + public void onError() { + try { + final List list = new ArrayList(); + + RxJavaHooks.setOnError(new Action1() { + @Override + public void call(Throwable t) { + list.add(t); + } + }); + + RxJavaHooks.onError(new TestException("Forced failure")); + + assertEquals(1, list.size()); + assertTestException(list, 0, "Forced failure"); + } finally { + RxJavaHooks.reset(); + } + } + + @Test + public void clear() throws Exception { + RxJavaHooks.reset(); + try { + RxJavaHooks.clear(); + for (Method m : RxJavaHooks.class.getMethods()) { + if (m.getName().startsWith("getOn")) { + assertNull(m.toString(), m.invoke(null)); + } + } + + } finally { + RxJavaHooks.reset(); + } + + for (Method m : RxJavaHooks.class.getMethods()) { + if (m.getName().startsWith("getOn") + && !m.getName().endsWith("Scheduler") + && !m.getName().contains("GenericScheduledExecutorService")) { + assertNotNull(m.toString(), m.invoke(null)); + } + } + + } + + @Test + public void onErrorNoHandler() { + try { + final List list = new ArrayList(); + + RxJavaHooks.setOnError(null); + + Thread.currentThread().setUncaughtExceptionHandler(new UncaughtExceptionHandler() { + + @Override + public void uncaughtException(Thread t, Throwable e) { + list.add(e); + + } + }); + + RxJavaHooks.onError(new TestException("Forced failure")); + + Thread.currentThread().setUncaughtExceptionHandler(null); + + // this will be printed on the console and should not crash + RxJavaHooks.onError(new TestException("Forced failure 3")); + + assertEquals(1, list.size()); + assertTestException(list, 0, "Forced failure"); + } finally { + RxJavaHooks.reset(); + Thread.currentThread().setUncaughtExceptionHandler(null); + } + } + + @Test + public void onErrorCrashes() { + try { + final List list = new ArrayList(); + + RxJavaHooks.setOnError(new Action1() { + @Override + public void call(Throwable t) { + throw new TestException("Forced failure 2"); + } + }); + + Thread.currentThread().setUncaughtExceptionHandler(new UncaughtExceptionHandler() { + + @Override + public void uncaughtException(Thread t, Throwable e) { + list.add(e); + + } + }); + + RxJavaHooks.onError(new TestException("Forced failure")); + + assertEquals(2, list.size()); + assertTestException(list, 0, "Forced failure 2"); + assertTestException(list, 1, "Forced failure"); + + Thread.currentThread().setUncaughtExceptionHandler(null); + + } finally { + RxJavaHooks.reset(); + Thread.currentThread().setUncaughtExceptionHandler(null); + } + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Test + public void clearIsPassthrough() { + try { + RxJavaHooks.clear(); + + assertNull(RxJavaHooks.onCreate((Observable.OnSubscribe)null)); + + Observable.OnSubscribe oos = new Observable.OnSubscribe() { + @Override + public void call(Object t) { + + } + }; + + assertSame(oos, RxJavaHooks.onCreate(oos)); + + assertNull(RxJavaHooks.onCreate((Single.OnSubscribe)null)); + + Single.OnSubscribe sos = new Single.OnSubscribe() { + @Override + public void call(Object t) { + + } + }; + + assertSame(sos, RxJavaHooks.onCreate(sos)); + + assertNull(RxJavaHooks.onCreate((Single.OnSubscribe)null)); + + Completable.OnSubscribe cos = new Completable.OnSubscribe() { + @Override + public void call(CompletableSubscriber t) { + + } + }; + + assertSame(cos, RxJavaHooks.onCreate(cos)); + + assertNull(RxJavaHooks.onScheduledAction(null)); + + Action0 action = Actions.empty(); + + assertSame(action, RxJavaHooks.onScheduledAction(action)); + + + assertNull(RxJavaHooks.onObservableStart(Observable.never(), null)); + + assertSame(oos, RxJavaHooks.onObservableStart(Observable.never(), oos)); + + assertNull(RxJavaHooks.onSingleStart(Single.just(1), null)); + + assertSame(sos, RxJavaHooks.onSingleStart(Single.just(1), sos)); + + assertNull(RxJavaHooks.onCompletableStart(Completable.never(), null)); + + assertSame(cos, RxJavaHooks.onCompletableStart(Completable.never(), cos)); + + Subscription subscription = Subscriptions.empty(); + + assertNull(RxJavaHooks.onObservableReturn(null)); + + assertSame(subscription, RxJavaHooks.onObservableReturn(subscription)); + + assertNull(RxJavaHooks.onSingleReturn(null)); + + assertSame(subscription, RxJavaHooks.onSingleReturn(subscription)); + + TestException ex = new TestException(); + + assertNull(RxJavaHooks.onObservableError(null)); + + assertSame(ex, RxJavaHooks.onObservableError(ex)); + + assertNull(RxJavaHooks.onSingleError(null)); + + assertSame(ex, RxJavaHooks.onSingleError(ex)); + + assertNull(RxJavaHooks.onCompletableError(null)); + + assertSame(ex, RxJavaHooks.onCompletableError(ex)); + + Observable.Operator oop = new Observable.Operator() { + @Override + public Object call(Object t) { + return t; + } + }; + + assertNull(RxJavaHooks.onObservableLift(null)); + + assertSame(oop, RxJavaHooks.onObservableLift(oop)); + + assertNull(RxJavaHooks.onSingleLift(null)); + + assertSame(oop, RxJavaHooks.onSingleLift(oop)); + + Completable.Operator cop = new Completable.Operator() { + @Override + public CompletableSubscriber call(CompletableSubscriber t) { + return t; + } + }; + + assertNull(RxJavaHooks.onCompletableLift(null)); + + assertSame(cop, RxJavaHooks.onCompletableLift(cop)); + + } finally { + RxJavaHooks.reset(); + } + } + + static void assertTestException(List list, int index, String message) { + assertTrue(list.get(index).toString(), list.get(index) instanceof TestException); + assertEquals(message, list.get(index).getMessage()); + } + + @Test + public void onXError() { + try { + final List list = new ArrayList(); + + final TestException ex = new TestException(); + + Func1 errorHandler = new Func1() { + @Override + public Throwable call(Throwable t) { + list.add(t); + return ex; + } + }; + + RxJavaHooks.setOnObservableSubscribeError(errorHandler); + + RxJavaHooks.setOnSingleSubscribeError(errorHandler); + + RxJavaHooks.setOnCompletableSubscribeError(errorHandler); + + assertSame(ex, RxJavaHooks.onObservableError(new TestException("Forced failure 1"))); + + assertSame(ex, RxJavaHooks.onSingleError(new TestException("Forced failure 2"))); + + assertSame(ex, RxJavaHooks.onCompletableError(new TestException("Forced failure 3"))); + + assertTestException(list, 0, "Forced failure 1"); + + assertTestException(list, 1, "Forced failure 2"); + + assertTestException(list, 2, "Forced failure 3"); + } finally { + RxJavaHooks.reset(); + } + } + + @SuppressWarnings("deprecation") + @Test + public void onPluginsXError() { + try { + RxJavaPlugins.getInstance().reset(); + RxJavaHooks.reset(); + + final List list = new ArrayList(); + + final TestException ex = new TestException(); + + final Func1 errorHandler = new Func1() { + @Override + public Throwable call(Throwable t) { + list.add(t); + return ex; + } + }; + + RxJavaPlugins.getInstance().registerObservableExecutionHook(new RxJavaObservableExecutionHook() { + @Override + public Throwable onSubscribeError(Throwable e) { + return errorHandler.call(e); + } + }); + + RxJavaPlugins.getInstance().registerSingleExecutionHook(new RxJavaSingleExecutionHook() { + @Override + public Throwable onSubscribeError(Throwable e) { + return errorHandler.call(e); + } + }); + + RxJavaPlugins.getInstance().registerCompletableExecutionHook(new RxJavaCompletableExecutionHook() { + @Override + public Throwable onSubscribeError(Throwable e) { + return errorHandler.call(e); + } + }); + + assertSame(ex, RxJavaHooks.onObservableError(new TestException("Forced failure 1"))); + + assertSame(ex, RxJavaHooks.onSingleError(new TestException("Forced failure 2"))); + + assertSame(ex, RxJavaHooks.onCompletableError(new TestException("Forced failure 3"))); + + assertTestException(list, 0, "Forced failure 1"); + + assertTestException(list, 1, "Forced failure 2"); + + assertTestException(list, 2, "Forced failure 3"); + } finally { + RxJavaPlugins.getInstance().reset(); + RxJavaHooks.reset(); + } + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Test + public void onXLift() { + try { + Completable.Operator cop = new Completable.Operator() { + @Override + public CompletableSubscriber call(CompletableSubscriber t) { + return t; + } + }; + + Observable.Operator oop = new Observable.Operator() { + @Override + public Object call(Object t) { + return t; + } + }; + + final int[] counter = { 0 }; + + RxJavaHooks.setOnObservableLift(new Func1() { + @Override + public Observable.Operator call(Observable.Operator t) { + counter[0]++; + return t; + } + }); + + RxJavaHooks.setOnSingleLift(new Func1() { + @Override + public Observable.Operator call(Observable.Operator t) { + counter[0]++; + return t; + } + }); + + RxJavaHooks.setOnCompletableLift(new Func1() { + @Override + public Completable.Operator call(Completable.Operator t) { + counter[0]++; + return t; + } + }); + + assertSame(oop, RxJavaHooks.onObservableLift(oop)); + + assertSame(oop, RxJavaHooks.onSingleLift(oop)); + + assertSame(cop, RxJavaHooks.onCompletableLift(cop)); + + assertEquals(3, counter[0]); + + } finally { + RxJavaHooks.reset(); + } + } + + @SuppressWarnings({ "rawtypes", "unchecked", "deprecation" }) + @Test + public void onPluginsXLift() { + try { + + RxJavaPlugins.getInstance().reset(); + RxJavaHooks.reset(); + + Completable.Operator cop = new Completable.Operator() { + @Override + public CompletableSubscriber call(CompletableSubscriber t) { + return t; + } + }; + + Observable.Operator oop = new Observable.Operator() { + @Override + public Object call(Object t) { + return t; + } + }; + + final int[] counter = { 0 }; + + final Func1 onObservableLift = new Func1() { + @Override + public Observable.Operator call(Observable.Operator t) { + counter[0]++; + return t; + } + }; + + final Func1 onCompletableLift = new Func1() { + @Override + public Completable.Operator call(Completable.Operator t) { + counter[0]++; + return t; + } + }; + + RxJavaPlugins.getInstance().registerObservableExecutionHook(new RxJavaObservableExecutionHook() { + @Override + public Observable.Operator onLift(Observable.Operator lift) { + return onObservableLift.call(lift); + } + }); + + RxJavaPlugins.getInstance().registerSingleExecutionHook(new RxJavaSingleExecutionHook() { + @Override + public Observable.Operator onLift(Observable.Operator lift) { + return onObservableLift.call(lift); + } + }); + + RxJavaPlugins.getInstance().registerCompletableExecutionHook(new RxJavaCompletableExecutionHook() { + @Override + public Completable.Operator onLift(Completable.Operator lift) { + return onCompletableLift.call(lift); + } + }); + + assertSame(oop, RxJavaHooks.onObservableLift(oop)); + + assertSame(oop, RxJavaHooks.onSingleLift(oop)); + + assertSame(cop, RxJavaHooks.onCompletableLift(cop)); + + assertEquals(3, counter[0]); + + } finally { + RxJavaPlugins.getInstance().reset(); + RxJavaHooks.reset(); + } + } + + @Test + public void noCallToHooksOnPlainError() { + + final boolean[] called = { false }; + + RxJavaHooks.setOnError(new Action1() { + @Override + public void call(Throwable t) { + called[0] = true; + } + }); + + try { + Observable.error(new TestException()) + .subscribe(new TestSubscriber()); + + assertFalse(called[0]); + } finally { + RxJavaHooks.reset(); } } } diff --git a/src/test/java/rx/plugins/RxJavaPluginsTest.java b/src/test/java/rx/plugins/RxJavaPluginsTest.java index b4171ea11a..50d6944a08 100644 --- a/src/test/java/rx/plugins/RxJavaPluginsTest.java +++ b/src/test/java/rx/plugins/RxJavaPluginsTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -18,6 +18,7 @@ import static org.junit.Assert.*; import static org.mockito.Mockito.mock; +import java.security.Permission; import java.util.*; import java.util.concurrent.TimeUnit; @@ -72,7 +73,7 @@ public void testErrorHandlerViaProperty() { public static class RxJavaErrorHandlerTestImpl extends RxJavaErrorHandler { private volatile Throwable e; - private volatile int count = 0; + private volatile int count; @Override public void handleError(Throwable e) { @@ -232,7 +233,7 @@ public Date call(Date date) { throw new IllegalStateException("Trigger OnNextValue"); } }) - .timeout(500, TimeUnit.MILLISECONDS) + .timeout(5000, TimeUnit.MILLISECONDS) .toBlocking().first(); fail("Did not expect onNext/onCompleted, got " + notExpected); } catch (IllegalStateException e) { @@ -263,31 +264,31 @@ private static String getFullClassNameForTestClass(Class cls) { return RxJavaPlugins.class.getPackage() .getName() + "." + RxJavaPluginsTest.class.getSimpleName() + "$" + cls.getSimpleName(); } - + @Test public void testShortPluginDiscovery() { Properties props = new Properties(); - + props.setProperty("rxjava.plugin.1.class", "Map"); props.setProperty("rxjava.plugin.1.impl", "java.util.HashMap"); props.setProperty("rxjava.plugin.xyz.class", "List"); props.setProperty("rxjava.plugin.xyz.impl", "java.util.ArrayList"); - + Object o = RxJavaPlugins.getPluginImplementationViaProperty(Map.class, props); - + assertTrue("" + o, o instanceof HashMap); - + o = RxJavaPlugins.getPluginImplementationViaProperty(List.class, props); - + assertTrue("" + o, o instanceof ArrayList); } - + @Test(expected = RuntimeException.class) public void testShortPluginDiscoveryMissing() { Properties props = new Properties(); - + props.setProperty("rxjava.plugin.1.class", "Map"); RxJavaPlugins.getPluginImplementationViaProperty(Map.class, props); @@ -344,4 +345,75 @@ public void onNext(Object o) { assertEquals(re, errorHandler.e); assertEquals(1, errorHandler.count); } + + @Test + public void systemPropertiesSecurityException() { + assertNull(RxJavaPlugins.getPluginImplementationViaProperty(Object.class, new Properties() { + + private static final long serialVersionUID = -4291534158508255616L; + + @Override + public Set> entrySet() { + return new HashSet>() { + + private static final long serialVersionUID = -7714005655772619143L; + + @Override + public Iterator> iterator() { + return new Iterator>() { + @Override + public boolean hasNext() { + return true; + } + + @Override + public Map.Entry next() { + throw new SecurityException(); + }; + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + }; + } + + @Override + public synchronized Object clone() { + return this; + } + })); + } + + @Test + public void securityManagerDenySystemProperties() { + SecurityManager old = System.getSecurityManager(); + try { + SecurityManager sm = new SecurityManager() { + @Override + public void checkPropertiesAccess() { + throw new SecurityException(); + } + + @Override + public void checkPermission(Permission perm) { + // allow restoring the security manager + } + + @Override + public void checkPermission(Permission perm, Object context) { + // allow restoring the security manager + } + }; + + System.setSecurityManager(sm); + assertTrue(RxJavaPlugins.getSystemPropertiesSafe().isEmpty()); + } finally { + System.setSecurityManager(old); + } + + assertFalse(RxJavaPlugins.getSystemPropertiesSafe().isEmpty()); + } } diff --git a/src/test/java/rx/schedulers/AbstractSchedulerConcurrencyTests.java b/src/test/java/rx/schedulers/AbstractSchedulerConcurrencyTests.java index 461ff7cce2..1a9541997c 100644 --- a/src/test/java/rx/schedulers/AbstractSchedulerConcurrencyTests.java +++ b/src/test/java/rx/schedulers/AbstractSchedulerConcurrencyTests.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -42,9 +42,9 @@ /** * Base tests for schedulers that involve threads (concurrency). - * + * * These can only run on Schedulers that launch threads since they expect async/concurrent behavior. - * + * * The Current/Immediate schedulers will not work with these tests. */ public abstract class AbstractSchedulerConcurrencyTests extends AbstractSchedulerTests { @@ -106,13 +106,13 @@ public void testUnsubscribeRecursiveScheduleFromOutside() throws InterruptedExce final Worker inner = getScheduler().createWorker(); try { inner.schedule(new Action0() { - + @Override public void call() { inner.schedule(new Action0() { - - int i = 0; - + + int i; + @Override public void call() { System.out.println("Run: " + i++); @@ -123,20 +123,20 @@ public void call() { unsubscribeLatch.await(); } catch (InterruptedException e) { // we expect the countDown if unsubscribe is not working - // or to be interrupted if unsubscribe is successful since + // or to be interrupted if unsubscribe is successful since // the unsubscribe will interrupt it as it is calling Future.cancel(true) // so we will ignore the stacktrace } } - + counter.incrementAndGet(); inner.schedule(this); } }); } - + }); - + latch.await(); inner.unsubscribe(); unsubscribeLatch.countDown(); @@ -154,28 +154,28 @@ public void testUnsubscribeRecursiveScheduleFromInside() throws InterruptedExcep final Worker inner = getScheduler().createWorker(); try { inner.schedule(new Action0() { - + @Override public void call() { inner.schedule(new Action0() { - - int i = 0; - + + int i; + @Override public void call() { System.out.println("Run: " + i++); if (i == 10) { inner.unsubscribe(); } - + counter.incrementAndGet(); inner.schedule(this); } }); } - + }); - + unsubscribeLatch.countDown(); Thread.sleep(200); // let time pass to see if the scheduler is still doing work assertEquals(10, counter.get()); @@ -190,16 +190,16 @@ public void testUnsubscribeRecursiveScheduleWithDelay() throws InterruptedExcept final CountDownLatch unsubscribeLatch = new CountDownLatch(1); final AtomicInteger counter = new AtomicInteger(); final Worker inner = getScheduler().createWorker(); - + try { inner.schedule(new Action0() { - + @Override public void call() { inner.schedule(new Action0() { - + long i = 1L; - + @Override public void call() { if (i++ == 10) { @@ -209,19 +209,19 @@ public void call() { unsubscribeLatch.await(); } catch (InterruptedException e) { // we expect the countDown if unsubscribe is not working - // or to be interrupted if unsubscribe is successful since + // or to be interrupted if unsubscribe is successful since // the unsubscribe will interrupt it as it is calling Future.cancel(true) // so we will ignore the stacktrace } } - + counter.incrementAndGet(); inner.schedule(this, 10, TimeUnit.MILLISECONDS); } }, 10, TimeUnit.MILLISECONDS); } }); - + latch.await(); inner.unsubscribe(); unsubscribeLatch.countDown(); @@ -238,9 +238,9 @@ public void recursionFromOuterActionAndUnsubscribeInside() throws InterruptedExc final Worker inner = getScheduler().createWorker(); try { inner.schedule(new Action0() { - - int i = 0; - + + int i; + @Override public void call() { i++; @@ -254,7 +254,7 @@ public void call() { } } }); - + latch.await(); } finally { inner.unsubscribe(); @@ -267,9 +267,9 @@ public void testRecursion() throws InterruptedException { final Worker inner = getScheduler().createWorker(); try { inner.schedule(new Action0() { - - private long i = 0; - + + private long i; + @Override public void call() { i++; @@ -283,7 +283,7 @@ public void call() { } } }); - + latch.await(); } finally { inner.unsubscribe(); @@ -297,7 +297,7 @@ public void testRecursionAndOuterUnsubscribe() throws InterruptedException { final CountDownLatch completionLatch = new CountDownLatch(1); final Worker inner = getScheduler().createWorker(); try { - Observable obs = Observable.create(new OnSubscribe() { + Observable obs = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(final Subscriber observer) { inner.schedule(new Action0() { @@ -305,26 +305,26 @@ public void call(final Subscriber observer) { public void call() { observer.onNext(42); latch.countDown(); - + // this will recursively schedule this task for execution again inner.schedule(this); } }); - + observer.add(Subscriptions.create(new Action0() { - + @Override public void call() { inner.unsubscribe(); observer.onCompleted(); completionLatch.countDown(); } - + })); - + } }); - + final AtomicInteger count = new AtomicInteger(); final AtomicBoolean completed = new AtomicBoolean(false); Subscription subscribe = obs.subscribe(new Subscriber() { @@ -333,31 +333,31 @@ public void onCompleted() { System.out.println("Completed"); completed.set(true); } - + @Override public void onError(Throwable e) { System.out.println("Error"); } - + @Override public void onNext(Integer args) { count.incrementAndGet(); System.out.println(args); } }); - + if (!latch.await(5000, TimeUnit.MILLISECONDS)) { fail("Timed out waiting on onNext latch"); } - + // now unsubscribe and ensure it stops the recursive loop subscribe.unsubscribe(); System.out.println("unsubscribe"); - + if (!completionLatch.await(5000, TimeUnit.MILLISECONDS)) { fail("Timed out waiting on completion latch"); } - + // the count can be 10 or higher due to thread scheduling of the unsubscribe vs the scheduler looping to emit the count assertTrue(count.get() >= 10); assertTrue(completed.get()); diff --git a/src/test/java/rx/schedulers/AbstractSchedulerTests.java b/src/test/java/rx/schedulers/AbstractSchedulerTests.java index e1252e7214..ff5c94071a 100644 --- a/src/test/java/rx/schedulers/AbstractSchedulerTests.java +++ b/src/test/java/rx/schedulers/AbstractSchedulerTests.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -49,16 +49,16 @@ public void testNestedActions() throws InterruptedException { final Scheduler.Worker inner = scheduler.createWorker(); try { final CountDownLatch latch = new CountDownLatch(1); - + final Action0 firstStepStart = mock(Action0.class); final Action0 firstStepEnd = mock(Action0.class); - + final Action0 secondStepStart = mock(Action0.class); final Action0 secondStepEnd = mock(Action0.class); - + final Action0 thirdStepStart = mock(Action0.class); final Action0 thirdStepEnd = mock(Action0.class); - + final Action0 firstAction = new Action0() { @Override public void call() { @@ -73,7 +73,7 @@ public void call() { secondStepStart.call(); inner.schedule(firstAction); secondStepEnd.call(); - + } }; final Action0 thirdAction = new Action0() { @@ -84,13 +84,13 @@ public void call() { thirdStepEnd.call(); } }; - + InOrder inOrder = inOrder(firstStepStart, firstStepEnd, secondStepStart, secondStepEnd, thirdStepStart, thirdStepEnd); - + inner.schedule(thirdAction); - + latch.await(); - + inOrder.verify(thirdStepStart, times(1)).call(); inOrder.verify(thirdStepEnd, times(1)).call(); inOrder.verify(secondStepStart, times(1)).call(); @@ -135,7 +135,7 @@ public String call(String s) { /** * The order of execution is nondeterministic. - * + * * @throws InterruptedException */ @SuppressWarnings("rawtypes") @@ -147,10 +147,10 @@ public final void testSequenceOfActions() throws InterruptedException { final CountDownLatch latch = new CountDownLatch(2); final Action0 first = mock(Action0.class); final Action0 second = mock(Action0.class); - + // make it wait until both the first and second are called doAnswer(new Answer() { - + @Override public Object answer(InvocationOnMock invocation) throws Throwable { try { @@ -161,7 +161,7 @@ public Object answer(InvocationOnMock invocation) throws Throwable { } }).when(first).call(); doAnswer(new Answer() { - + @Override public Object answer(InvocationOnMock invocation) throws Throwable { try { @@ -171,12 +171,12 @@ public Object answer(InvocationOnMock invocation) throws Throwable { } } }).when(second).call(); - + inner.schedule(first); inner.schedule(second); - + latch.await(); - + verify(first, times(1)).call(); verify(second, times(1)).call(); } finally { @@ -188,19 +188,19 @@ public Object answer(InvocationOnMock invocation) throws Throwable { public void testSequenceOfDelayedActions() throws InterruptedException { Scheduler scheduler = getScheduler(); final Scheduler.Worker inner = scheduler.createWorker(); - + try { final CountDownLatch latch = new CountDownLatch(1); final Action0 first = mock(Action0.class); final Action0 second = mock(Action0.class); - + inner.schedule(new Action0() { @Override public void call() { inner.schedule(first, 30, TimeUnit.MILLISECONDS); inner.schedule(second, 10, TimeUnit.MILLISECONDS); inner.schedule(new Action0() { - + @Override public void call() { latch.countDown(); @@ -208,10 +208,10 @@ public void call() { }, 40, TimeUnit.MILLISECONDS); } }); - + latch.await(); InOrder inOrder = inOrder(first, second); - + inOrder.verify(second, times(1)).call(); inOrder.verify(first, times(1)).call(); } finally { @@ -223,14 +223,14 @@ public void call() { public void testMixOfDelayedAndNonDelayedActions() throws InterruptedException { Scheduler scheduler = getScheduler(); final Scheduler.Worker inner = scheduler.createWorker(); - + try { final CountDownLatch latch = new CountDownLatch(1); final Action0 first = mock(Action0.class); final Action0 second = mock(Action0.class); final Action0 third = mock(Action0.class); final Action0 fourth = mock(Action0.class); - + inner.schedule(new Action0() { @Override public void call() { @@ -239,7 +239,7 @@ public void call() { inner.schedule(third, 100, TimeUnit.MILLISECONDS); inner.schedule(fourth); inner.schedule(new Action0() { - + @Override public void call() { latch.countDown(); @@ -247,10 +247,10 @@ public void call() { }, 400, TimeUnit.MILLISECONDS); } }); - + latch.await(); InOrder inOrder = inOrder(first, second, third, fourth); - + inOrder.verify(first, times(1)).call(); inOrder.verify(fourth, times(1)).call(); inOrder.verify(third, times(1)).call(); @@ -264,13 +264,13 @@ public void call() { public final void testRecursiveExecution() throws InterruptedException { final Scheduler scheduler = getScheduler(); final Scheduler.Worker inner = scheduler.createWorker(); - + try { - + final AtomicInteger i = new AtomicInteger(); final CountDownLatch latch = new CountDownLatch(1); inner.schedule(new Action0() { - + @Override public void call() { if (i.incrementAndGet() < 100) { @@ -280,7 +280,7 @@ public void call() { } } }); - + latch.await(); assertEquals(100, i.get()); } finally { @@ -292,15 +292,15 @@ public void call() { public final void testRecursiveExecutionWithDelayTime() throws InterruptedException { Scheduler scheduler = getScheduler(); final Scheduler.Worker inner = scheduler.createWorker(); - + try { final AtomicInteger i = new AtomicInteger(); final CountDownLatch latch = new CountDownLatch(1); - + inner.schedule(new Action0() { - - int state = 0; - + + int state; + @Override public void call() { i.set(state); @@ -310,9 +310,9 @@ public void call() { latch.countDown(); } } - + }); - + latch.await(); assertEquals(100, i.get()); } finally { @@ -322,13 +322,13 @@ public void call() { @Test public final void testRecursiveSchedulerInObservable() { - Observable obs = Observable.create(new OnSubscribe() { + Observable obs = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(final Subscriber observer) { final Scheduler.Worker inner = getScheduler().createWorker(); observer.add(inner); inner.schedule(new Action0() { - int i = 0; + int i; @Override public void call() { @@ -362,7 +362,7 @@ public void call(Integer v) { public final void testConcurrentOnNextFailsValidation() throws InterruptedException { final int count = 10; final CountDownLatch latch = new CountDownLatch(count); - Observable o = Observable.create(new OnSubscribe() { + Observable o = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(final Subscriber observer) { @@ -423,7 +423,7 @@ public final void testSubscribeOnNestedConcurrency() throws InterruptedException @Override public Observable call(final String v) { - return Observable.create(new OnSubscribe() { + return Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber observer) { @@ -450,7 +450,7 @@ public void call(Subscriber observer) { /** * Used to determine if onNext is being invoked concurrently. - * + * * @param */ private static class ConcurrentObserverValidator extends Subscriber { @@ -490,16 +490,16 @@ public void onNext(T args) { } } - + @Test public void periodicTaskCancelsItself() throws Exception { Scheduler scheduler = getScheduler(); - if (scheduler instanceof rx.internal.schedulers.ImmediateScheduler + if (scheduler instanceof rx.internal.schedulers.ImmediateScheduler || scheduler instanceof rx.internal.schedulers.TrampolineScheduler) { return; } Worker w = scheduler.createWorker(); - + try { final CountDownLatch cdl = new CountDownLatch(1); final int[] executions = { 0 }; @@ -508,22 +508,22 @@ public void periodicTaskCancelsItself() throws Exception { @Override public void call() { executions[0]++; - while (cancel.get() == null); - + while (cancel.get() == null) { } + cancel.get().unsubscribe(); cdl.countDown(); } }, 100, 100, TimeUnit.MILLISECONDS); - + cancel.set(s); - + if (!cdl.await(5, TimeUnit.SECONDS)) { s.unsubscribe(); Assert.fail("The await timed out"); } - + Assert.assertEquals(1, executions[0]); - + } finally { w.unsubscribe(); } diff --git a/src/test/java/rx/schedulers/ComputationSchedulerTests.java b/src/test/java/rx/schedulers/ComputationSchedulerTests.java index 2988fab822..d83a456f56 100644 --- a/src/test/java/rx/schedulers/ComputationSchedulerTests.java +++ b/src/test/java/rx/schedulers/ComputationSchedulerTests.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -47,13 +47,13 @@ public void testThreadSafetyWhenSchedulerIsHoppingBetweenThreads() { final HashMap map = new HashMap(); final Scheduler.Worker inner = Schedulers.computation().createWorker(); - + try { inner.schedule(new Action0() { - + private HashMap statefulMap = map; - int nonThreadSafeCounter = 0; - + int nonThreadSafeCounter; + @Override public void call() { Integer i = statefulMap.get("a"); @@ -75,17 +75,17 @@ public void call() { } } }); - + try { latch.await(); } catch (InterruptedException e) { e.printStackTrace(); } - + System.out.println("Count A: " + map.get("a")); System.out.println("Count B: " + map.get("b")); System.out.println("nonThreadSafeCounter: " + map.get("nonThreadSafeCounter")); - + assertEquals(NUM, map.get("a").intValue()); assertEquals(NUM, map.get("b").intValue()); assertEquals(NUM, map.get("nonThreadSafeCounter").intValue()); @@ -152,7 +152,7 @@ public final void testUnhandledErrorIsDeliveredToThreadHandler() throws Interrup public final void testHandledErrorIsNotDeliveredToThreadHandler() throws InterruptedException { SchedulerTests.testHandledErrorIsNotDeliveredToThreadHandler(getScheduler()); } - + @Test(timeout = 60000) public void testCancelledTaskRetention() throws InterruptedException { Worker w = Schedulers.computation().createWorker(); diff --git a/src/test/java/rx/schedulers/DeprecatedSchedulersTest.java b/src/test/java/rx/schedulers/DeprecatedSchedulersTest.java new file mode 100644 index 0000000000..288be2f2de --- /dev/null +++ b/src/test/java/rx/schedulers/DeprecatedSchedulersTest.java @@ -0,0 +1,73 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * 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 rx.schedulers; + +import org.junit.Test; +import static org.junit.Assert.*; + +import rx.*; +import rx.internal.util.SuppressAnimalSniffer; +import rx.internal.util.unsafe.UnsafeAccess; + +@SuppressWarnings("deprecation") +@SuppressAnimalSniffer +public class DeprecatedSchedulersTest { + + @Test + public void immediate() { + TestUtil.checkUtilityClass(ImmediateScheduler.class); + } + + @Test + public void newThread() { + TestUtil.checkUtilityClass(NewThreadScheduler.class); + } + + @Test + public void trampoline() { + TestUtil.checkUtilityClass(TrampolineScheduler.class); + } + + void checkWorker(Class schedulerClass) { + if (UnsafeAccess.isUnsafeAvailable()) { + try { + Scheduler s = (Scheduler)UnsafeAccess.UNSAFE.allocateInstance(schedulerClass); + + assertNull(s.createWorker()); + } catch (InstantiationException e) { + throw new IllegalStateException(e); + } + } + } + + @Test + public void immediateWorker() { + checkWorker(ImmediateScheduler.class); + } + + @Test + public void newThreadWorker() { + checkWorker(NewThreadScheduler.class); + } + + @Test + public void trampolineWorker() { + checkWorker(TrampolineScheduler.class); + } + + +} diff --git a/src/test/java/rx/schedulers/GenericScheduledExecutorServiceTest.java b/src/test/java/rx/schedulers/GenericScheduledExecutorServiceTest.java new file mode 100644 index 0000000000..4091cb52bf --- /dev/null +++ b/src/test/java/rx/schedulers/GenericScheduledExecutorServiceTest.java @@ -0,0 +1,63 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * 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 rx.schedulers; + +import java.util.concurrent.*; + +import org.junit.*; + +import rx.functions.Func0; +import rx.internal.schedulers.GenericScheduledExecutorService; +import rx.plugins.RxJavaHooks; + +public class GenericScheduledExecutorServiceTest { + + @Test + public void genericScheduledExecutorServiceHook() { + // make sure the class is initialized + Assert.assertNotNull(GenericScheduledExecutorService.class); + + final ScheduledExecutorService exec = Executors.newSingleThreadScheduledExecutor(); + try { + + RxJavaHooks.setOnGenericScheduledExecutorService(new Func0() { + @Override + public ScheduledExecutorService call() { + return exec; + } + }); + + Schedulers.shutdown(); + Schedulers.start(); + + Assert.assertSame(exec, GenericScheduledExecutorService.getInstance()); + + RxJavaHooks.setOnGenericScheduledExecutorService(null); + + Schedulers.shutdown(); + // start() is package private so had to move this test here + Schedulers.start(); + + Assert.assertNotSame(exec, GenericScheduledExecutorService.getInstance()); + + } finally { + RxJavaHooks.reset(); + exec.shutdownNow(); + } + + } +} diff --git a/src/test/java/rx/schedulers/ImmediateSchedulerTest.java b/src/test/java/rx/schedulers/ImmediateSchedulerTest.java index c68e4843b5..cd3bca91b1 100644 --- a/src/test/java/rx/schedulers/ImmediateSchedulerTest.java +++ b/src/test/java/rx/schedulers/ImmediateSchedulerTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. diff --git a/src/test/java/rx/schedulers/IoSchedulerTest.java b/src/test/java/rx/schedulers/IoSchedulerTest.java index 775f6fd507..b52b133968 100644 --- a/src/test/java/rx/schedulers/IoSchedulerTest.java +++ b/src/test/java/rx/schedulers/IoSchedulerTest.java @@ -18,6 +18,8 @@ import static org.junit.Assert.assertTrue; +import java.util.concurrent.atomic.AtomicBoolean; + import org.junit.Test; import rx.*; @@ -66,7 +68,7 @@ public final void testUnhandledErrorIsDeliveredToThreadHandler() throws Interrup public final void testHandledErrorIsNotDeliveredToThreadHandler() throws InterruptedException { SchedulerTests.testHandledErrorIsNotDeliveredToThreadHandler(getScheduler()); } - + @Test(timeout = 60000) public void testCancelledTaskRetention() throws InterruptedException { Worker w = Schedulers.io().createWorker(); @@ -83,4 +85,71 @@ public void testCancelledTaskRetention() throws InterruptedException { } } + // Tests that an uninterruptible worker does not get reused + @Test(timeout = 10000) + public void testUninterruptibleActionDoesNotBlockOtherAction() throws InterruptedException { + final Worker uninterruptibleWorker = Schedulers.io().createWorker(); + final AtomicBoolean running = new AtomicBoolean(false); + final AtomicBoolean shouldQuit = new AtomicBoolean(false); + try { + uninterruptibleWorker.schedule(new Action0() { + @Override + public void call() { + synchronized (running) { + running.set(true); + running.notifyAll(); + } + synchronized (shouldQuit) { + while (!shouldQuit.get()) { + try { + shouldQuit.wait(); + } catch (final InterruptedException ignored) { + } + } + } + synchronized (running) { + running.set(false); + running.notifyAll(); + } + } + }); + + // Wait for the action to start executing + synchronized (running) { + while (!running.get()) { + running.wait(); + } + } + } finally { + uninterruptibleWorker.unsubscribe(); + } + + final Worker otherWorker = Schedulers.io().createWorker(); + final AtomicBoolean otherActionRan = new AtomicBoolean(false); + try { + otherWorker.schedule(new Action0() { + @Override + public void call() { + otherActionRan.set(true); + } + }); + Thread.sleep(1000); // give the action a chance to run + } finally { + otherWorker.unsubscribe(); + } + + assertTrue(running.get()); // uninterruptible action keeps on running since InterruptedException is swallowed + assertTrue(otherActionRan.get()); + + // Wait for uninterruptibleWorker to exit (to clean up after ourselves) + synchronized (shouldQuit) { + shouldQuit.set(true); + shouldQuit.notifyAll(); + } + synchronized (running) { + while (running.get()) { + running.wait(); + } + } + } } diff --git a/src/test/java/rx/schedulers/NewThreadSchedulerTest.java b/src/test/java/rx/schedulers/NewThreadSchedulerTest.java index 48fa0d9887..57344cbf67 100644 --- a/src/test/java/rx/schedulers/NewThreadSchedulerTest.java +++ b/src/test/java/rx/schedulers/NewThreadSchedulerTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -53,7 +53,7 @@ public void testNoSelfInterrupt() throws InterruptedException { final CountDownLatch done = new CountDownLatch(1); final AtomicReference exception = new AtomicReference(); final AtomicBoolean interruptFlag = new AtomicBoolean(); - + ScheduledAction sa = (ScheduledAction)worker.schedule(new Action0() { @Override public void call() { @@ -64,7 +64,7 @@ public void call() { } } }); - + sa.add(Subscriptions.create(new Action0() { @Override public void call() { @@ -72,11 +72,11 @@ public void call() { done.countDown(); } })); - + run.countDown(); - + done.await(); - + Assert.assertEquals(null, exception.get()); Assert.assertFalse("Interrupted?!", interruptFlag.get()); } finally { diff --git a/src/test/java/rx/schedulers/ResetSchedulersTest.java b/src/test/java/rx/schedulers/ResetSchedulersTest.java index 2fc2c4eaa4..001743b019 100644 --- a/src/test/java/rx/schedulers/ResetSchedulersTest.java +++ b/src/test/java/rx/schedulers/ResetSchedulersTest.java @@ -1,3 +1,18 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * 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 rx.schedulers; diff --git a/src/test/java/rx/schedulers/SchedulerLifecycleTest.java b/src/test/java/rx/schedulers/SchedulerLifecycleTest.java index 49887f9e0d..14a7bab06e 100644 --- a/src/test/java/rx/schedulers/SchedulerLifecycleTest.java +++ b/src/test/java/rx/schedulers/SchedulerLifecycleTest.java @@ -34,10 +34,10 @@ public class SchedulerLifecycleTest { @Test public void testShutdown() throws InterruptedException { tryOutSchedulers(); - + System.out.println("testShutdown >> Giving time threads to spin-up"); Thread.sleep(500); - + Set rxThreads = new HashSet(); for (Thread t : Thread.getAllStackTraces().keySet()) { if (t.getName().startsWith("Rx")) { @@ -47,7 +47,7 @@ public void testShutdown() throws InterruptedException { Schedulers.shutdown(); System.out.println("testShutdown >> Giving time to threads to stop"); Thread.sleep(500); - + StringBuilder b = new StringBuilder(); for (Thread t : rxThreads) { if (t.isAlive()) { @@ -63,47 +63,47 @@ public void testShutdown() throws InterruptedException { Schedulers.start(); // restart them anyways fail("Rx Threads were still alive:\r\n" + b); } - + System.out.println("testShutdown >> Restarting schedulers..."); Schedulers.start(); - + tryOutSchedulers(); } private void tryOutSchedulers() throws InterruptedException { final CountDownLatch cdl = new CountDownLatch(4); - + final Action0 countAction = new Action0() { @Override public void call() { cdl.countDown(); } }; - + CompositeSubscription csub = new CompositeSubscription(); - + try { Worker w1 = Schedulers.computation().createWorker(); csub.add(w1); w1.schedule(countAction); - + Worker w2 = Schedulers.io().createWorker(); csub.add(w2); w2.schedule(countAction); - + Worker w3 = Schedulers.newThread().createWorker(); csub.add(w3); w3.schedule(countAction); - + GenericScheduledExecutorService.getInstance().execute(new Runnable() { @Override public void run() { countAction.call(); } }); - + RxRingBuffer.getSpscInstance().release(); - + if (!cdl.await(3, TimeUnit.SECONDS)) { fail("countAction was not run by every worker"); } @@ -111,14 +111,14 @@ public void run() { csub.unsubscribe(); } } - + @Test public void testStartIdempotence() throws InterruptedException { tryOutSchedulers(); - + System.out.println("testStartIdempotence >> giving some time"); Thread.sleep(500); - + Set rxThreads = new HashSet(); for (Thread t : Thread.getAllStackTraces().keySet()) { if (t.getName().startsWith("Rx")) { @@ -130,7 +130,7 @@ public void testStartIdempotence() throws InterruptedException { Schedulers.start(); System.out.println("testStartIdempotence >> giving some time again"); Thread.sleep(500); - + Set rxThreads2 = new HashSet(); for (Thread t : Thread.getAllStackTraces().keySet()) { if (t.getName().startsWith("Rx")) { @@ -138,7 +138,7 @@ public void testStartIdempotence() throws InterruptedException { System.out.println("testStartIdempotence >>>> " + t); } } - + assertEquals(rxThreads, rxThreads2); } } \ No newline at end of file diff --git a/src/test/java/rx/schedulers/SchedulerTests.java b/src/test/java/rx/schedulers/SchedulerTests.java index c07d3078f2..f750a47d4d 100644 --- a/src/test/java/rx/schedulers/SchedulerTests.java +++ b/src/test/java/rx/schedulers/SchedulerTests.java @@ -44,7 +44,7 @@ private SchedulerTests() { *

      * Schedulers which execute on a separate thread from their calling thread should exhibit this behavior. Schedulers * which execute on their calling thread may not. - * + * * @param scheduler the scheduler to test * @throws InterruptedException if some wait is interrupted */ @@ -65,8 +65,12 @@ public static void testUnhandledErrorIsDeliveredToThreadHandler(Scheduler schedu assertEquals("Should have received exactly 1 exception", 1, handler.count); Throwable cause = handler.caught; while (cause != null) { - if (error.equals(cause)) break; - if (cause == cause.getCause()) break; + if (error.equals(cause)) { + break; + } + if (cause == cause.getCause()) { + break; + } cause = cause.getCause(); } assertEquals("Our error should have been delivered to the handler", error, cause); @@ -105,8 +109,12 @@ public static void testHandledErrorIsNotDeliveredToThreadHandler(Scheduler sched Throwable cause = observer.error; while (cause != null) { - if (error.equals(cause)) break; - if (cause == cause.getCause()) break; + if (error.equals(cause)) { + break; + } + if (cause == cause.getCause()) { + break; + } cause = cause.getCause(); } assertEquals("Our error should have been delivered to the observer", error, cause); @@ -117,12 +125,12 @@ public static void testHandledErrorIsNotDeliveredToThreadHandler(Scheduler sched public static void testCancelledRetention(Scheduler.Worker w, boolean periodic) throws InterruptedException { System.out.println("Wait before GC"); - Thread.sleep(1000); + Thread.sleep(500); System.out.println("GC"); System.gc(); - Thread.sleep(1000); + Thread.sleep(500); MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean(); @@ -131,7 +139,7 @@ public static void testCancelledRetention(Scheduler.Worker w, boolean periodic) System.out.printf("Starting: %.3f MB%n", initial / 1024.0 / 1024.0); - int n = 500 * 1000; + int n = 100 * 1000; if (periodic) { final CountDownLatch cdl = new CountDownLatch(n); final Action0 action = new Action0() { @@ -165,12 +173,12 @@ public void call() { w.unsubscribe(); System.out.println("Wait before second GC"); - Thread.sleep(NewThreadWorker.PURGE_FREQUENCY + 2000); + Thread.sleep(NewThreadWorker.PURGE_FREQUENCY + 1000); System.out.println("Second GC"); System.gc(); - Thread.sleep(1000); + Thread.sleep(500); memHeap = memoryMXBean.getHeapMemoryUsage(); long finish = memHeap.getUsed(); @@ -183,8 +191,8 @@ public void call() { private static final class CapturingObserver implements Observer { CountDownLatch completed = new CountDownLatch(1); - int errorCount = 0; - int nextCount = 0; + int errorCount; + int nextCount; Throwable error; @Override diff --git a/src/test/java/rx/schedulers/SchedulerWhenTest.java b/src/test/java/rx/schedulers/SchedulerWhenTest.java new file mode 100644 index 0000000000..c3b9761e64 --- /dev/null +++ b/src/test/java/rx/schedulers/SchedulerWhenTest.java @@ -0,0 +1,222 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * 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 rx.schedulers; + +import static java.util.concurrent.TimeUnit.SECONDS; +import static rx.Observable.just; +import static rx.Observable.merge; + +import org.junit.Test; + +import rx.Completable; +import rx.Observable; +import rx.Scheduler; +import rx.functions.Func0; +import rx.functions.Func1; +import rx.internal.schedulers.SchedulerWhen; +import rx.observers.TestSubscriber; + +public class SchedulerWhenTest { + @Test + public void testAsyncMaxConcurrent() { + TestScheduler tSched = new TestScheduler(); + SchedulerWhen sched = maxConcurrentScheduler(tSched); + TestSubscriber tSub = TestSubscriber.create(); + + asyncWork(sched).subscribe(tSub); + + tSub.assertValueCount(0); + + tSched.advanceTimeBy(0, SECONDS); + tSub.assertValueCount(0); + + tSched.advanceTimeBy(1, SECONDS); + tSub.assertValueCount(2); + + tSched.advanceTimeBy(1, SECONDS); + tSub.assertValueCount(4); + + tSched.advanceTimeBy(1, SECONDS); + tSub.assertValueCount(5); + tSub.assertCompleted(); + + sched.unsubscribe(); + } + + @Test + public void testAsyncDelaySubscription() { + final TestScheduler tSched = new TestScheduler(); + SchedulerWhen sched = throttleScheduler(tSched); + TestSubscriber tSub = TestSubscriber.create(); + + asyncWork(sched).subscribe(tSub); + + tSub.assertValueCount(0); + + tSched.advanceTimeBy(0, SECONDS); + tSub.assertValueCount(0); + + tSched.advanceTimeBy(1, SECONDS); + tSub.assertValueCount(1); + + tSched.advanceTimeBy(1, SECONDS); + tSub.assertValueCount(1); + + tSched.advanceTimeBy(1, SECONDS); + tSub.assertValueCount(2); + + tSched.advanceTimeBy(1, SECONDS); + tSub.assertValueCount(2); + + tSched.advanceTimeBy(1, SECONDS); + tSub.assertValueCount(3); + + tSched.advanceTimeBy(1, SECONDS); + tSub.assertValueCount(3); + + tSched.advanceTimeBy(1, SECONDS); + tSub.assertValueCount(4); + + tSched.advanceTimeBy(1, SECONDS); + tSub.assertValueCount(4); + + tSched.advanceTimeBy(1, SECONDS); + tSub.assertValueCount(5); + tSub.assertCompleted(); + + sched.unsubscribe(); + } + + @Test + public void testSyncMaxConcurrent() { + TestScheduler tSched = new TestScheduler(); + SchedulerWhen sched = maxConcurrentScheduler(tSched); + TestSubscriber tSub = TestSubscriber.create(); + + syncWork(sched).subscribe(tSub); + + tSub.assertValueCount(0); + tSched.advanceTimeBy(0, SECONDS); + + // since all the work is synchronous nothing is blocked and its all done + tSub.assertValueCount(5); + tSub.assertCompleted(); + + sched.unsubscribe(); + } + + @Test + public void testSyncDelaySubscription() { + final TestScheduler tSched = new TestScheduler(); + SchedulerWhen sched = throttleScheduler(tSched); + TestSubscriber tSub = TestSubscriber.create(); + + syncWork(sched).subscribe(tSub); + + tSub.assertValueCount(0); + + tSched.advanceTimeBy(0, SECONDS); + tSub.assertValueCount(1); + + tSched.advanceTimeBy(1, SECONDS); + tSub.assertValueCount(2); + + tSched.advanceTimeBy(1, SECONDS); + tSub.assertValueCount(3); + + tSched.advanceTimeBy(1, SECONDS); + tSub.assertValueCount(4); + + tSched.advanceTimeBy(1, SECONDS); + tSub.assertValueCount(5); + tSub.assertCompleted(); + + sched.unsubscribe(); + } + + private Observable asyncWork(final Scheduler sched) { + return Observable.range(1, 5).flatMap(new Func1>() { + @Override + public Observable call(Integer t) { + return Observable.timer(1, SECONDS, sched); + } + }); + } + + private Observable syncWork(final Scheduler sched) { + return Observable.range(1, 5).flatMap(new Func1>() { + @Override + public Observable call(Integer t) { + return Observable.defer(new Func0>() { + @Override + public Observable call() { + return Observable.just(0l); + } + }).subscribeOn(sched); + } + }); + } + + private SchedulerWhen maxConcurrentScheduler(TestScheduler tSched) { + SchedulerWhen sched = new SchedulerWhen(new Func1>, Completable>() { + @Override + public Completable call(Observable> workerActions) { + Observable workers = workerActions.map(new Func1, Completable>() { + @Override + public Completable call(Observable actions) { + return Completable.concat(actions); + } + }); + return Completable.merge(workers, 2); + } + }, tSched); + return sched; + } + + private SchedulerWhen throttleScheduler(final TestScheduler tSched) { + SchedulerWhen sched = new SchedulerWhen(new Func1>, Completable>() { + @Override + public Completable call(Observable> workerActions) { + Observable workers = workerActions.map(new Func1, Completable>() { + @Override + public Completable call(Observable actions) { + return Completable.concat(actions); + } + }); + return Completable.concat(workers.map(new Func1() { + @Override + public Completable call(Completable worker) { + return worker.delay(1, SECONDS, tSched); + } + })); + } + }, tSched); + return sched; + } + + @Test(timeout = 1000) + public void testRaceConditions() { + Scheduler comp = Schedulers.computation(); + Scheduler limited = comp.when(new Func1>, Completable>() { + @Override + public Completable call(Observable> t) { + return Completable.merge(Observable.merge(t, 10)); + } + }); + + merge(just(just(1).subscribeOn(limited).observeOn(comp)).repeat(1000)).toBlocking().subscribe(); + } +} diff --git a/src/test/java/rx/schedulers/TestSchedulerTest.java b/src/test/java/rx/schedulers/TestSchedulerTest.java index f8a49eb0be..f26939d5bf 100644 --- a/src/test/java/rx/schedulers/TestSchedulerTest.java +++ b/src/test/java/rx/schedulers/TestSchedulerTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -17,25 +17,18 @@ import static org.junit.Assert.assertEquals; import static org.mockito.Matchers.anyLong; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.*; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import org.junit.Test; -import org.mockito.InOrder; -import org.mockito.Mockito; +import org.mockito.*; -import rx.Observable; +import rx.*; import rx.Observable.OnSubscribe; -import rx.Scheduler; -import rx.Subscriber; -import rx.Subscription; -import rx.functions.Action0; -import rx.functions.Func1; +import rx.functions.*; +import rx.observers.TestSubscriber; public class TestSchedulerTest { @@ -47,7 +40,7 @@ public final void testPeriodicScheduling() { final TestScheduler scheduler = new TestScheduler(); final Scheduler.Worker inner = scheduler.createWorker(); - + try { inner.schedulePeriodically(new Action0() { @Override @@ -56,27 +49,27 @@ public void call() { calledOp.call(scheduler.now()); } }, 1, 2, TimeUnit.SECONDS); - + verify(calledOp, never()).call(anyLong()); - + InOrder inOrder = Mockito.inOrder(calledOp); - + scheduler.advanceTimeBy(999L, TimeUnit.MILLISECONDS); inOrder.verify(calledOp, never()).call(anyLong()); - + scheduler.advanceTimeBy(1L, TimeUnit.MILLISECONDS); inOrder.verify(calledOp, times(1)).call(1000L); - + scheduler.advanceTimeBy(1999L, TimeUnit.MILLISECONDS); inOrder.verify(calledOp, never()).call(3000L); - + scheduler.advanceTimeBy(1L, TimeUnit.MILLISECONDS); inOrder.verify(calledOp, times(1)).call(3000L); - + scheduler.advanceTimeBy(5L, TimeUnit.SECONDS); inOrder.verify(calledOp, times(1)).call(5000L); inOrder.verify(calledOp, times(1)).call(7000L); - + inner.unsubscribe(); scheduler.advanceTimeBy(11L, TimeUnit.SECONDS); inOrder.verify(calledOp, never()).call(anyLong()); @@ -102,27 +95,27 @@ public void call() { calledOp.call(scheduler.now()); } }, 1, 2, TimeUnit.SECONDS); - + verify(calledOp, never()).call(anyLong()); - + InOrder inOrder = Mockito.inOrder(calledOp); - + scheduler.advanceTimeBy(999L, TimeUnit.MILLISECONDS); inOrder.verify(calledOp, never()).call(anyLong()); - + scheduler.advanceTimeBy(1L, TimeUnit.MILLISECONDS); inOrder.verify(calledOp, times(1)).call(1000L); - + scheduler.advanceTimeBy(1999L, TimeUnit.MILLISECONDS); inOrder.verify(calledOp, never()).call(3000L); - + scheduler.advanceTimeBy(1L, TimeUnit.MILLISECONDS); inOrder.verify(calledOp, times(1)).call(3000L); - + scheduler.advanceTimeBy(5L, TimeUnit.SECONDS); inOrder.verify(calledOp, times(1)).call(5000L); inOrder.verify(calledOp, times(1)).call(7000L); - + subscription.unsubscribe(); scheduler.advanceTimeBy(11L, TimeUnit.SECONDS); inOrder.verify(calledOp, never()).call(anyLong()); @@ -136,17 +129,17 @@ public final void testImmediateUnsubscribes() { TestScheduler s = new TestScheduler(); final Scheduler.Worker inner = s.createWorker(); final AtomicInteger counter = new AtomicInteger(0); - + try { inner.schedule(new Action0() { - + @Override public void call() { counter.incrementAndGet(); System.out.println("counter: " + counter.get()); inner.schedule(this); } - + }); inner.unsubscribe(); assertEquals(0, counter.get()); @@ -161,16 +154,16 @@ public final void testImmediateUnsubscribes2() { final Scheduler.Worker inner = s.createWorker(); try { final AtomicInteger counter = new AtomicInteger(0); - + final Subscription subscription = inner.schedule(new Action0() { - + @Override public void call() { counter.incrementAndGet(); System.out.println("counter: " + counter.get()); inner.schedule(this); } - + }); subscription.unsubscribe(); assertEquals(0, counter.get()); @@ -183,12 +176,12 @@ public void call() { public final void testNestedSchedule() { final TestScheduler scheduler = new TestScheduler(); final Scheduler.Worker inner = scheduler.createWorker(); - + try { final Action0 calledOp = mock(Action0.class); - + Observable poller; - poller = Observable.create(new OnSubscribe() { + poller = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(final Subscriber aSubscriber) { inner.schedule(new Action0() { @@ -202,19 +195,19 @@ public void call() { }); } }); - + InOrder inOrder = Mockito.inOrder(calledOp); - + Subscription sub; sub = poller.subscribe(); - + scheduler.advanceTimeTo(6, TimeUnit.SECONDS); inOrder.verify(calledOp, times(2)).call(); - + sub.unsubscribe(); scheduler.advanceTimeTo(11, TimeUnit.SECONDS); inOrder.verify(calledOp, never()).call(); - + sub = poller.subscribe(); scheduler.advanceTimeTo(12, TimeUnit.SECONDS); inOrder.verify(calledOp, times(1)).call(); @@ -222,4 +215,24 @@ public void call() { inner.unsubscribe(); } } + + @Test + public void resolution() { + for (final TimeUnit unit : TimeUnit.values()) { + TestScheduler scheduler = new TestScheduler(); + TestSubscriber testSubscriber = new TestSubscriber(); + + Observable.interval(30, unit, scheduler) + .map(new Func1() { + @Override + public String call(Long v) { + return v + "-" + unit; + } + }) + .subscribe(testSubscriber); + scheduler.advanceTimeTo(60, unit); + + testSubscriber.assertValues("0-" + unit, "1-" + unit); + } + } } diff --git a/src/test/java/rx/schedulers/TimeXTest.java b/src/test/java/rx/schedulers/TimeXTest.java new file mode 100644 index 0000000000..c236e5fda4 --- /dev/null +++ b/src/test/java/rx/schedulers/TimeXTest.java @@ -0,0 +1,107 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * 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 rx.schedulers; + +import org.junit.Test; +import static org.junit.Assert.*; + +public class TimeXTest { + + @Test + public void timestamped() { + Timestamped ts1 = new Timestamped(1L, 1); + Timestamped ts2 = new Timestamped(1L, 1); + Timestamped ts3 = new Timestamped(3L, 1); + Timestamped ts4 = new Timestamped(3L, 2); + Timestamped ts5 = new Timestamped(4L, null); + Timestamped ts6 = new Timestamped(4L, null); + Timestamped ts7 = new Timestamped(1L, new Integer(1)); // have unique reference + Timestamped ts8 = new Timestamped(1L, 3); + Timestamped ts9 = new Timestamped(1L, null); + + assertEquals(1L, ts1.getTimestampMillis()); + assertEquals(1, ts1.getValue().intValue()); + + assertNotEquals(ts1, null); + assertNotEquals(ts1, "string"); + + assertEquals(ts1, ts1); + assertEquals(ts1, ts2); + assertEquals(ts1, ts7); + assertEquals(ts7, ts1); + + assertNotEquals(ts1, ts3); + assertNotEquals(ts1, ts4); + assertNotEquals(ts1, ts8); + assertNotEquals(ts8, ts1); + assertNotEquals(ts1, ts9); + assertNotEquals(ts9, ts1); + + assertNotEquals(ts1, ts5); + assertNotEquals(ts5, ts1); + + assertEquals(ts5, ts6); + + assertEquals("Timestamped(timestampMillis = 1, value = 1)", ts1.toString()); + + assertEquals(ts1.hashCode(), ts2.hashCode()); + + assertEquals(ts5.hashCode(), ts6.hashCode()); + } + + @Test + public void timeInterval() { + TimeInterval ts1 = new TimeInterval(1L, 1); + TimeInterval ts2 = new TimeInterval(1L, 1); + TimeInterval ts3 = new TimeInterval(3L, 1); + TimeInterval ts4 = new TimeInterval(3L, 2); + TimeInterval ts5 = new TimeInterval(4L, null); + TimeInterval ts6 = new TimeInterval(4L, null); + TimeInterval ts7 = new TimeInterval(1L, new Integer(1)); // have unique reference + TimeInterval ts8 = new TimeInterval(1L, 3); + TimeInterval ts9 = new TimeInterval(1L, null); + + assertEquals(1L, ts1.getIntervalInMilliseconds()); + assertEquals(1, ts1.getValue().intValue()); + + assertNotEquals(ts1, null); + assertNotEquals(ts1, "string"); + + assertEquals(ts1, ts1); + assertEquals(ts1, ts2); + assertEquals(ts1, ts7); + assertEquals(ts7, ts1); + + assertNotEquals(ts1, ts3); + assertNotEquals(ts1, ts4); + assertNotEquals(ts1, ts8); + assertNotEquals(ts8, ts1); + assertNotEquals(ts1, ts9); + assertNotEquals(ts9, ts1); + + assertNotEquals(ts1, ts5); + assertNotEquals(ts5, ts1); + + assertEquals(ts5, ts6); + + assertEquals("TimeInterval [intervalInMilliseconds=1, value=1]", ts1.toString()); + + assertEquals(ts1.hashCode(), ts2.hashCode()); + + assertEquals(ts5.hashCode(), ts6.hashCode()); + } +} diff --git a/src/test/java/rx/schedulers/TrampolineSchedulerTest.java b/src/test/java/rx/schedulers/TrampolineSchedulerTest.java index aad14239a5..61e6b4f3e2 100644 --- a/src/test/java/rx/schedulers/TrampolineSchedulerTest.java +++ b/src/test/java/rx/schedulers/TrampolineSchedulerTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -71,27 +71,27 @@ public void testNestedTrampolineWithUnsubscribe() { try { workers.add(worker); worker.schedule(new Action0() { - + @Override public void call() { workers.add(doWorkOnNewTrampoline("A", workDone)); } - + }); - + final Worker worker2 = Schedulers.trampoline().createWorker(); workers.add(worker2); worker2.schedule(new Action0() { - + @Override public void call() { workers.add(doWorkOnNewTrampoline("B", workDone)); // we unsubscribe worker2 ... it should not affect work scheduled on a separate Trampoline.Worker worker2.unsubscribe(); } - + }); - + assertEquals(6, workDone.size()); assertEquals(Arrays.asList("A.1", "A.B.1", "A.B.2", "B.1", "B.B.1", "B.B.2"), workDone); } finally { @@ -125,7 +125,7 @@ public Subscription call(Long count) { return trampolineWorker.schedule(new Action0() { @Override - public void call() {} + public void call() { } }); } diff --git a/src/test/java/rx/subjects/AsyncSubjectTest.java b/src/test/java/rx/subjects/AsyncSubjectTest.java index 82f6843f90..0f2b2c1dbe 100644 --- a/src/test/java/rx/subjects/AsyncSubjectTest.java +++ b/src/test/java/rx/subjects/AsyncSubjectTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -204,7 +204,7 @@ public void testSubscribeCompletionRaceCondition() { /* * With non-threadsafe code this fails most of the time on my dev laptop and is non-deterministic enough * to act as a unit test to the race conditions. - * + * * With the synchronization code in place I can not get this to fail on my laptop. */ for (int i = 0; i < 50; i++) { @@ -276,7 +276,7 @@ public SubjectObserverThread(AsyncSubject subject) { @Override public void run() { try { - // a timeout exception will happen if we don't get a terminal state + // a timeout exception will happen if we don't get a terminal state String v = subject.timeout(2000, TimeUnit.MILLISECONDS).toBlocking().single(); value.set(v); } catch (Exception e) { @@ -284,7 +284,7 @@ public void run() { } } } - + @Test public void testOnErrorThrowsDoesntPreventDelivery() { AsyncSubject ps = AsyncSubject.create(); @@ -299,10 +299,10 @@ public void testOnErrorThrowsDoesntPreventDelivery() { } catch (OnErrorNotImplementedException e) { // ignore } - // even though the onError above throws we should still receive it on the other subscriber + // even though the onError above throws we should still receive it on the other subscriber assertEquals(1, ts.getOnErrorEvents().size()); } - + /** * This one has multiple failures so should get a CompositeException */ @@ -325,28 +325,28 @@ public void testOnErrorThrowsDoesntPreventDelivery2() { // we should have 5 of them assertEquals(5, e.getExceptions().size()); } - // even though the onError above throws we should still receive it on the other subscriber + // even though the onError above throws we should still receive it on the other subscriber assertEquals(1, ts.getOnErrorEvents().size()); } @Test public void testCurrentStateMethodsNormal() { AsyncSubject as = AsyncSubject.create(); - + assertFalse(as.hasValue()); assertFalse(as.hasThrowable()); assertFalse(as.hasCompleted()); assertNull(as.getValue()); assertNull(as.getThrowable()); - + as.onNext(1); - + assertTrue(as.hasValue()); assertFalse(as.hasThrowable()); assertFalse(as.hasCompleted()); assertEquals(1, as.getValue()); assertNull(as.getThrowable()); - + as.onCompleted(); assertTrue(as.hasValue()); assertFalse(as.hasThrowable()); @@ -354,19 +354,19 @@ public void testCurrentStateMethodsNormal() { assertEquals(1, as.getValue()); assertNull(as.getThrowable()); } - + @Test public void testCurrentStateMethodsEmpty() { AsyncSubject as = AsyncSubject.create(); - + assertFalse(as.hasValue()); assertFalse(as.hasThrowable()); assertFalse(as.hasCompleted()); assertNull(as.getValue()); assertNull(as.getThrowable()); - + as.onCompleted(); - + assertFalse(as.hasValue()); assertFalse(as.hasThrowable()); assertTrue(as.hasCompleted()); @@ -376,28 +376,28 @@ public void testCurrentStateMethodsEmpty() { @Test public void testCurrentStateMethodsError() { AsyncSubject as = AsyncSubject.create(); - + assertFalse(as.hasValue()); assertFalse(as.hasThrowable()); assertFalse(as.hasCompleted()); assertNull(as.getValue()); assertNull(as.getThrowable()); - + as.onError(new TestException()); - + assertFalse(as.hasValue()); assertTrue(as.hasThrowable()); assertFalse(as.hasCompleted()); assertNull(as.getValue()); assertTrue(as.getThrowable() instanceof TestException); } - + @Test public void testAsyncSubjectValueRelay() { AsyncSubject async = AsyncSubject.create(); async.onNext(1); async.onCompleted(); - + assertFalse(async.hasObservers()); assertTrue(async.hasCompleted()); assertFalse(async.hasThrowable()); @@ -409,7 +409,7 @@ public void testAsyncSubjectValueRelay() { public void testAsyncSubjectValueEmpty() { AsyncSubject async = AsyncSubject.create(); async.onCompleted(); - + assertFalse(async.hasObservers()); assertTrue(async.hasCompleted()); assertFalse(async.hasThrowable()); @@ -422,7 +422,7 @@ public void testAsyncSubjectValueError() { AsyncSubject async = AsyncSubject.create(); TestException te = new TestException(); async.onError(te); - + assertFalse(async.hasObservers()); assertFalse(async.hasCompleted()); assertTrue(async.hasThrowable()); @@ -430,45 +430,45 @@ public void testAsyncSubjectValueError() { assertNull(async.getValue()); assertFalse(async.hasValue()); } - + @Test public void backpressureOnline() { TestSubscriber ts = TestSubscriber.create(0); - + AsyncSubject subject = AsyncSubject.create(); - + subject.subscribe(ts); - + subject.onNext(1); subject.onCompleted(); - + ts.assertNoValues(); ts.assertNoErrors(); ts.assertNotCompleted(); - + ts.requestMore(1); - + ts.assertValue(1); ts.assertCompleted(); ts.assertNoErrors(); } - + @Test public void backpressureOffline() { TestSubscriber ts = TestSubscriber.create(0); - + AsyncSubject subject = AsyncSubject.create(); subject.onNext(1); subject.onCompleted(); - + subject.subscribe(ts); - + ts.assertNoValues(); ts.assertNoErrors(); ts.assertNotCompleted(); - + ts.requestMore(1); - + ts.assertValue(1); ts.assertCompleted(); ts.assertNoErrors(); diff --git a/src/test/java/rx/subjects/BehaviorSubjectTest.java b/src/test/java/rx/subjects/BehaviorSubjectTest.java index bc71fd212a..1c06ff73a1 100644 --- a/src/test/java/rx/subjects/BehaviorSubjectTest.java +++ b/src/test/java/rx/subjects/BehaviorSubjectTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -240,10 +240,10 @@ public void testCompletedAfterErrorIsNotSent3() { verify(o2, times(1)).onCompleted(); verifyNoMoreInteractions(o2); } - @Test(timeout = 1000) + @Test(timeout = 5000) public void testUnsubscriptionCase() { BehaviorSubject src = BehaviorSubject.create((String)null); - + for (int i = 0; i < 10; i++) { @SuppressWarnings("unchecked") final Observer o = mock(Observer.class); @@ -272,25 +272,25 @@ public void testStartEmpty() { @SuppressWarnings("unchecked") final Observer o = mock(Observer.class); InOrder inOrder = inOrder(o); - + source.subscribe(o); - + inOrder.verify(o, never()).onNext(any()); inOrder.verify(o, never()).onCompleted(); - + source.onNext(1); - + source.onCompleted(); - + source.onNext(2); - + verify(o, never()).onError(any(Throwable.class)); inOrder.verify(o).onNext(1); inOrder.verify(o).onCompleted(); inOrder.verifyNoMoreInteractions(); - - + + } @Test public void testStartEmptyThenAddOne() { @@ -311,9 +311,9 @@ public void testStartEmptyThenAddOne() { inOrder.verify(o).onCompleted(); inOrder.verifyNoMoreInteractions(); - + verify(o, never()).onError(any(Throwable.class)); - + } @Test public void testStartEmptyCompleteWithOne() { @@ -331,15 +331,15 @@ public void testStartEmptyCompleteWithOne() { verify(o).onCompleted(); verifyNoMoreInteractions(o); } - + @Test public void testTakeOneSubscriber() { BehaviorSubject source = BehaviorSubject.create(1); @SuppressWarnings("unchecked") final Observer o = mock(Observer.class); - + source.take(1).subscribe(o); - + verify(o).onNext(1); verify(o).onCompleted(); verifyNoMoreInteractions(o); @@ -347,7 +347,7 @@ public void testTakeOneSubscriber() { assertEquals(0, source.subscriberCount()); assertFalse(source.hasObservers()); } - + @Test public void testOnErrorThrowsDoesntPreventDelivery() { BehaviorSubject ps = BehaviorSubject.create(); @@ -362,10 +362,10 @@ public void testOnErrorThrowsDoesntPreventDelivery() { } catch (OnErrorNotImplementedException e) { // ignore } - // even though the onError above throws we should still receive it on the other subscriber + // even though the onError above throws we should still receive it on the other subscriber assertEquals(1, ts.getOnErrorEvents().size()); } - + /** * This one has multiple failures so should get a CompositeException */ @@ -388,7 +388,7 @@ public void testOnErrorThrowsDoesntPreventDelivery2() { // we should have 5 of them assertEquals(5, e.getExceptions().size()); } - // even though the onError above throws we should still receive it on the other subscriber + // even though the onError above throws we should still receive it on the other subscriber assertEquals(1, ts.getOnErrorEvents().size()); } @Test @@ -401,10 +401,10 @@ public void testEmissionSubscriptionRace() throws Exception { System.out.println(i); } final BehaviorSubject rs = BehaviorSubject.create(); - - final CountDownLatch finish = new CountDownLatch(1); - final CountDownLatch start = new CountDownLatch(1); - + + final CountDownLatch finish = new CountDownLatch(1); + final CountDownLatch start = new CountDownLatch(1); + worker.schedule(new Action0() { @Override public void call() { @@ -416,33 +416,33 @@ public void call() { rs.onNext(1); } }); - + final AtomicReference o = new AtomicReference(); - + rs.subscribeOn(s).observeOn(Schedulers.io()) .subscribe(new Observer() { - + @Override public void onCompleted() { o.set(-1); finish.countDown(); } - + @Override public void onError(Throwable e) { o.set(e); finish.countDown(); } - + @Override public void onNext(Object t) { o.set(t); finish.countDown(); } - + }); start.countDown(); - + if (!finish.await(5, TimeUnit.SECONDS)) { System.out.println(o.get()); System.out.println(rs.hasObservers()); @@ -463,52 +463,52 @@ public void call() { worker.unsubscribe(); } } - + @Test public void testCurrentStateMethodsNormalEmptyStart() { BehaviorSubject as = BehaviorSubject.create(); - + assertFalse(as.hasValue()); assertFalse(as.hasThrowable()); assertFalse(as.hasCompleted()); assertNull(as.getValue()); assertNull(as.getThrowable()); - + as.onNext(1); - + assertTrue(as.hasValue()); assertFalse(as.hasThrowable()); assertFalse(as.hasCompleted()); assertEquals(1, as.getValue()); assertNull(as.getThrowable()); - + as.onCompleted(); - + assertFalse(as.hasValue()); assertFalse(as.hasThrowable()); assertTrue(as.hasCompleted()); assertNull(as.getValue()); assertNull(as.getThrowable()); } - + @Test public void testCurrentStateMethodsNormalSomeStart() { BehaviorSubject as = BehaviorSubject.create((Object)1); - + assertTrue(as.hasValue()); assertFalse(as.hasThrowable()); assertFalse(as.hasCompleted()); assertEquals(1, as.getValue()); assertNull(as.getThrowable()); - + as.onNext(2); - + assertTrue(as.hasValue()); assertFalse(as.hasThrowable()); assertFalse(as.hasCompleted()); assertEquals(2, as.getValue()); assertNull(as.getThrowable()); - + as.onCompleted(); assertFalse(as.hasValue()); assertFalse(as.hasThrowable()); @@ -516,19 +516,19 @@ public void testCurrentStateMethodsNormalSomeStart() { assertNull(as.getValue()); assertNull(as.getThrowable()); } - + @Test public void testCurrentStateMethodsEmpty() { BehaviorSubject as = BehaviorSubject.create(); - + assertFalse(as.hasValue()); assertFalse(as.hasThrowable()); assertFalse(as.hasCompleted()); assertNull(as.getValue()); assertNull(as.getThrowable()); - + as.onCompleted(); - + assertFalse(as.hasValue()); assertFalse(as.hasThrowable()); assertTrue(as.hasCompleted()); @@ -538,28 +538,28 @@ public void testCurrentStateMethodsEmpty() { @Test public void testCurrentStateMethodsError() { BehaviorSubject as = BehaviorSubject.create(); - + assertFalse(as.hasValue()); assertFalse(as.hasThrowable()); assertFalse(as.hasCompleted()); assertNull(as.getValue()); assertNull(as.getThrowable()); - + as.onError(new TestException()); - + assertFalse(as.hasValue()); assertTrue(as.hasThrowable()); assertFalse(as.hasCompleted()); assertNull(as.getValue()); assertTrue(as.getThrowable() instanceof TestException); } - + @Test public void testBehaviorSubjectValueRelay() { BehaviorSubject async = BehaviorSubject.create(); async.onNext(1); async.onCompleted(); - + assertFalse(async.hasObservers()); assertTrue(async.hasCompleted()); assertFalse(async.hasThrowable()); @@ -575,7 +575,7 @@ public void testBehaviorSubjectValueRelay() { public void testBehaviorSubjectValueRelayIncomplete() { BehaviorSubject async = BehaviorSubject.create(); async.onNext(1); - + assertFalse(async.hasObservers()); assertFalse(async.hasCompleted()); assertFalse(async.hasThrowable()); @@ -590,7 +590,7 @@ public void testBehaviorSubjectValueRelayIncomplete() { @Test public void testBehaviorSubjectIncompleteEmpty() { BehaviorSubject async = BehaviorSubject.create(); - + assertFalse(async.hasObservers()); assertFalse(async.hasCompleted()); assertFalse(async.hasThrowable()); @@ -606,7 +606,7 @@ public void testBehaviorSubjectIncompleteEmpty() { public void testBehaviorSubjectEmpty() { BehaviorSubject async = BehaviorSubject.create(); async.onCompleted(); - + assertFalse(async.hasObservers()); assertTrue(async.hasCompleted()); assertFalse(async.hasThrowable()); @@ -623,7 +623,7 @@ public void testBehaviorSubjectError() { BehaviorSubject async = BehaviorSubject.create(); TestException te = new TestException(); async.onError(te); - + assertFalse(async.hasObservers()); assertFalse(async.hasCompleted()); assertTrue(async.hasThrowable()); diff --git a/src/test/java/rx/subjects/BufferUntilSubscriberTest.java b/src/test/java/rx/subjects/BufferUntilSubscriberTest.java index 556254ae9f..6d8aebe167 100644 --- a/src/test/java/rx/subjects/BufferUntilSubscriberTest.java +++ b/src/test/java/rx/subjects/BufferUntilSubscriberTest.java @@ -33,8 +33,9 @@ public class BufferUntilSubscriberTest { public void testIssue1677() throws InterruptedException { final AtomicLong counter = new AtomicLong(); final Integer[] numbers = new Integer[5000]; - for (int i = 0; i < numbers.length; i++) + for (int i = 0; i < numbers.length; i++) { numbers[i] = i + 1; + } final int NITERS = 250; final CountDownLatch latch = new CountDownLatch(NITERS); for (int iters = 0; iters < NITERS; iters++) { @@ -72,47 +73,49 @@ public void call(List integers) { } }) .subscribe(); - if (!innerLatch.await(30, TimeUnit.SECONDS)) + if (!innerLatch.await(30, TimeUnit.SECONDS)) { Assert.fail("Failed inner latch wait, iteration " + iters); + } } - if (!latch.await(30, TimeUnit.SECONDS)) + if (!latch.await(30, TimeUnit.SECONDS)) { Assert.fail("Incomplete! Went through " + latch.getCount() + " iterations"); - else + } else { Assert.assertEquals(NITERS, counter.get()); + } } - + @Test public void testBackpressure() { UnicastSubject bus = UnicastSubject.create(); for (int i = 0; i < 32; i++) { bus.onNext(i); } - + TestSubscriber ts = TestSubscriber.create(0); - + bus.subscribe(ts); - + ts.assertValueCount(0); ts.assertNoTerminalEvent(); - + ts.requestMore(10); - + ts.assertValueCount(10); - + ts.requestMore(22); ts.assertValueCount(32); Assert.assertFalse(bus.state.caughtUp); ts.requestMore(Long.MAX_VALUE); - + Assert.assertTrue(bus.state.caughtUp); for (int i = 32; i < 64; i++) { bus.onNext(i); } bus.onCompleted(); - + ts.assertValueCount(64); ts.assertNoErrors(); ts.assertCompleted(); @@ -124,11 +127,11 @@ public void testErrorCutsAhead() { bus.onNext(i); } bus.onError(new TestException()); - + TestSubscriber ts = TestSubscriber.create(0); - + bus.subscribe(ts); - + ts.assertNoValues(); ts.assertNotCompleted(); ts.assertError(TestException.class); @@ -139,16 +142,16 @@ public void testErrorCutsAheadAfterSubscribed() { for (int i = 0; i < 32; i++) { bus.onNext(i); } - + TestSubscriber ts = TestSubscriber.create(0); - + bus.subscribe(ts); ts.assertNoValues(); ts.assertNoTerminalEvent(); bus.onError(new TestException()); - + ts.assertNoValues(); ts.assertNotCompleted(); ts.assertError(TestException.class); @@ -159,19 +162,19 @@ public void testUnsubscribeClearsQueue() { for (int i = 0; i < 32; i++) { bus.onNext(i); } - + TestSubscriber ts = TestSubscriber.create(0); ts.unsubscribe(); - + bus.subscribe(ts); - + ts.assertNoTerminalEvent(); ts.assertNoValues(); - + Assert.assertTrue(bus.state.queue.isEmpty()); - + bus.onNext(32); - + Assert.assertTrue(bus.state.queue.isEmpty()); } } \ No newline at end of file diff --git a/src/test/java/rx/subjects/PublishSubjectTest.java b/src/test/java/rx/subjects/PublishSubjectTest.java index 93c9be4bd3..2a052abd04 100644 --- a/src/test/java/rx/subjects/PublishSubjectTest.java +++ b/src/test/java/rx/subjects/PublishSubjectTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -33,9 +33,7 @@ import rx.Observable; import rx.Observer; import rx.Subscription; -import rx.exceptions.CompositeException; -import rx.exceptions.OnErrorNotImplementedException; -import rx.exceptions.TestException; +import rx.exceptions.*; import rx.functions.Action1; import rx.functions.Func1; import rx.observers.TestSubscriber; @@ -332,7 +330,7 @@ public void testReSubscribe() { private final Throwable testException = new Throwable(); - @Test(timeout = 1000) + @Test(timeout = 5000) public void testUnsubscriptionCase() { PublishSubject src = PublishSubject.create(); @@ -367,14 +365,14 @@ public void onCompleted() { } }); src.onNext(v); - + inOrder.verify(o).onNext(v + ", " + v); inOrder.verify(o).onCompleted(); verify(o, never()).onError(any(Throwable.class)); } } - - + + @Test public void testOnErrorThrowsDoesntPreventDelivery() { PublishSubject ps = PublishSubject.create(); @@ -389,10 +387,10 @@ public void testOnErrorThrowsDoesntPreventDelivery() { } catch (OnErrorNotImplementedException e) { // ignore } - // even though the onError above throws we should still receive it on the other subscriber + // even though the onError above throws we should still receive it on the other subscriber assertEquals(1, ts.getOnErrorEvents().size()); } - + /** * This one has multiple failures so should get a CompositeException */ @@ -415,40 +413,40 @@ public void testOnErrorThrowsDoesntPreventDelivery2() { // we should have 5 of them assertEquals(5, e.getExceptions().size()); } - // even though the onError above throws we should still receive it on the other subscriber + // even though the onError above throws we should still receive it on the other subscriber assertEquals(1, ts.getOnErrorEvents().size()); } @Test public void testCurrentStateMethodsNormal() { PublishSubject as = PublishSubject.create(); - + assertFalse(as.hasThrowable()); assertFalse(as.hasCompleted()); assertNull(as.getThrowable()); - + as.onNext(1); - + assertFalse(as.hasThrowable()); assertFalse(as.hasCompleted()); assertNull(as.getThrowable()); - + as.onCompleted(); - + assertFalse(as.hasThrowable()); assertTrue(as.hasCompleted()); assertNull(as.getThrowable()); } - + @Test public void testCurrentStateMethodsEmpty() { PublishSubject as = PublishSubject.create(); - + assertFalse(as.hasThrowable()); assertFalse(as.hasCompleted()); assertNull(as.getThrowable()); - + as.onCompleted(); - + assertFalse(as.hasThrowable()); assertTrue(as.hasCompleted()); assertNull(as.getThrowable()); @@ -456,35 +454,35 @@ public void testCurrentStateMethodsEmpty() { @Test public void testCurrentStateMethodsError() { PublishSubject as = PublishSubject.create(); - + assertFalse(as.hasThrowable()); assertFalse(as.hasCompleted()); assertNull(as.getThrowable()); - + as.onError(new TestException()); - + assertTrue(as.hasThrowable()); assertFalse(as.hasCompleted()); assertTrue(as.getThrowable() instanceof TestException); } - + @Test public void testPublishSubjectValueRelay() { PublishSubject async = PublishSubject.create(); async.onNext(1); async.onCompleted(); - + assertFalse(async.hasObservers()); assertTrue(async.hasCompleted()); assertFalse(async.hasThrowable()); assertNull(async.getThrowable()); } - + @Test public void testPublishSubjectValueEmpty() { PublishSubject async = PublishSubject.create(); async.onCompleted(); - + assertFalse(async.hasObservers()); assertTrue(async.hasCompleted()); assertFalse(async.hasThrowable()); @@ -495,10 +493,70 @@ public void testPublishSubjectValueError() { PublishSubject async = PublishSubject.create(); TestException te = new TestException(); async.onError(te); - + assertFalse(async.hasObservers()); assertFalse(async.hasCompleted()); assertTrue(async.hasThrowable()); assertSame(te, async.getThrowable()); } + + @Test + public void backpressureFailFast() { + PublishSubject ps = PublishSubject.create(); + + TestSubscriber ts = TestSubscriber.create(1); + + ps.subscribe(ts); + + ps.onNext(1); + + ts.assertValue(1); + ts.assertNoErrors(); + ts.assertNotCompleted(); + + ps.onNext(2); + + ts.assertValue(1); + ts.assertError(MissingBackpressureException.class); + ts.assertNotCompleted(); + + assertEquals("PublishSubject: could not emit value due to lack of requests", ts.getOnErrorEvents().get(0).getMessage()); + } + + @Test + public void crossUnsubscribe() { + PublishSubject ps = PublishSubject.create(); + + final TestSubscriber ts0 = TestSubscriber.create(); + + TestSubscriber ts1 = new TestSubscriber() { + @Override + public void onNext(Integer t) { + super.onNext(t); + if (t == 2) { + ts0.unsubscribe(); + } + } + }; + + ps.subscribe(ts1); + ps.subscribe(ts0); + + ps.onNext(1); + + ts0.assertValue(1); + ts1.assertValue(1); + + ps.onNext(2); + ps.onCompleted(); + + ts0.assertValue(1); + ts0.assertNoErrors(); + ts0.assertNotCompleted(); + ts0.assertUnsubscribed(); + + ts1.assertValues(1, 2); + ts1.assertNoErrors(); + ts1.assertCompleted(); + } } diff --git a/src/test/java/rx/subjects/ReplaySubjectBoundedConcurrencyTest.java b/src/test/java/rx/subjects/ReplaySubjectBoundedConcurrencyTest.java index 1c29ee6348..954a9b8950 100644 --- a/src/test/java/rx/subjects/ReplaySubjectBoundedConcurrencyTest.java +++ b/src/test/java/rx/subjects/ReplaySubjectBoundedConcurrencyTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -52,7 +52,7 @@ public void testReplaySubjectConcurrentSubscribersDoingReplayDontBlockEachOther( @Override public void run() { - Observable.create(new OnSubscribe() { + Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber o) { @@ -154,33 +154,33 @@ public void onNext(Long args) { slowThread.join(); } - @Test +// @Test public void unboundedReplaySubjectConcurrentSubscriptionsLoop() throws Exception { for (int i = 0; i < 50; i++) { System.out.println(i + " --------------------------------------------------------------- "); unboundedReplaySubjectConcurrentSubscriptions(); } } - + @Test public void unboundedReplaySubjectConcurrentSubscriptions() throws InterruptedException { final ReplaySubject replay = ReplaySubject.createUnbounded(); - + ReplaySubjectConcurrencyTest.concurrencyTest(replay); } - @Test +// @Test public void unboundedTimeReplaySubjectConcurrentSubscriptionsLoop() throws Exception { for (int i = 0; i < 50; i++) { System.out.println(i + " --------------------------------------------------------------- "); unboundedTimeReplaySubjectConcurrentSubscriptions(); } } - + @Test public void unboundedTimeReplaySubjectConcurrentSubscriptions() throws InterruptedException { final ReplaySubject replay = ReplaySubject.createUnboundedTime(); - + ReplaySubjectConcurrencyTest.concurrencyTest(replay); } @@ -245,7 +245,7 @@ public void run() { } } - + /** * https://github.com/ReactiveX/RxJava/issues/1147 */ @@ -273,7 +273,7 @@ public SubjectObserverThread(ReplaySubject subject) { @Override public void run() { try { - // a timeout exception will happen if we don't get a terminal state + // a timeout exception will happen if we don't get a terminal state String v = subject.timeout(2000, TimeUnit.MILLISECONDS).toBlocking().single(); value.set(v); } catch (Exception e) { @@ -291,10 +291,10 @@ public void testReplaySubjectEmissionSubscriptionRace() throws Exception { System.out.println(i); } final ReplaySubject rs = ReplaySubject.createWithSize(2); - - final CountDownLatch finish = new CountDownLatch(1); - final CountDownLatch start = new CountDownLatch(1); - + + final CountDownLatch finish = new CountDownLatch(1); + final CountDownLatch start = new CountDownLatch(1); + worker.schedule(new Action0() { @Override public void call() { @@ -306,33 +306,33 @@ public void call() { rs.onNext(1); } }); - + final AtomicReference o = new AtomicReference(); - + rs.subscribeOn(s).observeOn(Schedulers.io()) .subscribe(new Observer() { - + @Override public void onCompleted() { o.set(-1); finish.countDown(); } - + @Override public void onError(Throwable e) { o.set(e); finish.countDown(); } - + @Override public void onNext(Object t) { o.set(t); finish.countDown(); } - + }); start.countDown(); - + if (!finish.await(5, TimeUnit.SECONDS)) { System.out.println(o.get()); System.out.println(rs.hasObservers()); @@ -357,7 +357,7 @@ public void call() { public void testConcurrentSizeAndHasAnyValue() throws InterruptedException { final ReplaySubject rs = ReplaySubject.createUnbounded(); final CyclicBarrier cb = new CyclicBarrier(2); - + Thread t = new Thread(new Runnable() { @Override public void run() { @@ -404,14 +404,14 @@ public void run() { } lastSize = size; } - + t.join(); } @Test(timeout = 5000) public void testConcurrentSizeAndHasAnyValueBounded() throws InterruptedException { final ReplaySubject rs = ReplaySubject.createWithSize(3); final CyclicBarrier cb = new CyclicBarrier(2); - + Thread t = new Thread(new Runnable() { @Override public void run() { @@ -447,14 +447,14 @@ public void run() { assertEquals(1, v2 - v1); } } - + t.join(); } @Test(timeout = 10000) public void testConcurrentSizeAndHasAnyValueTimeBounded() throws InterruptedException { final ReplaySubject rs = ReplaySubject.createWithTime(1, TimeUnit.MILLISECONDS, Schedulers.computation()); final CyclicBarrier cb = new CyclicBarrier(2); - + Thread t = new Thread(new Runnable() { @Override public void run() { @@ -497,7 +497,7 @@ public void run() { assertEquals(1, v2 - v1); } } - + t.join(); } } \ No newline at end of file diff --git a/src/test/java/rx/subjects/ReplaySubjectConcurrencyTest.java b/src/test/java/rx/subjects/ReplaySubjectConcurrencyTest.java index 14e57537d5..69b4c8cf69 100644 --- a/src/test/java/rx/subjects/ReplaySubjectConcurrencyTest.java +++ b/src/test/java/rx/subjects/ReplaySubjectConcurrencyTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -52,7 +52,7 @@ public void testReplaySubjectConcurrentSubscribersDoingReplayDontBlockEachOther( @Override public void run() { - Observable.create(new OnSubscribe() { + Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber o) { @@ -154,27 +154,27 @@ public void onNext(Long args) { slowThread.join(); } - @Test +// @Test public void testReplaySubjectConcurrentSubscriptionsLoop() throws Exception { for (int i = 0; i < 50; i++) { System.out.println(i + " --------------------------------------------------------------- "); testReplaySubjectConcurrentSubscriptions(); } } - + @Test public void testReplaySubjectConcurrentSubscriptions() throws InterruptedException { final ReplaySubject replay = ReplaySubject.create(); - + concurrencyTest(replay); } - + public static void concurrencyTest(final ReplaySubject replay) throws InterruptedException { Thread source = new Thread(new Runnable() { @Override public void run() { - Observable.create(new OnSubscribe() { + Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber o) { @@ -320,7 +320,7 @@ public void run() { } } - + /** * https://github.com/ReactiveX/RxJava/issues/1147 */ @@ -348,7 +348,7 @@ public SubjectObserverThread(ReplaySubject subject) { @Override public void run() { try { - // a timeout exception will happen if we don't get a terminal state + // a timeout exception will happen if we don't get a terminal state String v = subject.timeout(2000, TimeUnit.MILLISECONDS).toBlocking().single(); value.set(v); } catch (Exception e) { @@ -366,10 +366,10 @@ public void testReplaySubjectEmissionSubscriptionRace() throws Exception { System.out.println(i); } final ReplaySubject rs = ReplaySubject.create(); - - final CountDownLatch finish = new CountDownLatch(1); - final CountDownLatch start = new CountDownLatch(1); - + + final CountDownLatch finish = new CountDownLatch(1); + final CountDownLatch start = new CountDownLatch(1); + worker.schedule(new Action0() { @Override public void call() { @@ -381,33 +381,33 @@ public void call() { rs.onNext(1); } }); - + final AtomicReference o = new AtomicReference(); - + rs.subscribeOn(s).observeOn(Schedulers.io()) .subscribe(new Observer() { - + @Override public void onCompleted() { o.set(-1); finish.countDown(); } - + @Override public void onError(Throwable e) { o.set(e); finish.countDown(); } - + @Override public void onNext(Object t) { o.set(t); finish.countDown(); } - + }); start.countDown(); - + if (!finish.await(5, TimeUnit.SECONDS)) { System.out.println(o.get()); System.out.println(rs.hasObservers()); @@ -422,7 +422,7 @@ public void call() { rs.onCompleted(); } }); - + } } } finally { @@ -433,7 +433,7 @@ public void call() { public void testConcurrentSizeAndHasAnyValue() throws InterruptedException { final ReplaySubject rs = ReplaySubject.create(); final CyclicBarrier cb = new CyclicBarrier(2); - + Thread t = new Thread(new Runnable() { @Override public void run() { @@ -475,7 +475,7 @@ public void run() { } lastSize = size; } - + t.join(); } } diff --git a/src/test/java/rx/subjects/ReplaySubjectTest.java b/src/test/java/rx/subjects/ReplaySubjectTest.java index 7d5b5b8785..63b63ea16c 100644 --- a/src/test/java/rx/subjects/ReplaySubjectTest.java +++ b/src/test/java/rx/subjects/ReplaySubjectTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -334,23 +334,23 @@ public void onNext(String v) { System.out.println("after waiting for one"); subject.onNext("three"); - + System.out.println("sent three"); - - // if subscription blocked existing subscribers then 'makeSlow' would cause this to not be there yet + + // if subscription blocked existing subscribers then 'makeSlow' would cause this to not be there yet assertEquals("three", lastValueForObserver1.get()); - + System.out.println("about to send onCompleted"); - + subject.onCompleted(); System.out.println("completed subject"); - - // release + + // release makeSlow.countDown(); - + System.out.println("makeSlow released"); - + completed.await(); // all of them should be emitted with the last being "three" assertEquals("three", lastValueForObserver2.get()); @@ -359,19 +359,19 @@ public void onNext(String v) { @Test public void testSubscriptionLeak() { ReplaySubject replaySubject = ReplaySubject.create(); - + Subscription s = replaySubject.subscribe(); assertEquals(1, replaySubject.subscriberCount()); s.unsubscribe(); - + assertEquals(0, replaySubject.subscriberCount()); } - @Test(timeout = 1000) + @Test(timeout = 5000) public void testUnsubscriptionCase() { ReplaySubject src = ReplaySubject.create(); - + for (int i = 0; i < 10; i++) { @SuppressWarnings("unchecked") final Observer o = mock(Observer.class); @@ -415,10 +415,10 @@ public void testTerminateOnce() { source.onNext(1); source.onNext(2); source.onCompleted(); - + @SuppressWarnings("unchecked") final Observer o = mock(Observer.class); - + source.unsafeSubscribe(new Subscriber() { @Override @@ -436,7 +436,7 @@ public void onCompleted() { o.onCompleted(); } }); - + verify(o).onNext(1); verify(o).onNext(2); verify(o).onCompleted(); @@ -445,11 +445,11 @@ public void onCompleted() { @Test public void testReplay1AfterTermination() { ReplaySubject source = ReplaySubject.createWithSize(1); - + source.onNext(1); source.onNext(2); source.onCompleted(); - + for (int i = 0; i < 1; i++) { @SuppressWarnings("unchecked") Observer o = mock(Observer.class); @@ -483,16 +483,16 @@ public void testReplay1Directly() { verify(o).onCompleted(); verify(o, never()).onError(any(Throwable.class)); } - + @Test public void testReplayTimestampedAfterTermination() { TestScheduler scheduler = new TestScheduler(); ReplaySubject source = ReplaySubject.createWithTime(1, TimeUnit.SECONDS, scheduler); - + source.onNext(1); - + scheduler.advanceTimeBy(1, TimeUnit.SECONDS); - + source.onNext(2); scheduler.advanceTimeBy(1, TimeUnit.SECONDS); @@ -506,21 +506,21 @@ public void testReplayTimestampedAfterTermination() { Observer o = mock(Observer.class); source.subscribe(o); - + verify(o, never()).onNext(1); verify(o, never()).onNext(2); verify(o, never()).onNext(3); // late subscribers no longer replay stale data verify(o).onCompleted(); verify(o, never()).onError(any(Throwable.class)); } - + @Test public void testReplayTimestampedDirectly() { TestScheduler scheduler = new TestScheduler(); ReplaySubject source = ReplaySubject.createWithTime(1, TimeUnit.SECONDS, scheduler); source.onNext(1); - + scheduler.advanceTimeBy(1, TimeUnit.SECONDS); @SuppressWarnings("unchecked") @@ -529,24 +529,24 @@ public void testReplayTimestampedDirectly() { source.subscribe(o); source.onNext(2); - + scheduler.advanceTimeBy(1, TimeUnit.SECONDS); - + source.onNext(3); - + scheduler.advanceTimeBy(1, TimeUnit.SECONDS); - + source.onCompleted(); - + 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).onCompleted(); } - + @Test public void testOnErrorThrowsDoesntPreventDelivery() { ReplaySubject ps = ReplaySubject.create(); @@ -561,10 +561,10 @@ public void testOnErrorThrowsDoesntPreventDelivery() { } catch (OnErrorNotImplementedException e) { // ignore } - // even though the onError above throws we should still receive it on the other subscriber + // even though the onError above throws we should still receive it on the other subscriber assertEquals(1, ts.getOnErrorEvents().size()); } - + /** * This one has multiple failures so should get a CompositeException */ @@ -587,41 +587,41 @@ public void testOnErrorThrowsDoesntPreventDelivery2() { // we should have 5 of them assertEquals(5, e.getExceptions().size()); } - // even though the onError above throws we should still receive it on the other subscriber + // even though the onError above throws we should still receive it on the other subscriber assertEquals(1, ts.getOnErrorEvents().size()); } - + @Test public void testCurrentStateMethodsNormal() { ReplaySubject as = ReplaySubject.create(); - + assertFalse(as.hasThrowable()); assertFalse(as.hasCompleted()); assertNull(as.getThrowable()); - + as.onNext(1); - + assertFalse(as.hasThrowable()); assertFalse(as.hasCompleted()); assertNull(as.getThrowable()); - + as.onCompleted(); - + assertFalse(as.hasThrowable()); assertTrue(as.hasCompleted()); assertNull(as.getThrowable()); } - + @Test public void testCurrentStateMethodsEmpty() { ReplaySubject as = ReplaySubject.create(); - + assertFalse(as.hasThrowable()); assertFalse(as.hasCompleted()); assertNull(as.getThrowable()); - + as.onCompleted(); - + assertFalse(as.hasThrowable()); assertTrue(as.hasCompleted()); assertNull(as.getThrowable()); @@ -629,13 +629,13 @@ public void testCurrentStateMethodsEmpty() { @Test public void testCurrentStateMethodsError() { ReplaySubject as = ReplaySubject.create(); - + assertFalse(as.hasThrowable()); assertFalse(as.hasCompleted()); assertNull(as.getThrowable()); - + as.onError(new TestException()); - + assertTrue(as.hasThrowable()); assertFalse(as.hasCompleted()); assertTrue(as.getThrowable() instanceof TestException); @@ -643,20 +643,20 @@ public void testCurrentStateMethodsError() { @Test public void testSizeAndHasAnyValueUnbounded() { ReplaySubject rs = ReplaySubject.create(); - + assertEquals(0, rs.size()); assertFalse(rs.hasAnyValue()); - + rs.onNext(1); - + assertEquals(1, rs.size()); assertTrue(rs.hasAnyValue()); - + rs.onNext(1); assertEquals(2, rs.size()); assertTrue(rs.hasAnyValue()); - + rs.onCompleted(); assertEquals(2, rs.size()); @@ -665,43 +665,43 @@ public void testSizeAndHasAnyValueUnbounded() { @Test public void testSizeAndHasAnyValueEffectivelyUnbounded() { ReplaySubject rs = ReplaySubject.createUnbounded(); - + assertEquals(0, rs.size()); assertFalse(rs.hasAnyValue()); - + rs.onNext(1); - + assertEquals(1, rs.size()); assertTrue(rs.hasAnyValue()); - + rs.onNext(1); assertEquals(2, rs.size()); assertTrue(rs.hasAnyValue()); - + rs.onCompleted(); assertEquals(2, rs.size()); assertTrue(rs.hasAnyValue()); } - + @Test public void testSizeAndHasAnyValueUnboundedError() { ReplaySubject rs = ReplaySubject.create(); - + assertEquals(0, rs.size()); assertFalse(rs.hasAnyValue()); - + rs.onNext(1); - + assertEquals(1, rs.size()); assertTrue(rs.hasAnyValue()); - + rs.onNext(1); assertEquals(2, rs.size()); assertTrue(rs.hasAnyValue()); - + rs.onError(new TestException()); assertEquals(2, rs.size()); @@ -710,30 +710,30 @@ public void testSizeAndHasAnyValueUnboundedError() { @Test public void testSizeAndHasAnyValueEffectivelyUnboundedError() { ReplaySubject rs = ReplaySubject.createUnbounded(); - + assertEquals(0, rs.size()); assertFalse(rs.hasAnyValue()); - + rs.onNext(1); - + assertEquals(1, rs.size()); assertTrue(rs.hasAnyValue()); - + rs.onNext(1); assertEquals(2, rs.size()); assertTrue(rs.hasAnyValue()); - + rs.onError(new TestException()); assertEquals(2, rs.size()); assertTrue(rs.hasAnyValue()); } - + @Test public void testSizeAndHasAnyValueUnboundedEmptyError() { ReplaySubject rs = ReplaySubject.create(); - + rs.onError(new TestException()); assertEquals(0, rs.size()); @@ -742,17 +742,17 @@ public void testSizeAndHasAnyValueUnboundedEmptyError() { @Test public void testSizeAndHasAnyValueEffectivelyUnboundedEmptyError() { ReplaySubject rs = ReplaySubject.createUnbounded(); - + rs.onError(new TestException()); assertEquals(0, rs.size()); assertFalse(rs.hasAnyValue()); } - + @Test public void testSizeAndHasAnyValueUnboundedEmptyCompleted() { ReplaySubject rs = ReplaySubject.create(); - + rs.onCompleted(); assertEquals(0, rs.size()); @@ -761,41 +761,41 @@ public void testSizeAndHasAnyValueUnboundedEmptyCompleted() { @Test public void testSizeAndHasAnyValueEffectivelyUnboundedEmptyCompleted() { ReplaySubject rs = ReplaySubject.createUnbounded(); - + rs.onCompleted(); assertEquals(0, rs.size()); assertFalse(rs.hasAnyValue()); } - + @Test public void testSizeAndHasAnyValueSizeBounded() { ReplaySubject rs = ReplaySubject.createWithSize(1); - + assertEquals(0, rs.size()); assertFalse(rs.hasAnyValue()); - + for (int i = 0; i < 1000; i++) { rs.onNext(i); assertEquals(1, rs.size()); assertTrue(rs.hasAnyValue()); } - + rs.onCompleted(); assertEquals(1, rs.size()); assertTrue(rs.hasAnyValue()); } - + @Test public void testSizeAndHasAnyValueTimeBounded() { TestScheduler ts = new TestScheduler(); ReplaySubject rs = ReplaySubject.createWithTime(1, TimeUnit.SECONDS, ts); - + assertEquals(0, rs.size()); assertFalse(rs.hasAnyValue()); - + for (int i = 0; i < 1000; i++) { rs.onNext(i); ts.advanceTimeBy(500, TimeUnit.MILLISECONDS); @@ -805,7 +805,7 @@ public void testSizeAndHasAnyValueTimeBounded() { assertEquals(0, rs.size()); // stale data no longer peekable assertFalse(rs.hasAnyValue()); } - + rs.onCompleted(); assertEquals(0, rs.size()); @@ -821,9 +821,9 @@ public void testGetValues() { assertArrayEquals(Arrays.copyOf(expected, i + 1), rs.getValues()); } rs.onCompleted(); - + assertArrayEquals(expected, rs.getValues()); - + } @Test public void testGetValuesUnbounded() { @@ -835,17 +835,17 @@ public void testGetValuesUnbounded() { assertArrayEquals(Arrays.copyOf(expected, i + 1), rs.getValues()); } rs.onCompleted(); - + assertArrayEquals(expected, rs.getValues()); - + } - + @Test public void testReplaySubjectValueRelay() { ReplaySubject async = ReplaySubject.create(); async.onNext(1); async.onCompleted(); - + assertFalse(async.hasObservers()); assertTrue(async.hasCompleted()); assertFalse(async.hasThrowable()); @@ -861,7 +861,7 @@ public void testReplaySubjectValueRelay() { public void testReplaySubjectValueRelayIncomplete() { ReplaySubject async = ReplaySubject.create(); async.onNext(1); - + assertFalse(async.hasObservers()); assertFalse(async.hasCompleted()); assertFalse(async.hasThrowable()); @@ -879,7 +879,7 @@ public void testReplaySubjectValueRelayBounded() { async.onNext(0); async.onNext(1); async.onCompleted(); - + assertFalse(async.hasObservers()); assertTrue(async.hasCompleted()); assertFalse(async.hasThrowable()); @@ -896,7 +896,7 @@ public void testReplaySubjectValueRelayBoundedIncomplete() { ReplaySubject async = ReplaySubject.createWithSize(1); async.onNext(0); async.onNext(1); - + assertFalse(async.hasObservers()); assertFalse(async.hasCompleted()); assertFalse(async.hasThrowable()); @@ -911,7 +911,7 @@ public void testReplaySubjectValueRelayBoundedIncomplete() { @Test public void testReplaySubjectValueRelayBoundedEmptyIncomplete() { ReplaySubject async = ReplaySubject.createWithSize(1); - + assertFalse(async.hasObservers()); assertFalse(async.hasCompleted()); assertFalse(async.hasThrowable()); @@ -926,7 +926,7 @@ public void testReplaySubjectValueRelayBoundedEmptyIncomplete() { @Test public void testReplaySubjectValueRelayEmptyIncomplete() { ReplaySubject async = ReplaySubject.create(); - + assertFalse(async.hasObservers()); assertFalse(async.hasCompleted()); assertFalse(async.hasThrowable()); @@ -938,12 +938,12 @@ public void testReplaySubjectValueRelayEmptyIncomplete() { assertArrayEquals(new Integer[] { null }, async.getValues(new Integer[] { 0 })); assertArrayEquals(new Integer[] { null, 0 }, async.getValues(new Integer[] { 0, 0 })); } - + @Test public void testReplaySubjectEmpty() { ReplaySubject async = ReplaySubject.create(); async.onCompleted(); - + assertFalse(async.hasObservers()); assertTrue(async.hasCompleted()); assertFalse(async.hasThrowable()); @@ -960,7 +960,7 @@ public void testReplaySubjectError() { ReplaySubject async = ReplaySubject.create(); TestException te = new TestException(); async.onError(te); - + assertFalse(async.hasObservers()); assertFalse(async.hasCompleted()); assertTrue(async.hasThrowable()); @@ -972,12 +972,12 @@ public void testReplaySubjectError() { assertArrayEquals(new Integer[] { null }, async.getValues(new Integer[] { 0 })); assertArrayEquals(new Integer[] { null, 0 }, async.getValues(new Integer[] { 0, 0 })); } - + @Test public void testReplaySubjectBoundedEmpty() { ReplaySubject async = ReplaySubject.createWithSize(1); async.onCompleted(); - + assertFalse(async.hasObservers()); assertTrue(async.hasCompleted()); assertFalse(async.hasThrowable()); @@ -994,7 +994,7 @@ public void testReplaySubjectBoundedError() { ReplaySubject async = ReplaySubject.createWithSize(1); TestException te = new TestException(); async.onError(te); - + assertFalse(async.hasObservers()); assertFalse(async.hasCompleted()); assertTrue(async.hasThrowable()); @@ -1006,38 +1006,38 @@ public void testReplaySubjectBoundedError() { assertArrayEquals(new Integer[] { null }, async.getValues(new Integer[] { 0 })); assertArrayEquals(new Integer[] { null, 0 }, async.getValues(new Integer[] { 0, 0 })); } - + void backpressureLive(ReplaySubject rs) { TestSubscriber ts = TestSubscriber.create(0); - + rs.subscribe(ts); - + for (int i = 1; i <= 5; i++) { rs.onNext(i); } - + ts.assertNoValues(); - + ts.requestMore(2); - + ts.assertValues(1, 2); - + ts.requestMore(6); ts.assertValues(1, 2, 3, 4, 5); - + for (int i = 6; i <= 10; i++) { rs.onNext(i); } ts.assertValues(1, 2, 3, 4, 5, 6, 7, 8); - + rs.onCompleted(); - + ts.assertNotCompleted(); - + ts.requestMore(2); - + ts.assertValues(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); ts.assertNoErrors(); ts.assertCompleted(); @@ -1045,7 +1045,7 @@ void backpressureLive(ReplaySubject rs) { void backpressureOffline(ReplaySubject rs) { TestSubscriber ts = TestSubscriber.create(0); - + for (int i = 1; i <= 10; i++) { rs.onNext(i); } @@ -1054,19 +1054,19 @@ void backpressureOffline(ReplaySubject rs) { rs.subscribe(ts); ts.assertNoValues(); - + ts.requestMore(2); - + ts.assertValues(1, 2); - + ts.requestMore(6); - + ts.assertValues(1, 2, 3, 4, 5, 6, 7, 8); - + ts.assertNotCompleted(); - + ts.requestMore(2); - + ts.assertValues(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); ts.assertNoErrors(); ts.assertCompleted(); @@ -1074,7 +1074,7 @@ void backpressureOffline(ReplaySubject rs) { void backpressureOffline5(ReplaySubject rs) { TestSubscriber ts = TestSubscriber.create(0); - + for (int i = 1; i <= 10; i++) { rs.onNext(i); } @@ -1083,19 +1083,19 @@ void backpressureOffline5(ReplaySubject rs) { rs.subscribe(ts); ts.assertNoValues(); - + ts.requestMore(2); - + ts.assertValues(6, 7); - + ts.requestMore(2); - + ts.assertValues(6, 7, 8, 9); - + ts.assertNotCompleted(); - + ts.requestMore(1); - + ts.assertValues(6, 7, 8, 9, 10); ts.assertNoErrors(); ts.assertCompleted(); @@ -1141,4 +1141,56 @@ public void backpressureSizeAndTimeOffline() { backpressureOffline(ReplaySubject.createWithTimeAndSize(1, TimeUnit.DAYS, 100, Schedulers.immediate())); } + @Test + public void filtered() { + ReplaySubject subject = ReplaySubject.create(); + + TestSubscriber ts1 = TestSubscriber.create(); + TestSubscriber ts2 = TestSubscriber.create(); + + Observable o = subject.filter(new Func1() { + @Override + public Boolean call(Integer v) { + return v > 0; + } + }); + + o.subscribe(ts1); + o.subscribe(ts2); + + subject.onNext(1); + subject.onNext(2); + subject.onNext(0); + subject.onNext(3); + subject.onNext(0); + subject.onNext(6); + + ts1.assertValues(1, 2, 3, 6); + ts2.assertValues(1, 2, 3, 6); + + subject.onNext(0); + subject.onNext(7); + + ts1.assertValues(1, 2, 3, 6, 7); + ts2.assertValues(1, 2, 3, 6, 7); + } + + @Test + public void noOldEntries() { + TestScheduler scheduler = new TestScheduler(); + + ReplaySubject source = ReplaySubject.createWithTime(2, TimeUnit.SECONDS, scheduler); + + source.onNext(1); + source.onCompleted(); + + source.test().assertResult(1); + + source.test().assertResult(1); + + scheduler.advanceTimeBy(3, TimeUnit.SECONDS); + + source.test().assertResult(); + } + } diff --git a/src/test/java/rx/subjects/SerializedSubjectTest.java b/src/test/java/rx/subjects/SerializedSubjectTest.java index b31a458ffd..d0ced60d23 100644 --- a/src/test/java/rx/subjects/SerializedSubjectTest.java +++ b/src/test/java/rx/subjects/SerializedSubjectTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -35,7 +35,7 @@ public void testBasic() { ts.awaitTerminalEvent(); ts.assertReceivedOnNext(Arrays.asList("hello")); } - + @Test public void testDontWrapSerializedSubjectAgain() { PublishSubject s = PublishSubject.create(); diff --git a/src/test/java/rx/subjects/UnicastSubjectTest.java b/src/test/java/rx/subjects/UnicastSubjectTest.java new file mode 100644 index 0000000000..af9ef33468 --- /dev/null +++ b/src/test/java/rx/subjects/UnicastSubjectTest.java @@ -0,0 +1,101 @@ +package rx.subjects; + +import org.junit.Test; +import rx.functions.Action0; +import rx.observers.TestSubscriber; + +public class UnicastSubjectTest { + + @Test + public void testOneArgFactoryDelayError() throws Exception { + TestSubscriber subscriber = TestSubscriber.create(); + UnicastSubject s = UnicastSubject.create(true); + s.onNext(1L); + s.onNext(2L); + s.onError(new RuntimeException()); + s.subscribe(subscriber); + subscriber.assertValueCount(2); + subscriber.assertError(RuntimeException.class); + } + + @Test + public void testOneArgFactoryNoDelayError() throws Exception { + TestSubscriber subscriber = TestSubscriber.create(); + UnicastSubject s = UnicastSubject.create(false); + s.onNext(1L); + s.onNext(2L); + s.onError(new RuntimeException()); + s.subscribe(subscriber); + subscriber.assertValueCount(0); + subscriber.assertError(RuntimeException.class); + } + + @Test + public void testThreeArgsFactoryDelayError() throws Exception { + TestSubscriber subscriber = TestSubscriber.create(); + UnicastSubject s = UnicastSubject.create(16, new NoopAction0(), true); + s.onNext(1L); + s.onNext(2L); + s.onError(new RuntimeException()); + s.subscribe(subscriber); + subscriber.assertValueCount(2); + subscriber.assertError(RuntimeException.class); + } + + @Test + public void testThreeArgsFactoryNoDelayError() throws Exception { + TestSubscriber subscriber = TestSubscriber.create(); + UnicastSubject s = UnicastSubject.create(16, new NoopAction0(), false); + s.onNext(1L); + s.onNext(2L); + s.onError(new RuntimeException()); + s.subscribe(subscriber); + subscriber.assertValueCount(0); + subscriber.assertError(RuntimeException.class); + } + + @Test + public void testZeroArgsFactory() throws Exception { + TestSubscriber subscriber = TestSubscriber.create(); + UnicastSubject s = UnicastSubject.create(); + s.onNext(1L); + s.onNext(2L); + s.onError(new RuntimeException()); + s.subscribe(subscriber); + subscriber.assertValueCount(0); + subscriber.assertError(RuntimeException.class); + } + + @Test + public void testOneArgFactory() throws Exception { + TestSubscriber subscriber = TestSubscriber.create(); + UnicastSubject s = UnicastSubject.create(16); + s.onNext(1L); + s.onNext(2L); + s.onError(new RuntimeException()); + s.subscribe(subscriber); + subscriber.assertValueCount(0); + subscriber.assertError(RuntimeException.class); + } + + @Test + public void testTwoArgsFactory() throws Exception { + TestSubscriber subscriber = TestSubscriber.create(); + UnicastSubject s = UnicastSubject.create(16, new NoopAction0()); + s.onNext(1L); + s.onNext(2L); + s.onError(new RuntimeException()); + s.subscribe(subscriber); + subscriber.assertValueCount(0); + subscriber.assertError(RuntimeException.class); + } + + + + private static final class NoopAction0 implements Action0 { + + @Override + public void call() { + } + } +} diff --git a/src/test/java/rx/subscriptions/CompositeSubscriptionTest.java b/src/test/java/rx/subscriptions/CompositeSubscriptionTest.java index 4d3ebd753f..5c8165358e 100644 --- a/src/test/java/rx/subscriptions/CompositeSubscriptionTest.java +++ b/src/test/java/rx/subscriptions/CompositeSubscriptionTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -401,9 +401,9 @@ public void testAddAllConcurrent() throws InterruptedException { final CountDownLatch start = new CountDownLatch(1); final CountDownLatch end = new CountDownLatch(10); final List threads = new ArrayList(); - + final Queue errorQueue = new ConcurrentLinkedQueue(); - + for (int i = 0; i < count; i++) { final Thread t = new Thread() { @Override @@ -416,7 +416,7 @@ public void run() { public void unsubscribe() { counter.incrementAndGet(); } - + @Override public boolean isUnsubscribed() { return false; @@ -426,7 +426,7 @@ public boolean isUnsubscribed() { public void unsubscribe() { counter.incrementAndGet(); } - + @Override public boolean isUnsubscribed() { return false; @@ -436,7 +436,7 @@ public boolean isUnsubscribed() { public void unsubscribe() { counter.incrementAndGet(); } - + @Override public boolean isUnsubscribed() { return false; @@ -462,7 +462,7 @@ public boolean isUnsubscribed() { } assertEquals(30, counter.get()); - + assertEquals(errorQueue.toString(), 0, errorQueue.size()); } diff --git a/src/test/java/rx/subscriptions/MultipleAssignmentSubscriptionTest.java b/src/test/java/rx/subscriptions/MultipleAssignmentSubscriptionTest.java index 358f500ec3..3f926b6f72 100644 --- a/src/test/java/rx/subscriptions/MultipleAssignmentSubscriptionTest.java +++ b/src/test/java/rx/subscriptions/MultipleAssignmentSubscriptionTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -15,15 +15,11 @@ */ package rx.subscriptions; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; import static rx.subscriptions.Subscriptions.create; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; +import org.junit.*; import rx.Subscription; import rx.functions.Action0; @@ -72,6 +68,7 @@ public void subscribingWhenUnsubscribedCausesImmediateUnsubscription() { } @Test + @Ignore("This is prone to leaks") public void testSubscriptionRemainsAfterUnsubscribe() { MultipleAssignmentSubscription mas = new MultipleAssignmentSubscription(); @@ -80,4 +77,16 @@ public void testSubscriptionRemainsAfterUnsubscribe() { Assert.assertEquals(true, mas.get() == s); } + + @Test + public void subscriptionDoesntRemainAfterUnsubscribe() { + MultipleAssignmentSubscription mas = new MultipleAssignmentSubscription(); + + mas.set(s); + mas.unsubscribe(); + + assertNotEquals(s, mas.get()); + assertSame(mas.get(), Subscriptions.unsubscribed()); + } + } \ No newline at end of file diff --git a/src/test/java/rx/subscriptions/RefCountSubscriptionTest.java b/src/test/java/rx/subscriptions/RefCountSubscriptionTest.java index ce62f29e96..51d533db1e 100644 --- a/src/test/java/rx/subscriptions/RefCountSubscriptionTest.java +++ b/src/test/java/rx/subscriptions/RefCountSubscriptionTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. diff --git a/src/test/java/rx/subscriptions/SerialSubscriptionTests.java b/src/test/java/rx/subscriptions/SerialSubscriptionTests.java index 8e1d37fe2d..ae51ae1280 100644 --- a/src/test/java/rx/subscriptions/SerialSubscriptionTests.java +++ b/src/test/java/rx/subscriptions/SerialSubscriptionTests.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. diff --git a/src/test/java/rx/subscriptions/SubscriptionsTest.java b/src/test/java/rx/subscriptions/SubscriptionsTest.java index b780942323..f1b52b7cb2 100644 --- a/src/test/java/rx/subscriptions/SubscriptionsTest.java +++ b/src/test/java/rx/subscriptions/SubscriptionsTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -15,19 +15,20 @@ */ package rx.subscriptions; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; import static rx.subscriptions.Subscriptions.create; import org.junit.Test; -import rx.Subscription; +import rx.*; import rx.functions.Action0; public class SubscriptionsTest { + @Test + public void constructorShouldBePrivate() { + TestUtil.checkUtilityClass(Subscriptions.class); + } @Test public void testUnsubscribeOnlyOnce() { diff --git a/src/test/java/rx/test/OperatorTester.java b/src/test/java/rx/test/OperatorTester.java index ad03ad1969..12a5433cd0 100644 --- a/src/test/java/rx/test/OperatorTester.java +++ b/src/test/java/rx/test/OperatorTester.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -23,9 +23,9 @@ public final class OperatorTester { /* * This is purposefully package-only so it does not leak into the public API outside of this package. - * + * * This package is implementation details and not part of the Javadocs and thus can change without breaking backwards compatibility. - * + * * benjchristensen => I'm procrastinating the decision of where and how these types of classes (see rx.subjects.UnsubscribeTester) should exist. * If they are only for internal implementations then I don't want them as part of the API. * If they are truly useful for everyone to use then an "rx.testing" package may make sense. @@ -36,7 +36,7 @@ private OperatorTester() { /** * Used for mocking of Schedulers since many Scheduler implementations are static/final. - * + * * @param underlying the actual scheduler * @return the wrapping Scheduler instance */ diff --git a/src/test/java/rx/test/TestObstructionDetection.java b/src/test/java/rx/test/TestObstructionDetection.java index d24c6721ab..3f9d81cc10 100644 --- a/src/test/java/rx/test/TestObstructionDetection.java +++ b/src/test/java/rx/test/TestObstructionDetection.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -49,7 +49,7 @@ private TestObstructionDetection() { */ public static void checkObstruction() { final int ncpu = Runtime.getRuntime().availableProcessors(); - + final CountDownLatch cdl = new CountDownLatch(ncpu); final List workers = new ArrayList(); final Action0 task = new Action0() { @@ -58,7 +58,7 @@ public void call() { cdl.countDown(); } }; - + for (int i = 0; i < ncpu; i++) { workers.add(Schedulers.computation().createWorker()); } diff --git a/src/test/java/rx/test/TestObstructionDetectionTest.java b/src/test/java/rx/test/TestObstructionDetectionTest.java index f6db1db597..1af4070ed0 100644 --- a/src/test/java/rx/test/TestObstructionDetectionTest.java +++ b/src/test/java/rx/test/TestObstructionDetectionTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -39,7 +39,7 @@ public static void afterClass() { @Test(timeout = 10000, expected = ObstructionException.class) public void testObstruction() { Scheduler.Worker w = Schedulers.computation().createWorker(); - + try { w.schedule(new Action0() { @Override @@ -47,7 +47,7 @@ public void call() { try { Thread.sleep(5000); } catch (InterruptedException ex) { - + } } }); @@ -59,14 +59,14 @@ public void call() { @Test(timeout = 10000) public void testNoObstruction() { w = Schedulers.computation().createWorker(); - + w.schedule(new Action0() { @Override public void call() { try { Thread.sleep(500); } catch (InterruptedException ex) { - + } } }); diff --git a/src/test/java/rx/util/AssertObservable.java b/src/test/java/rx/util/AssertObservable.java index 728ea1bfb3..d1a0072a34 100644 --- a/src/test/java/rx/util/AssertObservable.java +++ b/src/test/java/rx/util/AssertObservable.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -42,7 +42,7 @@ public static void assertObservableEqualsBlocking(Observable expected, Ob * Asserts that two Observables are equal. If they are not, an {@link AssertionError} is thrown * with the given message. If expected and actual are * null, they are considered equal. - * + * * @param the value tpye * @param message * the identifying message for the {@link AssertionError} (null okay) @@ -59,7 +59,7 @@ public static void assertObservableEqualsBlocking(String message, Observable * Asserts that two {@link Observable}s are equal and returns an empty {@link Observable}. If * they are not, an {@link Observable} is returned that calls onError with an {@link AssertionError} when subscribed to. If expected and actual * are null, they are considered equal. - * + * * @param the value tpye * @param expected * Observable with expected values. @@ -102,24 +102,30 @@ public Notification call(Notification expectedNotfication, Notificati if (expectedNotfication.equals(actualNotification)) { StringBuilder message = new StringBuilder(); message.append(expectedNotfication.getKind()); - if (expectedNotfication.hasValue()) + if (expectedNotfication.hasValue()) { message.append(" ").append(expectedNotfication.getValue()); - if (expectedNotfication.hasThrowable()) + } + if (expectedNotfication.hasThrowable()) { message.append(" ").append(expectedNotfication.getThrowable()); + } return Notification.createOnNext("equals " + message.toString()); } else { StringBuilder error = new StringBuilder(); error.append("expected:<").append(expectedNotfication.getKind()); - if (expectedNotfication.hasValue()) + if (expectedNotfication.hasValue()) { error.append(" ").append(expectedNotfication.getValue()); - if (expectedNotfication.hasThrowable()) + } + if (expectedNotfication.hasThrowable()) { error.append(" ").append(expectedNotfication.getThrowable()); + } error.append("> but was:<").append(actualNotification.getKind()); - if (actualNotification.hasValue()) + if (actualNotification.hasValue()) { error.append(" ").append(actualNotification.getValue()); - if (actualNotification.hasThrowable()) + } + if (actualNotification.hasThrowable()) { error.append(" ").append(actualNotification.getThrowable()); + } error.append(">"); return Notification.createOnError(new AssertionError(error.toString())); @@ -136,10 +142,11 @@ public Notification call(Notification a, Notification b) message += "\n\t" + (b.isOnError() ? b.getThrowable().getMessage() : b.getValue()); fail |= b.isOnError(); - if (fail) + if (fail) { return Notification.createOnError(new AssertionError(message)); - else + } else { return Notification.createOnNext(message); + } } }; diff --git a/src/test/java/rx/util/AssertObservableTest.java b/src/test/java/rx/util/AssertObservableTest.java index 5f9e9a0ac1..14fdf2468a 100644 --- a/src/test/java/rx/util/AssertObservableTest.java +++ b/src/test/java/rx/util/AssertObservableTest.java @@ -1,12 +1,12 @@ /** * Copyright 2014 Netflix, Inc. - * + * * 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. @@ -15,7 +15,7 @@ */ package rx.util; -import org.junit.Test; +import org.junit.*; import rx.Observable; @@ -31,13 +31,23 @@ public void testPassNull() { AssertObservable.assertObservableEqualsBlocking("foo", null, null); } - @Test(expected = RuntimeException.class) + @Test public void testFailNotNull() { - AssertObservable.assertObservableEqualsBlocking("foo", Observable.just(1, 2), Observable.just(1)); + try { + AssertObservable.assertObservableEqualsBlocking("foo", Observable.just(1, 2), Observable.just(1)); + Assert.fail("Failed to throw"); + } catch (AssertionError expected) { + // expected + } } - @Test(expected = RuntimeException.class) + @Test public void testFailNull() { - AssertObservable.assertObservableEqualsBlocking("foo", Observable.just(1, 2), null); + try { + AssertObservable.assertObservableEqualsBlocking("foo", Observable.just(1, 2), null); + Assert.fail("Failed to throw"); + } catch (AssertionError expected) { + // expected + } } }