{"id":16838891,"url":"https://github.com/akarnokd/rxjavaextensions","last_synced_at":"2025-04-12T14:19:27.018Z","repository":{"id":38257978,"uuid":"63074445","full_name":"akarnokd/RxJavaExtensions","owner":"akarnokd","description":"RxJava 2.x \u0026 3.x extra sources, operators and components and ports of many 1.x companion libraries.","archived":false,"fork":false,"pushed_at":"2023-04-24T04:59:23.000Z","size":9480,"stargazers_count":686,"open_issues_count":8,"forks_count":51,"subscribers_count":19,"default_branch":"3.x","last_synced_at":"2024-10-14T12:26:46.947Z","etag":null,"topics":["extensions","reactive-streams","rxjava"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/akarnokd.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2016-07-11T14:22:11.000Z","updated_at":"2024-09-22T22:27:27.000Z","dependencies_parsed_at":"2024-10-25T18:40:58.866Z","dependency_job_id":"eeb071e2-7784-4b53-9231-dc734c0ef59d","html_url":"https://github.com/akarnokd/RxJavaExtensions","commit_stats":null,"previous_names":["akarnokd/rxjava2extensions"],"tags_count":80,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/akarnokd%2FRxJavaExtensions","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/akarnokd%2FRxJavaExtensions/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/akarnokd%2FRxJavaExtensions/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/akarnokd%2FRxJavaExtensions/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/akarnokd","download_url":"https://codeload.github.com/akarnokd/RxJavaExtensions/tar.gz/refs/heads/3.x","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247006663,"owners_count":20868033,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["extensions","reactive-streams","rxjava"],"created_at":"2024-10-13T12:27:09.204Z","updated_at":"2025-04-03T13:17:19.623Z","avatar_url":"https://github.com/akarnokd.png","language":"Java","readme":"# RxJavaExtensions\n\n\u003ca href='https://github.com/akarnokd/RxJavaExtensions/actions?query=workflow%3A%22Java+CI+with+Gradle%22'\u003e\u003cimg src='https://github.com/akarnokd/RxJavaExtensions/workflows/Java%20CI%20with%20Gradle/badge.svg'\u003e\u003c/a\u003e\n[![codecov.io](http://codecov.io/github/akarnokd/RxJavaExtensions/coverage.svg?branch=3.x)](http://codecov.io/github/akarnokd/RxJavaExtensions?branch=3.x)\n[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.github.akarnokd/rxjava3-extensions/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.github.akarnokd/rxjava3-extensions)\n[![Maven Central](https://maven-badges.herokuapp.com/maven-central/io.reactivex.rxjava3/rxjava/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.reactivex.rxjava3/rxjava)\n\nRxJava 3.x implementation of extra sources, operators and components and ports of many 1.x companion libraries.\n\n# Releases\n\n**gradle**\n\n```\ndependencies {\n    implementation \"com.github.akarnokd:rxjava3-extensions:3.1.1\"\n}\n```\n\nJavadoc: https://akarnokd.github.io/RxJavaExtensions/javadoc/index.html\n\n\nMaven search:\n\n[http://search.maven.org](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.github.akarnokd%22)\n\n# Features\n\n  - [Extra functional interfaces](#extra-functional-interfaces)\n  - [Mathematical operations over numerical sequences](#mathematical-operations-over-numerical-sequences)\n  - [String operations](#string-operations)\n  - [Asynchronous jumpstarting a sequence](#asynchronous-jumpstarting-a-sequence)\n  - [Computational expressions](#computational-expressions)\n  - [Join patterns](#join-patterns)\n  - [Debug support](#debug-support)\n    - [Function tagging](#function-tagging)\n    - [Protocol validation](#protocol-validation)\n    - [Multi-hook handlers](#multi-hook-handlers)\n  - Custom Processors and Subjects\n    - [SoloProcessor, PerhapsProcessor and NonoProcessor](#soloprocessor-perhapsprocessor-and-nonoprocessor)\n    - [MulticastProcessor](#multicastprocessor) *(Deprecated in 0.19.2!)*,\n    - [UnicastWorkSubject](#unicastworksubject),\n    - [DispatchWorkSubject](#dispatchworksubject),\n    - [DispatchWorkProcessor](#dispatchworkprocessor)\n  - [FlowableProcessor utils](#flowableprocessor-utils)\n    - [wrap](#wrap), [refCount](#refcount)\n  - [Custom Schedulers](#custom-schedulers)\n    - [SharedScheduler](#sharedscheduler)\n    - [ParallelScheduler](#parallelscheduler)\n    - [BlockingScheduler](#blockingscheduler)\n  - [Custom operators and transformers](#custom-operators-and-transformers)\n    - [valve()](#flowabletransformersvalve), [orderedMerge()](#flowablesorderedmerge), [bufferWhile()](#flowabletransformersbufferwhile),\n    - [bufferUntil()](#flowabletransformersbufferuntil), [bufferSplit()](#flowabletransformersbuffersplit), [spanout()](#flowabletransformersspanout),\n    - [mapFilter()](#flowabletransformersmapfilter), [onBackpressureTimeout()](#flowabletransformersonbackpressuretimeout), [repeat()](#flowablesrepeat),\n    - [repeatCallable()](#flowablesrepeatcallable), [every()](#flowabletransformersevery), [intervalBackpressure()](#flowablesintervalbackpressure),\n    - [cacheLast()](#flowabletransformerscachelast), [timeoutLast()](#flowabletransformerstimeoutlast--timeoutlastabsolute), [timeoutLastAbsolute()](#flowabletransformerstimeoutlast--timeoutlastabsolute),\n    - [debounceFirst()](#flowabletransformersdebouncefirst), [switchFlatMap()](#flowabletransformersswitchflatmap), [flatMapSync()](#flowabletransformersflatmapsync),\n    - [flatMapAsync()](#flowabletransformersflatmapasync), [switchIfEmpty()](#flowabletransformersswitchifempty--switchifemptyarray),\n    - [expand()](#flowabletransformersexpand), [mapAsync()](#flowabletransformersmapasync), [filterAsync()](#flowabletransformersfilterasync),\n    - [zipLatest()](#flowablesziplatest), [coalesce()](#flowabletransformerscoalesce),\n    - [windowWhile()](#flowabletransformerswindowwhile), [windowUntil()](#flowabletransformerswindowuntil), [windowSplit()](#flowabletransformerswindowsplit),\n    - [indexOf()](#flowabletransformersindexof), [requestObserveOn()](#flowabletransformersrequestobserveon), [requestSample()](#flowabletransformersrequestsample)\n    - [observeOnDrop()](#observabletransformersobserveondrop), [observeOnLatest()](#observabletransformersobserveonlatest), [generateAsync()](#flowablesgenerateasync),\n    - [partialCollect()](#flowabletransformerspartialcollect), [flatMapDrop()](#observabletransformersflatmapdrop), [flatMapLatest()](#observabletransformersflatmaplatest),\n    - [errorJump()](#flowabletransformerserrorjump), [flatMap on signal type](#flatmap-signal), [switchOnFirst()](#flowabletransformersswitchonfirst)\n  - [Custom parallel operators and transformers](#custom-parallel-operators-and-transformers)\n    - [sumX()](#paralleltransformerssumx)\n    - [orderedMerge()](#paralleltransformersorderedmerge)\n  - [Special Publisher implementations](#special-publisher-implementations)\n  - Custom consumers\n    - [FlowableConsumers](#flowableconsumers)\n      - [subscribeAutoDispose()](#subscribeautodispose)\n    - [ObservableConsumers](#observableconsumers)\n    - [SingleConsumers](#singleconsumers)\n    - [MaybeConsumers](#maybeconsumers)\n    - [CompletableConsumers](#completableconsumers)\n\n## Extra functional interfaces\n\nSupport the join-patterns and async-util with functional interfaces of consumers with 3-9 type arguments\nand have functional interfaces of functions without the `throws Exception`.\n\n  - `SimpleCallable\u003cT\u003e` - `Callable\u003cT\u003e` without `throws Exception`\n  - `Consumer3` - 3 argument `Consumer`\n  - `Consumer4` - 4 argument `Consumer`\n  - `Consumer5` - 5 argument `Consumer`\n  - `Consumer6` - 6 argument `Consumer`\n  - `Consumer7` - 7 argument `Consumer`\n  - `Consumer8` - 8 argument `Consumer`\n  - `Consumer9` - 9 argument `Consumer`\n  - `PlainFunction` - `Function` without `throws Exception`\n  - `PlainBiFunction` - `BiFunction` without `throws Exception`\n  - `PlainFunction3` - `Function3` without `throws Exception`\n  - `PlainFunction4` - `Function4` without `throws Exception`\n  - `PlainFunction5` - `Function5` without `throws Exception`\n  - `PlainFunction6` - `Function6` without `throws Exception`\n  - `PlainFunction7` - `Function7` without `throws Exception`\n  - `PlainFunction8` - `Function8` without `throws Exception`\n  - `PlainFunction9` - `Function9` without `throws Exception`\n  \n\nUtility functions supporting these can be found in `FunctionsEx` class.\n\n\n## Mathematical operations over numerical sequences\n\nAlthough most of the operations can be performed with `reduce`, these operators have lower overhead\nas they cut out the reboxing of primitive intermediate values.\n\nThe following operations are available in `MathFlowable` for `Flowable` sequences and `MathObservable` in `Observable`\nsequences:\n\n  - `averageDouble()`\n  - `averageFloat()`\n  - `max()`\n  - `min()`\n  - `sumDouble()`\n  - `sumFloat()`\n  - `sumInt()`\n  - `sumLong()`\n  \nExample\n\n```java\nMathFlowable.averageDouble(Flowable.range(1, 10))\n.test()\n.assertResult(5.5);\n\nFlowable.just(5, 1, 3, 2, 4)\n.to(MathFlowable::min)\n.test()\n.assertResult(1);\n```\n  \n## String operations\n\n### characters\nThe `StringFlowable` and `StringObservable` support streaming the characters of a `CharSequence`:\n\n```java\nStringFlowable.characters(\"Hello world\")\n.map(v -\u003e Characters.toLower((char)v))\n.subscribe(System.out::print, Throwable::printStackTrace, System.out::println);\n```\n\n### split\n\nSplits an incoming sequence of Strings based on a Regex pattern within and between subsequent elements if necessary.\n\n```java\nFlowable.just(\"abqw\", \"ercdqw\", \"eref\")\n.compose(StringFlowable.split(\"qwer\"))\n.test()\n.assertResult(\"ab\", \"cd\", \"ef\");\n\nFlowable.just(\"ab\", \":cde:\" \"fg\")\n.compose(StringFlowable.split(\":\"))\n.test()\n.assertResult(\"ab\", \"cde\", \"fg\");\n```\n\n## Asynchronous jumpstarting a sequence\n\nWrap functions and consumers into Flowables and Observables or into another layer of Functions.\nMost of these can now be achieved via `fromCallable` and some function composition in plain RxJava.\n\n### start \n\nRun a function or action once on a background thread and cache its result.\n\n```java\nAtomicInteger counter = new AtomicInteger();\n\nFlowable\u003cInteger\u003e source = AsyncFlowable.start(() -\u003e counter.incrementAndGet());\n\nsource.test()\n    .awaitDone(5, TimeUnit.SECONDS)\n    .assertResult(1);\n\nsource.test()\n    .awaitDone(5, TimeUnit.SECONDS)\n    .assertResult(1);\n```\n\n### toAsync\n\nCall a function (with parameters) to call a function inside a `Flowable`/`Observable` with the same\nparameter and have the result emitted by that `Flowable`/`Observable` from a background thread.\n\n```java\n\nFunction\u003cInteger, Flowable\u003cString\u003e\u003e func = AsyncFlowable.toAsync(\n    param -\u003e \"[\" + param + \"]\"\n);\n\nfunc.apply(1)\n    .test()\n    .awaitDone(5, TimeUnit.SECONDS)\n    .assertResult(\"[1]\")\n;\n```\n### startFuture\n\nRun a Supplier that returns a Future to call blocking get() on to get the solo value or exception.\n\n```java\nExecutorService exec = Executors.newSingleThreadedScheduler();\n\nAsyncFlowable.startFuture(() -\u003e exec.submit(() -\u003e 1))\n    .test()\n    .awaitDone(5, TimeUnit.SECONDS)\n    .assertResult(1);\n    \nexec.shutdown();\n```\n\n### deferFuture\n\nRun a Supplier that returns a Future to call blocking get() on to get a `Publisher` to stream back.\n\n```java\nExecutorService exec = Executors.newSingleThreadedScheduler();\n\nAsyncFlowable.startFuture(() -\u003e exec.submit(() -\u003e Flowable.range(1, 5)))\n    .test()\n    .awaitDone(5, TimeUnit.SECONDS)\n    .assertResult(1, 2, 3, 4, 5);\n    \nexec.shutdown();\n```\n\n### forEachFuture\n\nConsume a `Publisher` and have `Future` that completes when the consumption ends with `onComplete` or `onError`.\n\n```java\nFuture\u003cObject\u003e f = AsyncFlowable.forEachFuture(Flowable.range(1, 100), System.out::println);\n\nf.get();\n```\n\n### runAsync\n\nAllows emitting multiple values through a Processor mediator from a background thread and allows disposing\nthe sequence externally.\n\n```java\nAsyncFlowable.runAsync(Schedulers.single(),\n        UnicastProcessor.\u003cObject\u003ecreate(),\n        new BiConsumer\u003cSubscriber\u003cObject\u003e, Disposable\u003e() {\n            @Override\n            public void accept(Subscriber\u003c? super Object\u003e s, Disposable d) throws Exception {\n                s.onNext(1);\n                s.onNext(2);\n                s.onNext(3);\n                Thread.sleep(200);\n                s.onNext(4);\n                s.onNext(5);\n                s.onComplete();\n            }\n        }\n).test()\n.awaitDone(5, TimeUnit.SECONDS)\n.assertResult(1, 2, 3, 4, 5);\n```\n\n## Computational expressions\n\nThe operators on `StatementFlowable` and `StatementObservable` allow taking different branches at subscription time:\n\n### ifThen\n\nConditionally chose a source to subscribe to. This is similar to the imperative `if` statement but with reactive flows:\n\n```java\nif ((System.currentTimeMillis() \u0026 1) != 0) {\n    System.out.println(\"An odd millisecond\");\n} else {\n    System.out.println(\"An even millisecond\");\n}\n```\n\n```java\nFlowable\u003cString\u003e source = StatementFlowable.ifThen(\n    () -\u003e (System.currentTimeMillis() \u0026 1) != 0,\n    Flowable.just(\"An odd millisecond\"),\n    Flowable.just(\"An even millisecond\")\n);\n\nsource\n.delay(1, TimeUnit.MILLISECONDS)\n.repeat(1000)\n.subscribe(System.out::println);\n```\n\n### switchCase\n\nCalculate a key and pick a source from a Map. This is similar to the imperative `switch` statement:\n\n```java\nswitch ((int)(System.currentTimeMillis() \u0026 7)) {\ncase 1: System.out.println(\"one\"); break;\ncase 2: System.out.println(\"two\"); break;\ncase 3: System.out.println(\"three\"); break;\ndefault: System.out.println(\"Something else\");\n}\n```\n\n```java\nMap\u003cInteger, Flowable\u003cString\u003e\u003e map = new HashMap\u003c\u003e();\n\nmap.put(1, Flowable.just(\"one\"));\nmap.put(2, Flowable.just(\"two\"));\nmap.put(3, Flowable.just(\"three\"));\n\nFlowable\u003cString\u003e source = StatementFlowable.switchCase(\n    () -\u003e (int)(System.currentTimeMillis() \u0026 7),\n    map,\n    Flowable.just(\"Something else\")\n);\n\nsource\n.delay(1, TimeUnit.MILLISECONDS)\n.repeat(1000)\n.subscribe(System.out::println);\n```\n\n### doWhile\n\nResubscribe if a condition is true after the last subscription completed normally. This is similar to the imperative `do-while` loop (executing the loop body at least once):\n\n```java\nlong start = System.currentTimeMillis();\ndo {\n    Thread.sleep(1);\n    System.out.println(\"Working...\");\nwhile (start + 100 \u003e System.currentTimeMillis());\n```\n\n```java\nlong start = System.currentTimeMillis();\n\nFlowable\u003cString\u003e source = StatementFlowable.doWhile(\n    Flowable.just(\"Working...\").delay(1, TimeUnit.MILLISECONDS),\n    () -\u003e start + 100 \u003e System.currentTimeMillis()\n);\n\nsource.subscribe(System.out::println);\n```\n\n\n### whileDo\n\nSubscribe and resubscribe if a condition is true. This is similar to the imperative `while` loop (where the loop body may not execute if the condition is false\nto begin with):\n\n```java\n\nwhile ((System.currentTimeMillis() \u0026 1) != 0) {\n    System.out.println(\"What an odd millisecond!\");\n}\n```\n\n```java\nFlowable\u003cString\u003e source = StatementFlowable.whileDo(\n    Flowable.just(\"What an odd millisecond!\"),\n    () -\u003e (System.currentTimeMillis() \u0026 1) != 0\n);\n\nsource.subscribe(System.out::println);\n```\n\n## Join patterns\n\n(Conversion done)\n\nTBD: examples\n\n## Debug support\n\nBy default, RxJava 3's RxJavaPlugins only offers the ability to hook into the assembly process (i.e., when you apply an operator on a sequence or create one) unlike 1.x where there is an `RxJavaHooks.enableAssemblyTracking()` method. Since the standard format is of discussion there, 3.x doesn't have such feature built in but only\nin this extension library.\n\n### Usage\n\nYou enable tracking via:\n\n```java\nRxJavaAssemblyTracking.enable();\n```\n\nand disable via:\n\n```java\nRxJavaAssemblyTracking.disable();\n```\n\nNote that this doesn't save or preserve the old hooks (named `Assembly`) you may have set as of now.\n\n### Output\n\nIn debug mode, you can walk through the reference graph of Disposables and Subscriptions to find an `FlowableOnAssemblyX` named nodes (similar in the other base types) where there is an `assembled` field of type `RxJavaAssemblyException`. This has also a field named `stacktrace` that contains a pretty printed stacktrace string pointing to the assembly location:\n\n```\nRxJavaAssemblyException: assembled\nat io.reactivex.Completable.error(Completable.java:280)\nat hu.akarnokd.rxjava3.debug.RxJava3AssemblyTrackingTest.createCompletable(RxJava3AssemblyTrackingTest.java:78)\nat hu.akarnokd.rxjava3.debug.RxJava3AssemblyTrackingTest.completable(RxJava3AssemblyTrackingTest.java:185)\n```\n\nThis is a filtered list of stacktrace elements (skipping threading, unit test and self-related entries). Most modern IDEs should allow you to navigate to the locations when printed on (or pasted into) its console.\n\nTo avoid interference, the `RxJavaAssemblyException` is attached as the last cause to potential chain of the original exception that travels through each operator to the end consumer.\n\nYou can programmatically find this via:\n\n```java\nRxJavaAssemblyException assembled = RxJavaAssemblyException.find(someThrowable);\n\nif (assembled != null) {\n    System.err.println(assembled.stacktrace());\n}\n```\n\n### Function tagging\n\nOften, when a function throws or returns null, there is not enough information to\nlocate said function in the codebase. The `FunctionTagging` utility class offers\nstatic wrappers for RxJava function types that when fail or return null, a custom\nstring tag is added or appended to the exception and allows locating that function\nin your codebase. Since added logic has overhead, the tagging process has to be\nenabled and can be disabled as necessary.\n\n```java\nFunctionTagging.enable();\n\nFunction\u003cInteger, Integer\u003e tagged = FunctionTagging.tagFunction(v -\u003e null, \"F1\");\n\nFunctionTagging.disable();\n\nFunction\u003cInteger, Integer\u003e notTagged = FunctionTagging.tagFunction(v -\u003e null, \"F2\");\n\nassertNull(notTagged.apply(1));\n\ntry {\n   tagged.apply(1);\n   fail(\"Should have thrown\");\n} catch (NullPointerException ex) {\n   assertTrue(ex.getMessage().contains(\"F1\"));\n}\n```\n\nTo avoid lambda ambiguity, the methods are named `tagX` where `X` is the functional type name such as `BiFunction`, `Function3` etc.\n\nThe wrappers check for `null` parameters and if the wrapped function returns a `null` and throw a `NullPointerException` containing the parameter\nname (t1 .. t9) and the tag provided.\n\n### Protocol validation\n\nCustom operators and sources sometimes contain bugs that manifest themselves in odd sequence behavior or crashes \nfrom within the standard operators. Since the revealing stacktraces is often missing or incomplete, diagnosing \nsuch failures can be tiresome. Therefore, the `hu.akarnokd.rxjava3.debug.validator.RxJavaProtocolValidator` \nclass offers assembly hooks for the standard reactive base types.\n\nThe validation hooks can be enabled via `RxJavaProtocolValidator.enable()` and disabled via `RxJavaProtocolValidator.disable()`.\nThe validator also supports chaining with existing hooks via `enableAndChain()` which returns a `SavedHooks` instance\nto restore the original hooks specifically:\n\n```java\nSavedHooks hooks = RxJavaProtocolValidator.enableAndChain();\n\n// assemble and run flows\n// ...\n\nhooks.restore();\n```\n\nBy default, the violations, subclasses of `ProtocolNonConformanceException`, are reported to the `RxJavaPlugins.onError`\nhandler but can be overridden via `RxJavaProtocolValidator.setOnViolationHandler`.\n\n```java\nRxJavaProtocolValidator.setOnViolationHandler(e -\u003e e.printStackTrace());\n\nRxJavaProtocolValidator.enable();\n\n// ...\n```\n\nThe following error violations are detected:\n\n| Exception | Violation description |\n|-----------|-----------------------|\n| MultipleTerminationsException | When multiple calls to `onError` or `onComplete` happened. |\n| MultipleOnSubscribeCallsException | When multiple calls to `onSubscribe` happened |\n| NullOnErrorParameterException | When the `onError` was called with a `null` `Throwable`. |\n| NullOnNextParameterException | When the `onNext` was called with a `null` value. |\n| NullOnSubscribeParameterException | When the `onSubscribe` was called with a `null` `Disposable` or `Subscription`. |\n| NullOnSuccessParameterException | When the `onSuccess` was called with a `null` value. |\n| OnNextAfterTerminationException | Wen the `onNext` was called after `onError` or `onComplete`. |\n| OnSubscribeNotCalledException | When any of the `onNext`, `onSuccess`, `onError` or `onComplete` is invoked without invoking `onSubscribe` first. |\n| OnSuccessAfterTerminationException | Wen the `onSuccess` was called after `onError` or `onComplete`. |\n\n### Multi-hook handlers\n\nThe standard `RxJavaPlugins` allows only one hook to be associated with each main intercept option.\nIf multiple hooks should be invoked, that option is not directly supported by `RxJavaPlugins` but\ncan be built upon the single hook scheme.\n\nThe `hu.akarnokd.rxjava3.debug.multihook` package offers hook managers that can work with multiple hooks\nthemselves.\n\nThe various multi-hook managers are built upon the generic `MultiHandlerManager\u003cH\u003e` class. The class offers the `register`\nmethod to register a particular hook for which a `Disposable` is returned. This allows removing a particular\nhook without the need to remember the hook instance or the manager class.\n\n#### `OnScheduleMultiHookManager`\n\nOffers multi-hook management for the `RxJavaPlugins.setScheduleHandler` and `onSchedule` hooks.\n\nThe `enable()` method will install the instance as the main hook, the `disable()` will restore the default no-hook.\nThe convenience `append()` will take any existing hook, register it with the manager and install the manager as the main hook.\n\n## SoloProcessor, PerhapsProcessor and NonoProcessor\n\nThese are the backpressure-aware, Reactive-Streams Processor-based implementations of the `SingleSubject`, `MaybeSubject` and CompletableSubject respectively. Their usage is quite similar.\n\n\n```java\nPerhapsProcessor\u003cInteger\u003e ms = PerhapsProcessor.create();\n\nTestSubscriber\u003cInteger\u003e to = ms.test();\n\nms.onNext(1);\nms.onComplete();\n\nto.assertResult(1);\n```\n\nSimilarly with NonoProcessor, although calling `onNext(null)` will throw a `NullPointerException` to the caller.\n\n```java\nNonoProcessor cs = NonoProcessor.create();\n\nTestSubscriber\u003cVoid\u003e to2 = cs.test();\n\ncs.onComplete();\n\nto2.assertResult();\n```\n\nFinally\n\n\n```java\nSoloProcessor\u003cInteger\u003e ss = SoloProcessor.create();\n\nTestSubscriber\u003cInteger\u003e to3 = ss.test();\n\nss.onNext(1);\nss.onComplete();\n\nto3.assertResult(1);\n```\n\nNote that calling `onComplete` after `onNext` is optional with `SoloProcessor` but calling `onComplete` without calling `onNext` terminates the `SoloProcessor` with a `NoSuchElementException`.\n\n### MulticastProcessor\n\n*Moved to RxJava as standard processor: [`io.reactivex.rxjava3.processors.MulticastProcessor`](http://reactivex.io/RxJava/3.x/javadoc/io/reactivex/rxjava3/processors/MulticastProcessor.html)*.\n\n### UnicastWorkSubject\n\nA `Subject` variant that buffers items and allows one `Observer` to consume it at a time, but unlike `UnicastSubject`,\nonce the previous `Observer` disposes, a new `Observer` can subscribe and resume consuming the items.\n\n```java\nUnicastWorkSubject\u003cInteger\u003e uws = UnicastWorkSubject.create();\n\nuws.onNext(1);\nuws.onNext(2);\nuws.onNext(3);\nuws.onNext(4);\n\nuws.take(2).test().assertResult(1, 2);\nuws.take(2).test().assertResult(3, 4);\n\nuws.onComplete();\nuws.test().assertResult();\n```\n\n### DispatchWorkSubject\n\nA `Subject` variant that buffers items and allows one or more `Observer`s to exclusively consume one of the items in the buffer\nasynchronously. If there are no `Observer`s (or they all disposed), the `DispatchWorkSubject` will keep buffering and later\n`Observer`s can resume the consumption of the buffer.\n\n```java\nDispatchWorkSubject\u003cInteger\u003e dws = DispatchWorkSubject.create(Schedulers.computation());\n\nSingle\u003cList\u003cInteger\u003e\u003e asList = dws.toList();\n\nTestObserver\u003cList\u003cInteger\u003e\u003e to = Single\n    .zip(asList, asList, (a, b) -\u003e a.addAll(b))\n    .test();\n    \nObservable.range(1, 1000000).subscribe(dws);\n\nto.awaitDone(5, TimeUnit.SECONDS)\n.assertValueCount(1)\n.assertComplete()\n.assertNoErrors();\n\nassertEquals(1000000, to.values().get().size());\n```\n\n### DispatchWorkProcessor\n\nA `FlowableProcessor` variant that buffers items and allows one or more `Subscriber`s to exclusively consume one of the items in the buffer\nasynchronously. If there are no `Subscriber`s (or they all canceled), the `DispatchWorkProcessor` will keep buffering and later\n`Subscriber`s can resume the consumption of the buffer.\n\n```java\nDispatchWorkProcessor\u003cInteger\u003e dwp = DispatchWorkProcessor.create(Schedulers.computation());\n\nSingle\u003cList\u003cInteger\u003e\u003e asList = dwp.toList();\n\nTestObserver\u003cList\u003cInteger\u003e\u003e to = Single\n    .zip(asList, asList, (a, b) -\u003e a.addAll(b))\n    .test();\n    \nFlowable.range(1, 1000000).subscribe(dwp);\n\nto.awaitDone(5, TimeUnit.SECONDS)\n.assertValueCount(1)\n.assertComplete()\n.assertNoErrors();\n\nassertEquals(1000000, to.values().get().size());\n```\n\n## FlowableProcessor utils\n\nAn utility class that helps working with Reactive-Streams `Processor`, `FlowableProcessor`\n`Subject` instances via static methods.\n\n### wrap\n\nWraps an arbitrary `Processor` into a `FlowableProcessor`\n\n### refCount\n\nWraps a `FlowableProcessor`/`Subject` and makes sure if all subscribers/observer cancel/dispose\ntheir subscriptions, the upstream's `Subscription`/`Disposable` gets cancelled/disposed as well.\n\n## Custom Schedulers\n\n### SharedScheduler\n\nThis `Scheduler` implementation takes a `Worker` directly or from another `Scheduler` and shares it across its own `Worker`s while\nmaking sure disposing one of its own `SharedWorker` doesn't dispose any other `SharedWorker` or the underlying shared `Worker`.\n\nThis type of scheduler may help solve the problem when one has to return to the same thread/scheduler at different stages of the pipeline\nbut one doesn't want to or isn't able to use `SingleScheduler` or some other single-threaded thread-pool wrapped via `Schedulers.from()`.\n\n```java\nSharedScheduler shared = new SharedScheduler(Schedulers.io());\n\nFlowable.just(1)\n.subscribeOn(shared)\n.map(v -\u003e Thread.currentThread().getName())\n.observeOn(Schedulers.computation())\n.map(v -\u003e v.toLowerCase())\n.observeOn(shared)\n.map(v -\u003e v.equals(Thread.currentThread().getName().toLowerCase()))\n.blockingForEach(System.out::println);\n```\n\n### ParallelScheduler\n\nIt is similar to `Schedulers.computation()` but you can control the number of threads, the thread name prefix, the thread priority and to track each task submitted to its worker.\n\nTracking a task means that if one calls `Worker.dispose()`, all outstanding tasks is cancelled. However, certain use cases can get away with just preventing the execution of the task body and just run through all outstanding tasks yielding lower overhead.\n\nThe `ParallelScheduler` supports `start` and `shutdown` to start and stop the backing thread-pools. The non-`ThreadFactory` constructors create a daemon-thread backed set of single-threaded thread-pools.\n\n```java\nScheduler s = new ParallelScheduler(3);\n\ntry {\n    Flowable.range(1, 10)\n    .flatMap(v -\u003e Flowable.just(1).subscribeOn(s).map(v -\u003e v + 1))\n    .test()\n    .awaitDone(5, TimeUnit.SECONDS)\n    .assertValueSet(Arrays.asList(2, 3, 4, 5, 6, 7, 8, 9, 10, 11))\n    .assertComplete()\n    .assertNoErrors();\n} finally {\n    s.shutdown();\n}\n```\n\n### BlockingScheduler\n\nThis type of scheduler runs its execution loop on the \"current thread\", more specifically, the thread which invoked its `execute()` method. The method blocks until the `shutdown()` is invoked. This type of scheduler allows returning to the \"main\" thread from other threads.\n\n```java\npublic static void main(String[] args) {\n    BlockingScheduler scheduler = new BlockingScheduler();\n\n    scheduler.execute(() -\u003e {\n        Flowable.range(1,10)\n        .subscribeOn(Schedulers.io())\n        .observeOn(scheduler)\n        .doAfterTerminate(() -\u003e scheduler.shutdown())\n        .subscribe(v -\u003e System.out.println(v + \" on \" + Thread.currentThread()));\n    });\n    \n    System.out.println(\"BlockingScheduler finished\");\n}\n```\n\n## Custom operators and transformers\n\nThe custom transformers (to be applied with `Flowable.compose` for example), can be found in `hu.akarnokd.rxjava3.operators.FlowableTransformers` class. The custom source-like operators can be found in `hu.akarnokd.rxjava3.operators.Flowables` class. The operators and transformers for the other base\nreactive classes (will) follow the usual naming scheme.\n\n### FlowableTransformers.valve()\n\nPauses and resumes a main flow if the secondary flow signals false and true respectively.\n\nAlso available as `ObservableTransformers.valve()`.\n\n```java\nPublishProcessor\u003cBoolean\u003e valveSource = PublishProcessor.create();\n\nFlowable.intervalRange(1, 20, 1, 1, TimeUnit.SECONDS)\n.compose(FlowableTransformers.\u003cLong\u003evalve(valveSource))\n.subscribe(System.out::println, Throwable::printStackTrace);\n\nThread.sleep(3100);\n\nvalveSource.onNext(false);\n\nThread.sleep(5000);\n\nvalveSource.onNext(true);\n\nThread.sleep(3000);\n\nvalveSource.onNext(false);\n\nThread.sleep(6000);\n\nvalveSource.onNext(true);\n\nThread.sleep(3000);\n```\n\n### Flowables.orderedMerge()\n\nGiven a fixed number of input sources (which can be self-comparable or given a `Comparator`) merges them\ninto a single stream by repeatedly picking the smallest one from each source until all of them completes.\n\n```java\nFlowables.orderedMerge(Flowable.just(1, 3, 5), Flowable.just(2, 4, 6))\n.test()\n.assertResult(1, 2, 3, 4, 5, 6);\n```\n\n### FlowableTransformers.bufferWhile()\n\nBuffers into a list/collection while the given predicate returns true for\nthe current item, otherwise starts a new list/collection containing the given item (i.e., the \"separator\" ends up in the next list/collection).\n\n```java\nFlowable.just(\"1\", \"2\", \"#\", \"3\", \"#\", \"4\", \"#\")\n.compose(FlowableTransformers.bufferWhile(v -\u003e !\"#\".equals(v)))\n.test()\n.assertResult(\n    Arrays.asList(\"1\", \"2\"),\n    Arrays.asList(\"#\", \"3\"),\n    Arrays.asList(\"#\", \"4\"),\n    Arrays.asList(\"#\")\n);\n```\n\n### FlowableTransformers.bufferUntil()\n\nBuffers into a list/collection until the given predicate returns true for\nthe current item and starts an new empty list/collection  (i.e., the \"separator\" ends up in the same list/collection).\n\n```java\nFlowable.just(\"1\", \"2\", \"#\", \"3\", \"#\", \"4\", \"#\")\n.compose(FlowableTransformers.bufferUntil(v -\u003e \"#\".equals(v)))\n.test()\n.assertResult(\n    Arrays.asList(\"1\", \"2\", \"#\"),\n    Arrays.asList(\"3\", \"#\"),\n    Arrays.asList(\"4\", \"#\")\n);\n```\n\n### FlowableTransformers.bufferSplit()\n\nBuffers into a list/collection while the predicate returns false. When it returns true,\na new buffer is started and the particular item won't be in any of the buffers.\n\n```java\nFlowable.just(\"1\", \"2\", \"#\", \"3\", \"#\", \"4\", \"#\")\n.compose(FlowableTransformers.bufferSplit(v -\u003e \"#\".equals(v)))\n.test()\n.assertResult(\n    Arrays.asList(\"1\", \"2\"),\n    Arrays.asList(\"3\"),\n    Arrays.asList(\"4\")\n);\n```\n\n### FlowableTransformers.spanout()\n\nInserts a time delay between emissions from the upstream. For example, if the upstream emits 1, 2, 3 in a quick succession, a spanout(1, TimeUnit.SECONDS) will emit 1 immediately, 2 after a second and 3 after a second after 2. You can specify the initial delay, a custom scheduler and if an upstream error should be delayed after the normal items or not.\n\n```java\nFlowable.range(1, 10)\n.compose(FlowableTransformers.spanout(1, 1, TimeUnit.SECONDS))\n.doOnNext(v -\u003e System.out.println(System.currentTimeMillis() + \": \" + v))\n.test()\n.awaitDone(20, TimeUnit.SECONDS)\n.assertResult(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);\n```\n\n### FlowableTransformers.mapFilter()\n\nA callback `Consumer` is called with the current upstream value and a `BasicEmitter` on which doXXX methods can be called\nto transform a value, signal an error or stop a sequence. If none of the `doXXX` methods is called, the current value is dropped and another is requested from upstream. The operator is a pass-through for downstream requests otherwise.\n\n```java\nFlowable.range(1, 10)\n.compose(FlowableTransformers.mapFilter((v, e) -\u003e {\n    if (v % 2 == 0) {\n        e.doNext(v * 2);\n    }\n    if (v == 5) {\n        e.doComplete();\n    }\n}))\n.test()\n.assertResult(4, 8);\n```\n\n### FlowableTransformers.onBackpressureTimeout()\n\nConsumes the upstream in an unbounded manner and buffers elements until the downstream requests but each buffered element has an associated timeout after which it becomes unavailable. Note that this may create discontinuities in the stream. In addition, an overload allows specifying the maximum buffer size and an eviction action which gets triggered when the buffer reaches its\ncapacity or elements time out.\n\n```java\nFlowable.intervalRange(1, 5, 100, 100, TimeUnit.MILLISECONDS)\n        .compose(FlowableTransformers\n            .onBackpressureTimeout(2, 100, TimeUnit.MILLISECONDS,\n                 Schedulers.single(), System.out::println))\n        .test(0)\n        .awaitDone(5, TimeUnit.SECONDS)\n        .assertResult();\n```\n\n### Flowables.repeat()\n\nRepeats a scalar value indefinitely (until the downstream actually cancels), honoring backpressure and supporting synchronous fusion and/or conditional fusion.\n\n```java\nFlowable.repeat(\"doesn't matter\")\n.map(v -\u003e ThreadLocalRandom.current().nextDouble())\n.take(100)\n.all(v -\u003e v \u003c 1d)\n.test()\n.assertResult(true);\n```\n\n### Flowables.repeatSupplier()\n\nRepeatedly calls a supplier, indefinitely (until the downstream actually cancels) or if the supplier throws or returns null (when it signals `NullPointerException`), honoring backpressure and supporting synchronous fusion and/or conditional fusion.\n\n```java\nFlowable.repeatSupplier(() -\u003e ThreadLocalRandom.current().nextDouble())\n.take(100)\n.all(v -\u003e v \u003c 1d)\n.test()\n.assertResult(true);\n```\n\n### FlowableTransformers.every()\n\nRelays every Nth item from upstream (skipping the in-between items).\n\n```java\nFlowable.range(1, 5)\n.compose(FlowableTransformers.\u003cInteger\u003eevery(2))\n.test()\n.assertResult(2, 4)\n```\n\n### Flowables.intervalBackpressure()\n\nEmit an ever increasing series of long values, starting from 0L and \"buffer\"\nemissions in case the downstream can't keep up. The \"buffering\" is virtual and isn't accompanied by increased memory usage if it happens for a longer\nperiod of time.\n\n```java\nFlowables.intervalBackpressure(1, TimeUnit.MILLISECONDS)\n.observeOn(Schedulers.single())\n.take(1000)\n.test()\n.awaitDone(5, TimeUnit.SECONDS)\n.assertValueCount(1000)\n.assertNoErrors()\n.assertComplete();\n```\n\n### FlowableTransformers.cacheLast()\n\nCaches the very last value of the upstream source and relays/replays it to Subscribers. The difference from `replay(1)` is that this operator is guaranteed\nto hold onto exactly one value whereas `replay(1)` may keep a reference to the one before too due to continuity reasons.\n\n```java\nFlowable\u003cInteger\u003e f = Flowable.range(1, 5)\n.doOnSubscribe(s -\u003e System.out.println(\"Subscribed!\"))\n.compose(FlowableTransformers.cacheLast());\n\n// prints \"Subscribed!\"\nf.test().assertResult(5);\n\n// doesn't print anything else\nf.test().assertResult(5);\nf.test().assertResult(5);\n```\n\n### FlowableTransformers.timeoutLast() \u0026 timeoutLastAbsolute()\n\nThe operator consumes the upstream to get to the last value but completes if the\nsequence doesn't complete within the specified timeout. A use case is when the upstream generates estimates, each better than the previous but we'd like to receive the last of it and not wait for a potentially infinite series.\n\nThere are two variants: relative timeout and absolute timeout.\n\nWith relative timeout, the operator restarts the timeout after each upstream item, cancels the upstream and emits that latest item if the timeout happens:\n\n```java\nFlowable.just(0, 50, 100, 400)\n.flatMap(v -\u003e Flowable.timer(v, TimeUnit.MILLISECONDS).map(w -\u003e v))\n.compose(FlowableTransformers.timeoutLast(200, TimeUnit.MILLISECONDS))\n.test()\n.awaitDone(5, TimeUnit.SECONDS)\n.assertResult(100);\n```\n\nWith absolute timeout, the upstream operator is expected to complete within the\nspecified amount of time and if it doesn't, the upstream gets cancelled and the latest item emitted.\n\n```java\nFlowable.just(0, 50, 100, 150, 400)\n.flatMap(v -\u003e Flowable.timer(v, TimeUnit.MILLISECONDS).map(w -\u003e v))\n.compose(FlowableTransformers.timeoutLastAbsolute(200, TimeUnit.MILLISECONDS))\n.test()\n.awaitDone(5, TimeUnit.SECONDS)\n.assertResult(150);\n```\n\n### FlowableTransformers.debounceFirst()\n\nDebounces the upstream by taking an item and dropping subsequent items until the specified amount of time elapses after the last item, after which the process repeats.\n\n```java\nFlowable.just(0, 50, 100, 150, 400, 500, 550, 1000)\n.flatMap(v -\u003e Flowable.timer(v, TimeUnit.MILLISECONDS).map(w -\u003e v))\n.compose(FlowableTransformers.debounceFirst(200, TimeUnit.MILLISECONDS))\n.test()\n.awaitDone(5, TimeUnit.SECONDS)\n.assertResult(0, 400, 1000);\n```\n\n### FlowableTransformers.switchFlatMap()\n\nThis is a combination of switchMap and a limited flatMap. It merges a maximum number of Publishers at once but if a new inner Publisher gets mapped in and the active count is at max, the oldest active Publisher is cancelled and the new inner Publisher gets flattened as well. Running with `maxActive == 1` is equivalent to the plain `switchMap`.\n\n```java\nFlowable.just(100, 300, 500)\n.flatMap(v -\u003e Flowable.timer(v, TimeUnit.MILLISECONDS).map(w -\u003e v))\n.compose(FlowableTransformers.switchFlatMap(v -\u003e {\n    if (v == 100) {\n        return Flowable.intervalRange(1, 3, 75, 100, TimeUnit.MILLISECONDS)\n           .map(w -\u003e \"A\" + w);\n    } else\n    if (v == 300) {\n        return Flowable.intervalRange(1, 3, 10, 100, TimeUnit.MILLISECONDS)\n           .map(w -\u003e \"B\" + w);\n    }\n    return Flowable.intervalRange(1, 3, 20, 100, TimeUnit.MILLISECONDS)\n        .map(w -\u003e \"C\" + w);\n}, 2)\n.test()\n.awaitDone(5, TimeUnit.SECONDS)\n.assertResult(\"A1\", \"A2\", \"B1\", \"A3\", \"B2\", \"C1\", B3\", \"C2\", \"C3);\n``` \n\n### FlowableTransformers.flatMapSync()\n\nA bounded-concurrency `flatMap` implementation optimized for mostly non-trivial, largely synchronous sources in mind and using different tracking method and configurable merging strategy: depth-first consumes each inner source as much as possible before switching to the next; breadth-first consumes one element from each source in a round-robin fashion. Overloads allow specifying the concurrency level (32 default), inner-prefetch (`Flowable.bufferSize()` default) and the merge strategy (depth-first default).\n\n```java\nFlowable.range(1, 1000)\n.compose(FlowableTransformers.flatMapSync(v -\u003e Flowable.range(1, 1000)))\n.test()\n.assertValueCount(1_000_000)\n.assertNoErrors()\n.assertComplete();\n```\n\n### FlowableTransformers.flatMapAsync()\n\nA bounded-concurrency `flatMap` implementation taking a scheduler which is used for collecting and emitting items from the active sources and freeing up the inner sources to keep producing. It also uses a different tracking method and configurable merging strategy: depth-first consumes each inner source as much as possible before switching to the next; breadth-first consumes one element from each source in a round-robin fashion. Overloads allow specifying the concurrency level (32 default), inner-prefetch (`Flowable.bufferSize()` default) and the merge strategy (depth-first default).\n\n```java\nFlowable.range(1, 1000)\n.compose(FlowableTransformers.flatMapAsync(v -\u003e Flowable.range(1, 1000), Schedulers.single()))\n.test()\n.awaitDone(5, TimeUnit.SECONDS)\n.assertValueCount(1_000_000)\n.assertNoErrors()\n.assertComplete();\n```\n\n### FlowableTransformers.switchIfEmpty() \u0026 switchIfEmptyArray()\n\nSwitches to the alternatives, one after the other if the main source or the previous alternative turns out to be empty.\n\n```java\nFlowable.empty()\n.compose(FlowableTransformers.switchIfEmpty(Arrays.asList(Flowable.empty(), Flowable.range(1, 5))))\n.test()\n.assertResult(1, 2, 3, 4, 5);\n\nFlowable.empty()\n.compose(FlowableTransformers.switchIfEmptyArray(Flowable.empty(), Flowable.range(1, 5)))\n.test()\n.assertResult(1, 2, 3, 4, 5);\n```\n\n### FlowableTransformers.expand()\n\nStreams values from the main source, maps each of them onto another Publisher and recursively streams those Publisher values until all Publishers terminate.\nTwo recursing mode is available: breadth-first will stream the main source (level 1), then the Publishers generated by its items (level 2), then the Publishers generated by the level 2\nand so on; depth-first will take an item from the main source, maps it to a Publisher then takes an item from this Publisher and maps it further.\n\n```java\nFlowable.just(10)\n.compose(FlowableTransformers.expand(v -\u003e v == 0 ? Flowable.empty() : Flowable.just(v - 1)))\n.test()\n.assertResult(10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0);\n```\n\nDepth-first example:\n\n```java\nFlowable.just(new File(\".\"))\n.compose(FlowableTransformers.expand(file -\u003e {\n    if (file.isDirectory()) {\n        File[] files = file.listFiles();\n        if (files != null) {\n            return Flowable.fromArray(files);\n        }\n    }\n    return Flowable.empty();\n}, ExpandStrategy.DEPTH_FIRST))\n.subscribe(System.out::println);\n\n// prints something like\n// ~/git/RxJavaExtensions\n// ~/git/RxJavaExtensions/src\n// ~/git/RxJavaExtensions/src/main\n// ~/git/RxJavaExtensions/src/main/java\n// ~/git/RxJavaExtensions/src/main/java/hu\n// ~/git/RxJavaExtensions/src/main/java/hu/akarnokd\n// ~/git/RxJavaExtensions/src/main/java/hu/akarnokd/rxjava3\n// ~/git/RxJavaExtensions/src/main/java/hu/akarnokd/rxjava3/operators\n// ~/git/RxJavaExtensions/src/main/java/hu/akarnokd/rxjava3/operators/FlowableExpand.java\n// ...\n// ~/git/RxJavaExtensions/src/test\n// ~/git/RxJavaExtensions/src/test/java\n```\n\nBreadth-first example:\n\n```java\nFlowable.just(new File(\".\"))\n.compose(FlowableTransformers.expand(file -\u003e {\n    if (file.isDirectory()) {\n        File[] files = file.listFiles();\n        if (files != null) {\n            return Flowable.fromArray(files);\n        }\n    }\n    return Flowable.empty();\n}, ExpandStrategy.BREADTH_FIRST))\n.subscribe(System.out::println);\n\n// prints something like\n// ~/git/RxJavaExtensions\n// ~/git/RxJavaExtensions/src\n// ~/git/RxJavaExtensions/build\n// ~/git/RxJavaExtensions/gradle\n// ~/git/RxJavaExtensions/HEADER\n// ~/git/RxJavaExtensions/README.md\n// ...\n// ~/git/RxJavaExtensions/src/main\n// ~/git/RxJavaExtensions/src/test\n// ~/git/RxJavaExtensions/src/jmh\n// ~/git/RxJavaExtensions/src/main/java\n// ~/git/RxJavaExtensions/src/main/java/hu\n// ~/git/RxJavaExtensions/src/main/java/hu/akarnokd\n// ~/git/RxJavaExtensions/src/main/java/hu/akarnokd/rxjava3\n// ~/git/RxJavaExtensions/src/main/java/hu/akarnokd/rxjava3/operators\n// ~/git/RxJavaExtensions/src/main/java/hu/akarnokd/rxjava3/math\n// ~/git/RxJavaExtensions/src/main/java/hu/akarnokd/rxjava3/async\n// ~/git/RxJavaExtensions/src/main/java/hu/akarnokd/rxjava3/debug\n// ...\n// ~/git/RxJavaExtensions/src/main/java/hu/akarnokd/rxjava3/operators/FlowableExpand.java\n// ~/git/RxJavaExtensions/src/main/java/hu/akarnokd/rxjava3/operators/FlowableTransformers.java\n```\n\n### FlowableTransformers.mapAsync()\n\n**Also available as `ObservableTransformers.mapAsync().`**\n\nThis is an \"asynchronous\" version of the regular `map()` operator where an upstream value is mapped to a `Publisher` which\nis expected to emit a single value to be the result itself or through a combiner function become the result. Only\none such `Publisher` is executed at once and the source order is kept. If the `Publisher` is empty, no value is emitted\nand the sequence continues with the next upstream value. If the `Publisher` has more than one element, only the first\nelement is considered and the inner sequence gets cancelled after that first element.\n\n```java\nFlowable.range(1, 5)\n.compose(FlowableTransformers.mapAsync(v -\u003e \n    Flowable.just(v + 1).delay(1, TimeUnit.SECONDS)))\n.test()\n.awaitDone(10, TimeUnit.SECONDS)\n.assertResult(2, 3, 4, 5, 6);\n```\n\nExample when using a combiner function to combine the original and the generated values:\n\n```java\nFlowable.range(1, 5)\n.compose(FlowableTransformers.mapAsync(v -\u003e \n    Flowable.just(v + 1).delay(1, TimeUnit.SECONDS)), (v, w) -\u003e v + \"-\" + w)\n.test()\n.awaitDone(10, TimeUnit.SECONDS)\n.assertResult(\"1-2\", \"2-3\", \"3-4\", \"4-5\", \"5-6\");\n```\n\n### FlowableTransformers.filterAsync()\n\n**Also available as `ObservableTransformers.filterAsync().`**\n\nThis is an \"asynchronous\" version of the regular `filter()` operator where an upstream value is mapped to a `Publisher`\nwhich is expected to emit a single `true` or `false` that indicates the original value should go through. An empty `Publisher`\nis considered to be a `false` response. If the `Publisher` has more than one element, only the first\nelement is considered and the inner sequence gets cancelled after that first element.\n\n```java\nFlowable.range(1, 10)\n.compose(FlowableTransformers.filterAsync(v -\u003e Flowable.just(v).delay(1, TimeUnit.SECONDS).filter(v % 2 == 0))\n.test()\n.awaitDone(15, TimeUnit.SECONDS)\n.assertResult(2, 4, 6, 8, 10);\n```\n\n### FlowableTransformers.refCount()\n\n*Moved to RxJava as standard operators: \n[ConnectableObservable.refCount](http://reactivex.io/RxJava/3.x/javadoc/io/reactivex/rxjava3/observables/ConnectableObservable.html#refCount-int-long-java.util.concurrent.TimeUnit-io.reactivex.rxjava3.core.Scheduler-), \n[ConnectableFlowable.refCount](http://reactivex.io/RxJava/3.x/javadoc/io/reactivex/rxjava3/flowables/ConnectableFlowable.html#refCount-int-long-java.util.concurrent.TimeUnit-io.reactivex.rxjava3.core.Scheduler-).\n\n### Flowables.zipLatest()\n\nZips the latest values from multiple sources and calls a combiner function for them.\nIf one of the sources is faster then the others, its unconsumed values will be overwritten by newer\nvalues. \nUnlike `combineLatest`, source items are participating in the combination at most once; i.e., the \noperator emits only if all sources have produced an item.\nThe emission speed of this operator is determined by the slowest emitting source and the speed of the downstream consumer.\nThere are several overloads available: methods taking 2-4 sources and the respective combiner functions, a method taking a\nvarargs of sources and a method taking an `Iterable` of source `Publisher`s.\nThe operator supports combining and scheduling the emission of the result via a custom `Scheduler`, thus\nallows avoiding the buffering effects of the `observeOn` operator.\nThe operator terminates if any of the sources runs out of items and terminates by itself. \nThe operator works with asynchronous sources the best; synchronous sources may get consumed fully\nin order they appear among the parameters and possibly never emit more than one combined result even\nif the last source has more than one item.\n\n```java\nTestScheduler scheduler = new TestScheduler();\n\nTestSubscriber\u003cString\u003e ts = Flowables.zipLatest(toString,\n        Flowable.intervalRange(1, 6, 99, 100, TimeUnit.MILLISECONDS, scheduler),\n        Flowable.intervalRange(4, 3, 200, 200, TimeUnit.MILLISECONDS, scheduler)\n)\n.test();\n\nscheduler.advanceTimeBy(200, TimeUnit.MILLISECONDS);\n\nts.assertValue(\"[2, 4]\");\n\nscheduler.advanceTimeBy(200, TimeUnit.MILLISECONDS);\n\nts.assertValues(\"[2, 4]\", \"[4, 5]\");\n\nscheduler.advanceTimeBy(200, TimeUnit.MILLISECONDS);\n\nts.assertResult(\"[2, 4]\", \"[4, 5]\", \"[6, 6]\");\n```\n\n### FlowableTransformers.coalesce()\n\nCoalesces items from upstream into a container via a consumer and emits the container if\nthere is a downstream demand, otherwise it keeps coalescing into the same container. Note\nthat the operator keeps an internal unbounded buffer to collect up upstream values before\nthe coalescing happens and thus a computational heavy downstream hogging the emission thread\nmay lead to excessive memory usage. It is recommended to use `observeOn` in this case.\n\n```java\nFlowable.range(1, 5)\n.compose(FlowableTransformers.coalesce(ArrayList::new, (a, b) -\u003e a.add(b))\n.test(1)\n.assertValue(Arrays.asList(1))\n.requestMore(1)\n.assertResult(Arrays.asList(1), Arrays.asList(2, 3, 4, 5));\n```\n\n### FlowableTransformers.windowWhile\n\nEmits elements into a Flowable window while the given predicate returns true. \nIf the predicate returns false, a new Flowable window is emitted.\n\n```java\nFlowable.just(\"1\", \"2\", \"#\", \"3\", \"#\", \"4\", \"#\")\n.compose(FlowableTransformers.windowWhile(v -\u003e !\"#\".equals(v)))\n.flatMapSingle(v -\u003e v.toList())\n.test()\n.assertResult(\n    Arrays.asList(\"1\", \"2\"),\n    Arrays.asList(\"#\", \"3\"),\n    Arrays.asList(\"#\", \"4\"),\n    Arrays.asList(\"#\")\n);\n```\n\n### FlowableTransformers.windowUntil\n\nEmits elements into a Flowable window until the given predicate returns true \nat which point a new Flowable window is emitted.\n\n```java\nFlowable.just(\"1\", \"2\", \"#\", \"3\", \"#\", \"4\", \"#\")\n.compose(FlowableTransformers.windowUntil(v -\u003e \"#\".equals(v)))\n.flatMapSingle(v -\u003e v.toList())\n.test()\n.assertResult(\n    Arrays.asList(\"1\", \"2\", \"#\"),\n    Arrays.asList(\"3\", \"#\"),\n    Arrays.asList(\"4\", \"#\")\n);\n```\n\n### FlowableTransformers.windowSplit\n\nEmits elements into a Flowable window until the given predicate returns true at which\npoint a new Flowable window is emitted; the particular item will be dropped.\n\n```java\nFlowable.just(\"1\", \"2\", \"#\", \"3\", \"#\", \"4\", \"#\")\n.compose(FlowableTransformers.windowSplit(v -\u003e \"#\".equals(v)))\n.flatMapSingle(v -\u003e v.toList())\n.test()\n.assertResult(\n    Arrays.asList(\"1\", \"2\"),\n    Arrays.asList(\"3\"),\n    Arrays.asList(\"4\")\n);\n```\n\n### FlowableTransformers.indexOf\n\nReturns the first index of an element that matches a predicate or -1L if no elements match.\n(Also available for `Observable`s as `ObservableTransformers.indexOf()`.)\n\n```java\nFlowable.range(1, 5)\n.compose(FlowableTransformers.indexOf(v -\u003e v == 5))\n.test()\n.assertResult(4);\n```\n\n### FlowableTransformers.requestObserveOn\n\nRequests items one-by-one from the upstream on the specified `Scheduler` and emits the received items from the\ngiven `Scheduler` as well in a fashion that allows tasks to be interleaved on the target `Scheduler` (aka \"fair\" use)\non a much more granular basis than `Flowable.observeOn`.\n\n```java\nFlowable.range(1, 5)\n.compose(FlowableTransformers.requestObserveOn(Schedulers.single()))\n.test()\n.awaitDone(5, TimeUnit.SECONDS)\n.assertResult(1, 2, 3, 4, 5)\n;\n```\n\n### FlowableTransformers.requestSample\n\nPeriodically (and after an optional initial delay) issues a single `request(1)` to the upstream and forwards the\nitems to a downstream that must be ready to receive them.\n\n```java\nFlowables.repeatCallable(() -\u003e 1)\n.compose(FlowableTransformers.requestSample(1, TimeUnit.SECONDS, Schedulers.single()))\n.take(5)\n.test()\n.awaitDone(7, TimeUnit.SECONDS)\n.assertResult(1, 1, 1, 1, 1);\n```\n\nThe sampling can be of a more complex pattern by using another `Publisher` as the indicator when to request:\n\n```java\nFlowables.repeatCallable(() -\u003e 1)\n.compose(FlowableTransformers.requestSample(\n    Flowable.fromArray(100, 500, 1000, 2000, 5000)\n    .concatMap(delay -\u003e Flowable.timer(delay, TimeUnit.MILLISECONDS))\n))\n.take(5)\n.test()\n.awaitDone(10, TimeUnit.SECONDS)\n.assertResult(1, 1, 1, 1, 1);\n```\n\n### FlowableTransformers.switchOnFirst\n\nDepending on the very first item of the source sequence, see if that item matches a predicate and if so, switch to\na generated alternative sequence.\n\n```java\nFlowable.fromCallable(() -\u003e Math.random())\n.compose(FlowableTransformers.switchOnFirst(\n     v -\u003e v \u003c 0.5,\n     v -\u003e Flowable.just(v * 6)\n))\n.repeat(20)\n.subscribe(System.out::println);\n```\n\nNote that if one wishes to switch always based on the first item, one can use `v -\u003e true` for the predicate and return the resumption\nsequence conditionally in the selector property.\n\nNote that the initial value is not emitted if the predicate returns true. If one wants to keep that in the sequence, concatenate\nit to the sequence returned from the selector: `v -\u003e Flowable.just(v).concatWith(theNewSequence)`.\n\n### ObservableTransformers.observeOnDrop\n\nDrop upstream items while the downstream is working on an item in its `onNext` method on an `Scheduler`.\nThis is similar to `Flowable.onBackpressureDrop` but instead dropping on a lack of requests, items\nfrom upstream are dropped while a work indicator is active during the execution of the downstream's\n`onNext` method.\n\n```java\nObservable.range(1, 1000000)\n.compose(ObservableTransformers.observeOnDrop(Schedulers.io()))\n.doOnNext(v -\u003e Thread.sleep(1))\n.test()\n.awaitDone(5, TimeUnit.SECONDS)\n.assertOf(to -\u003e {\n    assertTrue(to.getValueCount() \u003e= 1 \u0026\u0026 to.getValueCount() \u003c= 1000000); \n});\n```\n\n### ObservableTransformers.observeOnLatest\n\nKeeps the latest item from the upstream while the downstream working on the current item in its `onNext`\nmethdo on a `Scheduler`, so that when it finishes with the current item, it can continue immediately\nwith the latest item from the upstream.\nThis is similar to `Flowable.onBackpressureLatest` except that the latest item is always picked up, not just when\nthere is also a downstream demand for items.\n\n```java\nObservable.range(1, 1000000)\n.compose(ObservableTransformers.observeOnLatest(Schedulers.io()))\n.doOnNext(v -\u003e Thread.sleep(1))\n.test()\n.awaitDone(5, TimeUnit.SECONDS)\n.assertOf(to -\u003e {\n    assertTrue(to.getValueCount() \u003e= 1 \u0026\u0026 to.getValueCount() \u003c= 1000000); \n});\n```\n\n### Flowables.generateAsync\n\nA source operator to bridge async APIs that can be repeatedly called to produce the next item \n(or terminate in some way) asynchronously and only call the API again once the result has \nbeen received and delivered to the downstream, while honoring the backpressure of the downstream.\nThis means if the downstream stops requesting, the API won't be called until the latest result has \nbeen requested and consumed by the downstream.\n\nExample APIs could be [AsyncEnumerable](https://github.com/akarnokd/async-enumerable#async-enumerable)\nstyle, coroutine style or [async-await](https://github.com/electronicarts/ea-async).\n\nLet's assume there is an async API with the following interface definition:\n\n```java\ninterface AsyncAPI\u003cT\u003e extends AutoCloseable {\n\n    CompletableFuture\u003cVoid\u003e nextValue(Consumer\u003c? super T\u003e onValue);\n\n}\n```\n\nWhen the call succeeds, the `onValue` is invoked with it. If there are no more items, the\n` CompletableFuture`  returned by the last ` nextValue`  is completed (with ` null` ).\nIf there is an error, the same ` CompletableFuture`  is completed exceptionally. Each\n` nextValue`  invocation creates a fresh ` CompletableFuture`  which can be cancelled\nif necessary. ` nextValue`  should not be invoked again until the ` onValue`  callback\nhas been notified.\n\nAn instance of this API can be obtained on demand, thus the state of this operator consists of the\n` AsyncAPI`  instance supplied for each individual {@code Subscriber}. The API can be transformed into\na ` Flowable`  as follows:\n\n```java\nFlowable\u003cInteger\u003e source = Flowables.\u003cInteger, AsyncAPI\u003cInteger\u003e\u003egenerateAsync(\n\n    // create a fresh API instance for each individual Subscriber\n    () -\u003e new AsyncAPIImpl\u003cInteger\u003e(),\n\n    // this BiFunction will be called once the operator is ready to receive the next item\n    // and will invoke it again only when that item is delivered via emitter.onNext()\n    (state, emitter) -\u003e {\n        // issue the async API call\n        CompletableFuture\u003cVoid\u003e f = state.nextValue(\n\n            // handle the value received\n            value -\u003e {\n\n                // we have the option to signal that item\n                if (value % 2 == 0) {\n                    emitter.onNext(value);\n                } else if (value == 101) {\n                    // or stop altogether, which will also trigger a cleanup\n                    emitter.onComplete();\n                } else {\n                    // or drop it and have the operator start a new call\n                    emitter.onNothing();\n                }\n            }\n        );\n\n        // This API call may not produce further items or fail\n        f.whenComplete((done, error) -\u003e {\n            // As per the CompletableFuture API, error != null is the error outcome,\n            // done is always null due to the Void type\n            if (error != null) {\n                emitter.onError(error);\n            } else {\n                emitter.onComplete();\n            }\n        });\n\n        // In case the downstream cancels, the current API call\n        // should be cancelled as well\n        emitter.replaceCancellable(() -\u003e f.cancel(true));\n\n        // some sources may want to create a fresh state object\n        // after each invocation of this generator\n        return state;\n    },\n\n    // cleanup the state object\n    state -\u003e { state.close(); }\n);\n```\n\n### FlowableTransformers.partialCollect\n\nAllows converting upstream items into output objects where an upstream item\nmay represent such output objects partially or may represent more than one\noutput object.\n\nFor example, given a stream of {@code byte[]} where each array could contain part\nof a larger object, and thus more than one subsequent arrays are required to construct\nthe output object. The same array could also contain more than one output items, therefore,\nit should be kept around in case the output is backpressured.\n\nThis example shows, given a flow of `String`s with embedded separator `|`, how one\ncan split them along the separator and have individual items returned, even when\nthey span multiple subsequent items (`cdefgh`) or more than one is present in a source item (`mno||pqr|s`). \n\n```java\nFlowable.just(\"ab|cdef\", \"gh|ijkl|\", \"mno||pqr|s\", \"|\", \"tuv|xy\", \"|z\")\n.compose(FlowableTransformers.partialCollect(new Consumer\u003cPartialCollectEmitter\u003cString, Integer, StringBuilder, String\u003e\u003e() {\n    @Override\n    public void accept(\n            PartialCollectEmitter\u003cString, Integer, StringBuilder, String\u003e emitter)\n            throws Exception {\n        Integer idx = emitter.getIndex();\n        if (idx == null) {\n            idx = 0;\n        }\n        StringBuilder sb = emitter.getAccumulator();\n        if (sb == null) {\n            sb = new StringBuilder();\n            emitter.setAccumulator(sb);\n        }\n\n        if (emitter.demand() != 0) {\n\n            boolean d = emitter.isComplete();\n            if (emitter.size() != 0) {\n                String str = emitter.getItem(0);\n\n                int j = str.indexOf('|', idx);\n\n                if (j \u003e= 0) {\n                    sb.append(str.substring(idx, j));\n                    emitter.next(sb.toString());\n                    sb.setLength(0);\n                    idx = j + 1;\n                } else {\n                    sb.append(str.substring(idx));\n                    emitter.dropItems(1);\n                    idx = 0;\n                }\n            } else if (d) {\n                if (sb.length() != 0) {\n                    emitter.next(sb.toString());\n                }\n                emitter.complete();\n                return;\n            }\n        }\n\n        emitter.setIndex(idx);\n    }\n}, Functions.emptyConsumer(), 128))\n.test()\n.assertResult(\n        \"ab\",\n        \"cdefgh\",\n        \"ijkl\",\n        \"mno\",\n        \"\",\n        \"pqr\",\n        \"s\",\n        \"tuv\",\n        \"xy\",\n        \"z\"\n);\n```\n\nNote that the resulting sequence completes only when the handler calls `emitter.complete()` because\nthe upstream's termination may not mean all output has been generated.\n\nThe operator allows generating more than one item per invocation of the handler, but the handler should\nalways check `emitter.demand()` if the downstream is ready to receive and `emitter.size()` to\nsee if there are more upstream items to produce. The operator will call the handler again if it\ndetects an item has been produced, therefore, there is no need to exhaustively process all source\nitems on one call (which may not be possible if only partial data is available).\n\nThe cleanup callback is called to release the source items if necessary (such as pooled `ByteBuffer`s)\nwhen it is dropped via `emitter.dropItems()` or when the operator gets cancelled.\n\nThe `emitter.dropItems()` has an additional function, indicate that the upstream can send more items\nas the previous ones have been consumed by the handler. Note though that the operator uses a low\nwatermark algorithm to replenish items from upstream (also called stable prefetch), that is, when\nmore than 75% of the `prefetch` parameter has been consumed, that many items are requested from\nthe upstream. This reduces an overhead the one-by-one requesting would have.\n\n### ObservableTransformers.flatMapDrop\n\nFlatMap only one `ObservableSource` at a time and ignore upstream values until it terminates.\nIn the following example, click events are practically ignored while the `requestData`\nis active.\n\n```java\nObservable\u003cClickEvent\u003e clicks = ...\n\nclicks.compose(\n    ObservableTransformers.flatMapDrop(click -\u003e \n        service.requestData()\n        .subscribeOn(Schedulers.io())\n    )\n)\n.observeOn(mainThread())\n.subscribe(data -\u003e { /* ... */ });\n```\n\n### ObservableTransformers.flatMapLatest\n\nFlatMap only one `ObservableSource` at a time and keep the latest upstream value until it terminates\nand resume with the `ObservableSource` mapped for that latest upstream value.\n\nUnlike `flatMapDrop`, this operator will resume with the latest upstream value and not wait for the upstream\nto signal a fresh item.\n\n```java\nObservable\u003cClickEvent\u003e clicks = ...\n\nclicks.compose(\n    ObservableTransformers.flatMapLatest(click -\u003e \n        service.requestData()\n        .subscribeOn(Schedulers.io())\n    )\n)\n.observeOn(mainThread())\n.subscribe(data -\u003e { /* ... */ });\n```\n\n### FlowableTransformers.errorJump\n\nAllows an upstream error to jump over an inner transformation and is\nthen re-applied once the inner transformation's returned `Flowable` terminates.\n\n\n```java\nFlowable.range(1, 5)\n    .concatWith(Flowable.\u003cInteger\u003eerror(new TestException()))\n    .compose(FlowableTransformers.errorJump(new FlowableTransformer\u003cInteger, List\u003cInteger\u003e\u003e() {\n        @Override\n        public Publisher\u003cList\u003cInteger\u003e\u003e apply(Flowable\u003cInteger\u003e v) {\n            return v.buffer(3);\n        }\n    }))\n    .test()\n    .assertFailure(TestException.class, Arrays.asList(1, 2, 3), Arrays.asList(4, 5));\n```\n\nAvailable also: `ObservableTransformers.errorJump()`\n\n### flatMap signal\n\nMap the upstream signals onto some reactive type and relay its events to the downstream.\n\nAvailability:\n\n\u003c!-- - `Flowables.flatMap{Completable|Single|Maybe}` --\u003e\n\u003c!-- - `Observables.flatMap{Completable|Single|Maybe}` --\u003e\n- `Single`\n  - `SingleTransformers.flatMap` (use with `Single.compose()`)\n  - `Singles.flatMapCompletable` (use with `Single.to()`)\n  - `Singles.flatMapMaybe` (use with `Single.to()`)\n  - `Singles.flatMapObservable` (use with `Single.to()`)\n  - `Singles.flatMapFlowable` (use with `Single.to()`)\n- `Maybe`\n  - `MaybeTransformers.flatMap` (use with `Maybe.compose()`)\n  - `Maybes.flatMapCompletable` (use with `Maybe.to()`)\n  - `Maybes.flatMapSingle` (use with `Maybe.to()`)\n  - `Maybes.flatMapObservable` (use with `Maybe.to()`)\n  - `Maybes.flatMapFlowable` (use with `Maybe.to()`)\n- `Completable`\n  - `CompletableTransformers.flatMap` (use with `Completable.compose()`)\n  - `Completables.flatMapSingle` (use with `Completable.to()`)\n  - `Completables.flatMapMaybe` (use with `Completable.to()`)\n  - `Completables.flatMapObservable` (use with `Completable.to()`)\n  - `Completables.flatMapFlowable` (use with `Completable.to()`)\n\nNote: same-type transformations for [Flowable.flatMap](http://reactivex.io/RxJava/3.x/javadoc/io/reactivex/Flowable.html#flatMap-io.reactivex.functions.Function-io.reactivex.functions.Function-io.reactivex.functions.Supplier-), [Observable.flatMap](http://reactivex.io/RxJava/3.x/javadoc/io/reactivex/Observable.html#flatMap-io.reactivex.functions.Function-io.reactivex.functions.Function-io.reactivex.functions.Supplier-) already exist in RxJava.\n\n## Custom parallel operators and transformers\n\n### ParallelTransformers.sumX()\n\nSums the numerical values on each rail as integer, long or double.\n\n```java\nFlowable.range(1, 5)\n.parallel(1)\n.compose(ParallelTransformers.\u003cInteger\u003esumInteger())\n.sequential()\n.test()\n.assertResult(15);\n\nFlowable.range(1, 5)\n.parallel(1)\n.compose(ParallelTransformers.\u003cInteger\u003esumLong())\n.sequential()\n.test()\n.assertResult(15L);\n\nFlowable.range(1, 5)\n.parallel(1)\n.compose(ParallelTransformers.\u003cInteger\u003esumDouble())\n.sequential()\n.test()\n.assertResult(15d);\n```\n\n### ParallelTransformers.orderedMerge()\n\nMerges the source `ParallelFlowable` rails in an ordered fashion picking the smallest of the available value from\nthem (determined by their natural order or via a `Comparator`). The operator supports delaying error and setting\nthe internal prefetch amount.\n\n```java\nParallelFlowable.fromArray(\n    Flowable.just(1, 3, 5, 7),\n    Flowable.just(0, 2, 4, 6, 8, 10)\n)\n.to(p -\u003e ParallelTransformers.orderedMerge(p, (a, b) -\u003e a.compareTo(b)))\n.test()\n.assertResult(0, 1, 2, 3, 4, 5, 6, 7, 8, 10);\n```\n\n\n## Special Publisher implementations\n\n### Nono - 0-error publisher\n\nThe `Publisher`-based sibling of the `Completable` type. The usage is practically the same as `Completable` with the exception that because `Nono` implements the Reactive-Streams `Publisher`, you can use it directly with operators of `Flowable` that accept `Publisher` in some form.\n\nExamples:\n\n```java\nNono.fromAction(() -\u003e System.out.println(\"Hello world!\"))\n    .subscribe();\n\nNono.fromAction(() -\u003e System.out.println(\"Hello world!\"))\n    .delay(1, TimeUnit.SECONDS)\n    .blockingSubscribe();\n\nNono.complete()\n    .test()\n    .assertResult();\n\nNono.error(new IOException())\n    .test()\n    .assertFailure(IOException.class);\n\nFlowable.range(1, 10)\n    .to(Nono::fromPublisher)\n    .test()\n    .assertResult();\n```\n\n#### NonoProcessor\n\nA hot, Reactive-Streams `Processor` implementation of `Nono`.\n\n```java\nNonoProcessor np = NonoProcessor.create();\n\nTestSubscriber\u003cVoid\u003e ts = np.test();\n\nnp.onComplete();\n\nts.assertResult();\n```\n\n\n### Solo - 1-error publisher\n\nThe `Publisher`-based sibling of the `Single` type. The usage is practically the same as `Single` with the exception that because `Solo` implements the Reactive-Streams `Publisher`, you can use it directly with operators of `Flowable` that accept `Publisher` in some form.\n\n`Solo`'s emission protocol is a restriction over the general `Publisher` protocol: one either calls `onNext` followed by `onComplete` or just `onError`. Operators will and should never call `onNext` followed by `onError` or `onComplete` on its own. Note that some operators may react to `onNext` immediately not waiting for an `onComplete` but on their emission side, `onComplete` is always called after an `onNext`.\n\nExamples:\n\n```java\nSolo.fromCallable(() -\u003e {\n    System.out.println(\"Hello world!\");\n    return 1;\n}).subscribe();\n\nSolo.fromCallable(() -\u003e \"Hello world!\")\n    .delay(1, TimeUnit.SECONDS)\n    .blockingSubscribe(System.out::println);\n\nFlowable.concat(Solo.just(1), Solo.just(2))\n.test()\n.assertResult(1, 2);\n```\n\n#### SoloProcessor\n\nA hot, Reactive-Streams `Processor` implementation of `Solo`.\n\n```java\nSoloProcessor\u003cInteger\u003e sp = SoloProcessor.create();\n\nTestSubscriber\u003cInteger\u003e ts = sp.test();\n\nsp.onNext(1);\nsp.onComplete();\n\nts.assertResult(1);\n```\n\n### Perhaps - 0-1-error publisher\n\nThe `Publisher`-based sibling of the `Maybe` type. The usage is practically the same as `Maybe` with the exception that because `Perhaps` implements the Reactive-Streams `Publisher`, you can use it directly with operators of `Flowable` that accept `Publisher` in some form.\n\n`Perhaps`'s emission protocol is a restriction over the general `Publisher` protocol: one either calls `onNext` followed by `onComplete`, `onComplete` only or just `onError`. Operators will and should never call `onNext` followed by `onError` on its own. Note that some operators may react to `onNext` immediately not waiting for an `onComplete` but on their emission side, `onComplete` is always called after an `onNext`.\n\nExamples:\n\n```java\nPerhaps.fromCallable(() -\u003e {\n    System.out.println(\"Hello world!\");\n    return 1;\n}).subscribe();\n\nPerhaps.fromCallable(() -\u003e \"Hello world!\")\n    .delay(1, TimeUnit.SECONDS)\n    .blockingSubscribe(System.out::println);\n\nFlowable.concat(Perhaps.just(1), Perhaps.just(2))\n.test()\n.assertResult(1, 2);\n\nPerhaps.fromCallable(() -\u003e {\n    System.out.println(\"Hello world!\");\n    return null;  // null is considered to indicate an empty Perhaps\n})\n.test()\n.assertResult();\n```\n\n#### PerhapsProcessor\n\nA hot, Reactive-Streams `Processor` implementation of `Perhaps`.\n\n```java\nPerhapsProcessor\u003cInteger\u003e ph = PerhapsProcessor.create();\n\nTestSubscriber\u003cInteger\u003e ts = ph.test();\n\nph.onNext(1);\nph.onComplete();\n\nts.assertResult(1);\n```\n\n## Custom consumers\n\nThe utility classes can be found in `hu.akarnokd.rxjava3.consumers` package.\n\n\n### FlowableConsumers\n\n#### `subscribeAutoDispose`\n\nWraps the given `onXXX` callbacks into a `Disposable` `Subscriber`,\nadds it to the given `CompositeDisposable` and ensures, that if the upstream\ncompletes or this particular `Disposable` is disposed, the `Subscriber` is removed\nfrom the given composite.\n\nThe Subscriber will be removed *after* the callback for the terminal event has been invoked.\n\n```java\nCompositeDisposable composite = new CompositeDisposable();\n\nDisposable d = FlowableConsumers.subscribeAutoDispose(\n    Flowable.just(1), composite,\n    System.out::println, Throwable::printStackTrace, () -\u003e System.out.println(\"Done\")\n);\n\nassertEquals(0, composite.size());\n\n// --------------------------\n\nDisposable d2 = FlowableConsumers.subscribeAutoDispose(\n    Flowable.never(), composite,\n    System.out::println, Throwable::printStackTrace, () -\u003e System.out.println(\"Done\")\n);\n\nassertEquals(1, composite.size());\n\nd2.dispose();\n\nassertEquals(0, composite.size());\n```\n\nThe Subscriber will be removed after the callback for the terminal event has been invoked.\n\n### ObservableConsumers\n\n- `subscribeAutoDispose`: Similar to `FlowableConsumers.subscribeAutoDispose()` but targeting `Observable`s and `Observer`s-like\nconsumers.\n\n### SingleConsumers\n\n- `subscribeAutoDispose`: Similar to `FlowableConsumers.subscribeAutoDispose()` but targeting `Single`s and `SingleObserver`s-like\nconsumers.\n\n### MaybeConsumers\n\n- `subscribeAutoDispose`: Similar to `FlowableConsumers.subscribeAutoDispose()` but targeting `Maybe`s and `MaybeObserver`s-like\nconsumers.\n\n### CompletableConsumers\n\n- `subscribeAutoDispose`: Similar to `FlowableConsumers.subscribeAutoDispose()` but targeting `Completable`s and `CompletableObserver`s-like\nconsumers.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fakarnokd%2Frxjavaextensions","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fakarnokd%2Frxjavaextensions","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fakarnokd%2Frxjavaextensions/lists"}