├── .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 super T> predicate;
48 | private boolean dropping = true;
49 |
50 | DropWhilePredicate(Predicate super T> 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 super T> 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 super T> 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 super T> 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 super R> 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 super T> 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