├── .gitignore ├── COPYING ├── LICENSE ├── README.md ├── akka ├── build.gradle └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── lightbend │ │ │ └── reactivestreams │ │ │ └── utils │ │ │ ├── AkkaEngine.java │ │ │ ├── AkkaEngineProvider.java │ │ │ └── TerminationWatcher.java │ └── resources │ │ └── META-INF │ │ └── services │ │ └── org.reactivestreams.utils.ReactiveStreamsEngine │ └── test │ └── java │ └── com │ └── lightbend │ └── reactivestreams │ └── utils │ ├── AkkaEngineProviderTest.java │ └── AkkaReactiveStreamsTck.java ├── api ├── build.gradle └── src │ └── main │ └── java │ └── org │ └── reactivestreams │ └── utils │ ├── CompletionBuilder.java │ ├── InternalStages.java │ ├── Predicates.java │ ├── ProcessorBuilder.java │ ├── PublisherBuilder.java │ ├── ReactiveStreams.java │ ├── ReactiveStreamsBuilder.java │ ├── ReactiveStreamsEngine.java │ ├── Reductions.java │ ├── SubscriberBuilder.java │ ├── SubscriberWithResult.java │ └── package-info.java ├── build.gradle ├── examples ├── build.gradle └── src │ └── main │ └── java │ └── org │ └── reactivestreams │ └── utils │ └── examples │ └── Examples.java ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── impl ├── build.gradle └── src │ ├── main │ └── java │ │ └── org │ │ └── reactivestreams │ │ └── utils │ │ └── impl │ │ ├── BuiltGraph.java │ │ ├── CancelStage.java │ │ ├── CaptureTerminationStage.java │ │ ├── CollectStage.java │ │ ├── ConcatStage.java │ │ ├── ConnectorStage.java │ │ ├── FailedStage.java │ │ ├── FilterStage.java │ │ ├── FindFirstStage.java │ │ ├── FlatMapCompletionStage.java │ │ ├── FlatMapIterableStage.java │ │ ├── FlatMapStage.java │ │ ├── GraphStage.java │ │ ├── MapStage.java │ │ ├── MutexExecutor.java │ │ ├── OfStage.java │ │ ├── Probes.java │ │ ├── PublisherOutlet.java │ │ ├── ReactiveStreamsEngineImpl.java │ │ ├── StageInlet.java │ │ ├── StageOutlet.java │ │ ├── StageOutletInlet.java │ │ ├── SubscriberInlet.java │ │ ├── TakeWhileStage.java │ │ └── WrappedProcessor.java │ └── test │ └── java │ └── org │ └── reactivestreams │ └── utils │ └── impl │ └── ReactiveStreamsEngineImplTck.java ├── rxjava ├── build.gradle └── src │ ├── main │ └── java │ │ └── com │ │ └── lightbend │ │ └── reactivestreams │ │ └── rxjava │ │ ├── BridgedProcessor.java │ │ ├── CancelInjectingPublisher.java │ │ ├── FlowSubscriberAdapter.java │ │ ├── RxJavaEngine.java │ │ ├── TerminationWatchingSubscriber.java │ │ └── WrappedProcessor.java │ └── test │ └── java │ └── com │ └── lightbend │ └── reactivestreams │ └── rxjava │ └── RxJavaReactiveStreamsTck.java ├── settings.gradle ├── spi ├── build.gradle └── src │ └── main │ └── java │ └── org │ └── reactivestreams │ └── utils │ └── spi │ ├── Graph.java │ ├── Stage.java │ ├── UnsupportedStageException.java │ └── package-info.java └── tck ├── build.gradle └── src └── main └── java └── org └── reactivestreams └── utils └── tck ├── AbstractStageVerification.java ├── CancelStageVerification.java ├── CollectStageVerification.java ├── ConcatStageVerification.java ├── EmptyProcessorVerification.java ├── FilterStageVerification.java ├── FindFirstStageVerification.java ├── FlatMapCompletionStageVerification.java ├── FlatMapIterableStageVerification.java ├── FlatMapStageVerification.java ├── MapStageVerification.java ├── OfStageVerification.java ├── ReactiveStreamsTck.java ├── SubscriberStageVerification.java └── TakeWhileStageVerification.java /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | out 3 | .gradle 4 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Creative Commons Legal Code 2 | 3 | CC0 1.0 Universal 4 | 5 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE 6 | LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN 7 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS 8 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES 9 | REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS 10 | PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM 11 | THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED 12 | HEREUNDER. 13 | 14 | Statement of Purpose 15 | 16 | The laws of most jurisdictions throughout the world automatically confer 17 | exclusive Copyright and Related Rights (defined below) upon the creator 18 | and subsequent owner(s) (each and all, an "owner") of an original work of 19 | authorship and/or a database (each, a "Work"). 20 | 21 | Certain owners wish to permanently relinquish those rights to a Work for 22 | the purpose of contributing to a commons of creative, cultural and 23 | scientific works ("Commons") that the public can reliably and without fear 24 | of later claims of infringement build upon, modify, incorporate in other 25 | works, reuse and redistribute as freely as possible in any form whatsoever 26 | and for any purposes, including without limitation commercial purposes. 27 | These owners may contribute to the Commons to promote the ideal of a free 28 | culture and the further production of creative, cultural and scientific 29 | works, or to gain reputation or greater distribution for their Work in 30 | part through the use and efforts of others. 31 | 32 | For these and/or other purposes and motivations, and without any 33 | expectation of additional consideration or compensation, the person 34 | associating CC0 with a Work (the "Affirmer"), to the extent that he or she 35 | is an owner of Copyright and Related Rights in the Work, voluntarily 36 | elects to apply CC0 to the Work and publicly distribute the Work under its 37 | terms, with knowledge of his or her Copyright and Related Rights in the 38 | Work and the meaning and intended legal effect of CC0 on those rights. 39 | 40 | 1. Copyright and Related Rights. A Work made available under CC0 may be 41 | protected by copyright and related or neighboring rights ("Copyright and 42 | Related Rights"). Copyright and Related Rights include, but are not 43 | limited to, the following: 44 | 45 | i. the right to reproduce, adapt, distribute, perform, display, 46 | communicate, and translate a Work; 47 | ii. moral rights retained by the original author(s) and/or performer(s); 48 | iii. publicity and privacy rights pertaining to a person's image or 49 | likeness depicted in a Work; 50 | iv. rights protecting against unfair competition in regards to a Work, 51 | subject to the limitations in paragraph 4(a), below; 52 | v. rights protecting the extraction, dissemination, use and reuse of data 53 | in a Work; 54 | vi. database rights (such as those arising under Directive 96/9/EC of the 55 | European Parliament and of the Council of 11 March 1996 on the legal 56 | protection of databases, and under any national implementation 57 | thereof, including any amended or successor version of such 58 | directive); and 59 | vii. other similar, equivalent or corresponding rights throughout the 60 | world based on applicable law or treaty, and any national 61 | implementations thereof. 62 | 63 | 2. Waiver. To the greatest extent permitted by, but not in contravention 64 | of, applicable law, Affirmer hereby overtly, fully, permanently, 65 | irrevocably and unconditionally waives, abandons, and surrenders all of 66 | Affirmer's Copyright and Related Rights and associated claims and causes 67 | of action, whether now known or unknown (including existing as well as 68 | future claims and causes of action), in the Work (i) in all territories 69 | worldwide, (ii) for the maximum duration provided by applicable law or 70 | treaty (including future time extensions), (iii) in any current or future 71 | medium and for any number of copies, and (iv) for any purpose whatsoever, 72 | including without limitation commercial, advertising or promotional 73 | purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each 74 | member of the public at large and to the detriment of Affirmer's heirs and 75 | successors, fully intending that such Waiver shall not be subject to 76 | revocation, rescission, cancellation, termination, or any other legal or 77 | equitable action to disrupt the quiet enjoyment of the Work by the public 78 | as contemplated by Affirmer's express Statement of Purpose. 79 | 80 | 3. Public License Fallback. Should any part of the Waiver for any reason 81 | be judged legally invalid or ineffective under applicable law, then the 82 | Waiver shall be preserved to the maximum extent permitted taking into 83 | account Affirmer's express Statement of Purpose. In addition, to the 84 | extent the Waiver is so judged Affirmer hereby grants to each affected 85 | person a royalty-free, non transferable, non sublicensable, non exclusive, 86 | irrevocable and unconditional license to exercise Affirmer's Copyright and 87 | Related Rights in the Work (i) in all territories worldwide, (ii) for the 88 | maximum duration provided by applicable law or treaty (including future 89 | time extensions), (iii) in any current or future medium and for any number 90 | of copies, and (iv) for any purpose whatsoever, including without 91 | limitation commercial, advertising or promotional purposes (the 92 | "License"). The License shall be deemed effective as of the date CC0 was 93 | applied by Affirmer to the Work. Should any part of the License for any 94 | reason be judged legally invalid or ineffective under applicable law, such 95 | partial invalidity or ineffectiveness shall not invalidate the remainder 96 | of the License, and in such case Affirmer hereby affirms that he or she 97 | will not (i) exercise any of his or her remaining Copyright and Related 98 | Rights in the Work or (ii) assert any associated claims and causes of 99 | action with respect to the Work, in either case contrary to Affirmer's 100 | express Statement of Purpose. 101 | 102 | 4. Limitations and Disclaimers. 103 | 104 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 105 | surrendered, licensed or otherwise affected by this document. 106 | b. Affirmer offers the Work as-is and makes no representations or 107 | warranties of any kind concerning the Work, express, implied, 108 | statutory or otherwise, including without limitation warranties of 109 | title, merchantability, fitness for a particular purpose, non 110 | infringement, or the absence of latent or other defects, accuracy, or 111 | the present or absence of errors, whether or not discoverable, all to 112 | the greatest extent permissible under applicable law. 113 | c. Affirmer disclaims responsibility for clearing rights of other persons 114 | that may apply to the Work or any use thereof, including without 115 | limitation any person's Copyright and Related Rights in the Work. 116 | Further, Affirmer disclaims responsibility for obtaining any necessary 117 | consents, permissions or other rights required for any use of the 118 | Work. 119 | d. Affirmer understands and acknowledges that Creative Commons is not a 120 | party to this document and has no duty or obligation with respect to 121 | this CC0 or use of the Work. 122 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Licensed under Public Domain (CC0) 2 | 3 | To the extent possible under law, the person who associated CC0 with 4 | this code has waived all copyright and related or neighboring 5 | rights to this code. 6 | 7 | You should have received a copy of the CC0 legalcode along with this 8 | work. If not, see . 9 | -------------------------------------------------------------------------------- /akka/build.gradle: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Licensed under Public Domain (CC0) * 3 | * * 4 | * To the extent possible under law, the person who associated CC0 with * 5 | * this code has waived all copyright and related or neighboring * 6 | * rights to this code. * 7 | * * 8 | * You should have received a copy of the CC0 legalcode along with this * 9 | * work. If not, see . * 10 | ******************************************************************************/ 11 | 12 | plugins { 13 | id 'java-library' 14 | } 15 | 16 | dependencies { 17 | api project(":api") 18 | implementation "com.typesafe.akka:akka-stream_2.12:2.5.9" 19 | 20 | testCompile project(":tck") 21 | } 22 | -------------------------------------------------------------------------------- /akka/src/main/java/com/lightbend/reactivestreams/utils/AkkaEngineProvider.java: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Licensed under Public Domain (CC0) * 3 | * * 4 | * To the extent possible under law, the person who associated CC0 with * 5 | * this code has waived all copyright and related or neighboring * 6 | * rights to this code. * 7 | * * 8 | * You should have received a copy of the CC0 legalcode along with this * 9 | * work. If not, see . * 10 | ******************************************************************************/ 11 | 12 | package com.lightbend.reactivestreams.utils; 13 | 14 | import akka.actor.ActorSystem; 15 | import akka.actor.BootstrapSetup; 16 | import akka.stream.ActorMaterializer; 17 | import akka.stream.Materializer; 18 | import com.typesafe.config.ConfigFactory; 19 | import org.reactivestreams.utils.ReactiveStreamsEngine; 20 | import org.reactivestreams.utils.SubscriberWithResult; 21 | import org.reactivestreams.utils.spi.Graph; 22 | import org.reactivestreams.utils.spi.UnsupportedStageException; 23 | import scala.compat.java8.FutureConverters; 24 | 25 | import java.lang.ref.Cleaner; 26 | import java.lang.ref.WeakReference; 27 | import java.util.concurrent.CompletionStage; 28 | import java.util.concurrent.Flow; 29 | import java.util.concurrent.ForkJoinPool; 30 | 31 | /** 32 | * Provides the Akka Engine to the JDK9 modules system. 33 | *

34 | * This will instantiate its own actor systems to run the streams. It uses weak references and a cleaner to ensure 35 | * that the actor system gets cleaned up. 36 | *

37 | * While JDK9 modules provided using a module descriptor can provide static method, right now we're providing this 38 | * module as a classpath service, and that doesn't support static methods. So, when used as an engine, this class 39 | * itself is used as the engine, and delegates to the actual engine, which has a weak reference kept to it. 40 | */ 41 | public class AkkaEngineProvider implements ReactiveStreamsEngine { 42 | 43 | /** 44 | * Used to clean up the actor system when the engine is no longer strongly referenceable. 45 | */ 46 | private static final Cleaner actorSystemCleaner = Cleaner.create(runnable -> { 47 | Thread thread = new Thread(runnable); 48 | // The default thread factory used by the cleaner sets the thread priority to this. 49 | thread.setPriority(Thread.MAX_PRIORITY - 2); 50 | thread.setName("Akka-Reactive-Streams-Engine-Cleaner"); 51 | return thread; 52 | }); 53 | 54 | private static volatile WeakReference cachedEngine = null; 55 | 56 | /** 57 | * Used to ensure only one instance of the engine exists at a time. 58 | */ 59 | private static final Object mutex = new Object(); 60 | 61 | /** 62 | * Using a static class rather than a lambda as advised by the Cleaner javadocs to ensure that we don't accidentally 63 | * close over a reference to the AkkaEngine. 64 | */ 65 | private static class ActorSystemCleanerTask implements Runnable { 66 | private final ActorSystem system; 67 | 68 | private ActorSystemCleanerTask(ActorSystem system) { 69 | this.system = system; 70 | } 71 | 72 | @Override 73 | public void run() { 74 | system.terminate(); 75 | // Let it terminate asynchronously, blocking while it does won't achieve anything. 76 | } 77 | } 78 | 79 | private static AkkaEngine createEngine() { 80 | ActorSystem system = ActorSystem.create("reactive-streams-engine", 81 | BootstrapSetup.create() 82 | // Use JDK common thread pool rather than instantiate our own. 83 | .withDefaultExecutionContext(FutureConverters.fromExecutorService(ForkJoinPool.commonPool())) 84 | // Be explicit about the classloader. 85 | .withClassloader(AkkaEngine.class.getClassLoader()) 86 | // Use empty config to ensure any other actor systems using the root config don't conflict. 87 | // todo maybe we want to be able to configure it? 88 | .withConfig(ConfigFactory.empty()) 89 | ); 90 | Materializer materializer = ActorMaterializer.create(system); 91 | 92 | AkkaEngine engine = new AkkaEngine(materializer); 93 | actorSystemCleaner.register(engine, new ActorSystemCleanerTask(system)); 94 | return engine; 95 | } 96 | 97 | /** 98 | * This method is used by the JDK9 modules service loader mechanism to load the engine. 99 | */ 100 | public static AkkaEngine provider() { 101 | 102 | AkkaEngine engine = null; 103 | // Double checked locking to initialize the weak reference the first time this method 104 | // is invoked. 105 | if (cachedEngine == null) { 106 | synchronized (mutex) { 107 | if (cachedEngine == null) { 108 | // We could just use the weak reference to get the engine later, but technically, 109 | // it could be collected before we get there, so we strongly reference it here to 110 | // ensure that doesn't happen. 111 | engine = createEngine(); 112 | cachedEngine = new WeakReference<>(engine); 113 | } 114 | } 115 | } 116 | if (engine == null) { 117 | // Double checked locking to ensure the weak reference is set. 118 | engine = cachedEngine.get(); 119 | if (engine == null) { 120 | synchronized (mutex) { 121 | engine = cachedEngine.get(); 122 | if (engine == null) { 123 | engine = createEngine(); 124 | cachedEngine = new WeakReference<>(engine); 125 | } 126 | } 127 | } 128 | } 129 | 130 | return engine; 131 | } 132 | 133 | private final AkkaEngine delegate; 134 | 135 | public AkkaEngineProvider() { 136 | this.delegate = provider(); 137 | } 138 | 139 | @Override 140 | public Flow.Publisher buildPublisher(Graph graph) throws UnsupportedStageException { 141 | return delegate.buildPublisher(graph); 142 | } 143 | 144 | @Override 145 | public SubscriberWithResult buildSubscriber(Graph graph) throws UnsupportedStageException { 146 | return delegate.buildSubscriber(graph); 147 | } 148 | 149 | @Override 150 | public Flow.Processor buildProcessor(Graph graph) throws UnsupportedStageException { 151 | return delegate.buildProcessor(graph); 152 | } 153 | 154 | @Override 155 | public CompletionStage buildCompletion(Graph graph) throws UnsupportedStageException { 156 | return delegate.buildCompletion(graph); 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /akka/src/main/java/com/lightbend/reactivestreams/utils/TerminationWatcher.java: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Licensed under Public Domain (CC0) * 3 | * * 4 | * To the extent possible under law, the person who associated CC0 with * 5 | * this code has waived all copyright and related or neighboring * 6 | * rights to this code. * 7 | * * 8 | * You should have received a copy of the CC0 legalcode along with this * 9 | * work. If not, see . * 10 | ******************************************************************************/ 11 | 12 | package com.lightbend.reactivestreams.utils; 13 | 14 | import akka.stream.*; 15 | import akka.stream.stage.*; 16 | import scala.Tuple2; 17 | 18 | import java.util.concurrent.CancellationException; 19 | import java.util.concurrent.CompletableFuture; 20 | import java.util.concurrent.CompletionStage; 21 | 22 | class TerminationWatcher extends GraphStageWithMaterializedValue, CompletionStage> { 23 | private final Inlet in = Inlet.create("TerminationWatcher.in"); 24 | private final Outlet out = Outlet.create("TerminationWatcher.out"); 25 | 26 | private final FlowShape shape = FlowShape.of(in, out); 27 | 28 | @Override 29 | public FlowShape shape() { 30 | return shape; 31 | } 32 | 33 | @Override 34 | public Tuple2> createLogicAndMaterializedValue(Attributes inheritedAttributes) { 35 | CompletableFuture completion = new CompletableFuture<>(); 36 | GraphStageLogic logic = new GraphStageLogic(shape()) { 37 | { 38 | setHandler(in, new AbstractInHandler() { 39 | @Override 40 | public void onPush() throws Exception { 41 | push(out, grab(in)); 42 | } 43 | 44 | @Override 45 | public void onUpstreamFinish() throws Exception { 46 | complete(out); 47 | completion.complete(null); 48 | } 49 | 50 | @Override 51 | public void onUpstreamFailure(Throwable ex) throws Exception { 52 | fail(out, ex); 53 | completion.completeExceptionally(ex); 54 | } 55 | }); 56 | setHandler(out, new AbstractOutHandler() { 57 | @Override 58 | public void onPull() throws Exception { 59 | pull(in); 60 | } 61 | 62 | @Override 63 | public void onDownstreamFinish() throws Exception { 64 | cancel(in); 65 | completion.completeExceptionally(new CancellationException("cancelled")); 66 | } 67 | }); 68 | } 69 | 70 | }; 71 | return new Tuple2<>(logic, completion); 72 | } 73 | } -------------------------------------------------------------------------------- /akka/src/main/resources/META-INF/services/org.reactivestreams.utils.ReactiveStreamsEngine: -------------------------------------------------------------------------------- 1 | com.lightbend.reactivestreams.utils.AkkaEngineProvider -------------------------------------------------------------------------------- /akka/src/test/java/com/lightbend/reactivestreams/utils/AkkaEngineProviderTest.java: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Licensed under Public Domain (CC0) * 3 | * * 4 | * To the extent possible under law, the person who associated CC0 with * 5 | * this code has waived all copyright and related or neighboring * 6 | * rights to this code. * 7 | * * 8 | * You should have received a copy of the CC0 legalcode along with this * 9 | * work. If not, see . * 10 | ******************************************************************************/ 11 | 12 | package com.lightbend.reactivestreams.utils; 13 | 14 | import akka.Done; 15 | import akka.actor.ActorSystem; 16 | import akka.actor.Cancellable; 17 | import akka.japi.Pair; 18 | import akka.stream.ActorMaterializer; 19 | import akka.stream.javadsl.AsPublisher; 20 | import akka.stream.javadsl.JavaFlowSupport; 21 | import akka.stream.javadsl.Keep; 22 | import akka.stream.javadsl.Source; 23 | import org.reactivestreams.utils.ReactiveStreams; 24 | import org.testng.annotations.Test; 25 | import scala.compat.java8.FutureConverters; 26 | import scala.concurrent.duration.Duration; 27 | 28 | import java.util.List; 29 | import java.util.concurrent.Flow; 30 | import java.util.concurrent.TimeUnit; 31 | import java.util.concurrent.atomic.AtomicReference; 32 | 33 | import static org.testng.Assert.assertEquals; 34 | import static org.testng.Assert.assertFalse; 35 | 36 | public class AkkaEngineProviderTest { 37 | 38 | @Test 39 | public void akkaEngineProviderIsProvided() throws Exception { 40 | assertEquals( 41 | ReactiveStreams.of(1).toList().build() 42 | .toCompletableFuture().get(1, TimeUnit.SECONDS), 43 | List.of(1)); 44 | } 45 | 46 | @Test 47 | public void actorSystemIsCleanedUpWhenThereAreNoMoreReferences() throws Exception { 48 | // First get a reference 49 | AkkaEngine engine = AkkaEngineProvider.provider(); 50 | // And get the actor system from it 51 | ActorSystem system = ((ActorMaterializer) engine.materializer).system(); 52 | // Clear reference 53 | engine = null; 54 | // Wait a while in case there are streams running from other tests 55 | Thread.sleep(300); 56 | // And gc 57 | System.gc(); 58 | // Now wait for the system to shutdown 59 | FutureConverters.toJava(system.whenTerminated()).toCompletableFuture().get(10, TimeUnit.SECONDS); 60 | } 61 | 62 | @Test 63 | public void aRunningStreamShouldPreventActorSystemFromShuttingDown() throws Exception { 64 | AkkaEngine engine = AkkaEngineProvider.provider(); 65 | ActorSystem system = ((ActorMaterializer) engine.materializer).system(); 66 | 67 | Flow.Publisher publisher = 68 | Source.tick( 69 | Duration.create(100, TimeUnit.MILLISECONDS), 70 | Duration.create(100, TimeUnit.MILLISECONDS), 71 | Done.getInstance() 72 | ) 73 | .runWith(JavaFlowSupport.Sink.asPublisher(AsPublisher.WITHOUT_FANOUT), engine.materializer); 74 | 75 | AtomicReference error = new AtomicReference<>(); 76 | ReactiveStreams.fromPublisher(publisher).forEach(d -> { 77 | if (error.get() != null) { 78 | throw error.get(); 79 | } 80 | }).build(engine); 81 | publisher = null; 82 | 83 | engine = null; 84 | Thread.sleep(300); 85 | 86 | // And gc 87 | System.gc(); 88 | // Wait for it to possibly complete 89 | Thread.sleep(1000); 90 | // Now ensure it doesn't complete 91 | assertFalse(system.whenTerminated().isCompleted()); 92 | 93 | // Stop the stream 94 | error.set(new RuntimeException()); 95 | // Wait for the stream to shutdown 96 | Thread.sleep(1000); 97 | // gc again 98 | System.gc(); 99 | // Now ensure it does complete 100 | FutureConverters.toJava(system.whenTerminated()).toCompletableFuture().get(10, TimeUnit.SECONDS); 101 | } 102 | 103 | } 104 | -------------------------------------------------------------------------------- /akka/src/test/java/com/lightbend/reactivestreams/utils/AkkaReactiveStreamsTck.java: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Licensed under Public Domain (CC0) * 3 | * * 4 | * To the extent possible under law, the person who associated CC0 with * 5 | * this code has waived all copyright and related or neighboring * 6 | * rights to this code. * 7 | * * 8 | * You should have received a copy of the CC0 legalcode along with this * 9 | * work. If not, see . * 10 | ******************************************************************************/ 11 | 12 | package com.lightbend.reactivestreams.utils; 13 | 14 | import akka.actor.ActorSystem; 15 | import akka.stream.ActorMaterializer; 16 | import akka.stream.Materializer; 17 | import org.reactivestreams.utils.ReactiveStreamsEngine; 18 | import org.reactivestreams.utils.tck.CancelStageVerification; 19 | import org.reactivestreams.utils.tck.FlatMapStageVerification; 20 | import org.reactivestreams.utils.tck.ReactiveStreamsTck; 21 | import org.reactivestreams.tck.TestEnvironment; 22 | import org.testng.annotations.AfterSuite; 23 | 24 | /** 25 | * TCK verification for the {@link AkkaEngine} implementation of the {@link ReactiveStreamsEngine}. 26 | */ 27 | public class AkkaReactiveStreamsTck extends ReactiveStreamsTck { 28 | 29 | public AkkaReactiveStreamsTck() { 30 | super(new TestEnvironment()); 31 | } 32 | 33 | private ActorSystem system; 34 | private Materializer materializer; 35 | 36 | @AfterSuite 37 | public void shutdownActorSystem() { 38 | if (system != null) { 39 | system.terminate(); 40 | } 41 | } 42 | 43 | @Override 44 | protected AkkaEngine createEngine() { 45 | system = ActorSystem.create(); 46 | materializer = ActorMaterializer.create(system); 47 | return new AkkaEngine(materializer); 48 | } 49 | 50 | @Override 51 | protected boolean isEnabled(Object test) { 52 | // Disabled due to https://github.com/akka/akka/issues/24719 53 | return !(test instanceof FlatMapStageVerification.InnerSubscriberVerification) && 54 | // Disabled due to https://github.com/akka/akka/pull/24749 55 | !(test instanceof CancelStageVerification.SubscriberVerification); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /api/build.gradle: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Licensed under Public Domain (CC0) * 3 | * * 4 | * To the extent possible under law, the person who associated CC0 with * 5 | * this code has waived all copyright and related or neighboring * 6 | * rights to this code. * 7 | * * 8 | * You should have received a copy of the CC0 legalcode along with this * 9 | * work. If not, see . * 10 | ******************************************************************************/ 11 | 12 | plugins { 13 | id 'java-library' 14 | } 15 | 16 | dependencies { 17 | api project(":spi") 18 | } 19 | -------------------------------------------------------------------------------- /api/src/main/java/org/reactivestreams/utils/CompletionBuilder.java: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Licensed under Public Domain (CC0) * 3 | * * 4 | * To the extent possible under law, the person who associated CC0 with * 5 | * this code has waived all copyright and related or neighboring * 6 | * rights to this code. * 7 | * * 8 | * You should have received a copy of the CC0 legalcode along with this * 9 | * work. If not, see . * 10 | ******************************************************************************/ 11 | 12 | package org.reactivestreams.utils; 13 | 14 | import org.reactivestreams.utils.spi.Stage; 15 | 16 | import java.util.concurrent.CompletionStage; 17 | 18 | /** 19 | * A builder for a closed reactive streams graph. 20 | *

21 | * When built, this builder returns a {@link CompletionStage} that will be redeemed with the result produced by the 22 | * subscriber of the stream when the stream completes normally, or will be redeemed with an error if the stream 23 | * encounters an error. 24 | * 25 | * @param The result of the stream. 26 | * @see ReactiveStreams 27 | */ 28 | public final class CompletionBuilder extends ReactiveStreamsBuilder> { 29 | 30 | CompletionBuilder(Stage stage, ReactiveStreamsBuilder previous) { 31 | super(stage, previous); 32 | } 33 | 34 | @Override 35 | public CompletionStage build(ReactiveStreamsEngine engine) { 36 | return engine.buildCompletion(toGraph(false, false)); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /api/src/main/java/org/reactivestreams/utils/InternalStages.java: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Licensed under Public Domain (CC0) * 3 | * * 4 | * To the extent possible under law, the person who associated CC0 with * 5 | * this code has waived all copyright and related or neighboring * 6 | * rights to this code. * 7 | * * 8 | * You should have received a copy of the CC0 legalcode along with this * 9 | * work. If not, see . * 10 | ******************************************************************************/ 11 | 12 | package org.reactivestreams.utils; 13 | 14 | import org.reactivestreams.utils.spi.Stage; 15 | 16 | /** 17 | * Internal stages, used to capture the graph while being built, but never passed to a 18 | * {@link ReactiveStreamsEngine}. 19 | */ 20 | class InternalStages { 21 | 22 | interface InternalStage extends Stage {} 23 | 24 | /** 25 | * An identity stage - this stage simply passes is input to its output unchanged. It's used to represent processor 26 | * builders that have had no stages defined. 27 | *

28 | * It gets ignored by the {@link ReactiveStreamsBuilder} when encountered. 29 | */ 30 | static final class Identity implements InternalStage { 31 | private Identity() { 32 | } 33 | 34 | static final Identity INSTANCE = new Identity(); 35 | } 36 | 37 | /** 38 | * A nested stage. This is used to avoid having to rebuild the entire graph (which is represented as an immutable 39 | * cons) whenever two graphs are joined, or a stage is prepended into the graph. 40 | *

41 | * It gets flattened out by the {@link ReactiveStreamsBuilder} when building the graph. 42 | */ 43 | static final class Nested implements InternalStage { 44 | private final ReactiveStreamsBuilder stage; 45 | 46 | Nested(ReactiveStreamsBuilder stage) { 47 | this.stage = stage; 48 | } 49 | 50 | ReactiveStreamsBuilder getBuilder() { 51 | return stage; 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /api/src/main/java/org/reactivestreams/utils/Predicates.java: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Licensed under Public Domain (CC0) * 3 | * * 4 | * To the extent possible under law, the person who associated CC0 with * 5 | * this code has waived all copyright and related or neighboring * 6 | * rights to this code. * 7 | * * 8 | * You should have received a copy of the CC0 legalcode along with this * 9 | * work. If not, see . * 10 | ******************************************************************************/ 11 | 12 | package org.reactivestreams.utils; 13 | 14 | import java.util.function.Predicate; 15 | 16 | /** 17 | * Stateful predicates. 18 | */ 19 | class Predicates { 20 | 21 | /** 22 | * Predicate used to implement skip with a filter function. 23 | */ 24 | static class SkipPredicate implements Predicate { 25 | private final long toSkip; 26 | private long count = 0; 27 | 28 | SkipPredicate(long toSkip) { 29 | this.toSkip = toSkip; 30 | } 31 | 32 | @Override 33 | public boolean test(T t) { 34 | if (count < toSkip) { 35 | count++; 36 | return false; 37 | } else { 38 | return true; 39 | } 40 | } 41 | } 42 | 43 | /** 44 | * Predicate used to implement drop while with a filter function. 45 | */ 46 | static class DropWhilePredicate implements Predicate { 47 | private final Predicate predicate; 48 | private boolean dropping = true; 49 | 50 | DropWhilePredicate(Predicate predicate) { 51 | this.predicate = predicate; 52 | } 53 | 54 | @Override 55 | public boolean test(T t) { 56 | if (dropping) { 57 | if (predicate.test(t)) { 58 | return false; 59 | } else { 60 | dropping = true; 61 | return true; 62 | } 63 | } else { 64 | return true; 65 | } 66 | } 67 | } 68 | 69 | /** 70 | * Predicate used to implement limit(). 71 | * 72 | * This returns false when the limit is reached, not exceeded, and is intended to be used with an inclusive takeWhile, 73 | * this ensures that the stream completes as soon as the limit is reached, rather than having to wait for the next 74 | * element before the stream is completed. 75 | * 76 | * As a consequence, this can't be used with a limit of 0. 77 | */ 78 | static class LimitPredicate implements Predicate { 79 | private final long limitTo; 80 | private long count = 0; 81 | 82 | LimitPredicate(long limitTo) { 83 | assert limitTo > 0; 84 | this.limitTo = limitTo; 85 | } 86 | 87 | @Override 88 | public boolean test(T t) { 89 | return ++count < limitTo; 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /api/src/main/java/org/reactivestreams/utils/ReactiveStreamsBuilder.java: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Licensed under Public Domain (CC0) * 3 | * * 4 | * To the extent possible under law, the person who associated CC0 with * 5 | * this code has waived all copyright and related or neighboring * 6 | * rights to this code. * 7 | * * 8 | * You should have received a copy of the CC0 legalcode along with this * 9 | * work. If not, see . * 10 | ******************************************************************************/ 11 | 12 | package org.reactivestreams.utils; 13 | 14 | import org.reactivestreams.utils.spi.Graph; 15 | import org.reactivestreams.utils.spi.Stage; 16 | 17 | import java.util.*; 18 | 19 | /** 20 | * Superclass of all reactive streams builders. 21 | * 22 | * @param The shape of the graph being built. 23 | * @see ReactiveStreams 24 | */ 25 | public abstract class ReactiveStreamsBuilder { 26 | 27 | private final Stage stage; 28 | private final ReactiveStreamsBuilder previous; 29 | 30 | ReactiveStreamsBuilder(Stage stage, ReactiveStreamsBuilder previous) { 31 | this.stage = stage; 32 | this.previous = previous; 33 | } 34 | 35 | /** 36 | * Build this stream, using the first {@link ReactiveStreamsEngine} found by the {@link ServiceLoader}. 37 | * 38 | * @return The graph of the given shape. 39 | */ 40 | public S build() { 41 | Optional engine = ServiceLoader.load(ReactiveStreamsEngine.class).findFirst(); 42 | 43 | if (engine.isPresent()) { 44 | return build(engine.get()); 45 | } else { 46 | throw new IllegalStateException("No implementation of ReactiveStreamsEngine service could be found."); 47 | } 48 | } 49 | 50 | /** 51 | * Build this stream, using the supplied {@link ReactiveStreamsEngine}. 52 | * 53 | * @param engine The engine to build the stream with. 54 | * @return The graph of the given shape. 55 | */ 56 | public abstract S build(ReactiveStreamsEngine engine); 57 | 58 | Graph toGraph(boolean expectInlet, boolean expectOutlet) { 59 | ArrayDeque deque = new ArrayDeque<>(); 60 | flatten(deque); 61 | Graph graph = new Graph(Collections.unmodifiableCollection(deque)); 62 | 63 | if (expectInlet) { 64 | if (!graph.hasInlet()) { 65 | throw new IllegalStateException("Expected to build a graph with an inlet, but no inlet was found: " + graph); 66 | } 67 | } else if (graph.hasInlet()) { 68 | throw new IllegalStateException("Expected to build a graph with no inlet, but an inlet was found: " + graph); 69 | } 70 | 71 | if (expectOutlet) { 72 | if (!graph.hasOutlet()) { 73 | throw new IllegalStateException("Expected to build a graph with an outlet, but no outlet was found: " + graph); 74 | } 75 | } else if (graph.hasOutlet()) { 76 | throw new IllegalStateException("Expected to build a graph with no outlet, but an outlet was found: " + graph); 77 | } 78 | 79 | return graph; 80 | } 81 | 82 | private void flatten(Deque stages) { 83 | if (stage == InternalStages.Identity.INSTANCE) { 84 | // Ignore, no need to add an identity stage 85 | } else if (stage instanceof InternalStages.Nested) { 86 | ((InternalStages.Nested) stage).getBuilder().flatten(stages); 87 | } else { 88 | stages.addFirst(stage); 89 | } 90 | 91 | if (previous != null) { 92 | previous.flatten(stages); 93 | } 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /api/src/main/java/org/reactivestreams/utils/ReactiveStreamsEngine.java: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Licensed under Public Domain (CC0) * 3 | * * 4 | * To the extent possible under law, the person who associated CC0 with * 5 | * this code has waived all copyright and related or neighboring * 6 | * rights to this code. * 7 | * * 8 | * You should have received a copy of the CC0 legalcode along with this * 9 | * work. If not, see . * 10 | ******************************************************************************/ 11 | 12 | package org.reactivestreams.utils; 13 | 14 | import org.reactivestreams.utils.spi.Graph; 15 | import org.reactivestreams.utils.spi.UnsupportedStageException; 16 | 17 | import java.util.concurrent.CompletionStage; 18 | import java.util.concurrent.Flow.*; 19 | 20 | /** 21 | * An engine for turning reactive streams graphs into {@link java.util.concurrent.Flow} publishers/subscribers. 22 | *

23 | * The zero argument {@link ReactiveStreamsBuilder#build()} method will use the {@link java.util.ServiceLoader} to load 24 | * an engine for the current context classloader. It does not cache engines between invocations. If instantiating an 25 | * engine is expensive (eg, it creates threads), then it is recommended that the implementation does its own caching 26 | * by providing the engine using a static provider method. 27 | */ 28 | public interface ReactiveStreamsEngine { 29 | 30 | /** 31 | * Build a {@link Publisher} from the given stages. 32 | *

33 | * The passed in graph will have an outlet and no inlet. 34 | * 35 | * @param graph The stages to build the publisher from. Will not be empty. 36 | * @param The type of elements that the publisher publishes. 37 | * @return A publisher that implements the passed in graph of stages. 38 | * @throws UnsupportedStageException If a stage in the stages is not supported by this Reactive Streams engine. 39 | */ 40 | Publisher buildPublisher(Graph graph) throws UnsupportedStageException; 41 | 42 | /** 43 | * Build a {@link Subscriber} from the given stages. 44 | *

45 | * The passed in graph will have an inlet and no outlet. 46 | * 47 | * @param graph The graph to build the subscriber from. Will not be empty. 48 | * @param The type of elements that the subscriber subscribes to. 49 | * @param The result of subscribing to the stages. 50 | * @return A subscriber that implements the passed in graph of stages. 51 | * @throws UnsupportedStageException If a stage in the stages is not supported by this Reactive Streams engine. 52 | */ 53 | SubscriberWithResult buildSubscriber(Graph graph) throws UnsupportedStageException; 54 | 55 | /** 56 | * Build a {@link Processor} from the given stages. 57 | *

58 | * The passed in graph will have an inlet and an outlet. 59 | * 60 | * @param graph The graph to build the processor from. If empty, then the processor should be an identity processor. 61 | * @param The type of elements that the processor subscribes to. 62 | * @param The type of elements that the processor publishes. 63 | * @return A processor that implements the passed in graph of stages. 64 | * @throws UnsupportedStageException If a stage in the stages is not supported by this Reactive Streams engine. 65 | */ 66 | Processor buildProcessor(Graph graph) throws UnsupportedStageException; 67 | 68 | /** 69 | * Build a closed graph from the given stages. 70 | *

71 | * The passed in graph will have no inlet and no outlet. 72 | * 73 | * @param graph The graph to build the closed graph from. Will not be empty. 74 | * @param The type of the result of running the closed graph. 75 | * @return A CompletionStage of the result of running the graph. 76 | * @throws UnsupportedStageException If a stage in the stages is not supported by this reactive streams engine. 77 | */ 78 | CompletionStage buildCompletion(Graph graph) throws UnsupportedStageException; 79 | 80 | } 81 | -------------------------------------------------------------------------------- /api/src/main/java/org/reactivestreams/utils/Reductions.java: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Licensed under Public Domain (CC0) * 3 | * * 4 | * To the extent possible under law, the person who associated CC0 with * 5 | * this code has waived all copyright and related or neighboring * 6 | * rights to this code. * 7 | * * 8 | * You should have received a copy of the CC0 legalcode along with this * 9 | * work. If not, see . * 10 | ******************************************************************************/ 11 | 12 | package org.reactivestreams.utils; 13 | 14 | import java.util.Optional; 15 | import java.util.function.BiFunction; 16 | import java.util.function.BinaryOperator; 17 | import java.util.stream.Collector; 18 | 19 | /** 20 | * Reduction utilities that convert arguments supplied to reduce methods on the builders to Collectors. 21 | */ 22 | class Reductions { 23 | 24 | static Collector> reduce(BinaryOperator reducer) { 25 | 26 | return Collector.of(Reduction::new, 27 | (r, t) -> { 28 | if (r.value == null) { 29 | r.value = t; 30 | } else { 31 | r.value = reducer.apply(r.value, t); 32 | } 33 | }, 34 | (r, s) -> { 35 | if (r.value == null) { 36 | return r.replace(s.value); 37 | } else if (s.value != null) { 38 | return r.replace(reducer.apply(r.value, s.value)); 39 | } else { 40 | return r; 41 | } 42 | }, 43 | r -> { 44 | if (r.value == null) { 45 | return Optional.empty(); 46 | } else { 47 | return Optional.of(r.value); 48 | } 49 | } 50 | ); 51 | } 52 | 53 | static Collector reduce(T identity, BinaryOperator reducer) { 54 | 55 | return Collector.of(() -> new Reduction<>(identity), 56 | (r, t) -> r.value = reducer.apply(r.value, t), 57 | (r, s) -> r.replace(reducer.apply(r.value, s.value)), 58 | r -> r.value 59 | ); 60 | } 61 | 62 | static Collector reduce(S identity, 63 | BiFunction accumulator, 64 | BinaryOperator combiner) { 65 | 66 | return Collector.of(() -> new Reduction<>(identity), 67 | (r, t) -> r.value = accumulator.apply(r.value, t), 68 | (r, s) -> r.replace(combiner.apply(r.value, s.value)), 69 | r -> r.value 70 | ); 71 | } 72 | 73 | private static class Reduction { 74 | T value; 75 | 76 | Reduction() { 77 | } 78 | 79 | Reduction(T value) { 80 | this.value = value; 81 | } 82 | 83 | Reduction replace(T newValue) { 84 | this.value = newValue; 85 | return this; 86 | } 87 | } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /api/src/main/java/org/reactivestreams/utils/SubscriberBuilder.java: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Licensed under Public Domain (CC0) * 3 | * * 4 | * To the extent possible under law, the person who associated CC0 with * 5 | * this code has waived all copyright and related or neighboring * 6 | * rights to this code. * 7 | * * 8 | * You should have received a copy of the CC0 legalcode along with this * 9 | * work. If not, see . * 10 | ******************************************************************************/ 11 | 12 | package org.reactivestreams.utils; 13 | 14 | import org.reactivestreams.utils.spi.Stage; 15 | 16 | import java.util.concurrent.Flow.*; 17 | 18 | /** 19 | * A builder for a {@link Subscriber} and its result. 20 | *

21 | * When built, this builder returns a {@link SubscriberWithResult}, which encapsulates both a {@link Subscriber} and a 22 | * {@link java.util.concurrent.CompletionStage} that will be redeemed with the result produced by the subscriber when 23 | * the stream completes normally, or will be redeemed with an error if the subscriber receives an error. 24 | * 25 | * @param The type of the elements that this subscriber consumes. 26 | * @param The type of the result that this subscriber emits. 27 | * @see ReactiveStreams 28 | */ 29 | public final class SubscriberBuilder extends ReactiveStreamsBuilder> { 30 | 31 | SubscriberBuilder(Stage stage, ReactiveStreamsBuilder previous) { 32 | super(stage, previous); 33 | } 34 | 35 | @Override 36 | public SubscriberWithResult build(ReactiveStreamsEngine engine) { 37 | return engine.buildSubscriber(toGraph(true, false)); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /api/src/main/java/org/reactivestreams/utils/SubscriberWithResult.java: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Licensed under Public Domain (CC0) * 3 | * * 4 | * To the extent possible under law, the person who associated CC0 with * 5 | * this code has waived all copyright and related or neighboring * 6 | * rights to this code. * 7 | * * 8 | * You should have received a copy of the CC0 legalcode along with this * 9 | * work. If not, see . * 10 | ******************************************************************************/ 11 | 12 | package org.reactivestreams.utils; 13 | 14 | import java.util.concurrent.CompletionStage; 15 | import java.util.concurrent.Flow.*; 16 | 17 | /** 18 | * A subscriber with a result. 19 | *

20 | * The result is provided through a {@link CompletionStage}, which is redeemed when the subscriber receives a 21 | * completion or error signal, or otherwise cancels the stream. 22 | * 23 | * @param The type of the elements that the subscriber consumes. 24 | * @param The type of the result that the subscriber emits. 25 | */ 26 | public final class SubscriberWithResult { 27 | private final Subscriber subscriber; 28 | private final CompletionStage result; 29 | 30 | public SubscriberWithResult(Subscriber subscriber, CompletionStage result) { 31 | this.subscriber = subscriber; 32 | this.result = result; 33 | } 34 | 35 | /** 36 | * Get the subscriber. 37 | * 38 | * @return The subscriber. 39 | */ 40 | public Subscriber getSubscriber() { 41 | return subscriber; 42 | } 43 | 44 | /** 45 | * Get the result. 46 | * 47 | * @return The result. 48 | */ 49 | public CompletionStage getResult() { 50 | return result; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /api/src/main/java/org/reactivestreams/utils/package-info.java: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Licensed under Public Domain (CC0) * 3 | * * 4 | * To the extent possible under law, the person who associated CC0 with * 5 | * this code has waived all copyright and related or neighboring * 6 | * rights to this code. * 7 | * * 8 | * You should have received a copy of the CC0 legalcode along with this * 9 | * work. If not, see . * 10 | ******************************************************************************/ 11 | 12 | /** 13 | * Reactive streams support. 14 | *

15 | * This provides utilities for building stream graphs that consume or produce elements using the 16 | * {@link java.util.concurrent.Flow.Publisher}, {@link java.util.concurrent.Flow.Subscriber} and 17 | * {@link java.util.concurrent.Flow.Processor} interfaces. 18 | *

19 | * There are four primary classes used for building these graphs: 20 | *

21 | *

    22 | *
  • {@link PublisherBuilder}, which when built produces a {@link java.util.concurrent.Flow.Publisher}
  • 23 | *
  • {@link SubscriberBuilder}, which when built produces a {@link SubscriberWithResult}
  • 24 | *
  • {@link ProcessorBuilder}, which when built produces a {@link java.util.concurrent.Flow.Processor}
  • 25 | *
  • {@link CompletionBuilder}, which when built produces a {@link java.util.concurrent.CompletionStage}
  • 26 | *
27 | *

28 | * Operations on these builders may change the shape of the builder, for example, {@link ProcessorBuilder#toList()} 29 | * changes the builder to a {@link SubscriberBuilder}, since the processor now has a termination stage to direct its 30 | * elements to. 31 | *

32 | * {@link SubscriberBuilder}'s are a special case, in that they don't just build a 33 | * {@link java.util.concurrent.Flow.Subscriber}, they build a {@link SubscriberWithResult}, which encapsulates both a 34 | * {@link java.util.concurrent.Flow.Subscriber} and a {@link java.util.concurrent.CompletionStage} of the result of the 35 | * subscriber processing. This {@link java.util.concurrent.CompletionStage} will be redeemed with a result when the 36 | * stream terminates normally, or if the stream terminates with an error, will be redeemed with an error. The result is 37 | * specific to whatever the {@link java.util.concurrent.Flow.Subscriber} does, for example, in the case of 38 | * {@link ProcessorBuilder#toList()}, the result will be a {@link java.util.List} of all the elements produced by the 39 | * {@link java.util.concurrent.Flow.Processor}, while in the case of {@link ProcessorBuilder#findFirst()}, it's an 40 | * {@link java.util.Optional} of the first element of the stream. In some cases, there is no result, in which case the 41 | * result is the {@link Void} type, and the {@link java.util.concurrent.CompletionStage} is only useful for signalling 42 | * normal or error termination of the stream. 43 | *

44 | * The {@link CompletionBuilder} builds a closed graph, in that case both a {@link java.util.concurrent.Flow.Publisher} 45 | * and {@link java.util.concurrent.Flow.Subscriber} have been provided, and building the graph will run it and return 46 | * the result of the {@link java.util.concurrent.Flow.Subscriber} as a {@link java.util.concurrent.CompletionStage}. 47 | *

48 | * An example use of this API is perhaps you have a {@link java.util.concurrent.Flow.Publisher} of rows from a database, 49 | * and you want to output it as a comma separated list of lines to publish to an HTTP client request body, which 50 | * expects a {@link java.util.concurrent.Flow.Publisher} of {@link java.nio.ByteBuffer}. Here's how this might be implemented: 51 | *

52 | *

53 |  *   Publisher<Row> rowsPublisher = ...;
54 |  *
55 |  *   Publisher<ByteBuffer> bodyPublisher =
56 |  *     // Create a publisher builder from the rows publisher
57 |  *     ReactiveStreams.fromPublisher(rowsPublisher)
58 |  *       // Map the rows to CSV strings
59 |  *       .map(row ->
60 |  *         String.format("%s, %s, %d\n", row.getString("firstName"),
61 |  *           row.getString("lastName"), row.getInt("age))
62 |  *       )
63 |  *       // Convert to ByteBuffer
64 |  *       .map(line -> ByteBuffer.wrap(line.getBytes("utf-8")))
65 |  *       // Build the publisher
66 |  *       .build();
67 |  *
68 |  *   // Make the request
69 |  *   HttpClient client = HttpClient.newHttpClient();
70 |  *   client.send(
71 |  *     HttpRequest
72 |  *       .newBuilder(new URI("http://www.foo.com/"))
73 |  *       .POST(BodyProcessor.fromPublisher(bodyPublisher))
74 |  *       .build()
75 |  *   );
76 |  * 
77 | */ 78 | package org.reactivestreams.utils; -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Licensed under Public Domain (CC0) * 3 | * * 4 | * To the extent possible under law, the person who associated CC0 with * 5 | * this code has waived all copyright and related or neighboring * 6 | * rights to this code. * 7 | * * 8 | * You should have received a copy of the CC0 legalcode along with this * 9 | * work. If not, see . * 10 | ******************************************************************************/ 11 | 12 | subprojects { 13 | afterEvaluate { 14 | repositories { 15 | 16 | mavenLocal() 17 | mavenCentral() 18 | 19 | } 20 | } 21 | } 22 | 23 | buildscript { 24 | repositories { jcenter() } 25 | 26 | dependencies { 27 | classpath 'com.netflix.nebula:gradle-aggregate-javadocs-plugin:2.2.+' 28 | } 29 | } 30 | 31 | apply plugin: 'nebula-aggregate-javadocs' 32 | -------------------------------------------------------------------------------- /examples/build.gradle: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Licensed under Public Domain (CC0) * 3 | * * 4 | * To the extent possible under law, the person who associated CC0 with * 5 | * this code has waived all copyright and related or neighboring * 6 | * rights to this code. * 7 | * * 8 | * You should have received a copy of the CC0 legalcode along with this * 9 | * work. If not, see . * 10 | ******************************************************************************/ 11 | 12 | plugins { 13 | id 'java-library' 14 | } 15 | 16 | dependencies { 17 | api project(":api") 18 | implementation project(":akka") 19 | } 20 | -------------------------------------------------------------------------------- /examples/src/main/java/org/reactivestreams/utils/examples/Examples.java: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Licensed under Public Domain (CC0) * 3 | * * 4 | * To the extent possible under law, the person who associated CC0 with * 5 | * this code has waived all copyright and related or neighboring * 6 | * rights to this code. * 7 | * * 8 | * You should have received a copy of the CC0 legalcode along with this * 9 | * work. If not, see . * 10 | ******************************************************************************/ 11 | 12 | package org.reactivestreams.utils.examples; 13 | 14 | import org.reactivestreams.utils.ReactiveStreams; 15 | import org.reactivestreams.utils.SubscriberWithResult; 16 | 17 | import java.nio.ByteBuffer; 18 | import java.util.ArrayList; 19 | import java.util.List; 20 | import java.util.Optional; 21 | import java.util.concurrent.CompletionStage; 22 | import java.util.concurrent.Flow; 23 | import java.util.stream.Collectors; 24 | import java.util.stream.IntStream; 25 | 26 | public class Examples { 27 | 28 | public void closedGraph() { 29 | 30 | // First, a trivial example. This is just to show the fluency of the API. 31 | // It would make no sense to do this in practice, since the JDK8 streams API itself 32 | // will do the below operations much more optimally. 33 | CompletionStage> result = ReactiveStreams 34 | .fromIterable(() -> IntStream.range(1, 1000).boxed().iterator()) 35 | .filter(i -> (i & 1) == 1) 36 | .map(i -> i * 2) 37 | .collect(Collectors.reducing((i, j) -> i + j)) 38 | .build(); 39 | } 40 | 41 | public void providePublisher() { 42 | 43 | // Let's say we need to provide a Publisher, perhaps we want to transform 44 | // a list of objects to CSV. 45 | List domainObjects = new ArrayList<>(); 46 | 47 | Flow.Publisher publisher = ReactiveStreams 48 | .fromIterable(domainObjects) 49 | .map(obj -> String.format("%s,%s\n", obj.getField1(), obj.getField2())) 50 | .map(line -> ByteBuffer.wrap(line.getBytes())) 51 | .build(); 52 | 53 | } 54 | 55 | public void provideSubscriber() { 56 | 57 | // Let's say we need to provide a subscriber (eg, like the JDK9 HttpClient requires when 58 | // consuming response bodies), and we want to parse them using a processor provided by 59 | // another library into domain objects. 60 | Flow.Processor parser = createParser(); 61 | 62 | SubscriberWithResult> sr = 63 | ReactiveStreams.builder() 64 | .via(parser) 65 | .toList() 66 | .build(); 67 | 68 | Flow.Subscriber subscriber = sr.getSubscriber(); 69 | CompletionStage> result = sr.getResult(); 70 | 71 | } 72 | 73 | public void provideProcessor() { 74 | 75 | // Let's say a messaging library requires us to process messages, and then emit 76 | // the message identifier for each message successfully processed, so that it 77 | // can consider the message processed (and commit it). 78 | Flow.Processor, MessageIdentifier> processor = 79 | ReactiveStreams.>builder() 80 | .map(message -> { 81 | handleObject(message.getMessage()); 82 | return message.getIdentifier(); 83 | }) 84 | .build(); 85 | } 86 | 87 | public void consumePublisher() { 88 | 89 | // Let's say a library gives us a publisher, which we want to parse as MyDomainObject. 90 | Flow.Publisher bytesPublisher = makeRequest(); 91 | 92 | Flow.Processor parser = createParser(); 93 | 94 | CompletionStage> result = ReactiveStreams 95 | .fromPublisher(bytesPublisher) 96 | .via(parser) 97 | .toList() 98 | .build(); 99 | 100 | } 101 | 102 | public void feedSubscriber() { 103 | 104 | // Let's say some library has given us a subscriber that we have to feed. 105 | List domainObjects = new ArrayList<>(); 106 | 107 | Flow.Subscriber subscriber = createSubscriber(); 108 | 109 | CompletionStage completion = ReactiveStreams 110 | .fromIterable(domainObjects) 111 | .map(obj -> String.format("%s,%s\n", obj.getField1(), obj.getField2())) 112 | .map(line -> ByteBuffer.wrap(line.getBytes())) 113 | .to(subscriber) 114 | .build(); 115 | 116 | } 117 | 118 | Flow.Publisher makeRequest() { 119 | return ReactiveStreams.empty().build(); 120 | } 121 | 122 | Flow.Subscriber createSubscriber() { 123 | return ReactiveStreams.builder().findFirst().build().getSubscriber(); 124 | } 125 | 126 | void handleObject(MyDomainObject obj) {} 127 | 128 | class Message { 129 | MessageIdentifier getIdentifier() { 130 | return new MessageIdentifier(); 131 | } 132 | T getMessage() { 133 | return null; 134 | } 135 | } 136 | 137 | class MessageIdentifier {} 138 | 139 | Flow.Processor createParser() { 140 | return ReactiveStreams.builder().map(bytes -> new MyDomainObject()).build(); 141 | } 142 | 143 | class MyDomainObject { 144 | String getField1() { return "field1"; } 145 | String getField2() { return "field2"; } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lightbend/reactive-streams-utils/8442f6c07f8be53d6b8bce190061cbd4ca3b7665/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | # Licensed under Public Domain (CC0) # 3 | # # 4 | # To the extent possible under law, the person who associated CC0 with # 5 | # this code has waived all copyright and related or neighboring # 6 | # rights to this code. # 7 | # # 8 | # You should have received a copy of the CC0 legalcode along with this # 9 | # work. If not, see . # 10 | ################################################################################ 11 | 12 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.3.1-bin.zip 13 | distributionBase=GRADLE_USER_HOME 14 | distributionPath=wrapper/dists 15 | zipStorePath=wrapper/dists 16 | zipStoreBase=GRADLE_USER_HOME 17 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /impl/build.gradle: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Licensed under Public Domain (CC0) * 3 | * * 4 | * To the extent possible under law, the person who associated CC0 with * 5 | * this code has waived all copyright and related or neighboring * 6 | * rights to this code. * 7 | * * 8 | * You should have received a copy of the CC0 legalcode along with this * 9 | * work. If not, see . * 10 | ******************************************************************************/ 11 | 12 | plugins { 13 | id 'java-library' 14 | } 15 | 16 | dependencies { 17 | api project(":api") 18 | testCompile project(":tck") 19 | } 20 | -------------------------------------------------------------------------------- /impl/src/main/java/org/reactivestreams/utils/impl/CancelStage.java: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Licensed under Public Domain (CC0) * 3 | * * 4 | * To the extent possible under law, the person who associated CC0 with * 5 | * this code has waived all copyright and related or neighboring * 6 | * rights to this code. * 7 | * * 8 | * You should have received a copy of the CC0 legalcode along with this * 9 | * work. If not, see . * 10 | ******************************************************************************/ 11 | 12 | package org.reactivestreams.utils.impl; 13 | 14 | import java.util.concurrent.CompletableFuture; 15 | import java.util.function.Consumer; 16 | 17 | /** 18 | * A cancel stage. 19 | */ 20 | class CancelStage extends GraphStage implements InletListener { 21 | private final StageInlet inlet; 22 | private final CompletableFuture result; 23 | 24 | CancelStage(BuiltGraph builtGraph, StageInlet inlet, CompletableFuture result) { 25 | super(builtGraph); 26 | this.inlet = inlet; 27 | this.result = result; 28 | 29 | inlet.setListener(this); 30 | } 31 | 32 | @Override 33 | protected void postStart() { 34 | if (!inlet.isClosed()) { 35 | inlet.cancel(); 36 | } 37 | result.complete(null); 38 | } 39 | 40 | @Override 41 | public void onPush() { 42 | } 43 | 44 | @Override 45 | public void onUpstreamFinish() { 46 | } 47 | 48 | @Override 49 | public void onUpstreamFailure(Throwable error) { 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /impl/src/main/java/org/reactivestreams/utils/impl/CaptureTerminationStage.java: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Licensed under Public Domain (CC0) * 3 | * * 4 | * To the extent possible under law, the person who associated CC0 with * 5 | * this code has waived all copyright and related or neighboring * 6 | * rights to this code. * 7 | * * 8 | * You should have received a copy of the CC0 legalcode along with this * 9 | * work. If not, see . * 10 | ******************************************************************************/ 11 | 12 | package org.reactivestreams.utils.impl; 13 | 14 | import java.util.concurrent.CancellationException; 15 | import java.util.concurrent.CompletableFuture; 16 | 17 | /** 18 | * Stage that just captures termination signals, and redeems the given completable future when it does. 19 | */ 20 | public class CaptureTerminationStage extends GraphStage implements InletListener, OutletListener { 21 | private final StageInlet inlet; 22 | private final StageOutlet outlet; 23 | private final CompletableFuture result; 24 | 25 | public CaptureTerminationStage(BuiltGraph builtGraph, StageInlet inlet, StageOutlet outlet, CompletableFuture result) { 26 | super(builtGraph); 27 | this.inlet = inlet; 28 | this.outlet = outlet; 29 | this.result = result; 30 | 31 | inlet.setListener(this); 32 | outlet.setListener(this); 33 | } 34 | 35 | @Override 36 | public void onPush() { 37 | outlet.push(inlet.grab()); 38 | } 39 | 40 | @Override 41 | public void onUpstreamFinish() { 42 | outlet.complete(); 43 | result.complete(null); 44 | } 45 | 46 | @Override 47 | public void onUpstreamFailure(Throwable error) { 48 | outlet.fail(error); 49 | result.completeExceptionally(error); 50 | } 51 | 52 | @Override 53 | public void onPull() { 54 | inlet.pull(); 55 | } 56 | 57 | @Override 58 | public void onDownstreamFinish() { 59 | inlet.cancel(); 60 | result.completeExceptionally(new CancellationException("Cancelled")); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /impl/src/main/java/org/reactivestreams/utils/impl/CollectStage.java: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Licensed under Public Domain (CC0) * 3 | * * 4 | * To the extent possible under law, the person who associated CC0 with * 5 | * this code has waived all copyright and related or neighboring * 6 | * rights to this code. * 7 | * * 8 | * You should have received a copy of the CC0 legalcode along with this * 9 | * work. If not, see . * 10 | ******************************************************************************/ 11 | 12 | package org.reactivestreams.utils.impl; 13 | 14 | import java.util.concurrent.CompletableFuture; 15 | import java.util.stream.Collector; 16 | 17 | /** 18 | * Stage that collects elements into a collector. 19 | */ 20 | class CollectStage extends GraphStage implements InletListener { 21 | private final StageInlet inlet; 22 | private final CompletableFuture result; 23 | private final Collector collector; 24 | private A container; 25 | 26 | public CollectStage(BuiltGraph builtGraph, StageInlet inlet, 27 | CompletableFuture result, Collector collector) { 28 | super(builtGraph); 29 | this.inlet = inlet; 30 | this.result = result; 31 | this.collector = collector; 32 | 33 | container = collector.supplier().get(); 34 | inlet.setListener(this); 35 | } 36 | 37 | @Override 38 | protected void postStart() { 39 | // It's possible that an earlier stage finished immediately, so check first 40 | if (!inlet.isClosed()) { 41 | inlet.pull(); 42 | } 43 | } 44 | 45 | @Override 46 | public void onPush() { 47 | collector.accumulator().accept(container, inlet.grab()); 48 | inlet.pull(); 49 | } 50 | 51 | @Override 52 | public void onUpstreamFinish() { 53 | result.complete(collector.finisher().apply(container)); 54 | container = null; 55 | } 56 | 57 | @Override 58 | public void onUpstreamFailure(Throwable error) { 59 | result.completeExceptionally(error); 60 | container = null; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /impl/src/main/java/org/reactivestreams/utils/impl/ConcatStage.java: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Licensed under Public Domain (CC0) * 3 | * * 4 | * To the extent possible under law, the person who associated CC0 with * 5 | * this code has waived all copyright and related or neighboring * 6 | * rights to this code. * 7 | * * 8 | * You should have received a copy of the CC0 legalcode along with this * 9 | * work. If not, see . * 10 | ******************************************************************************/ 11 | 12 | package org.reactivestreams.utils.impl; 13 | 14 | public class ConcatStage extends GraphStage implements OutletListener { 15 | 16 | private final StageInlet first; 17 | private final StageInlet second; 18 | private final StageOutlet outlet; 19 | 20 | private Throwable secondError; 21 | 22 | public ConcatStage(BuiltGraph builtGraph, StageInlet first, StageInlet second, StageOutlet outlet) { 23 | super(builtGraph); 24 | this.first = first; 25 | this.second = second; 26 | this.outlet = outlet; 27 | 28 | first.setListener(new FirstInletListener()); 29 | second.setListener(new SecondInletListener()); 30 | outlet.setListener(this); 31 | } 32 | 33 | @Override 34 | public void onPull() { 35 | if (first.isClosed()) { 36 | second.pull(); 37 | } else { 38 | first.pull(); 39 | } 40 | } 41 | 42 | @Override 43 | public void onDownstreamFinish() { 44 | if (!first.isClosed()) { 45 | first.cancel(); 46 | } 47 | if (!second.isClosed()) { 48 | second.cancel(); 49 | } 50 | } 51 | 52 | private class FirstInletListener implements InletListener { 53 | @Override 54 | public void onPush() { 55 | outlet.push(first.grab()); 56 | } 57 | 58 | @Override 59 | public void onUpstreamFinish() { 60 | if (second.isClosed()) { 61 | if (secondError != null) { 62 | outlet.fail(secondError); 63 | } else { 64 | outlet.complete(); 65 | } 66 | } else if (outlet.isAvailable()) { 67 | second.pull(); 68 | } 69 | } 70 | 71 | @Override 72 | public void onUpstreamFailure(Throwable error) { 73 | outlet.fail(error); 74 | if (!second.isClosed()) { 75 | second.cancel(); 76 | } 77 | } 78 | } 79 | 80 | private class SecondInletListener implements InletListener { 81 | @Override 82 | public void onPush() { 83 | outlet.push(second.grab()); 84 | } 85 | 86 | @Override 87 | public void onUpstreamFinish() { 88 | if (first.isClosed()) { 89 | outlet.complete(); 90 | } 91 | } 92 | 93 | @Override 94 | public void onUpstreamFailure(Throwable error) { 95 | if (first.isClosed()) { 96 | outlet.fail(error); 97 | } else { 98 | secondError = error; 99 | } 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /impl/src/main/java/org/reactivestreams/utils/impl/ConnectorStage.java: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Licensed under Public Domain (CC0) * 3 | * * 4 | * To the extent possible under law, the person who associated CC0 with * 5 | * this code has waived all copyright and related or neighboring * 6 | * rights to this code. * 7 | * * 8 | * You should have received a copy of the CC0 legalcode along with this * 9 | * work. If not, see . * 10 | ******************************************************************************/ 11 | 12 | package org.reactivestreams.utils.impl; 13 | 14 | import java.util.concurrent.Flow; 15 | 16 | /** 17 | * Connector stage. Does nothing but connects a publisher to a subscriber when the graph starts. 18 | */ 19 | public class ConnectorStage extends GraphStage { 20 | private final Flow.Publisher publisher; 21 | private final Flow.Subscriber subscriber; 22 | 23 | public ConnectorStage(BuiltGraph builtGraph, Flow.Publisher publisher, Flow.Subscriber subscriber) { 24 | super(builtGraph); 25 | this.publisher = publisher; 26 | this.subscriber = subscriber; 27 | } 28 | 29 | @Override 30 | protected void postStart() { 31 | publisher.subscribe(subscriber); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /impl/src/main/java/org/reactivestreams/utils/impl/FailedStage.java: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Licensed under Public Domain (CC0) * 3 | * * 4 | * To the extent possible under law, the person who associated CC0 with * 5 | * this code has waived all copyright and related or neighboring * 6 | * rights to this code. * 7 | * * 8 | * You should have received a copy of the CC0 legalcode along with this * 9 | * work. If not, see . * 10 | ******************************************************************************/ 11 | 12 | package org.reactivestreams.utils.impl; 13 | 14 | /** 15 | * A failed stage. Does nothing but fails the stream when the graph starts. 16 | */ 17 | class FailedStage extends GraphStage implements OutletListener { 18 | private final Throwable error; 19 | private final StageOutlet outlet; 20 | 21 | public FailedStage(BuiltGraph builtGraph, StageOutlet outlet, Throwable error) { 22 | super(builtGraph); 23 | this.outlet = outlet; 24 | this.error = error; 25 | 26 | outlet.setListener(this); 27 | } 28 | 29 | @Override 30 | protected void postStart() { 31 | if (!outlet.isClosed()) { 32 | outlet.fail(error); 33 | } 34 | } 35 | 36 | @Override 37 | public void onPull() { 38 | } 39 | 40 | @Override 41 | public void onDownstreamFinish() { 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /impl/src/main/java/org/reactivestreams/utils/impl/FilterStage.java: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Licensed under Public Domain (CC0) * 3 | * * 4 | * To the extent possible under law, the person who associated CC0 with * 5 | * this code has waived all copyright and related or neighboring * 6 | * rights to this code. * 7 | * * 8 | * You should have received a copy of the CC0 legalcode along with this * 9 | * work. If not, see . * 10 | ******************************************************************************/ 11 | 12 | package org.reactivestreams.utils.impl; 13 | 14 | import java.util.function.Predicate; 15 | 16 | /** 17 | * A filter stage. 18 | */ 19 | class FilterStage extends GraphStage implements InletListener, OutletListener { 20 | private final StageInlet inlet; 21 | private final StageOutlet outlet; 22 | private final Predicate predicate; 23 | 24 | FilterStage(BuiltGraph builtGraph, StageInlet inlet, StageOutlet outlet, Predicate predicate) { 25 | super(builtGraph); 26 | this.inlet = inlet; 27 | this.outlet = outlet; 28 | this.predicate = predicate; 29 | 30 | inlet.setListener(this); 31 | outlet.setListener(this); 32 | } 33 | 34 | @Override 35 | public void onPush() { 36 | T element = inlet.grab(); 37 | if (predicate.test(element)) { 38 | outlet.push(element); 39 | } else { 40 | inlet.pull(); 41 | } 42 | } 43 | 44 | @Override 45 | public void onUpstreamFinish() { 46 | outlet.complete(); 47 | } 48 | 49 | @Override 50 | public void onUpstreamFailure(Throwable error) { 51 | outlet.fail(error); 52 | } 53 | 54 | @Override 55 | public void onPull() { 56 | inlet.pull(); 57 | } 58 | 59 | @Override 60 | public void onDownstreamFinish() { 61 | inlet.cancel(); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /impl/src/main/java/org/reactivestreams/utils/impl/FindFirstStage.java: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Licensed under Public Domain (CC0) * 3 | * * 4 | * To the extent possible under law, the person who associated CC0 with * 5 | * this code has waived all copyright and related or neighboring * 6 | * rights to this code. * 7 | * * 8 | * You should have received a copy of the CC0 legalcode along with this * 9 | * work. If not, see . * 10 | ******************************************************************************/ 11 | 12 | package org.reactivestreams.utils.impl; 13 | 14 | import java.util.Optional; 15 | import java.util.concurrent.CompletableFuture; 16 | 17 | class FindFirstStage extends GraphStage implements InletListener { 18 | 19 | private final StageInlet inlet; 20 | private final CompletableFuture> result; 21 | 22 | FindFirstStage(BuiltGraph builtGraph, StageInlet inlet, CompletableFuture> result) { 23 | super(builtGraph); 24 | this.inlet = inlet; 25 | this.result = result; 26 | 27 | inlet.setListener(this); 28 | } 29 | 30 | @Override 31 | protected void postStart() { 32 | if (!inlet.isClosed()) { 33 | inlet.pull(); 34 | } 35 | } 36 | 37 | @Override 38 | public void onPush() { 39 | result.complete(Optional.of(inlet.grab())); 40 | inlet.cancel(); 41 | } 42 | 43 | @Override 44 | public void onUpstreamFinish() { 45 | result.complete(Optional.empty()); 46 | } 47 | 48 | @Override 49 | public void onUpstreamFailure(Throwable error) { 50 | result.completeExceptionally(error); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /impl/src/main/java/org/reactivestreams/utils/impl/FlatMapCompletionStage.java: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Licensed under Public Domain (CC0) * 3 | * * 4 | * To the extent possible under law, the person who associated CC0 with * 5 | * this code has waived all copyright and related or neighboring * 6 | * rights to this code. * 7 | * * 8 | * You should have received a copy of the CC0 legalcode along with this * 9 | * work. If not, see . * 10 | ******************************************************************************/ 11 | 12 | package org.reactivestreams.utils.impl; 13 | 14 | import java.util.concurrent.CompletionStage; 15 | import java.util.function.Function; 16 | 17 | /** 18 | * Flat maps to completion stages of elements. 19 | */ 20 | class FlatMapCompletionStage extends GraphStage implements InletListener, OutletListener { 21 | private final StageInlet inlet; 22 | private final StageOutlet outlet; 23 | private final Function> mapper; 24 | 25 | private Throwable error; 26 | 27 | FlatMapCompletionStage(BuiltGraph builtGraph, StageInlet inlet, StageOutlet outlet, Function> mapper) { 28 | super(builtGraph); 29 | this.inlet = inlet; 30 | this.outlet = outlet; 31 | this.mapper = mapper; 32 | 33 | inlet.setListener(this); 34 | outlet.setListener(this); 35 | } 36 | 37 | @Override 38 | public void onPush() { 39 | CompletionStage future = mapper.apply(inlet.grab()); 40 | future.whenCompleteAsync((result, error) -> { 41 | if (!outlet.isClosed()) { 42 | if (error == null) { 43 | outlet.push(result); 44 | if (inlet.isClosed()) { 45 | if (this.error != null) { 46 | outlet.fail(this.error); 47 | } else { 48 | outlet.complete(); 49 | } 50 | } 51 | } else { 52 | 53 | outlet.fail(error); 54 | if (!inlet.isClosed()) { 55 | inlet.cancel(); 56 | } 57 | } 58 | } 59 | }, executor()); 60 | } 61 | 62 | @Override 63 | public void onUpstreamFinish() { 64 | if (!activeCompletionStage()) { 65 | outlet.complete(); 66 | } 67 | } 68 | 69 | @Override 70 | public void onUpstreamFailure(Throwable error) { 71 | if (activeCompletionStage()) { 72 | this.error = error; 73 | } else { 74 | outlet.fail(error); 75 | } 76 | } 77 | 78 | private boolean activeCompletionStage() { 79 | return outlet.isAvailable() && !inlet.isPulled(); 80 | } 81 | 82 | @Override 83 | public void onPull() { 84 | inlet.pull(); 85 | } 86 | 87 | @Override 88 | public void onDownstreamFinish() { 89 | inlet.cancel(); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /impl/src/main/java/org/reactivestreams/utils/impl/FlatMapIterableStage.java: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Licensed under Public Domain (CC0) * 3 | * * 4 | * To the extent possible under law, the person who associated CC0 with * 5 | * this code has waived all copyright and related or neighboring * 6 | * rights to this code. * 7 | * * 8 | * You should have received a copy of the CC0 legalcode along with this * 9 | * work. If not, see . * 10 | ******************************************************************************/ 11 | 12 | package org.reactivestreams.utils.impl; 13 | 14 | import java.util.Iterator; 15 | import java.util.function.Function; 16 | 17 | /** 18 | * A flatmap to iterable stage. 19 | */ 20 | class FlatMapIterableStage extends GraphStage implements InletListener, OutletListener { 21 | private final StageInlet inlet; 22 | private final StageOutlet outlet; 23 | private final Function> mapper; 24 | 25 | private Throwable error; 26 | private Iterator iterator; 27 | 28 | FlatMapIterableStage(BuiltGraph builtGraph, StageInlet inlet, StageOutlet outlet, Function> mapper) { 29 | super(builtGraph); 30 | this.inlet = inlet; 31 | this.outlet = outlet; 32 | this.mapper = mapper; 33 | 34 | inlet.setListener(this); 35 | outlet.setListener(this); 36 | } 37 | 38 | @Override 39 | public void onPush() { 40 | Iterator iterator = mapper.apply(inlet.grab()).iterator(); 41 | 42 | if (iterator.hasNext()) { 43 | this.iterator = iterator; 44 | 45 | outlet.push(iterator.next()); 46 | // Make sure we're still on the same iterator in case a recursive call changed things 47 | if (!iterator.hasNext() && this.iterator == iterator) { 48 | this.iterator = null; 49 | } 50 | } else { 51 | inlet.pull(); 52 | } 53 | } 54 | 55 | @Override 56 | public void onUpstreamFinish() { 57 | if (iterator == null) { 58 | outlet.complete(); 59 | } 60 | } 61 | 62 | @Override 63 | public void onUpstreamFailure(Throwable error) { 64 | if (iterator == null) { 65 | outlet.fail(error); 66 | } else { 67 | this.error = error; 68 | } 69 | } 70 | 71 | @Override 72 | public void onPull() { 73 | if (iterator == null) { 74 | inlet.pull(); 75 | } else { 76 | Iterator iterator = this.iterator; 77 | outlet.push(iterator.next()); 78 | if (!iterator.hasNext() && this.iterator == iterator) { 79 | this.iterator = null; 80 | if (inlet.isClosed()) { 81 | if (error != null) { 82 | outlet.fail(error); 83 | } else { 84 | outlet.complete(); 85 | } 86 | } 87 | } 88 | } 89 | } 90 | 91 | @Override 92 | public void onDownstreamFinish() { 93 | inlet.cancel(); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /impl/src/main/java/org/reactivestreams/utils/impl/FlatMapStage.java: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Licensed under Public Domain (CC0) * 3 | * * 4 | * To the extent possible under law, the person who associated CC0 with * 5 | * this code has waived all copyright and related or neighboring * 6 | * rights to this code. * 7 | * * 8 | * You should have received a copy of the CC0 legalcode along with this * 9 | * work. If not, see . * 10 | ******************************************************************************/ 11 | 12 | package org.reactivestreams.utils.impl; 13 | 14 | import org.reactivestreams.utils.spi.Graph; 15 | 16 | import java.util.function.Function; 17 | 18 | class FlatMapStage extends GraphStage implements InletListener, OutletListener { 19 | private final StageInlet inlet; 20 | private final StageOutlet outlet; 21 | private final Function mapper; 22 | 23 | private BuiltGraph.SubStageInlet substream; 24 | private Throwable error; 25 | 26 | FlatMapStage(BuiltGraph builtGraph, StageInlet inlet, StageOutlet outlet, Function mapper) { 27 | super(builtGraph); 28 | this.inlet = inlet; 29 | this.outlet = outlet; 30 | this.mapper = mapper; 31 | 32 | inlet.setListener(this); 33 | outlet.setListener(this); 34 | } 35 | 36 | @Override 37 | public void onPush() { 38 | Graph graph = mapper.apply(inlet.grab()); 39 | substream = createSubInlet(graph); 40 | substream.setListener(new InletListener() { 41 | @Override 42 | public void onPush() { 43 | outlet.push(substream.grab()); 44 | } 45 | 46 | @Override 47 | public void onUpstreamFinish() { 48 | substream = null; 49 | if (inlet.isClosed()) { 50 | if (error != null) { 51 | outlet.fail(error); 52 | } else { 53 | outlet.complete(); 54 | } 55 | } else if (outlet.isAvailable()) { 56 | inlet.pull(); 57 | } 58 | } 59 | 60 | @Override 61 | public void onUpstreamFailure(Throwable error) { 62 | outlet.fail(error); 63 | if (!inlet.isClosed()) { 64 | inlet.cancel(); 65 | } 66 | } 67 | }); 68 | substream.start(); 69 | substream.pull(); 70 | } 71 | 72 | @Override 73 | public void onUpstreamFinish() { 74 | if (substream == null) { 75 | outlet.complete(); 76 | } 77 | } 78 | 79 | @Override 80 | public void onUpstreamFailure(Throwable error) { 81 | if (substream == null) { 82 | outlet.fail(error); 83 | } else { 84 | this.error = error; 85 | } 86 | } 87 | 88 | @Override 89 | public void onPull() { 90 | if (substream == null) { 91 | inlet.pull(); 92 | } else { 93 | substream.pull(); 94 | } 95 | } 96 | 97 | @Override 98 | public void onDownstreamFinish() { 99 | if (!inlet.isClosed()) { 100 | inlet.cancel(); 101 | } 102 | if (substream != null) { 103 | substream.cancel(); 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /impl/src/main/java/org/reactivestreams/utils/impl/GraphStage.java: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Licensed under Public Domain (CC0) * 3 | * * 4 | * To the extent possible under law, the person who associated CC0 with * 5 | * this code has waived all copyright and related or neighboring * 6 | * rights to this code. * 7 | * * 8 | * You should have received a copy of the CC0 legalcode along with this * 9 | * work. If not, see . * 10 | ******************************************************************************/ 11 | 12 | package org.reactivestreams.utils.impl; 13 | 14 | import org.reactivestreams.utils.spi.Graph; 15 | 16 | import java.util.concurrent.Executor; 17 | 18 | /** 19 | * Superclass of all graph stages. 20 | */ 21 | abstract class GraphStage { 22 | 23 | private final BuiltGraph builtGraph; 24 | 25 | GraphStage(BuiltGraph builtGraph) { 26 | this.builtGraph = builtGraph; 27 | } 28 | 29 | /** 30 | * Create a sub inlet for the given graph. 31 | *

32 | * After being created, the inlet should have an inlet listener attached to it, and then it should be started. 33 | * 34 | * @param graph The graph. 35 | * @return The inlet. 36 | */ 37 | protected BuiltGraph.SubStageInlet createSubInlet(Graph graph) { 38 | return builtGraph.buildSubInlet(graph); 39 | } 40 | 41 | protected Executor executor() { 42 | return builtGraph; 43 | } 44 | 45 | /** 46 | * Run a callback after the graph has started. 47 | *

48 | * When implementing this, it's important to remember that this is executed *after* the graph has started. It's 49 | * possible that the stage will receive other signals before this is executed, which may have been triggered from 50 | * the postStart methods on other stages. So this should not be used to do initialisation that should be done 51 | * before the stage is ready to receive signals, that initialisation should be done in the constructor, rather, 52 | * this can be used to initiate signals, but care needs to be taken, for example, a stage that just completes 53 | * immediately should check whether the outlet is completed first, since it may have been by a previous callback. 54 | */ 55 | protected void postStart() { 56 | // Do nothing by default 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /impl/src/main/java/org/reactivestreams/utils/impl/MapStage.java: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Licensed under Public Domain (CC0) * 3 | * * 4 | * To the extent possible under law, the person who associated CC0 with * 5 | * this code has waived all copyright and related or neighboring * 6 | * rights to this code. * 7 | * * 8 | * You should have received a copy of the CC0 legalcode along with this * 9 | * work. If not, see . * 10 | ******************************************************************************/ 11 | 12 | package org.reactivestreams.utils.impl; 13 | 14 | import java.util.function.Function; 15 | 16 | /** 17 | * A map stage. 18 | */ 19 | class MapStage extends GraphStage implements InletListener, OutletListener { 20 | private final StageInlet inlet; 21 | private final StageOutlet outlet; 22 | private final Function mapper; 23 | 24 | MapStage(BuiltGraph builtGraph, StageInlet inlet, StageOutlet outlet, Function mapper) { 25 | super(builtGraph); 26 | this.inlet = inlet; 27 | this.outlet = outlet; 28 | this.mapper = mapper; 29 | 30 | inlet.setListener(this); 31 | outlet.setListener(this); 32 | } 33 | 34 | @Override 35 | public void onPush() { 36 | outlet.push(mapper.apply(inlet.grab())); 37 | } 38 | 39 | @Override 40 | public void onUpstreamFinish() { 41 | outlet.complete(); 42 | } 43 | 44 | @Override 45 | public void onUpstreamFailure(Throwable error) { 46 | outlet.fail(error); 47 | } 48 | 49 | @Override 50 | public void onPull() { 51 | inlet.pull(); 52 | } 53 | 54 | @Override 55 | public void onDownstreamFinish() { 56 | inlet.cancel(); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /impl/src/main/java/org/reactivestreams/utils/impl/MutexExecutor.java: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Licensed under Public Domain (CC0) * 3 | * * 4 | * To the extent possible under law, the person who associated CC0 with * 5 | * this code has waived all copyright and related or neighboring * 6 | * rights to this code. * 7 | * * 8 | * You should have received a copy of the CC0 legalcode along with this * 9 | * work. If not, see . * 10 | ******************************************************************************/ 11 | 12 | package org.reactivestreams.utils.impl; 13 | 14 | import java.util.Objects; 15 | import java.util.concurrent.Executor; 16 | import java.util.concurrent.atomic.AtomicReference; 17 | 18 | /** 19 | * Executor that provides mutual exclusion between the operations submitted to it. 20 | * 21 | * All operations are delegated to the wrapped executor, however only one operation 22 | * at a time will be submitted to that executor. The queuing of operations is done 23 | * in a non blocking fashion. 24 | */ 25 | final class MutexExecutor implements Executor { 26 | private final Executor delegate; 27 | private final AtomicReference last = new AtomicReference<>(); 28 | 29 | MutexExecutor(Executor delegate) { 30 | this.delegate = delegate; 31 | } 32 | 33 | @Override 34 | public void execute(final Runnable command) { 35 | final RunNode newNode = new RunNode(Objects.requireNonNull(command, "Runnable must not be null")); 36 | final RunNode prevLast = last.getAndSet(newNode); 37 | if (prevLast != null) 38 | prevLast.lazySet(newNode); 39 | else 40 | delegate.execute(() -> runAll(newNode)); 41 | } 42 | 43 | protected void reportFailure(final Thread runner, final Runnable thrower, final Throwable thrown) { 44 | if (thrown instanceof InterruptedException) { 45 | // TODO: Current task was interrupted, set interrupted flag and proceed is a valid strategy? 46 | runner.interrupt(); 47 | } else { // TODO: complement the most appropriate way of dealing with fatal Throwables 48 | final Thread.UncaughtExceptionHandler ueh = runner.getUncaughtExceptionHandler(); 49 | if (ueh != null) 50 | ueh.uncaughtException(runner, thrown); 51 | else thrown.printStackTrace(); 52 | // TODO: Rethrow or something else? Is there a sensible fallback here? 53 | } 54 | } 55 | 56 | // Runs a single RunNode and deals with any Throwables it throws 57 | private final void run(final RunNode current) { 58 | try { current.runnable.run(); } catch (final Throwable thrown) { 59 | reportFailure(Thread.currentThread(), current.runnable, thrown); 60 | } 61 | } 62 | 63 | // Runs all the RunNodes starting with `next` 64 | private final void runAll(RunNode next) { 65 | for(;;) { 66 | final RunNode current = next; 67 | run(current); 68 | if ((next = current.get()) == null) { // try advance, if we get null test 69 | if (last.compareAndSet(current, null)) return; // end-of-queue: we're done. 70 | else while((next = current.get()) == null) Thread.onSpinWait(); // try advance until next is visible. 71 | } 72 | } 73 | } 74 | 75 | private static class RunNode extends AtomicReference { 76 | final Runnable runnable; 77 | RunNode(final Runnable runnable) { 78 | this.runnable = runnable; 79 | } 80 | } 81 | } -------------------------------------------------------------------------------- /impl/src/main/java/org/reactivestreams/utils/impl/OfStage.java: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Licensed under Public Domain (CC0) * 3 | * * 4 | * To the extent possible under law, the person who associated CC0 with * 5 | * this code has waived all copyright and related or neighboring * 6 | * rights to this code. * 7 | * * 8 | * You should have received a copy of the CC0 legalcode along with this * 9 | * work. If not, see . * 10 | ******************************************************************************/ 11 | 12 | package org.reactivestreams.utils.impl; 13 | 14 | import java.util.Iterator; 15 | 16 | /** 17 | * Of stage. 18 | */ 19 | class OfStage extends GraphStage implements OutletListener { 20 | private final StageOutlet outlet; 21 | private Iterator elements; 22 | 23 | public OfStage(BuiltGraph builtGraph, StageOutlet outlet, Iterable elements) { 24 | super(builtGraph); 25 | this.outlet = outlet; 26 | this.elements = elements.iterator(); 27 | 28 | outlet.setListener(this); 29 | } 30 | 31 | @Override 32 | protected void postStart() { 33 | if (!outlet.isClosed()) { 34 | if (!elements.hasNext()) { 35 | outlet.complete(); 36 | } 37 | } 38 | } 39 | 40 | @Override 41 | public void onPull() { 42 | outlet.push(elements.next()); 43 | if (!elements.hasNext() && !outlet.isClosed()) { 44 | outlet.complete(); 45 | } 46 | } 47 | 48 | @Override 49 | public void onDownstreamFinish() { 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /impl/src/main/java/org/reactivestreams/utils/impl/Probes.java: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Licensed under Public Domain (CC0) * 3 | * * 4 | * To the extent possible under law, the person who associated CC0 with * 5 | * this code has waived all copyright and related or neighboring * 6 | * rights to this code. * 7 | * * 8 | * You should have received a copy of the CC0 legalcode along with this * 9 | * work. If not, see . * 10 | ******************************************************************************/ 11 | 12 | package org.reactivestreams.utils.impl; 13 | 14 | import java.util.concurrent.atomic.AtomicLong; 15 | import java.util.concurrent.Flow.*; 16 | 17 | public class Probes { 18 | 19 | private static final AtomicLong counter = new AtomicLong(1000); 20 | 21 | public static Subscriber subscriber(String name, Subscriber probed) { 22 | return new SubscriberProbe<>(name + "-" + counter.getAndIncrement(), probed); 23 | } 24 | 25 | public static Publisher publisher(String name, Publisher probed) { 26 | return new PublisherProbe<>(name + "-" + counter.getAndIncrement(), probed); 27 | } 28 | 29 | private static class Probe { 30 | private final String name; 31 | 32 | protected Probe(String name) { 33 | this.name = name; 34 | } 35 | 36 | protected void trace(String methodName, Object description, Runnable operation) { 37 | log("ENTER " + methodName + " " + description); 38 | try { 39 | operation.run(); 40 | log("LEAVE " + methodName); 41 | } catch (RuntimeException e) { 42 | log("ERROR " + methodName); 43 | e.printStackTrace(); 44 | throw e; 45 | } 46 | } 47 | 48 | protected void log(String msg) { 49 | System.out.println(System.currentTimeMillis() + " " + name + " - " + msg); 50 | } 51 | } 52 | 53 | private static class SubscriptionProbe extends Probe implements Subscription { 54 | private final Subscription probed; 55 | 56 | public SubscriptionProbe(String name, Subscription probed) { 57 | super(name); 58 | this.probed = probed; 59 | } 60 | 61 | @Override 62 | public void request(long n) { 63 | trace("request", n, () -> probed.request(n)); 64 | } 65 | 66 | @Override 67 | public void cancel() { 68 | trace("cancel", "", () -> probed.cancel()); 69 | } 70 | } 71 | 72 | private static class SubscriberProbe extends Probe implements Subscriber { 73 | 74 | private final String name; 75 | private final Subscriber probed; 76 | 77 | public SubscriberProbe(String name, Subscriber probed) { 78 | super(name); 79 | this.name = name; 80 | this.probed = probed; 81 | } 82 | 83 | @Override 84 | public void onSubscribe(Subscription s) { 85 | trace("onSubscribe", s, () -> { 86 | if (s == null) { 87 | probed.onSubscribe(s); 88 | } else { 89 | probed.onSubscribe(new SubscriptionProbe(name, s)); 90 | } 91 | }); 92 | } 93 | 94 | @Override 95 | public void onNext(T t) { 96 | trace("onNext", t, () -> probed.onNext(t)); 97 | } 98 | 99 | @Override 100 | public void onError(Throwable t) { 101 | trace("onError", t, () -> probed.onError(t)); 102 | } 103 | 104 | @Override 105 | public void onComplete() { 106 | trace("onComplete", "", () -> probed.onComplete()); 107 | } 108 | } 109 | 110 | private static class PublisherProbe extends Probe implements Publisher { 111 | private final String name; 112 | private final Publisher probed; 113 | 114 | public PublisherProbe(String name, Publisher probed) { 115 | super(name); 116 | this.name = name; 117 | this.probed = probed; 118 | } 119 | 120 | @Override 121 | public void subscribe(Subscriber s) { 122 | trace("subscribe", s, () -> { 123 | if (s == null) { 124 | probed.subscribe(s); 125 | } else { 126 | probed.subscribe(new SubscriberProbe<>(name, s)); 127 | } 128 | }); 129 | } 130 | } 131 | } 132 | 133 | 134 | -------------------------------------------------------------------------------- /impl/src/main/java/org/reactivestreams/utils/impl/PublisherOutlet.java: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Licensed under Public Domain (CC0) * 3 | * * 4 | * To the extent possible under law, the person who associated CC0 with * 5 | * this code has waived all copyright and related or neighboring * 6 | * rights to this code. * 7 | * * 8 | * You should have received a copy of the CC0 legalcode along with this * 9 | * work. If not, see . * 10 | ******************************************************************************/ 11 | 12 | package org.reactivestreams.utils.impl; 13 | 14 | import java.util.Objects; 15 | import java.util.concurrent.Flow; 16 | 17 | /** 18 | * An outlet that is a publisher. 19 | * 20 | * This is either the last outlet for a graph that has an outlet, or is used to connect a Processor or Publisher stage 21 | * in a graph. 22 | */ 23 | final class PublisherOutlet implements StageOutlet, Flow.Publisher, Flow.Subscription, Port, UnrolledSignal { 24 | 25 | private final BuiltGraph builtGraph; 26 | 27 | private Flow.Subscriber subscriber; 28 | private boolean pulled; 29 | private long demand; 30 | private boolean finished; 31 | private Throwable failure; 32 | private OutletListener listener; 33 | 34 | PublisherOutlet(BuiltGraph builtGraph) { 35 | this.builtGraph = builtGraph; 36 | } 37 | 38 | @Override 39 | public void onStreamFailure(Throwable reason) { 40 | if (!finished) { 41 | finished = true; 42 | demand = 0; 43 | if (subscriber != null) { 44 | try { 45 | subscriber.onError(reason); 46 | } catch (Exception e) { 47 | // Ignore 48 | } 49 | } else { 50 | failure = reason; 51 | } 52 | listener.onDownstreamFinish(); 53 | } 54 | } 55 | 56 | @Override 57 | public void verifyReady() { 58 | if (listener == null) { 59 | throw new IllegalStateException("Cannot start stream without inlet listener set"); 60 | } 61 | } 62 | 63 | @Override 64 | public void subscribe(Flow.Subscriber subscriber) { 65 | Objects.requireNonNull(subscriber, "Subscriber must not be null"); 66 | builtGraph.execute(() -> { 67 | if (this.subscriber != null) { 68 | subscriber.onSubscribe(new Flow.Subscription() { 69 | @Override 70 | public void request(long n) { 71 | } 72 | 73 | @Override 74 | public void cancel() { 75 | } 76 | }); 77 | subscriber.onError(new IllegalStateException("This publisher only supports one subscriber")); 78 | } else { 79 | this.subscriber = subscriber; 80 | subscriber.onSubscribe(this); 81 | if (finished) { 82 | if (failure != null) { 83 | subscriber.onError(failure); 84 | failure = null; 85 | this.subscriber = null; 86 | } else { 87 | subscriber.onComplete(); 88 | this.subscriber = null; 89 | } 90 | } 91 | } 92 | }); 93 | } 94 | 95 | @Override 96 | public void request(long n) { 97 | builtGraph.execute(() -> { 98 | if (!finished) { 99 | if (n <= 0) { 100 | onStreamFailure(new IllegalArgumentException("Request demand must be greater than zero")); 101 | } else { 102 | boolean existingDemand = demand > 0; 103 | demand = demand + n; 104 | if (demand <= 0) { 105 | demand = Long.MAX_VALUE; 106 | } 107 | if (!existingDemand) { 108 | doPull(); 109 | } 110 | } 111 | } 112 | }); 113 | } 114 | 115 | @Override 116 | public void signal() { 117 | if (!finished && !pulled) { 118 | doPull(); 119 | } 120 | } 121 | 122 | private void doPull() { 123 | pulled = true; 124 | listener.onPull(); 125 | } 126 | 127 | @Override 128 | public void cancel() { 129 | builtGraph.execute(() -> { 130 | subscriber = null; 131 | if (!finished) { 132 | finished = true; 133 | demand = 0; 134 | listener.onDownstreamFinish(); 135 | } 136 | }); 137 | } 138 | 139 | @Override 140 | public void push(T element) { 141 | Objects.requireNonNull(element, "Elements cannot be null"); 142 | if (finished) { 143 | throw new IllegalStateException("Can't push after publisher is finished"); 144 | } else if (demand <= 0) { 145 | throw new IllegalStateException("Push without pull"); 146 | } 147 | pulled = false; 148 | if (demand != Long.MAX_VALUE) { 149 | demand -= 1; 150 | } 151 | subscriber.onNext(element); 152 | if (demand > 0) { 153 | builtGraph.enqueueSignal(this); 154 | } 155 | } 156 | 157 | @Override 158 | public boolean isAvailable() { 159 | return !finished && pulled; 160 | } 161 | 162 | @Override 163 | public void complete() { 164 | if (finished) { 165 | throw new IllegalStateException("Can't complete twice"); 166 | } else { 167 | finished = true; 168 | demand = 0; 169 | if (subscriber != null) { 170 | subscriber.onComplete(); 171 | subscriber = null; 172 | } 173 | } 174 | } 175 | 176 | @Override 177 | public boolean isClosed() { 178 | return finished; 179 | } 180 | 181 | @Override 182 | public void fail(Throwable error) { 183 | Objects.requireNonNull(error, "Error must not be null"); 184 | if (finished) { 185 | throw new IllegalStateException("Can't complete twice"); 186 | } else { 187 | finished = true; 188 | demand = 0; 189 | if (subscriber != null) { 190 | subscriber.onError(error); 191 | subscriber = null; 192 | } else { 193 | failure = error; 194 | } 195 | } 196 | } 197 | 198 | @Override 199 | public void setListener(OutletListener listener) { 200 | this.listener = Objects.requireNonNull(listener, "Listener must not be null"); 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /impl/src/main/java/org/reactivestreams/utils/impl/ReactiveStreamsEngineImpl.java: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Licensed under Public Domain (CC0) * 3 | * * 4 | * To the extent possible under law, the person who associated CC0 with * 5 | * this code has waived all copyright and related or neighboring * 6 | * rights to this code. * 7 | * * 8 | * You should have received a copy of the CC0 legalcode along with this * 9 | * work. If not, see . * 10 | ******************************************************************************/ 11 | 12 | package org.reactivestreams.utils.impl; 13 | 14 | import org.reactivestreams.utils.ReactiveStreamsEngine; 15 | import org.reactivestreams.utils.SubscriberWithResult; 16 | import org.reactivestreams.utils.spi.Graph; 17 | import org.reactivestreams.utils.spi.UnsupportedStageException; 18 | 19 | import java.util.concurrent.CompletionStage; 20 | import java.util.concurrent.Flow; 21 | import java.util.concurrent.ForkJoinPool; 22 | 23 | /** 24 | * Implementation of the reactive streams engine. 25 | */ 26 | public class ReactiveStreamsEngineImpl implements ReactiveStreamsEngine { 27 | @Override 28 | public Flow.Publisher buildPublisher(Graph graph) throws UnsupportedStageException { 29 | return BuiltGraph.buildPublisher(ForkJoinPool.commonPool(), graph); 30 | } 31 | 32 | @Override 33 | public SubscriberWithResult buildSubscriber(Graph graph) throws UnsupportedStageException { 34 | return BuiltGraph.buildSubscriber(ForkJoinPool.commonPool(), graph); 35 | } 36 | 37 | @Override 38 | public Flow.Processor buildProcessor(Graph graph) throws UnsupportedStageException { 39 | return BuiltGraph.buildProcessor(ForkJoinPool.commonPool(), graph); 40 | } 41 | 42 | @Override 43 | public CompletionStage buildCompletion(Graph graph) throws UnsupportedStageException { 44 | return BuiltGraph.buildCompletion(ForkJoinPool.commonPool(), graph); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /impl/src/main/java/org/reactivestreams/utils/impl/StageInlet.java: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Licensed under Public Domain (CC0) * 3 | * * 4 | * To the extent possible under law, the person who associated CC0 with * 5 | * this code has waived all copyright and related or neighboring * 6 | * rights to this code. * 7 | * * 8 | * You should have received a copy of the CC0 legalcode along with this * 9 | * work. If not, see . * 10 | ******************************************************************************/ 11 | 12 | package org.reactivestreams.utils.impl; 13 | 14 | /** 15 | * An inlet that a stage may interact with. 16 | * 17 | * @param The type of signal this stage deals with. 18 | */ 19 | interface StageInlet { 20 | 21 | /** 22 | * Send a pull signal to this inlet. This will allow an upstream stage to push an element. 23 | *

24 | * The inlet may only be pulled if it is not closed and hasn't already been pulled since it last received an element. 25 | */ 26 | void pull(); 27 | 28 | /** 29 | * Whether this inlet has been pulled. 30 | */ 31 | boolean isPulled(); 32 | 33 | /** 34 | * Whether this inlet is available to be grabbed. 35 | */ 36 | boolean isAvailable(); 37 | 38 | /** 39 | * Whether this inlet has been closed, either due to it being explicitly cancelled, or due to an 40 | * upstream finish or failure being received. 41 | */ 42 | boolean isClosed(); 43 | 44 | /** 45 | * Cancel this inlet. No signals may be sent after this is invoked, and no signals will be received. 46 | */ 47 | void cancel(); 48 | 49 | /** 50 | * Grab the last pushed element from this inlet. 51 | *

52 | * Grabbing the element will cause it to be removed from the inlet - an element cannot be grabbed twice. 53 | *

54 | * This may only be invoked if a prior {@link InletListener#onPush()} signal has been received. 55 | * 56 | * @return The grabbed element. 57 | */ 58 | T grab(); 59 | 60 | /** 61 | * Set the listener for signals from this inlet. 62 | * 63 | * @param listener The listener. 64 | */ 65 | void setListener(InletListener listener); 66 | } 67 | 68 | /** 69 | * A listener for signals to an inlet. 70 | */ 71 | interface InletListener { 72 | 73 | /** 74 | * Indicates that an element has been pushed. The element can be received using {@link StageInlet#grab()}. 75 | */ 76 | void onPush(); 77 | 78 | /** 79 | * Indicates that upstream has completed the stream. No signals may be sent to the inlet after this has been invoked. 80 | */ 81 | void onUpstreamFinish(); 82 | 83 | /** 84 | * Indicates that upstream has completed the stream with a failure. No signals may be sent to the inlet after this has 85 | * been invoked. 86 | */ 87 | void onUpstreamFailure(Throwable error); 88 | } 89 | -------------------------------------------------------------------------------- /impl/src/main/java/org/reactivestreams/utils/impl/StageOutlet.java: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Licensed under Public Domain (CC0) * 3 | * * 4 | * To the extent possible under law, the person who associated CC0 with * 5 | * this code has waived all copyright and related or neighboring * 6 | * rights to this code. * 7 | * * 8 | * You should have received a copy of the CC0 legalcode along with this * 9 | * work. If not, see . * 10 | ******************************************************************************/ 11 | 12 | package org.reactivestreams.utils.impl; 13 | 14 | /** 15 | * An outlet that a stage may interact with. 16 | * 17 | * @param The type of elements that this outlet supports. 18 | */ 19 | interface StageOutlet { 20 | 21 | /** 22 | * Push an element. 23 | *

24 | * An element may only be pushed if an {@link OutletListener#onPull()} signal has been received, and the outlet 25 | * hasn't been completed, failed or a {@link OutletListener#onDownstreamFinish()} hasn't been received. 26 | * 27 | * @param element The element to push. 28 | */ 29 | void push(T element); 30 | 31 | /** 32 | * Whether this outlet is available for an element to be pushed. 33 | */ 34 | boolean isAvailable(); 35 | 36 | /** 37 | * Complete this outlet. 38 | */ 39 | void complete(); 40 | 41 | /** 42 | * Whether this outlet is closed, either due to sending a complete or fail signal, or due to downstream 43 | * completing by invoking {@link OutletListener#onDownstreamFinish()}. 44 | */ 45 | boolean isClosed(); 46 | 47 | /** 48 | * Fail this outlet. 49 | * 50 | * @param error The error to fail it with. 51 | */ 52 | void fail(Throwable error); 53 | 54 | /** 55 | * Set the listener for signals from this outlet. 56 | * 57 | * @param listener The listener to set. 58 | */ 59 | void setListener(OutletListener listener); 60 | } 61 | 62 | /** 63 | * An listener to receive signals from an outlet. 64 | */ 65 | interface OutletListener { 66 | /** 67 | * A pull signal, indicates that downstream is ready to be pushed to. 68 | */ 69 | void onPull(); 70 | 71 | /** 72 | * A completion signal, indicates that downstream has completed. No further signals may be sent to this outlet after 73 | * this signal is received. 74 | */ 75 | void onDownstreamFinish(); 76 | } 77 | -------------------------------------------------------------------------------- /impl/src/main/java/org/reactivestreams/utils/impl/StageOutletInlet.java: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Licensed under Public Domain (CC0) * 3 | * * 4 | * To the extent possible under law, the person who associated CC0 with * 5 | * this code has waived all copyright and related or neighboring * 6 | * rights to this code. * 7 | * * 8 | * You should have received a copy of the CC0 legalcode along with this * 9 | * work. If not, see . * 10 | ******************************************************************************/ 11 | 12 | package org.reactivestreams.utils.impl; 13 | 14 | import java.util.Objects; 15 | 16 | /** 17 | * A stage outlet and inlet. Elements passed in to the outlet are forwarded to the inlet, and backpressure from the 18 | * inlet flows to the outlet. 19 | * 20 | * This port is for use between two stages of a graph. 21 | */ 22 | final class StageOutletInlet implements Port { 23 | private final BuiltGraph builtGraph; 24 | 25 | private InletListener inletListener; 26 | private OutletListener outletListener; 27 | private boolean inletPulled; 28 | /** 29 | * The pushed element is an element that has been pushed but for which onPush has not yet been invoked. Once onPush 30 | * is invoked, it is transferred to currentElement. The reason for this separation is that pushing of elements is not 31 | * done directly, in order to avoid infinite recursions between stages doing a push/pull back and forth. 32 | */ 33 | private T pushedElement; 34 | private T currentElement; 35 | private boolean outletFinished; 36 | private boolean inletFinished; 37 | private Throwable failure; 38 | 39 | StageOutletInlet(BuiltGraph builtGraph) { 40 | this.builtGraph = builtGraph; 41 | } 42 | 43 | @Override 44 | public void onStreamFailure(Throwable reason) { 45 | if (!outletFinished) { 46 | outletFinished = true; 47 | if (outletListener != null) { 48 | outletListener.onDownstreamFinish(); 49 | } 50 | } 51 | if (!inletFinished) { 52 | inletFinished = true; 53 | if (inletListener != null) { 54 | inletListener.onUpstreamFailure(reason); 55 | } 56 | } 57 | } 58 | 59 | @Override 60 | public void verifyReady() { 61 | if (inletListener == null) { 62 | throw new IllegalStateException("Cannot start stream without inlet listener set"); 63 | } 64 | if (outletListener == null) { 65 | throw new IllegalStateException("Cannot start stream without outlet listener set"); 66 | } 67 | } 68 | 69 | final class Outlet implements StageOutlet, UnrolledSignal { 70 | @Override 71 | public void push(T element) { 72 | Objects.requireNonNull(element, "Elements cannot be null"); 73 | if (outletFinished) { 74 | throw new IllegalStateException("Can't push element after complete"); 75 | } else if (!inletPulled || currentElement != null || pushedElement != null) { 76 | throw new IllegalStateException("Can't push element to outlet when it hasn't pulled"); 77 | } else { 78 | pushedElement = element; 79 | builtGraph.enqueueSignal(this); 80 | } 81 | } 82 | 83 | @Override 84 | public void signal() { 85 | if (!inletFinished) { 86 | currentElement = pushedElement; 87 | pushedElement = null; 88 | inletListener.onPush(); 89 | // Possible that there was a pull/push cycle done during that onPush, 90 | // followed by a complete, in which case, we don't want to publish that 91 | // complete yet. 92 | if (outletFinished && pushedElement == null && !inletFinished) { 93 | inletFinished = true; 94 | if (failure != null) { 95 | inletListener.onUpstreamFailure(failure); 96 | failure = null; 97 | } else { 98 | inletListener.onUpstreamFinish(); 99 | } 100 | } 101 | } 102 | } 103 | 104 | @Override 105 | public boolean isAvailable() { 106 | return !outletFinished && inletPulled && pushedElement == null && currentElement == null; 107 | } 108 | 109 | @Override 110 | public void complete() { 111 | if (outletFinished) { 112 | throw new IllegalStateException("Can't complete twice."); 113 | } 114 | outletFinished = true; 115 | inletPulled = false; 116 | if (pushedElement == null && currentElement == null && !inletFinished) { 117 | inletFinished = true; 118 | inletListener.onUpstreamFinish(); 119 | } 120 | } 121 | 122 | @Override 123 | public boolean isClosed() { 124 | return outletFinished; 125 | } 126 | 127 | @Override 128 | public void fail(Throwable error) { 129 | Objects.requireNonNull(error, "Error must not be null"); 130 | if (outletFinished) { 131 | throw new IllegalStateException("Can't complete twice."); 132 | } 133 | outletFinished = true; 134 | inletPulled = false; 135 | if (pushedElement == null && currentElement == null && !inletFinished) { 136 | inletFinished = true; 137 | inletListener.onUpstreamFailure(error); 138 | } else { 139 | failure = error; 140 | } 141 | } 142 | 143 | @Override 144 | public void setListener(OutletListener listener) { 145 | outletListener = Objects.requireNonNull(listener, "Cannot register null listener"); 146 | } 147 | } 148 | 149 | final class Inlet implements StageInlet { 150 | 151 | @Override 152 | public void pull() { 153 | if (inletFinished) { 154 | throw new IllegalStateException("Can't pull after complete"); 155 | } else if (inletPulled) { 156 | throw new IllegalStateException("Can't pull twice"); 157 | } else if (currentElement != null) { 158 | throw new IllegalStateException("Can't pull without having grabbed the previous element"); 159 | } 160 | if (!outletFinished) { 161 | inletPulled = true; 162 | outletListener.onPull(); 163 | } 164 | } 165 | 166 | @Override 167 | public boolean isPulled() { 168 | return inletPulled; 169 | } 170 | 171 | @Override 172 | public boolean isAvailable() { 173 | return currentElement != null; 174 | } 175 | 176 | @Override 177 | public boolean isClosed() { 178 | return inletFinished; 179 | } 180 | 181 | @Override 182 | public void cancel() { 183 | if (inletFinished) { 184 | throw new IllegalStateException("Stage already finished"); 185 | } 186 | inletFinished = true; 187 | currentElement = null; 188 | inletPulled = false; 189 | if (!outletFinished) { 190 | outletFinished = true; 191 | outletListener.onDownstreamFinish(); 192 | } 193 | } 194 | 195 | @Override 196 | public T grab() { 197 | if (currentElement == null) { 198 | throw new IllegalStateException("Grab without onPush notification"); 199 | } 200 | T grabbed = currentElement; 201 | inletPulled = false; 202 | currentElement = null; 203 | return grabbed; 204 | } 205 | 206 | @Override 207 | public void setListener(InletListener listener) { 208 | inletListener = Objects.requireNonNull(listener, "Cannot register null listener"); 209 | } 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /impl/src/main/java/org/reactivestreams/utils/impl/TakeWhileStage.java: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Licensed under Public Domain (CC0) * 3 | * * 4 | * To the extent possible under law, the person who associated CC0 with * 5 | * this code has waived all copyright and related or neighboring * 6 | * rights to this code. * 7 | * * 8 | * You should have received a copy of the CC0 legalcode along with this * 9 | * work. If not, see . * 10 | ******************************************************************************/ 11 | 12 | package org.reactivestreams.utils.impl; 13 | 14 | import java.util.function.Predicate; 15 | 16 | /** 17 | * Take while stage. 18 | */ 19 | class TakeWhileStage extends GraphStage implements InletListener, OutletListener { 20 | private final StageInlet inlet; 21 | private final StageOutlet outlet; 22 | private final Predicate predicate; 23 | private final boolean inclusive; 24 | 25 | TakeWhileStage(BuiltGraph builtGraph, StageInlet inlet, StageOutlet outlet, Predicate predicate, boolean inclusive) { 26 | super(builtGraph); 27 | this.inlet = inlet; 28 | this.outlet = outlet; 29 | this.predicate = predicate; 30 | this.inclusive = inclusive; 31 | 32 | inlet.setListener(this); 33 | outlet.setListener(this); 34 | } 35 | 36 | @Override 37 | public void onPush() { 38 | T element = inlet.grab(); 39 | if (predicate.test(element)) { 40 | outlet.push(element); 41 | } else { 42 | if (inclusive) { 43 | outlet.push(element); 44 | } 45 | outlet.complete(); 46 | inlet.cancel(); 47 | } 48 | } 49 | 50 | @Override 51 | public void onUpstreamFinish() { 52 | outlet.complete(); 53 | } 54 | 55 | @Override 56 | public void onUpstreamFailure(Throwable error) { 57 | outlet.fail(error); 58 | } 59 | 60 | @Override 61 | public void onPull() { 62 | inlet.pull(); 63 | } 64 | 65 | @Override 66 | public void onDownstreamFinish() { 67 | inlet.cancel(); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /impl/src/main/java/org/reactivestreams/utils/impl/WrappedProcessor.java: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Licensed under Public Domain (CC0) * 3 | * * 4 | * To the extent possible under law, the person who associated CC0 with * 5 | * this code has waived all copyright and related or neighboring * 6 | * rights to this code. * 7 | * * 8 | * You should have received a copy of the CC0 legalcode along with this * 9 | * work. If not, see . * 10 | ******************************************************************************/ 11 | 12 | package org.reactivestreams.utils.impl; 13 | 14 | import java.util.concurrent.Flow; 15 | 16 | /** 17 | * Processor that wraps a subscriber and publisher. 18 | */ 19 | public class WrappedProcessor implements Flow.Processor { 20 | private final Flow.Subscriber subscriber; 21 | private final Flow.Publisher publisher; 22 | 23 | public WrappedProcessor(Flow.Subscriber subscriber, Flow.Publisher publisher) { 24 | this.subscriber = subscriber; 25 | this.publisher = publisher; 26 | } 27 | 28 | @Override 29 | public void subscribe(Flow.Subscriber subscriber) { 30 | publisher.subscribe(subscriber); 31 | } 32 | 33 | @Override 34 | public void onSubscribe(Flow.Subscription subscription) { 35 | subscriber.onSubscribe(subscription); 36 | } 37 | 38 | @Override 39 | public void onNext(T item) { 40 | subscriber.onNext(item); 41 | } 42 | 43 | @Override 44 | public void onError(Throwable throwable) { 45 | subscriber.onError(throwable); 46 | } 47 | 48 | @Override 49 | public void onComplete() { 50 | subscriber.onComplete(); 51 | } 52 | } 53 | 54 | -------------------------------------------------------------------------------- /impl/src/test/java/org/reactivestreams/utils/impl/ReactiveStreamsEngineImplTck.java: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Licensed under Public Domain (CC0) * 3 | * * 4 | * To the extent possible under law, the person who associated CC0 with * 5 | * this code has waived all copyright and related or neighboring * 6 | * rights to this code. * 7 | * * 8 | * You should have received a copy of the CC0 legalcode along with this * 9 | * work. If not, see . * 10 | ******************************************************************************/ 11 | 12 | package org.reactivestreams.utils.impl; 13 | 14 | import org.reactivestreams.tck.TestEnvironment; 15 | import org.reactivestreams.utils.tck.ReactiveStreamsTck; 16 | 17 | public class ReactiveStreamsEngineImplTck extends ReactiveStreamsTck { 18 | 19 | public ReactiveStreamsEngineImplTck() { 20 | super(new TestEnvironment(100)); 21 | } 22 | 23 | @Override 24 | protected ReactiveStreamsEngineImpl createEngine() { 25 | return new ReactiveStreamsEngineImpl(); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /rxjava/build.gradle: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Licensed under Public Domain (CC0) * 3 | * * 4 | * To the extent possible under law, the person who associated CC0 with * 5 | * this code has waived all copyright and related or neighboring * 6 | * rights to this code. * 7 | * * 8 | * You should have received a copy of the CC0 legalcode along with this * 9 | * work. If not, see . * 10 | ******************************************************************************/ 11 | 12 | plugins { 13 | id 'java-library' 14 | } 15 | 16 | dependencies { 17 | api project(":api") 18 | implementation "io.reactivex.rxjava2:rxjava:2.1.9" 19 | implementation "com.github.akarnokd:rxjava2-jdk9-interop:0.1.9" 20 | implementation "com.github.akarnokd:rxjava2-jdk8-interop:0.2.9" 21 | 22 | testCompile project(":tck") 23 | } 24 | -------------------------------------------------------------------------------- /rxjava/src/main/java/com/lightbend/reactivestreams/rxjava/CancelInjectingPublisher.java: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Licensed under Public Domain (CC0) * 3 | * * 4 | * To the extent possible under law, the person who associated CC0 with * 5 | * this code has waived all copyright and related or neighboring * 6 | * rights to this code. * 7 | * * 8 | * You should have received a copy of the CC0 legalcode along with this * 9 | * work. If not, see . * 10 | ******************************************************************************/ 11 | 12 | package com.lightbend.reactivestreams.rxjava; 13 | 14 | import org.reactivestreams.Publisher; 15 | import org.reactivestreams.Subscriber; 16 | import org.reactivestreams.Subscription; 17 | 18 | import java.util.Objects; 19 | import java.util.concurrent.atomic.AtomicBoolean; 20 | 21 | /** 22 | * Injects cancellation into a publisher if it hasn't been subscribed to before cancelIfNotSubscribed is invoked. 23 | */ 24 | public class CancelInjectingPublisher implements Publisher { 25 | private final Publisher delegate; 26 | private final AtomicBoolean subscribed = new AtomicBoolean(); 27 | 28 | public CancelInjectingPublisher(Publisher delegate) { 29 | this.delegate = delegate; 30 | } 31 | 32 | @Override 33 | public void subscribe(Subscriber subscriber) { 34 | Objects.requireNonNull(subscriber); 35 | if (subscribed.compareAndSet(false, true)) { 36 | delegate.subscribe(subscriber); 37 | } else { 38 | subscriber.onSubscribe(new Subscription() { 39 | @Override 40 | public void request(long n) { } 41 | @Override 42 | public void cancel() { } 43 | }); 44 | subscriber.onError(new IllegalStateException("CancelInjectingPublisher only supports one subscriber")); 45 | } 46 | } 47 | 48 | public void cancelIfNotSubscribed() { 49 | if (subscribed.compareAndSet(false, true)) { 50 | delegate.subscribe(new Subscriber() { 51 | @Override 52 | public void onSubscribe(Subscription subscription) { 53 | Objects.requireNonNull(subscription); 54 | subscription.cancel(); 55 | } 56 | @Override 57 | public void onNext(T item) { 58 | Objects.requireNonNull(item); 59 | } 60 | 61 | @Override 62 | public void onError(Throwable throwable) { 63 | Objects.requireNonNull(throwable); 64 | } 65 | 66 | @Override 67 | public void onComplete() { } 68 | }); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /rxjava/src/main/java/com/lightbend/reactivestreams/rxjava/FlowSubscriberAdapter.java: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Licensed under Public Domain (CC0) * 3 | * * 4 | * To the extent possible under law, the person who associated CC0 with * 5 | * this code has waived all copyright and related or neighboring * 6 | * rights to this code. * 7 | * * 8 | * You should have received a copy of the CC0 legalcode along with this * 9 | * work. If not, see . * 10 | ******************************************************************************/ 11 | 12 | package com.lightbend.reactivestreams.rxjava; 13 | 14 | import org.reactivestreams.Subscriber; 15 | import org.reactivestreams.Subscription; 16 | 17 | import java.util.Objects; 18 | import java.util.concurrent.Flow; 19 | 20 | /** 21 | * For some reason this doesn't exist (or at least, isn't made public) in RxJava. 22 | */ 23 | class FlowSubscriberAdapter implements Subscriber { 24 | private final Flow.Subscriber delegate; 25 | 26 | FlowSubscriberAdapter(Flow.Subscriber delegate) { 27 | this.delegate = delegate; 28 | } 29 | 30 | @Override 31 | public void onSubscribe(Subscription subscription) { 32 | Objects.requireNonNull(subscription, "Subscription must not be null"); 33 | delegate.onSubscribe(new Flow.Subscription() { 34 | @Override 35 | public void request(long n) { 36 | subscription.request(n); 37 | } 38 | @Override 39 | public void cancel() { 40 | subscription.cancel(); 41 | } 42 | }); 43 | } 44 | 45 | @Override 46 | public void onNext(T t) { 47 | delegate.onNext(t); 48 | } 49 | 50 | @Override 51 | public void onError(Throwable t) { 52 | delegate.onError(t); 53 | } 54 | 55 | @Override 56 | public void onComplete() { 57 | delegate.onComplete(); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /rxjava/src/main/java/com/lightbend/reactivestreams/rxjava/TerminationWatchingSubscriber.java: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Licensed under Public Domain (CC0) * 3 | * * 4 | * To the extent possible under law, the person who associated CC0 with * 5 | * this code has waived all copyright and related or neighboring * 6 | * rights to this code. * 7 | * * 8 | * You should have received a copy of the CC0 legalcode along with this * 9 | * work. If not, see . * 10 | ******************************************************************************/ 11 | 12 | package com.lightbend.reactivestreams.rxjava; 13 | 14 | import java.util.Objects; 15 | import java.util.concurrent.CancellationException; 16 | import java.util.concurrent.CompletableFuture; 17 | import java.util.concurrent.CompletionStage; 18 | import java.util.concurrent.Flow; 19 | 20 | class TerminationWatchingSubscriber implements Flow.Subscriber { 21 | 22 | private final CompletableFuture termination = new CompletableFuture<>(); 23 | private final Flow.Subscriber delegate; 24 | 25 | TerminationWatchingSubscriber(Flow.Subscriber delegate) { 26 | this.delegate = delegate; 27 | } 28 | 29 | CompletionStage getTermination() { 30 | return termination; 31 | } 32 | 33 | @Override 34 | public void onSubscribe(Flow.Subscription subscription) { 35 | Objects.requireNonNull(subscription, "Subscription must not be null"); 36 | delegate.onSubscribe(new Subscription(subscription)); 37 | } 38 | 39 | @Override 40 | public void onNext(T item) { 41 | delegate.onNext(item); 42 | } 43 | 44 | @Override 45 | public void onError(Throwable throwable) { 46 | termination.completeExceptionally(throwable); 47 | delegate.onError(throwable); 48 | } 49 | 50 | @Override 51 | public void onComplete() { 52 | termination.complete(null); 53 | delegate.onComplete(); 54 | } 55 | 56 | private class Subscription implements Flow.Subscription { 57 | private final Flow.Subscription delegate; 58 | 59 | public Subscription(Flow.Subscription delegate) { 60 | this.delegate = delegate; 61 | } 62 | 63 | @Override 64 | public void request(long n) { 65 | delegate.request(n); 66 | } 67 | 68 | @Override 69 | public void cancel() { 70 | termination.completeExceptionally(new CancellationException()); 71 | delegate.cancel(); 72 | 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /rxjava/src/main/java/com/lightbend/reactivestreams/rxjava/WrappedProcessor.java: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Licensed under Public Domain (CC0) * 3 | * * 4 | * To the extent possible under law, the person who associated CC0 with * 5 | * this code has waived all copyright and related or neighboring * 6 | * rights to this code. * 7 | * * 8 | * You should have received a copy of the CC0 legalcode along with this * 9 | * work. If not, see . * 10 | ******************************************************************************/ 11 | 12 | package com.lightbend.reactivestreams.rxjava; 13 | 14 | import java.util.concurrent.Flow; 15 | 16 | /** 17 | * Processor that wraps a publisher and subscriber 18 | */ 19 | class WrappedProcessor implements Flow.Processor { 20 | private final Flow.Subscriber subscriber; 21 | private final Flow.Publisher publisher; 22 | 23 | public WrappedProcessor(Flow.Subscriber subscriber, Flow.Publisher publisher) { 24 | this.subscriber = subscriber; 25 | this.publisher = publisher; 26 | } 27 | 28 | @Override 29 | public void subscribe(Flow.Subscriber subscriber) { 30 | publisher.subscribe(subscriber); 31 | } 32 | 33 | @Override 34 | public void onSubscribe(Flow.Subscription subscription) { 35 | subscriber.onSubscribe(subscription); 36 | } 37 | 38 | @Override 39 | public void onNext(T item) { 40 | subscriber.onNext(item); 41 | } 42 | 43 | @Override 44 | public void onError(Throwable throwable) { 45 | subscriber.onError(throwable); 46 | } 47 | 48 | @Override 49 | public void onComplete() { 50 | subscriber.onComplete(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /rxjava/src/test/java/com/lightbend/reactivestreams/rxjava/RxJavaReactiveStreamsTck.java: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Licensed under Public Domain (CC0) * 3 | * * 4 | * To the extent possible under law, the person who associated CC0 with * 5 | * this code has waived all copyright and related or neighboring * 6 | * rights to this code. * 7 | * * 8 | * You should have received a copy of the CC0 legalcode along with this * 9 | * work. If not, see . * 10 | ******************************************************************************/ 11 | 12 | package com.lightbend.reactivestreams.rxjava; 13 | 14 | import org.reactivestreams.tck.TestEnvironment; 15 | import org.reactivestreams.utils.tck.ReactiveStreamsTck; 16 | 17 | public class RxJavaReactiveStreamsTck extends ReactiveStreamsTck { 18 | 19 | public RxJavaReactiveStreamsTck() { 20 | super(new TestEnvironment()); 21 | } 22 | 23 | @Override 24 | protected RxJavaEngine createEngine() { 25 | return new RxJavaEngine(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Licensed under Public Domain (CC0) * 3 | * * 4 | * To the extent possible under law, the person who associated CC0 with * 5 | * this code has waived all copyright and related or neighboring * 6 | * rights to this code. * 7 | * * 8 | * You should have received a copy of the CC0 legalcode along with this * 9 | * work. If not, see . * 10 | ******************************************************************************/ 11 | 12 | rootProject.name = 'reactive-streams-utils' 13 | 14 | include 'spi' 15 | include 'api' 16 | include 'tck' 17 | include 'impl' 18 | include 'akka' 19 | include 'examples' 20 | include 'rxjava' 21 | include 'benchmark' 22 | -------------------------------------------------------------------------------- /spi/build.gradle: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Licensed under Public Domain (CC0) * 3 | * * 4 | * To the extent possible under law, the person who associated CC0 with * 5 | * this code has waived all copyright and related or neighboring * 6 | * rights to this code. * 7 | * * 8 | * You should have received a copy of the CC0 legalcode along with this * 9 | * work. If not, see . * 10 | ******************************************************************************/ 11 | 12 | plugins { 13 | id 'java-library' 14 | } 15 | -------------------------------------------------------------------------------- /spi/src/main/java/org/reactivestreams/utils/spi/Graph.java: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Licensed under Public Domain (CC0) * 3 | * * 4 | * To the extent possible under law, the person who associated CC0 with * 5 | * this code has waived all copyright and related or neighboring * 6 | * rights to this code. * 7 | * * 8 | * You should have received a copy of the CC0 legalcode along with this * 9 | * work. If not, see . * 10 | ******************************************************************************/ 11 | 12 | package org.reactivestreams.utils.spi; 13 | 14 | import java.util.Collection; 15 | 16 | /** 17 | * A graph. 18 | * 19 | * Reactive Streams engines are required to convert the stages of this graph into a stream with interfaces according 20 | * to the shape. The shape is governed by whether the graph has an inlet, an outlet, neither or both. 21 | */ 22 | public class Graph { 23 | private final Collection stages; 24 | private final boolean hasInlet; 25 | private final boolean hasOutlet; 26 | 27 | /** 28 | * Create a graph from the given stages. 29 | * 30 | * The stages of this graph will be validated to ensure that connect properly, that is, every stage but the first 31 | * stage must have an inlet to receive a stream from the previous stage, and every stage but the last stage must have 32 | * an outlet to produce a stream from the next stage. 33 | * 34 | * If the first stage has an inlet, then this graph has an inlet, and can therefore be represented as a 35 | * {@link java.util.concurrent.Flow.Subscriber}. If the last stage has an outlet, then this graph has an outlet, and 36 | * therefore can be represented as a {@link java.util.concurrent.Flow.Publisher}. 37 | * 38 | * @param stages The stages. 39 | */ 40 | public Graph(Collection stages) { 41 | 42 | boolean hasInlet = true; 43 | Stage lastStage = null; 44 | 45 | for (Stage stage : stages) { 46 | if (lastStage != null && !lastStage.hasOutlet()) { 47 | throw new IllegalStateException("Graph required an outlet from the previous stage " + lastStage + " but none was found."); 48 | } 49 | 50 | if (lastStage != null) { 51 | if (!stage.hasInlet()) { 52 | throw new IllegalStateException("Stage encountered in graph with no inlet after the first stage: " + stage); 53 | } 54 | } else { 55 | hasInlet = stage.hasInlet(); 56 | } 57 | 58 | lastStage = stage; 59 | } 60 | 61 | this.hasInlet = hasInlet; 62 | this.hasOutlet = lastStage == null || lastStage.hasOutlet(); 63 | this.stages = stages; 64 | } 65 | 66 | /** 67 | * Get the stages of this graph. 68 | */ 69 | public Collection getStages() { 70 | return stages; 71 | } 72 | 73 | /** 74 | * Returns true if this graph has an inlet, ie, if this graph can be turned into a 75 | * {@link java.util.concurrent.Flow.Subscriber}. 76 | */ 77 | public boolean hasInlet() { 78 | return hasInlet; 79 | } 80 | 81 | /** 82 | * Returns true if this graph has an outlet, ie, if this graph can be turned into a 83 | * {@link java.util.concurrent.Flow.Publisher}. 84 | */ 85 | public boolean hasOutlet() { 86 | return hasOutlet; 87 | } 88 | 89 | @Override 90 | public String toString() { 91 | return "Graph{" + 92 | "stages=" + stages + 93 | '}'; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /spi/src/main/java/org/reactivestreams/utils/spi/UnsupportedStageException.java: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Licensed under Public Domain (CC0) * 3 | * * 4 | * To the extent possible under law, the person who associated CC0 with * 5 | * this code has waived all copyright and related or neighboring * 6 | * rights to this code. * 7 | * * 8 | * You should have received a copy of the CC0 legalcode along with this * 9 | * work. If not, see . * 10 | ******************************************************************************/ 11 | 12 | package org.reactivestreams.utils.spi; 13 | 14 | /** 15 | * Exception thrown when a reactive streams engine doesn't support a stage that is passed to it. 16 | *

17 | * All reactive streams engines should support all stages, but this allows for a graceful mechanism to report issues, 18 | * for example if in a future version a new stage is added that is not recognised by an existing implementation. 19 | */ 20 | public class UnsupportedStageException extends RuntimeException { 21 | public UnsupportedStageException(Stage stage) { 22 | super("The " + stage.getClass().getSimpleName() + " stage is not supported by this Reactive Streams engine."); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /spi/src/main/java/org/reactivestreams/utils/spi/package-info.java: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Licensed under Public Domain (CC0) * 3 | * * 4 | * To the extent possible under law, the person who associated CC0 with * 5 | * this code has waived all copyright and related or neighboring * 6 | * rights to this code. * 7 | * * 8 | * You should have received a copy of the CC0 legalcode along with this * 9 | * work. If not, see . * 10 | ******************************************************************************/ 11 | 12 | /** 13 | * The Reactive Streams utils SPI. 14 | * 15 | * Implementors are expected to implement the {@code ReactiveStreamsEngine} interface, and use this SPI to inspect 16 | * the graph of stages. 17 | * 18 | * A TCK is also provided that validates that an implementation is both correct according to this specification, and 19 | * the Reactive Streams specification. 20 | */ 21 | package org.reactivestreams.utils.spi; -------------------------------------------------------------------------------- /tck/build.gradle: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Licensed under Public Domain (CC0) * 3 | * * 4 | * To the extent possible under law, the person who associated CC0 with * 5 | * this code has waived all copyright and related or neighboring * 6 | * rights to this code. * 7 | * * 8 | * You should have received a copy of the CC0 legalcode along with this * 9 | * work. If not, see . * 10 | ******************************************************************************/ 11 | 12 | plugins { 13 | id 'java-library' 14 | } 15 | 16 | dependencies { 17 | api project(":api") 18 | compile "org.reactivestreams:reactive-streams-tck-flow:1.0.2" 19 | } 20 | -------------------------------------------------------------------------------- /tck/src/main/java/org/reactivestreams/utils/tck/AbstractStageVerification.java: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Licensed under Public Domain (CC0) * 3 | * * 4 | * To the extent possible under law, the person who associated CC0 with * 5 | * this code has waived all copyright and related or neighboring * 6 | * rights to this code. * 7 | * * 8 | * You should have received a copy of the CC0 legalcode along with this * 9 | * work. If not, see . * 10 | ******************************************************************************/ 11 | 12 | package org.reactivestreams.utils.tck; 13 | 14 | import org.reactivestreams.utils.ReactiveStreams; 15 | import org.reactivestreams.utils.ReactiveStreamsEngine; 16 | import org.reactivestreams.FlowAdapters; 17 | import org.reactivestreams.Processor; 18 | import org.reactivestreams.Publisher; 19 | import org.reactivestreams.tck.IdentityProcessorVerification; 20 | import org.reactivestreams.tck.TestEnvironment; 21 | import org.reactivestreams.tck.flow.FlowPublisherVerification; 22 | import org.reactivestreams.tck.flow.FlowSubscriberBlackboxVerification; 23 | import org.reactivestreams.tck.flow.FlowSubscriberWhiteboxVerification; 24 | 25 | import java.util.List; 26 | import java.util.concurrent.*; 27 | 28 | abstract class AbstractStageVerification { 29 | 30 | final ReactiveStreamsEngine engine; 31 | final TestEnvironment environment; 32 | final ScheduledExecutorService executorService; 33 | 34 | AbstractStageVerification(ReactiveStreamsTck.VerificationDeps deps) { 35 | this.engine = deps.engine(); 36 | this.environment = deps.testEnvironment(); 37 | this.executorService = deps.executorService(); 38 | } 39 | 40 | abstract List reactiveStreamsTckVerifiers(); 41 | 42 | T await(CompletionStage future) { 43 | try { 44 | return future.toCompletableFuture().get(environment.defaultTimeoutMillis(), TimeUnit.MILLISECONDS); 45 | } catch (InterruptedException e) { 46 | throw new RuntimeException(e); 47 | } catch (ExecutionException e) { 48 | if (e.getCause() instanceof RuntimeException) { 49 | throw (RuntimeException) e.getCause(); 50 | } else { 51 | throw new RuntimeException(e.getCause()); 52 | } 53 | } catch (TimeoutException e) { 54 | throw new RuntimeException("Future timed out after " + environment.defaultTimeoutMillis() + "ms", e); 55 | } 56 | } 57 | 58 | abstract class StagePublisherVerification extends FlowPublisherVerification { 59 | 60 | StagePublisherVerification() { 61 | super(AbstractStageVerification.this.environment); 62 | } 63 | 64 | @Override 65 | public Flow.Publisher createFailedFlowPublisher() { 66 | return ReactiveStreams.failed(new RuntimeException("failed")).build(engine); 67 | } 68 | } 69 | 70 | /** 71 | * This uses IdentityProcessorVerification rather than IdentityFlowProcessorVerification due to 72 | * https://github.com/reactive-streams/reactive-streams-jvm/issues/425 73 | */ 74 | abstract class StageProcessorVerification extends IdentityProcessorVerification { 75 | StageProcessorVerification() { 76 | super(AbstractStageVerification.this.environment); 77 | } 78 | 79 | @Override 80 | public ExecutorService publisherExecutorService() { 81 | return executorService; 82 | } 83 | 84 | protected Flow.Publisher createFailedFlowPublisher() { 85 | return ReactiveStreams.failed(new RuntimeException("failed")).build(engine); 86 | } 87 | 88 | protected abstract Flow.Processor createIdentityFlowProcessor(int bufferSize); 89 | 90 | @Override 91 | public final Processor createIdentityProcessor(int bufferSize) { 92 | return FlowAdapters.toProcessor(createIdentityFlowProcessor(bufferSize)); 93 | } 94 | 95 | @Override 96 | public final Publisher createFailedPublisher() { 97 | Flow.Publisher failed = createFailedFlowPublisher(); 98 | if (failed == null) return null; 99 | return FlowAdapters.toPublisher(failed); 100 | } 101 | 102 | @Override 103 | public long maxSupportedSubscribers() { 104 | return 1; 105 | } 106 | } 107 | 108 | abstract class StageSubscriberWhiteboxVerification extends FlowSubscriberWhiteboxVerification { 109 | public StageSubscriberWhiteboxVerification() { 110 | super(AbstractStageVerification.this.environment); 111 | } 112 | } 113 | 114 | abstract class StageSubscriberBlackboxVerification extends FlowSubscriberBlackboxVerification { 115 | StageSubscriberBlackboxVerification() { 116 | super(AbstractStageVerification.this.environment); 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /tck/src/main/java/org/reactivestreams/utils/tck/CancelStageVerification.java: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Licensed under Public Domain (CC0) * 3 | * * 4 | * To the extent possible under law, the person who associated CC0 with * 5 | * this code has waived all copyright and related or neighboring * 6 | * rights to this code. * 7 | * * 8 | * You should have received a copy of the CC0 legalcode along with this * 9 | * work. If not, see . * 10 | ******************************************************************************/ 11 | 12 | package org.reactivestreams.utils.tck; 13 | 14 | import org.reactivestreams.utils.ReactiveStreams; 15 | import org.testng.SkipException; 16 | import org.testng.annotations.Test; 17 | 18 | import java.util.List; 19 | import java.util.concurrent.CompletableFuture; 20 | import java.util.concurrent.CompletionStage; 21 | import java.util.concurrent.Flow; 22 | 23 | public class CancelStageVerification extends AbstractStageVerification { 24 | CancelStageVerification(ReactiveStreamsTck.VerificationDeps deps) { 25 | super(deps); 26 | } 27 | 28 | @Test 29 | public void cancelStageShouldCancelTheStage() { 30 | CompletableFuture cancelled = new CompletableFuture<>(); 31 | CompletionStage result = ReactiveStreams.fromPublisher(s -> { 32 | s.onSubscribe(new Flow.Subscription() { 33 | @Override 34 | public void request(long n) { 35 | } 36 | 37 | @Override 38 | public void cancel() { 39 | cancelled.complete(null); 40 | } 41 | }); 42 | }).cancel().build(engine); 43 | await(cancelled); 44 | await(result); 45 | } 46 | 47 | @Override 48 | List reactiveStreamsTckVerifiers() { 49 | return List.of(new SubscriberVerification()); 50 | } 51 | 52 | public class SubscriberVerification extends StageSubscriberBlackboxVerification { 53 | @Override 54 | public Flow.Subscriber createFlowSubscriber() { 55 | return ReactiveStreams.builder().cancel().build(engine).getSubscriber(); 56 | } 57 | 58 | @Override 59 | public Object createElement(int element) { 60 | return element; 61 | } 62 | 63 | @Override 64 | public void required_spec201_blackbox_mustSignalDemandViaSubscriptionRequest() throws Throwable { 65 | throw new SkipException("Cancel subscriber does not need to signal demand."); 66 | } 67 | 68 | @Override 69 | public void required_spec209_blackbox_mustBePreparedToReceiveAnOnCompleteSignalWithPrecedingRequestCall() throws Throwable { 70 | throw new SkipException("Cancel subscriber does not need to signal demand."); 71 | } 72 | 73 | @Override 74 | public void required_spec210_blackbox_mustBePreparedToReceiveAnOnErrorSignalWithPrecedingRequestCall() throws Throwable { 75 | throw new SkipException("Cancel subscriber does not need to signal demand."); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /tck/src/main/java/org/reactivestreams/utils/tck/CollectStageVerification.java: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Licensed under Public Domain (CC0) * 3 | * * 4 | * To the extent possible under law, the person who associated CC0 with * 5 | * this code has waived all copyright and related or neighboring * 6 | * rights to this code. * 7 | * * 8 | * You should have received a copy of the CC0 legalcode along with this * 9 | * work. If not, see . * 10 | ******************************************************************************/ 11 | 12 | package org.reactivestreams.utils.tck; 13 | 14 | import org.reactivestreams.utils.ReactiveStreams; 15 | import org.testng.annotations.Test; 16 | 17 | import java.util.List; 18 | import java.util.concurrent.Flow; 19 | import java.util.stream.Collectors; 20 | 21 | import static org.testng.Assert.assertEquals; 22 | 23 | public class CollectStageVerification extends AbstractStageVerification { 24 | 25 | CollectStageVerification(ReactiveStreamsTck.VerificationDeps deps) { 26 | super(deps); 27 | } 28 | 29 | @Test 30 | public void toListStageShouldReturnAList() { 31 | assertEquals(await(ReactiveStreams.of(1, 2, 3) 32 | .toList().build(engine)), List.of(1, 2, 3)); 33 | } 34 | 35 | @Test 36 | public void toListStageShouldReturnEmpty() { 37 | assertEquals(await(ReactiveStreams.of() 38 | .toList().build(engine)), List.of()); 39 | } 40 | 41 | @Test 42 | public void finisherFunctionShouldBeInvoked() { 43 | assertEquals(await(ReactiveStreams.of("1", "2", "3") 44 | .collect(Collectors.joining(", ")).build(engine)), "1, 2, 3"); 45 | } 46 | 47 | @Test(expectedExceptions = RuntimeException.class, expectedExceptionsMessageRegExp = "failed") 48 | public void toListStageShouldPropagateErrors() { 49 | await(ReactiveStreams.failed(new RuntimeException("failed")) 50 | .toList().build(engine)); 51 | } 52 | 53 | @Override 54 | List reactiveStreamsTckVerifiers() { 55 | return List.of(new SubscriberVerification()); 56 | } 57 | 58 | class SubscriberVerification extends StageSubscriberBlackboxVerification { 59 | @Override 60 | public Flow.Subscriber createFlowSubscriber() { 61 | return ReactiveStreams.builder().toList().build(engine).getSubscriber(); 62 | } 63 | 64 | @Override 65 | public Integer createElement(int element) { 66 | return element; 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /tck/src/main/java/org/reactivestreams/utils/tck/ConcatStageVerification.java: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Licensed under Public Domain (CC0) * 3 | * * 4 | * To the extent possible under law, the person who associated CC0 with * 5 | * this code has waived all copyright and related or neighboring * 6 | * rights to this code. * 7 | * * 8 | * You should have received a copy of the CC0 legalcode along with this * 9 | * work. If not, see . * 10 | ******************************************************************************/ 11 | 12 | package org.reactivestreams.utils.tck; 13 | 14 | import org.reactivestreams.utils.ReactiveStreams; 15 | import org.testng.annotations.Test; 16 | 17 | import java.util.List; 18 | import java.util.concurrent.CompletableFuture; 19 | import java.util.concurrent.CompletionStage; 20 | import java.util.concurrent.Flow; 21 | import java.util.stream.IntStream; 22 | import java.util.stream.LongStream; 23 | 24 | import static org.testng.Assert.assertEquals; 25 | 26 | public class ConcatStageVerification extends AbstractStageVerification { 27 | 28 | ConcatStageVerification(ReactiveStreamsTck.VerificationDeps deps) { 29 | super(deps); 30 | } 31 | 32 | @Test 33 | public void concatStageShouldConcatTwoGraphs() { 34 | assertEquals(await( 35 | ReactiveStreams.concat( 36 | ReactiveStreams.of(1, 2, 3), 37 | ReactiveStreams.of(4, 5, 6) 38 | ) 39 | .toList() 40 | .build(engine) 41 | ), List.of(1, 2, 3, 4, 5, 6)); 42 | } 43 | 44 | @Test(expectedExceptions = RuntimeException.class, expectedExceptionsMessageRegExp = "failed") 45 | public void concatStageShouldCancelSecondStageIfFirstFails() { 46 | CancelCapturingPublisher cancelCapture = new CancelCapturingPublisher<>(); 47 | 48 | CompletionStage completion = ReactiveStreams.concat( 49 | ReactiveStreams.failed(new RuntimeException("failed")), 50 | ReactiveStreams.fromPublisher(cancelCapture) 51 | ) 52 | .forEach(e -> {}) 53 | .build(engine); 54 | 55 | await(cancelCapture.getCancelled()); 56 | await(completion); 57 | } 58 | 59 | @Test 60 | public void concatStageShouldCancelSecondStageIfFirstCancellationOccursDuringFirst() { 61 | CancelCapturingPublisher cancelCapture = new CancelCapturingPublisher<>(); 62 | 63 | CompletionStage> result = ReactiveStreams.concat( 64 | ReactiveStreams.fromIterable(() -> IntStream.range(1, 1000000).boxed().iterator()), 65 | ReactiveStreams.fromPublisher(cancelCapture) 66 | ) 67 | .limit(5) 68 | .toList() 69 | .build(engine); 70 | 71 | await(cancelCapture.getCancelled()); 72 | assertEquals(await(result), List.of(1, 2, 3, 4, 5)); 73 | } 74 | 75 | @Override 76 | List reactiveStreamsTckVerifiers() { 77 | return List.of(new PublisherVerification()); 78 | } 79 | 80 | class PublisherVerification extends StagePublisherVerification { 81 | @Override 82 | public Flow.Publisher createFlowPublisher(long elements) { 83 | long toEmitFromFirst = elements / 2; 84 | 85 | return ReactiveStreams.concat( 86 | ReactiveStreams.fromIterable( 87 | () -> LongStream.rangeClosed(1, toEmitFromFirst).boxed().iterator() 88 | ), 89 | ReactiveStreams.fromIterable( 90 | () -> LongStream.rangeClosed(toEmitFromFirst + 1, elements).boxed().iterator() 91 | ) 92 | ).build(engine); 93 | } 94 | } 95 | 96 | private static class CancelCapturingPublisher implements Flow.Publisher { 97 | private final CompletableFuture cancelled = new CompletableFuture<>(); 98 | 99 | @Override 100 | public void subscribe(Flow.Subscriber subscriber) { 101 | subscriber.onSubscribe(new Flow.Subscription() { 102 | @Override 103 | public void request(long n) { 104 | } 105 | @Override 106 | public void cancel() { 107 | cancelled.complete(null); 108 | } 109 | }); 110 | } 111 | 112 | public CompletableFuture getCancelled() { 113 | return cancelled; 114 | } 115 | } 116 | 117 | } 118 | -------------------------------------------------------------------------------- /tck/src/main/java/org/reactivestreams/utils/tck/EmptyProcessorVerification.java: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Licensed under Public Domain (CC0) * 3 | * * 4 | * To the extent possible under law, the person who associated CC0 with * 5 | * this code has waived all copyright and related or neighboring * 6 | * rights to this code. * 7 | * * 8 | * You should have received a copy of the CC0 legalcode along with this * 9 | * work. If not, see . * 10 | ******************************************************************************/ 11 | 12 | package org.reactivestreams.utils.tck; 13 | 14 | import org.reactivestreams.utils.ReactiveStreams; 15 | 16 | import java.util.List; 17 | import java.util.concurrent.Flow; 18 | 19 | public class EmptyProcessorVerification extends AbstractStageVerification { 20 | 21 | public EmptyProcessorVerification(ReactiveStreamsTck.VerificationDeps deps) { 22 | super(deps); 23 | } 24 | 25 | @Override 26 | List reactiveStreamsTckVerifiers() { 27 | return List.of(new ProcessorVerification()); 28 | } 29 | 30 | public class ProcessorVerification extends StageProcessorVerification { 31 | @Override 32 | protected Flow.Processor createIdentityFlowProcessor(int bufferSize) { 33 | return ReactiveStreams.builder().build(engine); 34 | } 35 | 36 | @Override 37 | public Integer createElement(int element) { 38 | return element; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /tck/src/main/java/org/reactivestreams/utils/tck/FilterStageVerification.java: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Licensed under Public Domain (CC0) * 3 | * * 4 | * To the extent possible under law, the person who associated CC0 with * 5 | * this code has waived all copyright and related or neighboring * 6 | * rights to this code. * 7 | * * 8 | * You should have received a copy of the CC0 legalcode along with this * 9 | * work. If not, see . * 10 | ******************************************************************************/ 11 | 12 | package org.reactivestreams.utils.tck; 13 | 14 | import org.reactivestreams.utils.CompletionBuilder; 15 | import org.reactivestreams.utils.ReactiveStreams; 16 | import org.testng.annotations.Test; 17 | 18 | import java.util.List; 19 | import java.util.concurrent.Flow; 20 | 21 | import static org.testng.Assert.assertEquals; 22 | 23 | public class FilterStageVerification extends AbstractStageVerification { 24 | 25 | FilterStageVerification(ReactiveStreamsTck.VerificationDeps deps) { 26 | super(deps); 27 | } 28 | 29 | @Test 30 | public void filterStageShouldFilterElements() { 31 | assertEquals(await(ReactiveStreams.of(1, 2, 3, 4, 5, 6) 32 | .filter(i -> (i & 1) == 1) 33 | .toList() 34 | .build(engine)), List.of(1, 3, 5)); 35 | } 36 | 37 | @Test(expectedExceptions = RuntimeException.class, expectedExceptionsMessageRegExp = "failed") 38 | public void filterStageShouldPropagateRuntimeExceptions() { 39 | await(ReactiveStreams.of("foo") 40 | .filter(foo -> { 41 | throw new RuntimeException("failed"); 42 | }) 43 | .toList() 44 | .build(engine)); 45 | } 46 | 47 | @Test 48 | public void filterStageShouldSupportSkip() { 49 | assertEquals(await(ReactiveStreams.of(1, 2, 3, 4) 50 | .skip(2) 51 | .toList() 52 | .build(engine)), List.of(3, 4)); 53 | } 54 | 55 | @Test 56 | public void filterStageShouldSupportDropWhile() { 57 | assertEquals(await(ReactiveStreams.of(1, 2, 3, 4) 58 | .dropWhile(i -> i < 3) 59 | .toList() 60 | .build(engine)), List.of(3, 4)); 61 | } 62 | 63 | @Test 64 | public void filterStageShouldInstantiatePredicateOncePerRun() { 65 | CompletionBuilder> completion = 66 | ReactiveStreams.of(1, 2, 3, 4, 5, 6) 67 | .skip(3) 68 | .toList(); 69 | 70 | assertEquals(await(completion.build(engine)), List.of(4, 5, 6)); 71 | assertEquals(await(completion.build(engine)), List.of(4, 5, 6)); 72 | } 73 | 74 | 75 | @Override 76 | List reactiveStreamsTckVerifiers() { 77 | return List.of( 78 | new ProcessorVerification() 79 | ); 80 | } 81 | 82 | class ProcessorVerification extends StageProcessorVerification { 83 | 84 | @Override 85 | protected Flow.Processor createIdentityFlowProcessor(int bufferSize) { 86 | return ReactiveStreams.builder().filter(i -> true).build(engine); 87 | } 88 | 89 | @Override 90 | protected Flow.Publisher createFailedFlowPublisher() { 91 | return ReactiveStreams.failed(new RuntimeException("failed")) 92 | .filter(i -> true).build(engine); 93 | } 94 | 95 | @Override 96 | public Integer createElement(int element) { 97 | return element; 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /tck/src/main/java/org/reactivestreams/utils/tck/FindFirstStageVerification.java: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Licensed under Public Domain (CC0) * 3 | * * 4 | * To the extent possible under law, the person who associated CC0 with * 5 | * this code has waived all copyright and related or neighboring * 6 | * rights to this code. * 7 | * * 8 | * You should have received a copy of the CC0 legalcode along with this * 9 | * work. If not, see . * 10 | ******************************************************************************/ 11 | 12 | package org.reactivestreams.utils.tck; 13 | 14 | import org.reactivestreams.utils.ReactiveStreams; 15 | import org.testng.annotations.Test; 16 | 17 | import java.util.List; 18 | import java.util.Optional; 19 | import java.util.concurrent.Flow; 20 | 21 | import static org.testng.Assert.assertEquals; 22 | 23 | public class FindFirstStageVerification extends AbstractStageVerification { 24 | 25 | FindFirstStageVerification(ReactiveStreamsTck.VerificationDeps deps) { 26 | super(deps); 27 | } 28 | 29 | @Test 30 | public void findFirstStageShouldFindTheFirstElement() { 31 | assertEquals(await(ReactiveStreams.of(1, 2, 3) 32 | .findFirst().build(engine)), Optional.of(1)); 33 | } 34 | 35 | @Test 36 | public void findFirstStageShouldReturnEmpty() { 37 | assertEquals(await(ReactiveStreams.of() 38 | .findFirst().build(engine)), Optional.empty()); 39 | } 40 | 41 | @Test(expectedExceptions = RuntimeException.class, expectedExceptionsMessageRegExp = "failed") 42 | public void findFirstStageShouldPropagateErrors() { 43 | await(ReactiveStreams.failed(new RuntimeException("failed")) 44 | .findFirst().build(engine)); 45 | } 46 | 47 | @Override 48 | List reactiveStreamsTckVerifiers() { 49 | return List.of(new SubscriberVerification()); 50 | } 51 | 52 | class SubscriberVerification extends StageSubscriberBlackboxVerification { 53 | @Override 54 | public Flow.Subscriber createFlowSubscriber() { 55 | return ReactiveStreams.builder().findFirst().build(engine).getSubscriber(); 56 | } 57 | 58 | @Override 59 | public Integer createElement(int element) { 60 | return element; 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /tck/src/main/java/org/reactivestreams/utils/tck/FlatMapCompletionStageVerification.java: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Licensed under Public Domain (CC0) * 3 | * * 4 | * To the extent possible under law, the person who associated CC0 with * 5 | * this code has waived all copyright and related or neighboring * 6 | * rights to this code. * 7 | * * 8 | * You should have received a copy of the CC0 legalcode along with this * 9 | * work. If not, see . * 10 | ******************************************************************************/ 11 | 12 | package org.reactivestreams.utils.tck; 13 | 14 | import org.reactivestreams.utils.ReactiveStreams; 15 | import org.testng.annotations.Test; 16 | 17 | import java.util.List; 18 | import java.util.concurrent.CompletableFuture; 19 | import java.util.concurrent.CompletionStage; 20 | import java.util.concurrent.Flow; 21 | import java.util.concurrent.atomic.AtomicInteger; 22 | import java.util.function.Function; 23 | 24 | import static org.testng.Assert.assertEquals; 25 | 26 | public class FlatMapCompletionStageVerification extends AbstractStageVerification { 27 | FlatMapCompletionStageVerification(ReactiveStreamsTck.VerificationDeps deps) { 28 | super(deps); 29 | } 30 | 31 | @Test 32 | public void flatMapCsStageShouldMapFutures() throws Exception { 33 | CompletableFuture one = new CompletableFuture<>(); 34 | CompletableFuture two = new CompletableFuture<>(); 35 | CompletableFuture three = new CompletableFuture<>(); 36 | 37 | CompletionStage> result = ReactiveStreams.of(one, two, three) 38 | .flatMapCompletionStage(Function.identity()) 39 | .toList() 40 | .build(engine); 41 | 42 | Thread.sleep(100); 43 | 44 | one.complete(1); 45 | two.complete(2); 46 | three.complete(3); 47 | 48 | assertEquals(await(result), List.of(1, 2, 3)); 49 | } 50 | 51 | @Test 52 | public void flatMapCsStageShouldMaintainOrderOfFutures() throws Exception { 53 | CompletableFuture one = new CompletableFuture<>(); 54 | CompletableFuture two = new CompletableFuture<>(); 55 | CompletableFuture three = new CompletableFuture<>(); 56 | 57 | CompletionStage> result = ReactiveStreams.of(one, two, three) 58 | .flatMapCompletionStage(Function.identity()) 59 | .toList() 60 | .build(engine); 61 | 62 | three.complete(3); 63 | Thread.sleep(100); 64 | two.complete(2); 65 | Thread.sleep(100); 66 | one.complete(1); 67 | 68 | assertEquals(await(result), List.of(1, 2, 3)); 69 | } 70 | 71 | @Test 72 | public void flatMapCsStageShouldOnlyMapOneElementAtATime() throws Exception { 73 | CompletableFuture one = new CompletableFuture<>(); 74 | CompletableFuture two = new CompletableFuture<>(); 75 | CompletableFuture three = new CompletableFuture<>(); 76 | 77 | AtomicInteger concurrentMaps = new AtomicInteger(0); 78 | 79 | CompletionStage> result = ReactiveStreams.of(one, two, three) 80 | .flatMapCompletionStage(i -> { 81 | assertEquals(1, concurrentMaps.incrementAndGet()); 82 | return i; 83 | }) 84 | .toList() 85 | .build(engine); 86 | 87 | Thread.sleep(100); 88 | concurrentMaps.decrementAndGet(); 89 | one.complete(1); 90 | Thread.sleep(100); 91 | concurrentMaps.decrementAndGet(); 92 | two.complete(2); 93 | Thread.sleep(100); 94 | concurrentMaps.decrementAndGet(); 95 | three.complete(3); 96 | 97 | assertEquals(await(result), List.of(1, 2, 3)); 98 | } 99 | 100 | @Override 101 | List reactiveStreamsTckVerifiers() { 102 | return List.of(new ProcessorVerification()); 103 | } 104 | 105 | public class ProcessorVerification extends StageProcessorVerification { 106 | @Override 107 | protected Flow.Processor createIdentityFlowProcessor(int bufferSize) { 108 | return ReactiveStreams.builder() 109 | .flatMapCompletionStage(CompletableFuture::completedFuture) 110 | .build(engine); 111 | } 112 | 113 | @Override 114 | public Integer createElement(int element) { 115 | return element; 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /tck/src/main/java/org/reactivestreams/utils/tck/FlatMapIterableStageVerification.java: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Licensed under Public Domain (CC0) * 3 | * * 4 | * To the extent possible under law, the person who associated CC0 with * 5 | * this code has waived all copyright and related or neighboring * 6 | * rights to this code. * 7 | * * 8 | * You should have received a copy of the CC0 legalcode along with this * 9 | * work. If not, see . * 10 | ******************************************************************************/ 11 | 12 | package org.reactivestreams.utils.tck; 13 | 14 | import org.reactivestreams.utils.ReactiveStreams; 15 | import org.testng.annotations.Test; 16 | 17 | import java.util.List; 18 | import java.util.concurrent.Flow; 19 | 20 | import static org.testng.Assert.assertEquals; 21 | 22 | public class FlatMapIterableStageVerification extends AbstractStageVerification { 23 | FlatMapIterableStageVerification(ReactiveStreamsTck.VerificationDeps deps) { 24 | super(deps); 25 | } 26 | 27 | @Test 28 | public void flatMapIterableStageShouldMapElements() { 29 | assertEquals(await(ReactiveStreams.of(1, 2, 3) 30 | .flatMapIterable(n -> List.of(n, n, n)) 31 | .toList() 32 | .build(engine)), List.of(1, 1, 1, 2, 2, 2, 3, 3, 3)); 33 | } 34 | 35 | @Test(expectedExceptions = RuntimeException.class, expectedExceptionsMessageRegExp = "failed") 36 | public void flatMapIterableStageShouldPropagateRuntimeExceptions() { 37 | await(ReactiveStreams.of("foo") 38 | .flatMapIterable(foo -> { 39 | throw new RuntimeException("failed"); 40 | }) 41 | .toList() 42 | .build(engine)); 43 | } 44 | 45 | @Override 46 | List reactiveStreamsTckVerifiers() { 47 | return List.of(new ProcessorVerification()); 48 | } 49 | 50 | /** 51 | * Verifies the outer processor. 52 | */ 53 | public class ProcessorVerification extends StageProcessorVerification { 54 | 55 | @Override 56 | protected Flow.Processor createIdentityFlowProcessor(int bufferSize) { 57 | return ReactiveStreams.builder().flatMapIterable(List::of).build(engine); 58 | } 59 | 60 | @Override 61 | protected Flow.Publisher createFailedFlowPublisher() { 62 | return ReactiveStreams.failed(new RuntimeException("failed")) 63 | .flatMapIterable(List::of).build(engine); 64 | } 65 | 66 | @Override 67 | public Integer createElement(int element) { 68 | return element; 69 | } 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /tck/src/main/java/org/reactivestreams/utils/tck/FlatMapStageVerification.java: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Licensed under Public Domain (CC0) * 3 | * * 4 | * To the extent possible under law, the person who associated CC0 with * 5 | * this code has waived all copyright and related or neighboring * 6 | * rights to this code. * 7 | * * 8 | * You should have received a copy of the CC0 legalcode along with this * 9 | * work. If not, see . * 10 | ******************************************************************************/ 11 | 12 | package org.reactivestreams.utils.tck; 13 | 14 | import org.reactivestreams.utils.ReactiveStreams; 15 | import org.testng.annotations.Test; 16 | 17 | import java.util.List; 18 | import java.util.concurrent.CompletableFuture; 19 | import java.util.concurrent.CompletionStage; 20 | import java.util.concurrent.Flow; 21 | import java.util.concurrent.TimeUnit; 22 | import java.util.concurrent.atomic.AtomicBoolean; 23 | import java.util.concurrent.atomic.AtomicInteger; 24 | import java.util.function.Function; 25 | 26 | import static org.testng.Assert.assertEquals; 27 | 28 | public class FlatMapStageVerification extends AbstractStageVerification { 29 | FlatMapStageVerification(ReactiveStreamsTck.VerificationDeps deps) { 30 | super(deps); 31 | } 32 | 33 | @Test 34 | public void flatMapStageShouldMapElements() { 35 | assertEquals(await(ReactiveStreams.of(1, 2, 3) 36 | .flatMap(n -> ReactiveStreams.of(n, n, n)) 37 | .toList() 38 | .build(engine)), List.of(1, 1, 1, 2, 2, 2, 3, 3, 3)); 39 | } 40 | 41 | @Test(expectedExceptions = RuntimeException.class, expectedExceptionsMessageRegExp = "failed") 42 | public void flatMapStageShouldPropagateRuntimeExceptions() { 43 | await(ReactiveStreams.of("foo") 44 | .flatMap(foo -> { 45 | throw new RuntimeException("failed"); 46 | }) 47 | .toList() 48 | .build(engine)); 49 | } 50 | 51 | @Test 52 | public void flatMapStageShouldOnlySubscribeToOnePublisherAtATime() throws Exception { 53 | AtomicInteger activePublishers = new AtomicInteger(); 54 | 55 | // A publisher that publishes one element 100ms after being requested, 56 | // and then completes 100ms later. It also uses activePublishers to ensure 57 | // that it is the only publisher that is subscribed to at any one time. 58 | class ScheduledPublisher implements Flow.Publisher { 59 | private final int id; 60 | ScheduledPublisher(int id) { 61 | this.id = id; 62 | } 63 | private AtomicBoolean published = new AtomicBoolean(false); 64 | @Override 65 | public void subscribe(Flow.Subscriber subscriber) { 66 | assertEquals(activePublishers.incrementAndGet(), 1); 67 | subscriber.onSubscribe(new Flow.Subscription() { 68 | @Override 69 | public void request(long n) { 70 | if (published.compareAndSet(false, true)) { 71 | executorService.schedule(() -> { 72 | subscriber.onNext(id); 73 | executorService.schedule(() -> { 74 | activePublishers.decrementAndGet(); 75 | subscriber.onComplete(); 76 | }, 100, TimeUnit.MILLISECONDS); 77 | }, 100, TimeUnit.MILLISECONDS); 78 | } 79 | } 80 | @Override 81 | public void cancel() { 82 | } 83 | }); 84 | } 85 | } 86 | 87 | CompletionStage> result = ReactiveStreams.of(1, 2, 3, 4, 5) 88 | .flatMap(id -> ReactiveStreams.fromPublisher(new ScheduledPublisher(id))) 89 | .toList() 90 | .build(engine); 91 | 92 | assertEquals(result.toCompletableFuture().get(2, TimeUnit.SECONDS), 93 | List.of(1, 2, 3, 4, 5)); 94 | } 95 | 96 | 97 | @Override 98 | List reactiveStreamsTckVerifiers() { 99 | return List.of(new OuterProcessorVerification(), new InnerSubscriberVerification()); 100 | } 101 | 102 | /** 103 | * Verifies the outer processor. 104 | */ 105 | public class OuterProcessorVerification extends StageProcessorVerification { 106 | 107 | @Override 108 | protected Flow.Processor createIdentityFlowProcessor(int bufferSize) { 109 | return ReactiveStreams.builder().flatMap(ReactiveStreams::of).build(engine); 110 | } 111 | 112 | @Override 113 | protected Flow.Publisher createFailedFlowPublisher() { 114 | return ReactiveStreams.failed(new RuntimeException("failed")) 115 | .flatMap(ReactiveStreams::of).build(engine); 116 | } 117 | 118 | @Override 119 | public Integer createElement(int element) { 120 | return element; 121 | } 122 | } 123 | 124 | /** 125 | * Verifies the inner subscriber passed to publishers produced by the mapper function. 126 | */ 127 | public class InnerSubscriberVerification extends StageSubscriberWhiteboxVerification { 128 | 129 | @Override 130 | protected Flow.Subscriber createFlowSubscriber(WhiteboxSubscriberProbe probe) { 131 | CompletableFuture> subscriber = new CompletableFuture<>(); 132 | ReactiveStreams.of(ReactiveStreams.fromPublisher(subscriber::complete)) 133 | .flatMap(Function.identity()) 134 | .to(new Flow.Subscriber<>() { 135 | @Override 136 | public void onSubscribe(Flow.Subscription subscription) { 137 | // We need to initially request an element to ensure that we get the publisher. 138 | subscription.request(1); 139 | probe.registerOnSubscribe(new SubscriberPuppet() { 140 | @Override 141 | public void triggerRequest(long elements) { 142 | subscription.request(elements); 143 | } 144 | 145 | @Override 146 | public void signalCancel() { 147 | subscription.cancel(); 148 | } 149 | }); 150 | } 151 | 152 | @Override 153 | public void onNext(Integer item) { 154 | probe.registerOnNext(item); 155 | } 156 | 157 | @Override 158 | public void onError(Throwable throwable) { 159 | probe.registerOnError(throwable); 160 | } 161 | 162 | @Override 163 | public void onComplete() { 164 | probe.registerOnComplete(); 165 | } 166 | }) 167 | .build(engine); 168 | 169 | return (Flow.Subscriber) await(subscriber); 170 | } 171 | 172 | @Override 173 | public Integer createElement(int element) { 174 | return element; 175 | } 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /tck/src/main/java/org/reactivestreams/utils/tck/MapStageVerification.java: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Licensed under Public Domain (CC0) * 3 | * * 4 | * To the extent possible under law, the person who associated CC0 with * 5 | * this code has waived all copyright and related or neighboring * 6 | * rights to this code. * 7 | * * 8 | * You should have received a copy of the CC0 legalcode along with this * 9 | * work. If not, see . * 10 | ******************************************************************************/ 11 | 12 | package org.reactivestreams.utils.tck; 13 | 14 | import org.reactivestreams.utils.ReactiveStreams; 15 | import org.testng.annotations.Test; 16 | 17 | import java.util.List; 18 | import java.util.concurrent.Flow; 19 | import java.util.function.Function; 20 | 21 | import static org.testng.Assert.assertEquals; 22 | 23 | public class MapStageVerification extends AbstractStageVerification { 24 | 25 | MapStageVerification(ReactiveStreamsTck.VerificationDeps deps) { 26 | super(deps); 27 | } 28 | 29 | @Test 30 | public void mapStageShouldMapElements() { 31 | assertEquals(await(ReactiveStreams.of(1, 2, 3) 32 | .map(Object::toString) 33 | .toList() 34 | .build(engine)), List.of("1", "2", "3")); 35 | } 36 | 37 | @Test(expectedExceptions = RuntimeException.class, expectedExceptionsMessageRegExp = "failed") 38 | public void mapStageShouldPropagateRuntimeExceptions() { 39 | await(ReactiveStreams.of("foo") 40 | .map(foo -> { 41 | throw new RuntimeException("failed"); 42 | }) 43 | .toList() 44 | .build(engine)); 45 | } 46 | 47 | @Override 48 | List reactiveStreamsTckVerifiers() { 49 | return List.of( 50 | new ProcessorVerification() 51 | ); 52 | } 53 | 54 | public class ProcessorVerification extends StageProcessorVerification { 55 | 56 | @Override 57 | protected Flow.Processor createIdentityFlowProcessor(int bufferSize) { 58 | return ReactiveStreams.builder().map(Function.identity()).build(engine); 59 | } 60 | 61 | @Override 62 | protected Flow.Publisher createFailedFlowPublisher() { 63 | return ReactiveStreams.failed(new RuntimeException("failed")) 64 | .map(Function.identity()).build(engine); 65 | } 66 | 67 | @Override 68 | public Integer createElement(int element) { 69 | return element; 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /tck/src/main/java/org/reactivestreams/utils/tck/OfStageVerification.java: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Licensed under Public Domain (CC0) * 3 | * * 4 | * To the extent possible under law, the person who associated CC0 with * 5 | * this code has waived all copyright and related or neighboring * 6 | * rights to this code. * 7 | * * 8 | * You should have received a copy of the CC0 legalcode along with this * 9 | * work. If not, see . * 10 | ******************************************************************************/ 11 | 12 | package org.reactivestreams.utils.tck; 13 | 14 | import org.reactivestreams.utils.ReactiveStreams; 15 | import org.testng.annotations.Test; 16 | 17 | import java.util.List; 18 | import java.util.concurrent.Flow; 19 | import java.util.stream.LongStream; 20 | 21 | import static org.testng.Assert.assertEquals; 22 | 23 | public class OfStageVerification extends AbstractStageVerification { 24 | 25 | OfStageVerification(ReactiveStreamsTck.VerificationDeps deps) { 26 | super(deps); 27 | } 28 | 29 | @Test 30 | public void iterableStageShouldEmitManyElements() { 31 | assertEquals(await( 32 | ReactiveStreams.of("a", "b", "c") 33 | .toList() 34 | .build(engine) 35 | ), List.of("a", "b", "c")); 36 | } 37 | 38 | @Test 39 | public void emptyIterableStageShouldEmitNoElements() { 40 | assertEquals(await( 41 | ReactiveStreams.empty() 42 | .toList() 43 | .build(engine) 44 | ), List.of()); 45 | } 46 | 47 | @Test 48 | public void singleIterableStageShouldEmitOneElement() { 49 | assertEquals(await( 50 | ReactiveStreams.of("a") 51 | .toList() 52 | .build(engine) 53 | ), List.of("a")); 54 | } 55 | 56 | @Override 57 | List reactiveStreamsTckVerifiers() { 58 | return List.of(new PublisherVerification()); 59 | } 60 | 61 | public class PublisherVerification extends StagePublisherVerification { 62 | @Override 63 | public Flow.Publisher createFlowPublisher(long elements) { 64 | return ReactiveStreams.fromIterable( 65 | () -> LongStream.rangeClosed(1, elements).boxed().iterator() 66 | ).build(engine); 67 | } 68 | } 69 | 70 | 71 | } 72 | -------------------------------------------------------------------------------- /tck/src/main/java/org/reactivestreams/utils/tck/ReactiveStreamsTck.java: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Licensed under Public Domain (CC0) * 3 | * * 4 | * To the extent possible under law, the person who associated CC0 with * 5 | * this code has waived all copyright and related or neighboring * 6 | * rights to this code. * 7 | * * 8 | * You should have received a copy of the CC0 legalcode along with this * 9 | * work. If not, see . * 10 | ******************************************************************************/ 11 | 12 | package org.reactivestreams.utils.tck; 13 | 14 | import org.reactivestreams.utils.ReactiveStreamsEngine; 15 | import org.reactivestreams.tck.TestEnvironment; 16 | import org.testng.annotations.AfterSuite; 17 | import org.testng.annotations.Factory; 18 | 19 | import java.util.ArrayList; 20 | import java.util.Arrays; 21 | import java.util.List; 22 | import java.util.concurrent.Executors; 23 | import java.util.concurrent.ScheduledExecutorService; 24 | import java.util.function.Function; 25 | 26 | /** 27 | * The Reactive Streams TCK. 28 | *

29 | * A concrete class that extends this class is all that is needed to verify a {@link ReactiveStreamsEngine} against 30 | * this TCK. 31 | *

32 | * It produces a number of TestNG test classes via the TestNG {@link Factory} annotated {@link #allTests()} method. 33 | * 34 | * @param The type of the Reactive Streams engine. 35 | */ 36 | public abstract class ReactiveStreamsTck { 37 | 38 | private final TestEnvironment testEnvironment; 39 | 40 | public ReactiveStreamsTck(TestEnvironment testEnvironment) { 41 | this.testEnvironment = testEnvironment; 42 | } 43 | 44 | /** 45 | * Override to provide the reactive streams engine. 46 | */ 47 | protected abstract E createEngine(); 48 | 49 | /** 50 | * Override to implement custom shutdown logic for the Reactive Streams engine. 51 | */ 52 | protected void shutdownEngine(E engine) { 53 | // By default, do nothing. 54 | } 55 | 56 | /** 57 | * Override this to disable/enable tests, useful for debugging one test at a time. 58 | */ 59 | protected boolean isEnabled(Object test) { 60 | return true; 61 | } 62 | 63 | private E engine; 64 | private ScheduledExecutorService executorService; 65 | 66 | @AfterSuite(alwaysRun = true) 67 | public void shutdownEngine() { 68 | if (engine != null) { 69 | shutdownEngine(engine); 70 | } 71 | } 72 | 73 | @Factory 74 | public Object[] allTests() { 75 | engine = createEngine(); 76 | executorService = Executors.newScheduledThreadPool(4); 77 | 78 | List> stageVerifications = Arrays.asList( 79 | OfStageVerification::new, 80 | MapStageVerification::new, 81 | FlatMapStageVerification::new, 82 | FilterStageVerification::new, 83 | FindFirstStageVerification::new, 84 | CollectStageVerification::new, 85 | TakeWhileStageVerification::new, 86 | FlatMapCompletionStageVerification::new, 87 | FlatMapIterableStageVerification::new, 88 | ConcatStageVerification::new, 89 | EmptyProcessorVerification::new, 90 | CancelStageVerification::new, 91 | SubscriberStageVerification::new 92 | ); 93 | 94 | List allTests = new ArrayList<>(); 95 | VerificationDeps deps = new VerificationDeps(); 96 | for (Function creator : stageVerifications) { 97 | AbstractStageVerification stageVerification = creator.apply(deps); 98 | allTests.add(stageVerification); 99 | allTests.addAll(stageVerification.reactiveStreamsTckVerifiers()); 100 | } 101 | 102 | return allTests.stream().filter(this::isEnabled).toArray(); 103 | } 104 | 105 | class VerificationDeps { 106 | ReactiveStreamsEngine engine() { 107 | return engine; 108 | } 109 | 110 | TestEnvironment testEnvironment() { 111 | return testEnvironment; 112 | } 113 | 114 | ScheduledExecutorService executorService() { 115 | return executorService; 116 | } 117 | } 118 | 119 | } 120 | -------------------------------------------------------------------------------- /tck/src/main/java/org/reactivestreams/utils/tck/SubscriberStageVerification.java: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Licensed under Public Domain (CC0) * 3 | * * 4 | * To the extent possible under law, the person who associated CC0 with * 5 | * this code has waived all copyright and related or neighboring * 6 | * rights to this code. * 7 | * * 8 | * You should have received a copy of the CC0 legalcode along with this * 9 | * work. If not, see . * 10 | ******************************************************************************/ 11 | 12 | package org.reactivestreams.utils.tck; 13 | 14 | import org.reactivestreams.utils.ReactiveStreams; 15 | import org.testng.annotations.Test; 16 | 17 | import java.util.List; 18 | import java.util.concurrent.CancellationException; 19 | import java.util.concurrent.CompletionStage; 20 | import java.util.concurrent.Flow; 21 | 22 | public class SubscriberStageVerification extends AbstractStageVerification { 23 | SubscriberStageVerification(ReactiveStreamsTck.VerificationDeps deps) { 24 | super(deps); 25 | } 26 | 27 | @Test 28 | public void subscriberStageShouldRedeemCompletionStageWhenCompleted() { 29 | CompletionStage result = ReactiveStreams.of().to( 30 | ReactiveStreams.builder().ignore().build(engine).getSubscriber() 31 | ).build(engine); 32 | await(result); 33 | } 34 | 35 | @Test(expectedExceptions = RuntimeException.class, expectedExceptionsMessageRegExp = "failed") 36 | public void subscriberStageShouldRedeemCompletionStageWhenFailed() { 37 | CompletionStage result = ReactiveStreams.failed(new RuntimeException("failed")).to( 38 | ReactiveStreams.builder().ignore().build(engine).getSubscriber() 39 | ).build(engine); 40 | await(result); 41 | } 42 | 43 | @Test(expectedExceptions = CancellationException.class) 44 | public void subscriberStageShouldRedeemCompletionStageWithCancellationExceptionWhenCancelled() { 45 | CompletionStage result = ReactiveStreams.fromPublisher(subscriber -> { 46 | subscriber.onSubscribe(new Flow.Subscription() { 47 | @Override 48 | public void request(long n) { 49 | } 50 | @Override 51 | public void cancel() { 52 | } 53 | }); 54 | }).to( 55 | ReactiveStreams.builder().cancel().build(engine).getSubscriber() 56 | ).build(engine); 57 | await(result); 58 | } 59 | 60 | @Override 61 | List reactiveStreamsTckVerifiers() { 62 | return List.of(); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /tck/src/main/java/org/reactivestreams/utils/tck/TakeWhileStageVerification.java: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Licensed under Public Domain (CC0) * 3 | * * 4 | * To the extent possible under law, the person who associated CC0 with * 5 | * this code has waived all copyright and related or neighboring * 6 | * rights to this code. * 7 | * * 8 | * You should have received a copy of the CC0 legalcode along with this * 9 | * work. If not, see . * 10 | ******************************************************************************/ 11 | 12 | package org.reactivestreams.utils.tck; 13 | 14 | import org.reactivestreams.utils.ReactiveStreams; 15 | import org.testng.annotations.Test; 16 | 17 | import java.util.List; 18 | import java.util.concurrent.CompletableFuture; 19 | import java.util.concurrent.Flow; 20 | 21 | import static org.testng.Assert.assertEquals; 22 | 23 | public class TakeWhileStageVerification extends AbstractStageVerification { 24 | 25 | TakeWhileStageVerification(ReactiveStreamsTck.VerificationDeps deps) { 26 | super(deps); 27 | } 28 | 29 | @Test 30 | public void takeWhileStageShouldTakeWhileConditionIsTrue() { 31 | assertEquals(await(ReactiveStreams.of(1, 2, 3, 4, 5, 6, 1, 2) 32 | .takeWhile(i -> i < 5) 33 | .toList() 34 | .build(engine)), List.of(1, 2, 3, 4)); 35 | } 36 | 37 | @Test 38 | public void takeWhileStageShouldEmitEmpty() { 39 | assertEquals(await(ReactiveStreams.of(1, 2, 3, 4, 5, 6) 40 | .takeWhile(i -> false) 41 | .toList() 42 | .build(engine)), List.of()); 43 | } 44 | 45 | @Test 46 | public void takeWhileStageShouldSupportLimit() { 47 | assertEquals(await(ReactiveStreams.of(1, 2, 3, 4, 5, 6) 48 | .limit(3) 49 | .toList() 50 | .build(engine)), List.of(1, 2, 3)); 51 | } 52 | 53 | @Test 54 | public void takeWhileShouldCancelUpStreamWhenDone() { 55 | CompletableFuture cancelled = new CompletableFuture<>(); 56 | ReactiveStreams.fromPublisher(subscriber -> 57 | subscriber.onSubscribe(new Flow.Subscription() { 58 | @Override 59 | public void request(long n) { 60 | subscriber.onNext(1); 61 | } 62 | @Override 63 | public void cancel() { 64 | cancelled.complete(null); 65 | } 66 | }) 67 | ).limit(1) 68 | .toList() 69 | .build(engine); 70 | await(cancelled); 71 | } 72 | 73 | @Test 74 | public void takeWhileShouldIgnoreSubsequentErrorsWhenDone() { 75 | assertEquals(await( 76 | ReactiveStreams.of(1, 2, 3, 4) 77 | .flatMap(i -> { 78 | if (i == 4) return ReactiveStreams.failed(new RuntimeException("failed")); 79 | else return ReactiveStreams.of(i); 80 | }) 81 | .limit(3) 82 | .toList() 83 | .build(engine) 84 | ), List.of(1, 2, 3)); 85 | } 86 | 87 | @Test 88 | public void takeWhileShouldSupportLimitingToZero() { 89 | assertEquals(await( 90 | ReactiveStreams.of(1, 2, 3, 4) 91 | .limit(0) 92 | .toList() 93 | .build(engine) 94 | ), List.of()); 95 | } 96 | 97 | @Override 98 | List reactiveStreamsTckVerifiers() { 99 | return List.of(new ProcessorVerification()); 100 | } 101 | 102 | public class ProcessorVerification extends StageProcessorVerification { 103 | @Override 104 | protected Flow.Processor createIdentityFlowProcessor(int bufferSize) { 105 | return ReactiveStreams.builder() 106 | .takeWhile(t -> true) 107 | .build(engine); 108 | } 109 | 110 | @Override 111 | public Integer createElement(int element) { 112 | return element; 113 | } 114 | } 115 | } 116 | --------------------------------------------------------------------------------