() {
57 | @Override
58 | public void onSuccess(String result) {
59 | completionStage.complete(result);
60 | }
61 |
62 | @Override
63 | public void onFailure(Throwable t) {
64 | completionStage.completeExceptionally(t);
65 | }
66 | });
67 |
68 | # Design
69 | The best way to understand how it works is to check the [code](https://github.com/lukas-krecan/completion-stage/blob/master/src/main/java/net/javacrumbs/completionstage/SimpleCompletionStage.java).
70 | I have written two articles describing design decisions behind the implementation you can read it [here](http://java.dzone.com/articles/implementing-java-8) and [here](http://java.dzone.com/articles/implementing-java-8-0).
71 |
72 | # Why can't I just use a CompletableFuture?
73 |
74 | You definitely can. The main problem I have that it is tightly coupled with fork-join framework.
75 | And fork-join framework is meant to be used mainly for CPU intensive tasks. But my usual use-case
76 | is the opposite, I want to use asynchronous processing mainly for blocking tasks. I do not want to
77 | block a thread while waiting for a network or database operation.
78 |
79 | Of course, you do not have to use fork-join executors with CompletableFutures. You just have to be careful since it is
80 | the default choice for async methods.
81 |
82 | # Release notes
83 |
84 | ### 0.0.9
85 | * Fine grained locks
86 |
87 | ### 0.0.8
88 | * SimpleCompletionStage made extensible
89 |
90 | ### 0.0.7
91 | * Incorrect release - do not use
92 |
93 | ### 0.0.6
94 | * Added CompletableCompletionStage.doCompleteMethods
95 |
96 | ### 0.0.5
97 | * Added factory methods to CompletionStageFactory
98 |
99 | ### 0.0.4
100 | * Small optimizations
101 |
102 | ### 0.0.3
103 | * thenComposeAsync error handling fixed
104 | * thenCombineAsync uses correct executor
105 | * whenCompleteAsync passes value to next stage
106 | * Internal refactoring
107 |
108 | ### 0.0.2
109 | * Fixed error handling in thenCombine function
110 |
111 |
112 |
113 |
--------------------------------------------------------------------------------
/completion-stage.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/header.txt:
--------------------------------------------------------------------------------
1 | Copyright 2009-2015 the original author or authors.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 |
5 | net.javacrumbs.completion-stage
6 | completion-stage
7 | 0.0.10-SNAPSHOT
8 |
9 |
10 | org.sonatype.oss
11 | oss-parent
12 | 9
13 |
14 |
15 |
16 |
17 | org.mockito
18 | mockito-core
19 | 1.9.5
20 | test
21 |
22 |
23 | junit
24 | junit
25 | 4.11
26 | test
27 |
28 |
29 | org.assertj
30 | assertj-core
31 | 1.7.0
32 | test
33 |
34 |
35 | org.springframework
36 | spring-core
37 | 4.0.3.RELEASE
38 | test
39 |
40 |
41 |
42 |
43 |
44 | The Apache Software License, Version 2.0
45 | LICENSE.txt
46 |
47 |
48 |
49 |
50 | release-sign-artifacts
51 |
52 |
53 | performRelease
54 | true
55 |
56 |
57 |
58 |
59 |
60 | org.apache.maven.plugins
61 | maven-gpg-plugin
62 |
63 | 195BE743
64 |
65 | 1.6
66 |
67 |
68 | sign-artifacts
69 | verify
70 |
71 | sign
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 | scm:git:git@github.com:lukas-krecan/completion-stage.git
83 | scm:git:git@github.com:lukas-krecan/completion-stage.git
84 | scm:git:git@github.com:lukas-krecan/completion-stage.git
85 |
86 |
87 |
88 |
89 |
90 | com.mycila.maven-license-plugin
91 | maven-license-plugin
92 | 1.9.0
93 |
94 |
95 |
96 | LICENSE.txt
97 |
98 |
99 |
100 |
101 | org.apache.maven.plugins
102 | maven-jar-plugin
103 | 2.5
104 |
105 |
106 | org.apache.maven.plugins
107 | maven-javadoc-plugin
108 | 2.10.1
109 |
110 |
111 | org.apache.maven.plugins
112 | maven-source-plugin
113 | 2.4
114 |
115 |
116 |
117 |
118 |
119 |
120 | org.apache.maven.plugins
121 | maven-compiler-plugin
122 | 3.2
123 |
124 | 1.8
125 | 1.8
126 |
127 |
128 |
129 |
130 |
131 |
132 |
--------------------------------------------------------------------------------
/src/main/java/net/javacrumbs/completionstage/CallbackRegistry.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2009-2015 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package net.javacrumbs.completionstage;
17 |
18 | import java.util.LinkedList;
19 | import java.util.Objects;
20 | import java.util.Queue;
21 | import java.util.concurrent.Executor;
22 | import java.util.function.Consumer;
23 |
24 | /**
25 | * Registry for Consumer callbacks. Works as a state machine switching between InitialState, IntermediateState, Success and Failure state.
26 | *
27 | * Inspired by {@code org.springframework.util.concurrent.ListenableFutureCallbackRegistry} and
28 | * {@code com.google.common.util.concurrent.ExecutionList}
29 | *
30 | * Explicit synchronization only around blocks responsible for state switching.
31 | */
32 | final class CallbackRegistry {
33 | private State state = InitialState.instance();
34 |
35 | private final Object mutex = new Object();
36 |
37 | /**
38 | * Adds the given callbacks to this registry.
39 | */
40 | void addCallbacks(Consumer super T> successCallback, Consumer failureCallback, Executor executor) {
41 | Objects.requireNonNull(successCallback, "'successCallback' must not be null");
42 | Objects.requireNonNull(failureCallback, "'failureCallback' must not be null");
43 | Objects.requireNonNull(executor, "'executor' must not be null");
44 |
45 | synchronized (mutex) {
46 | state = state.addCallbacks(successCallback, failureCallback, executor);
47 | }
48 | }
49 |
50 | /**
51 | * To be called to set the result value.
52 | *
53 | * @param result the result value
54 | * @return true if this result will be used (first result registered)
55 | */
56 | boolean success(T result) {
57 | State oldState;
58 | synchronized (mutex) {
59 | if (state.isCompleted()) {
60 | return false;
61 | }
62 | oldState = state;
63 | state = state.getSuccessState(result);
64 | }
65 | oldState.callSuccessCallbacks(result);
66 | return true;
67 |
68 | }
69 |
70 | /**
71 | * To be called to set the failure exception
72 | *
73 | * @param failure the exception
74 | * @return true if this result will be used (first result registered)
75 | */
76 | boolean failure(Throwable failure) {
77 | State oldState;
78 | synchronized (mutex) {
79 | if (state.isCompleted()) {
80 | return false;
81 | }
82 | oldState = state;
83 | state = state.getFailureState(failure);
84 | }
85 | oldState.callFailureCallbacks(failure);
86 | return true;
87 | }
88 |
89 | /**
90 | * State of the registry. All subclasses are meant to be used form a synchronized block and are NOT
91 | * thread safe on their own.
92 | */
93 | private static abstract class State {
94 | protected abstract State addCallbacks(Consumer super S> successCallback, Consumer failureCallback, Executor executor);
95 |
96 | protected State getSuccessState(S result) {
97 | throw new IllegalStateException("success method should not be called multiple times");
98 | }
99 |
100 | protected void callSuccessCallbacks(S result) {
101 | }
102 |
103 | protected State getFailureState(Throwable failure) {
104 | throw new IllegalStateException("failure method should not be called multiple times");
105 | }
106 |
107 | protected void callFailureCallbacks(Throwable failure) {
108 | }
109 |
110 | protected boolean isCompleted() {
111 | return true;
112 | }
113 | }
114 |
115 | /**
116 | * Result is not known yet and no callbacks registered. Using shared instance so we do not allocate instance where
117 | * it may not be needed.
118 | */
119 | private static class InitialState extends State {
120 | private static final InitialState instance = new InitialState<>();
121 |
122 | @Override
123 | protected State addCallbacks(Consumer super S> successCallback, Consumer failureCallback, Executor executor) {
124 | IntermediateState intermediateState = new IntermediateState<>();
125 | intermediateState.addCallbacks(successCallback, failureCallback, executor);
126 | return intermediateState;
127 | }
128 |
129 | @Override
130 | protected State getSuccessState(S result) {
131 | return new SuccessState<>(result);
132 | }
133 |
134 | @Override
135 | protected State getFailureState(Throwable failure) {
136 | return new FailureState<>(failure);
137 | }
138 |
139 | @Override
140 | protected boolean isCompleted() {
141 | return false;
142 | }
143 |
144 | @SuppressWarnings("unchecked")
145 | private static State instance() {
146 | return (State) instance;
147 | }
148 | }
149 |
150 | /**
151 | * Result is not known yet.
152 | */
153 | private static class IntermediateState extends State {
154 | private final Queue> callbacks = new LinkedList<>();
155 |
156 | @Override
157 | protected State addCallbacks(Consumer super S> successCallback, Consumer failureCallback, Executor executor) {
158 | callbacks.add(new CallbackHolder<>(successCallback, failureCallback, executor));
159 | return this;
160 | }
161 |
162 | @Override
163 | protected State getSuccessState(S result) {
164 | return new SuccessState<>(result);
165 | }
166 |
167 | @Override
168 | protected void callSuccessCallbacks(S result) {
169 | // no need to remove callbacks from the queue, this instance will be thrown away at once
170 | for (CallbackHolder super S> callback : callbacks) {
171 | callback.callSuccessCallback(result);
172 | }
173 | }
174 |
175 | @Override
176 | protected State getFailureState(Throwable failure) {
177 | return new FailureState<>(failure);
178 | }
179 |
180 | @Override
181 | protected void callFailureCallbacks(Throwable failure) {
182 | // no need to remove callbacks from the queue, this instance will be thrown away at once
183 | for (CallbackHolder super S> callback : callbacks) {
184 | callback.callFailureCallback(failure);
185 | }
186 | }
187 |
188 | @Override
189 | protected boolean isCompleted() {
190 | return false;
191 | }
192 | }
193 |
194 | /**
195 | * Holds the result.
196 | */
197 | private static final class SuccessState extends State {
198 | private final S result;
199 |
200 | private SuccessState(S result) {
201 | this.result = result;
202 | }
203 |
204 | @Override
205 | protected State addCallbacks(Consumer super S> successCallback, Consumer failureCallback, Executor executor) {
206 | callCallback(successCallback, result, executor);
207 | return this;
208 | }
209 | }
210 |
211 | /**
212 | * Holds the failure.
213 | */
214 | private static final class FailureState extends State {
215 | private final Throwable failure;
216 |
217 | private FailureState(Throwable failure) {
218 | this.failure = failure;
219 | }
220 |
221 | @Override
222 | protected State addCallbacks(Consumer super S> successCallback, Consumer failureCallback, Executor executor) {
223 | callCallback(failureCallback, failure, executor);
224 | return this;
225 | }
226 | }
227 |
228 |
229 | private static final class CallbackHolder {
230 | private final Consumer successCallback;
231 | private final Consumer failureCallback;
232 | private final Executor executor;
233 |
234 | private CallbackHolder(Consumer successCallback, Consumer failureCallback, Executor executor) {
235 | this.successCallback = successCallback;
236 | this.failureCallback = failureCallback;
237 | this.executor = executor;
238 | }
239 |
240 | void callSuccessCallback(S result) {
241 | callCallback(successCallback, result, executor);
242 | }
243 |
244 | void callFailureCallback(Throwable failure) {
245 | callCallback(failureCallback, failure, executor);
246 | }
247 | }
248 |
249 | private static void callCallback(Consumer callback, T value, Executor executor) {
250 | executor.execute(() -> callback.accept(value));
251 | }
252 |
253 | }
254 |
--------------------------------------------------------------------------------
/src/main/java/net/javacrumbs/completionstage/CompletableCompletionStage.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2009-2015 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package net.javacrumbs.completionstage;
17 |
18 | import java.util.concurrent.CompletionStage;
19 |
20 | /**
21 | * Completion stage you can complete. On top of standard {@link java.util.concurrent.CompletionStage} methods
22 | * provides {@link net.javacrumbs.completionstage.CompletableCompletionStage#complete(Object)} and
23 | * {@link net.javacrumbs.completionstage.CompletableCompletionStage#completeExceptionally(Throwable)}.
24 | */
25 | public interface CompletableCompletionStage extends CompletionStage {
26 |
27 | /**
28 | * Call this if you want to start processing of the result..
29 | *
30 | * @param result the result value.
31 | * @return {@code true} if this invocation caused this CompletionStage
32 | * to transition to a completed state, else {@code false}
33 | */
34 | public boolean complete(T result);
35 |
36 | /**
37 | * Call this if you want to start processing of failure.
38 | *
39 | * @param ex the exception
40 | * @return {@code true} if this invocation caused this CompletionStage
41 | * to transition to a completed state, else {@code false}
42 | */
43 | public boolean completeExceptionally(Throwable ex);
44 |
45 | /**
46 | * Sets this CompletionStage as success with provided value
47 | * if it hasn't been already completed. Same as {@link #complete(Object) complete(T)}, the only difference
48 | * is the return type which makes this method more suitable to be used as method reference.
49 | *
50 | * @param result the success value. May be null.
51 | */
52 | public default void doComplete(T result) {
53 | complete(result);
54 | }
55 |
56 | /**
57 | * Accepts a value and a throwable to complete this CompletionStage
58 | * if it hasn't been already completed. If throwable is null, completes normally, if
59 | * throwable is not null, completes exceptionally.
60 | *
61 | * @param result the success value. May be null.
62 | * Completes this computation as a success with this value only if throwable is null.
63 | * @param throwable the failure value.
64 | * If not null, completes this computation as a failure with this value.
65 | */
66 | public default void doComplete(T result, Throwable throwable) {
67 | if (throwable == null) {
68 | complete(result);
69 | } else {
70 | completeExceptionally(throwable);
71 | }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/main/java/net/javacrumbs/completionstage/CompletionStageAdapter.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2009-2015 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package net.javacrumbs.completionstage;
17 |
18 | import java.util.concurrent.CompletionStage;
19 | import java.util.concurrent.Executor;
20 | import java.util.function.BiConsumer;
21 | import java.util.function.BiFunction;
22 | import java.util.function.Consumer;
23 | import java.util.function.Function;
24 |
25 | /**
26 | * Contains boring methods from {@link java.util.concurrent.CompletionStage} that just call
27 | * async method with executor.
28 | * @param
29 | */
30 | abstract class CompletionStageAdapter implements CompletionStage {
31 | protected static final Executor SAME_THREAD_EXECUTOR = new Executor() {
32 | @Override
33 | public void execute(Runnable command) {
34 | command.run();
35 | }
36 | @Override
37 | public String toString() {
38 | return "SAME_THREAD_EXECUTOR";
39 | }
40 | };
41 | /**
42 | * Default executor to be used for Async methods.
43 | */
44 | private final Executor defaultExecutor;
45 |
46 | CompletionStageAdapter(Executor defaultExecutor) {
47 | this.defaultExecutor = defaultExecutor;
48 | }
49 |
50 | @Override
51 | public CompletionStage thenApply(Function super T, ? extends U> fn) {
52 | return thenApplyAsync(fn, SAME_THREAD_EXECUTOR);
53 | }
54 |
55 | @Override
56 | public CompletionStage thenApplyAsync(Function super T, ? extends U> fn) {
57 | return thenApplyAsync(fn, defaultExecutor);
58 | }
59 |
60 | @Override
61 | public CompletionStage thenAccept(Consumer super T> action) {
62 | return thenAcceptAsync(action, SAME_THREAD_EXECUTOR);
63 | }
64 |
65 | @Override
66 | public CompletionStage thenAcceptAsync(Consumer super T> action) {
67 | return thenAcceptAsync(action, defaultExecutor);
68 | }
69 |
70 | @Override
71 | public CompletionStage thenRun(Runnable action) {
72 | return thenRunAsync(action, SAME_THREAD_EXECUTOR);
73 | }
74 |
75 | @Override
76 | public CompletionStage thenRunAsync(Runnable action) {
77 | return thenRunAsync(action, defaultExecutor);
78 | }
79 |
80 | @Override
81 | public CompletionStage thenCombine(CompletionStage extends U> other, BiFunction super T, ? super U, ? extends V> fn) {
82 | return thenCombineAsync(other, fn, SAME_THREAD_EXECUTOR);
83 | }
84 |
85 | @Override
86 | public CompletionStage thenCombineAsync(CompletionStage extends U> other, BiFunction super T, ? super U, ? extends V> fn) {
87 | return thenCombineAsync(other, fn, defaultExecutor);
88 | }
89 |
90 | @Override
91 | public CompletionStage thenAcceptBoth(CompletionStage extends U> other, BiConsumer super T, ? super U> action) {
92 | return thenAcceptBothAsync(other, action, SAME_THREAD_EXECUTOR);
93 | }
94 |
95 | @Override
96 | public CompletionStage thenAcceptBothAsync(CompletionStage extends U> other, BiConsumer super T, ? super U> action) {
97 | return thenAcceptBothAsync(other, action, defaultExecutor);
98 | }
99 |
100 | @Override
101 | public CompletionStage runAfterBoth(CompletionStage> other, Runnable action) {
102 | return runAfterBothAsync(other, action, SAME_THREAD_EXECUTOR);
103 | }
104 |
105 | @Override
106 | public CompletionStage runAfterBothAsync(CompletionStage> other, Runnable action) {
107 | return runAfterBothAsync(other, action, defaultExecutor);
108 | }
109 |
110 | @Override
111 | public CompletionStage applyToEither(CompletionStage extends T> other, Function super T, U> fn) {
112 | return applyToEitherAsync(other, fn, SAME_THREAD_EXECUTOR);
113 | }
114 |
115 | @Override
116 | public CompletionStage applyToEitherAsync(CompletionStage extends T> other, Function super T, U> fn) {
117 | return applyToEitherAsync(other, fn, defaultExecutor);
118 | }
119 |
120 | @Override
121 | public CompletionStage acceptEither(CompletionStage extends T> other, Consumer super T> action) {
122 | return acceptEitherAsync(other, action, SAME_THREAD_EXECUTOR);
123 | }
124 |
125 | @Override
126 | public CompletionStage acceptEitherAsync(CompletionStage extends T> other, Consumer super T> action) {
127 | return acceptEitherAsync(other, action, defaultExecutor);
128 | }
129 |
130 | @Override
131 | public CompletionStage runAfterEither(CompletionStage> other, Runnable action) {
132 | return runAfterEitherAsync(other, action, SAME_THREAD_EXECUTOR);
133 | }
134 |
135 | @Override
136 | public CompletionStage runAfterEitherAsync(CompletionStage> other, Runnable action) {
137 | return runAfterEitherAsync(other, action, defaultExecutor);
138 | }
139 |
140 | @Override
141 | public CompletionStage thenCompose(Function super T, ? extends CompletionStage> fn) {
142 | return thenComposeAsync(fn, SAME_THREAD_EXECUTOR);
143 | }
144 |
145 | @Override
146 | public CompletionStage thenComposeAsync(Function super T, ? extends CompletionStage> fn) {
147 | return thenComposeAsync(fn, defaultExecutor);
148 | }
149 |
150 | @Override
151 | public CompletionStage whenComplete(BiConsumer super T, ? super Throwable> action) {
152 | return whenCompleteAsync(action, SAME_THREAD_EXECUTOR);
153 | }
154 |
155 | @Override
156 | public CompletionStage whenCompleteAsync(BiConsumer super T, ? super Throwable> action) {
157 | return whenCompleteAsync(action, defaultExecutor);
158 | }
159 |
160 | @Override
161 | public CompletionStage handle(BiFunction super T, Throwable, ? extends U> fn) {
162 | return handleAsync(fn, SAME_THREAD_EXECUTOR);
163 | }
164 |
165 | @Override
166 | public CompletionStage handleAsync(BiFunction super T, Throwable, ? extends U> fn) {
167 | return handleAsync(fn, defaultExecutor);
168 | }
169 |
170 | protected final Executor getDefaultExecutor() {
171 | return defaultExecutor;
172 | }
173 | }
174 |
--------------------------------------------------------------------------------
/src/main/java/net/javacrumbs/completionstage/CompletionStageFactory.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2009-2015 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package net.javacrumbs.completionstage;
17 |
18 | import net.javacrumbs.completionstage.spi.CompletableCompletionStageFactory;
19 |
20 | import java.util.Objects;
21 | import java.util.concurrent.CompletionStage;
22 | import java.util.concurrent.Executor;
23 | import java.util.function.Supplier;
24 |
25 | /**
26 | * Factory for {@link java.util.concurrent.CompletionStage} implementation.
27 | */
28 | public class CompletionStageFactory implements CompletableCompletionStageFactory {
29 | private final Executor defaultAsyncExecutor;
30 |
31 | /**
32 | * Creates factory.
33 | * @param defaultAsyncExecutor executor to be used for async methods without executor parameter
34 | */
35 | public CompletionStageFactory(Executor defaultAsyncExecutor) {
36 | this.defaultAsyncExecutor = defaultAsyncExecutor;
37 | }
38 |
39 | /**
40 | * Creates completion stage.
41 | * @param type of the CompletionStage
42 | * @return CompletionStage
43 | */
44 | public CompletableCompletionStage createCompletionStage() {
45 | return new SimpleCompletionStage<>(defaultAsyncExecutor, this);
46 | }
47 |
48 | /**
49 | * Returns a new CompletionStage that is already completed with
50 | * the given value.
51 | *
52 | * @param value the value
53 | * @param the type of the value
54 | * @return the completed CompletionStage
55 | */
56 | public final CompletionStage completedStage(T value) {
57 | CompletableCompletionStage result = createCompletionStage();
58 | result.complete(value);
59 | return result;
60 | }
61 |
62 | /**
63 | * Returns a new CompletionStage that is asynchronously completed
64 | * by a task running in the defaultAsyncExecutor with
65 | * the value obtained by calling the given Supplier.
66 | *
67 | * @param supplier a function returning the value to be used
68 | * to complete the returned CompletionStage
69 | * @param the function's return type
70 | * @return the new CompletionStage
71 | */
72 | public final CompletionStage supplyAsync(Supplier supplier) {
73 | return supplyAsync(supplier, defaultAsyncExecutor);
74 | }
75 |
76 | /**
77 | * Returns a new CompletionStage that is asynchronously completed
78 | * by a task running in the given executor with the value obtained
79 | * by calling the given Supplier. Subsequent completion stages will
80 | * use defaultAsyncExecutor as their default executor.
81 | *
82 | * @param supplier a function returning the value to be used
83 | * to complete the returned CompletionStage
84 | * @param executor the executor to use for asynchronous execution
85 | * @param the function's return type
86 | * @return the new CompletionStage
87 | */
88 | public final CompletionStage supplyAsync(Supplier supplier, Executor executor) {
89 | Objects.requireNonNull(supplier, "supplier must not be null");
90 | return completedStage(null).thenApplyAsync((ignored) -> supplier.get(), executor);
91 | }
92 |
93 | /**
94 | * Returns a new CompletionStage that is asynchronously completed
95 | * by a task running in the defaultAsyncExecutor after
96 | * it runs the given action.
97 | *
98 | * @param runnable the action to run before completing the
99 | * returned CompletionStage
100 | * @return the new CompletionStage
101 | */
102 | public final CompletionStage runAsync(Runnable runnable) {
103 | return runAsync(runnable, defaultAsyncExecutor);
104 | }
105 |
106 | /**
107 | * Returns a new CompletionStage that is asynchronously completed
108 | * by a task running in the given executor after it runs the given
109 | * action. Subsequent completion stages will
110 | * use defaultAsyncExecutor as their default executor.
111 | *
112 | * @param runnable the action to run before completing the
113 | * returned CompletionStage
114 | * @param executor the executor to use for asynchronous execution
115 | * @return the new CompletionStage
116 | */
117 | public final CompletionStage runAsync(Runnable runnable, Executor executor) {
118 | Objects.requireNonNull(runnable, "runnable must not be null");
119 | return completedStage(null).thenRunAsync(runnable, executor);
120 | }
121 |
122 | protected final Executor getDefaultAsyncExecutor() {
123 | return defaultAsyncExecutor;
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/src/main/java/net/javacrumbs/completionstage/SimpleCompletionStage.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2009-2015 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package net.javacrumbs.completionstage;
17 |
18 | import net.javacrumbs.completionstage.spi.CompletableCompletionStageFactory;
19 |
20 | import java.util.concurrent.CompletableFuture;
21 | import java.util.concurrent.CompletionException;
22 | import java.util.concurrent.CompletionStage;
23 | import java.util.concurrent.Executor;
24 | import java.util.function.BiConsumer;
25 | import java.util.function.BiFunction;
26 | import java.util.function.Consumer;
27 | import java.util.function.Function;
28 | import java.util.function.Supplier;
29 |
30 | /**
31 | * Please do not use this class directly, use {@link CompletionStageFactory} to create instances.
32 | * {@link java.util.concurrent.CompletionStage} implementation that is built on top of standard executors.
33 | */
34 | public class SimpleCompletionStage extends CompletionStageAdapter implements CompletableCompletionStage {
35 |
36 | private final CallbackRegistry callbackRegistry = new CallbackRegistry<>();
37 | private final CompletableCompletionStageFactory completionStageFactory;
38 |
39 | /**
40 | * Creates SimpleCompletionStage.
41 | *
42 | * @param defaultExecutor executor to be used for all async method without executor parameter.
43 | * @param completionStageFactory factory to create next stages
44 | */
45 | public SimpleCompletionStage(Executor defaultExecutor, CompletableCompletionStageFactory completionStageFactory) {
46 | super(defaultExecutor);
47 | this.completionStageFactory = completionStageFactory;
48 | }
49 |
50 | /**
51 | * Notifies all callbacks about the result.
52 | *
53 | * @param result result of the previous stage.
54 | */
55 | @Override
56 | public boolean complete(T result) {
57 | return callbackRegistry.success(result);
58 | }
59 |
60 | /**
61 | * Notifies all callbacks about the failure.
62 | *
63 | * @param ex exception thrown from the previous stage.
64 | */
65 | @Override
66 | public boolean completeExceptionally(Throwable ex) {
67 | return callbackRegistry.failure(ex);
68 | }
69 |
70 | @Override
71 | public CompletionStage thenApplyAsync(
72 | Function super T, ? extends U> fn,
73 | Executor executor
74 | ) {
75 | CompletableCompletionStage nextStage = newCompletableCompletionStage();
76 | addCallbacks(
77 | result -> acceptResult(nextStage, () -> fn.apply(result)),
78 | handleFailure(nextStage),
79 | executor
80 | );
81 | return nextStage;
82 | }
83 |
84 | @Override
85 | public CompletionStage thenAcceptAsync(Consumer super T> action, Executor executor) {
86 | return thenApplyAsync(convertConsumerToFunction(action), executor);
87 | }
88 |
89 | @Override
90 | public CompletionStage thenRunAsync(Runnable action, Executor executor) {
91 | return thenApplyAsync(convertRunnableToFunction(action), executor);
92 | }
93 |
94 | @Override
95 | public CompletionStage thenCombineAsync(
96 | CompletionStage extends U> other,
97 | BiFunction super T, ? super U, ? extends V> fn,
98 | Executor executor) {
99 | return thenCompose(result1 -> other.thenApplyAsync(result2 -> fn.apply(result1, result2), executor));
100 | }
101 |
102 | @Override
103 | public CompletionStage thenAcceptBothAsync(CompletionStage extends U> other, BiConsumer super T, ? super U> action, Executor executor) {
104 | return thenCombineAsync(
105 | other,
106 | // transform BiConsumer to BiFunction
107 | (t, u) -> {
108 | action.accept(t, u);
109 | return null;
110 | },
111 | executor
112 | );
113 | }
114 |
115 | @Override
116 | public CompletionStage runAfterBothAsync(CompletionStage> other, Runnable action, Executor executor) {
117 | return thenCombineAsync(
118 | other,
119 | // transform Runnable to BiFunction
120 | (t, r) -> {
121 | action.run();
122 | return null;
123 | },
124 | executor
125 | );
126 | }
127 |
128 | @Override
129 | public CompletionStage applyToEitherAsync(
130 | CompletionStage extends T> other,
131 | Function super T, U> fn,
132 | Executor executor) {
133 | return doApplyToEitherAsync(this, other, fn, executor);
134 | }
135 |
136 | /**
137 | * This method exists just to reconcile generics when called from {@link #runAfterEitherAsync}
138 | * which has unexpected type of parameter "other". The alternative is to ignore compiler warning.
139 | */
140 | private CompletionStage doApplyToEitherAsync(
141 | CompletionStage extends R> first,
142 | CompletionStage extends R> second,
143 | Function super R, U> fn,
144 | Executor executor) {
145 | CompletableCompletionStage nextStage = newCompletableCompletionStage();
146 |
147 | // only the first result is accepted by completion stage,
148 | // the other one is ignored
149 | BiConsumer action = completeHandler(nextStage);
150 | first.whenComplete(action);
151 | second.whenComplete(action);
152 |
153 | return nextStage.thenApplyAsync(fn, executor);
154 | }
155 |
156 | @Override
157 | public CompletionStage acceptEitherAsync(CompletionStage extends T> other, Consumer super T> action, Executor executor) {
158 | return applyToEitherAsync(other, convertConsumerToFunction(action), executor);
159 | }
160 |
161 | @Override
162 | public CompletionStage runAfterEitherAsync(CompletionStage> other, Runnable action, Executor executor) {
163 | return doApplyToEitherAsync(this, other, convertRunnableToFunction(action), executor);
164 | }
165 |
166 | @Override
167 | public CompletionStage thenComposeAsync(Function super T, ? extends CompletionStage> fn, Executor executor) {
168 | CompletableCompletionStage nextStage = newCompletableCompletionStage();
169 | addCallbacks(
170 | result1 -> {
171 | try {
172 | fn.apply(result1).whenComplete( completeHandler(nextStage) );
173 | } catch (Throwable e) {
174 | handleFailure(nextStage, e);
175 | }
176 | },
177 | handleFailure(nextStage),
178 | executor
179 | );
180 | return nextStage;
181 | }
182 |
183 | @Override
184 | public CompletionStage exceptionally(Function fn) {
185 | CompletableCompletionStage nextStage = newCompletableCompletionStage();
186 | addCallbacks(
187 | nextStage::complete,
188 | e -> acceptResult(nextStage, () -> fn.apply(e)),
189 | SAME_THREAD_EXECUTOR
190 | );
191 | return nextStage;
192 | }
193 |
194 | @Override
195 | public CompletionStage whenCompleteAsync(BiConsumer super T, ? super Throwable> action, Executor executor) {
196 | CompletableCompletionStage nextStage = newCompletableCompletionStage();
197 | addCallbacks(
198 | result -> acceptResult(
199 | nextStage,
200 | () -> {
201 | action.accept(result, null);
202 | return result;
203 | }
204 | ),
205 | failure -> {
206 | try {
207 | action.accept(null, failure);
208 | handleFailure(nextStage, failure);
209 | } catch (Throwable e) {
210 | handleFailure(nextStage, e);
211 | }
212 | }, executor
213 | );
214 | return nextStage;
215 | }
216 |
217 | @Override
218 | public CompletionStage handleAsync(
219 | BiFunction super T, Throwable, ? extends U> fn,
220 | Executor executor) {
221 | CompletableCompletionStage nextStage = newCompletableCompletionStage();
222 | addCallbacks(
223 | result -> acceptResult(nextStage,() -> fn.apply(result, null)),
224 | // exceptions are treated as success
225 | e -> acceptResult(nextStage, () -> fn.apply(null, e)),
226 | executor
227 | );
228 | return nextStage;
229 | }
230 |
231 | @Override
232 | public CompletableFuture toCompletableFuture() {
233 | CompletableFuture completableFuture = new CompletableFuture<>();
234 | addCallbacks(
235 | completableFuture::complete,
236 | completableFuture::completeExceptionally,
237 | SAME_THREAD_EXECUTOR
238 | );
239 | return completableFuture;
240 | }
241 |
242 |
243 | private CompletableCompletionStage newCompletableCompletionStage() {
244 | return completionStageFactory.createCompletionStage();
245 | }
246 |
247 |
248 | private Function convertConsumerToFunction(Consumer super T> action) {
249 | return result -> {
250 | action.accept(result);
251 | return null;
252 | };
253 | }
254 |
255 | private Function convertRunnableToFunction(Runnable action) {
256 | return result -> {
257 | action.run();
258 | return null;
259 | };
260 | }
261 |
262 | /**
263 | * Accepts result provided by the Supplier. If an exception is thrown by the supplier, completes exceptionally.
264 | *
265 | * @param supplier generates result
266 | *
267 | */
268 |
269 | private static void acceptResult(CompletableCompletionStage s, Supplier extends T> supplier) {
270 | try {
271 | // exception can be thrown only by supplier. All callbacks are generated by us and they do not throw any exceptions
272 | s.complete(supplier.get());
273 | } catch (Throwable e) {
274 | handleFailure(s, e);
275 | }
276 | }
277 |
278 | /**
279 | * Handler that can be used in whenComplete method.
280 | *
281 | * @return BiConsumer that passes values to this CompletionStage.
282 | */
283 | private static BiConsumer completeHandler(CompletableCompletionStage s) {
284 | return (result, failure) -> {
285 | if (failure == null) {
286 | s.complete(result);
287 | } else {
288 | handleFailure(s, failure);
289 | }
290 | };
291 | }
292 |
293 | /**
294 | * Wraps exception completes exceptionally.
295 | */
296 | private static Consumer handleFailure(CompletableCompletionStage> s) {
297 | return (e) -> handleFailure(s, e);
298 | }
299 |
300 | private static void handleFailure(CompletableCompletionStage> s, Throwable e) {
301 | s.completeExceptionally(wrapException(e));
302 | }
303 |
304 |
305 | /**
306 | * Wraps exception to a {@link java.util.concurrent.CompletionException} if needed.
307 | *
308 | * @param e exception to be wrapped
309 | * @return CompletionException
310 | */
311 | private static Throwable wrapException(Throwable e) {
312 | if (e instanceof CompletionException) {
313 | return e;
314 | } else {
315 | return new CompletionException(e);
316 | }
317 | }
318 |
319 | private void addCallbacks(Consumer super T> successCallback, Consumer failureCallback, Executor executor) {
320 | callbackRegistry.addCallbacks(successCallback, failureCallback, executor);
321 | }
322 | }
323 |
--------------------------------------------------------------------------------
/src/main/java/net/javacrumbs/completionstage/spi/CompletableCompletionStageFactory.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2009-2016 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package net.javacrumbs.completionstage.spi;
17 |
18 | import net.javacrumbs.completionstage.CompletableCompletionStage;
19 | import net.javacrumbs.completionstage.SimpleCompletionStage;
20 |
21 | /**
22 | * Factory for customizing {@link SimpleCompletionStage}.
23 | */
24 | public interface CompletableCompletionStageFactory {
25 | CompletableCompletionStage createCompletionStage();
26 | }
27 |
--------------------------------------------------------------------------------
/src/test/java/net/javacrumbs/completionstage/AbstractCompletionStageTest.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2009-2015 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package net.javacrumbs.completionstage;
17 |
18 | import org.junit.Test;
19 | import org.mockito.ArgumentMatcher;
20 |
21 | import java.util.List;
22 | import java.util.concurrent.CompletableFuture;
23 | import java.util.concurrent.CompletionException;
24 | import java.util.concurrent.CompletionStage;
25 | import java.util.concurrent.CopyOnWriteArrayList;
26 | import java.util.concurrent.CountDownLatch;
27 | import java.util.concurrent.ExecutionException;
28 | import java.util.concurrent.Executor;
29 | import java.util.function.BiConsumer;
30 | import java.util.function.BiFunction;
31 | import java.util.function.Consumer;
32 | import java.util.function.Function;
33 |
34 | import static java.lang.Thread.currentThread;
35 | import static org.assertj.core.api.Assertions.assertThat;
36 | import static org.junit.Assert.assertEquals;
37 | import static org.junit.Assert.assertNotEquals;
38 | import static org.junit.Assert.fail;
39 | import static org.mockito.Matchers.any;
40 | import static org.mockito.Matchers.anyString;
41 | import static org.mockito.Matchers.argThat;
42 | import static org.mockito.Matchers.isNull;
43 | import static org.mockito.Mockito.doThrow;
44 | import static org.mockito.Mockito.mock;
45 | import static org.mockito.Mockito.times;
46 | import static org.mockito.Mockito.verify;
47 | import static org.mockito.Mockito.verifyZeroInteractions;
48 | import static org.mockito.Mockito.when;
49 |
50 |
51 | /**
52 | * original test followingStagesShouldBeCalledInTeSameThread
53 | * callback hell
54 | * followingStagesShouldBeCalledInTeSameThread - can be executed in the main thread
55 | * Duplicity
56 | * Transformation to function
57 | * then compose
58 | * state
59 | * coverage is great
60 | * exception handling in combine (which one will handle the exception?)
61 | * CompletableFuture.doThenCombine
62 | */
63 | @SuppressWarnings("unchecked")
64 | public abstract class AbstractCompletionStageTest {
65 | protected static final String VALUE = "test";
66 | protected static final String VALUE2 = "value2";
67 | protected static final RuntimeException EXCEPTION = new RuntimeException("Test");
68 | protected static final String IN_EXECUTOR_THREAD_NAME = "in executor";
69 | protected static final String IN_DEFAULT_EXECUTOR_THREAD_NAME = "in default executor";
70 | private static final String MAIN = "main";
71 | private final Executor executor = new ThreadNamingExecutor(IN_EXECUTOR_THREAD_NAME);
72 | private final Consumer consumer = mock(Consumer.class);
73 | private final BiConsumer biConsumer = mock(BiConsumer.class);
74 | private final Consumer intConsumer = mock(Consumer.class);
75 |
76 | protected abstract CompletionStage createCompletionStage(String value);
77 |
78 | protected abstract CompletionStage createCompletionStage(Throwable e);
79 |
80 | protected abstract void finish(CompletionStage c);
81 |
82 | private final List failures = new CopyOnWriteArrayList<>();
83 |
84 | protected Executor defaultExecutor = new ThreadNamingExecutor(IN_DEFAULT_EXECUTOR_THREAD_NAME);
85 |
86 | protected boolean finished() {
87 | return true;
88 | }
89 |
90 | @Test
91 | public void acceptEitherAcceptsOnlyOneValue() {
92 | CompletionStage completionStage = createCompletionStage(VALUE);
93 | CompletionStage completionStage2 = createCompletionStage(VALUE2);
94 | finish(completionStage);
95 | finish(completionStage2);
96 |
97 | completionStage.acceptEither(completionStage2, consumer);
98 |
99 | verify(consumer, times(1)).accept(any(String.class));
100 | }
101 |
102 | @Test
103 | public void runAfterEitherCalledOnlyOnce() {
104 | CompletionStage completionStage = createCompletionStage(VALUE);
105 | CompletionStage completionStage2 = createCompletionStage(VALUE2);
106 | finish(completionStage);
107 |
108 | Runnable runnable = mock(Runnable.class);
109 | completionStage.runAfterEither(completionStage2, runnable);
110 |
111 | finish(completionStage2);
112 |
113 | verify(runnable, times(1)).run();
114 | }
115 |
116 | @Test
117 | public void acceptShouldWork() {
118 | CompletionStage completionStage = createCompletionStage(VALUE);
119 |
120 | completionStage.thenAccept(consumer);
121 |
122 | finish(completionStage);
123 |
124 | verify(consumer).accept(VALUE);
125 | }
126 |
127 | @Test
128 | public void acceptAsyncShouldBeCalledUsingExecutor() throws InterruptedException {
129 | CompletionStage completionStage = createCompletionStage(VALUE);
130 |
131 | CountDownLatch waitLatch = new CountDownLatch(1);
132 |
133 | completionStage.thenAcceptAsync(r -> {
134 | assertEquals(IN_EXECUTOR_THREAD_NAME, currentThread().getName());
135 | waitLatch.countDown();
136 | }, executor).exceptionally(errorHandler(waitLatch));
137 |
138 | finish(completionStage);
139 |
140 | waitLatch.await();
141 | assertThat(failures).isEmpty();
142 | }
143 |
144 | @Test
145 | public void followingStagesShouldBeCalledInTeSameThread() throws InterruptedException {
146 | CompletionStage completionStage = createCompletionStage(VALUE);
147 |
148 | CountDownLatch waitLatch = new CountDownLatch(1);
149 |
150 | completionStage
151 | .thenApplyAsync(r -> {
152 | assertEquals(IN_EXECUTOR_THREAD_NAME, currentThread().getName());
153 | return "a";
154 | }, executor)
155 | .thenAccept(r -> {
156 | // In fact it can be executed even in main thread depending if the previous callback finished sooner than
157 | // thenAccept is called
158 | // assertEquals(IN_EXECUTOR_THREAD_NAME, currentThread().getName());
159 | assertEquals("a", r);
160 | waitLatch.countDown();
161 | })
162 | .exceptionally(errorHandler(waitLatch));
163 |
164 | finish(completionStage);
165 |
166 | waitLatch.await();
167 | assertThat(failures).isEmpty();
168 | }
169 |
170 | @Test
171 | public void allAsyncCallShouldBeCalledInDefaultExecutor() throws InterruptedException {
172 | CompletionStage completionStage = createCompletionStage(VALUE);
173 |
174 | CountDownLatch waitLatch = new CountDownLatch(1);
175 | currentThread().setName(MAIN);
176 |
177 | completionStage
178 | .thenApplyAsync(r -> {
179 | assertNotEquals(MAIN, currentThread().getName());
180 | return "a";
181 | })
182 | .thenAcceptAsync(r -> {
183 | assertNotEquals(MAIN, currentThread().getName());
184 | assertEquals("a", r);
185 | waitLatch.countDown();
186 | })
187 | .exceptionally(errorHandler(waitLatch));
188 |
189 | finish(completionStage);
190 |
191 | waitLatch.await();
192 | assertThat(failures).isEmpty();
193 | }
194 |
195 | private Function errorHandler(CountDownLatch waitLatch) {
196 | return e -> {
197 | failures.add(e);
198 | waitLatch.countDown();
199 | return null;
200 | };
201 | }
202 |
203 | @Test
204 | public void whenCompleteShouldAcceptUnwrappedException() {
205 | CompletionStage completionStage = createCompletionStage(EXCEPTION);
206 |
207 | completionStage.whenComplete(biConsumer);
208 |
209 | finish(completionStage);
210 | verify(biConsumer, times(1)).accept(null, EXCEPTION);
211 | }
212 |
213 | @Test
214 | public void handleShouldAcceptUnwrappedException() {
215 | CompletionStage completionStage = createCompletionStage(EXCEPTION);
216 |
217 | BiFunction handler = mock(BiFunction.class);
218 | completionStage.handle(handler);
219 |
220 | finish(completionStage);
221 | verify(handler, times(1)).apply(null, EXCEPTION);
222 | }
223 |
224 | @Test
225 | public void exceptionallyShouldTranslateExceptionToAValue() {
226 | CompletionStage completionStage = createCompletionStage(EXCEPTION);
227 |
228 | Function function = mock(Function.class);
229 | when(function.apply(EXCEPTION)).thenReturn(VALUE);
230 | completionStage.exceptionally(function).thenAccept(consumer);
231 |
232 | finish(completionStage);
233 |
234 | verify(function, times(1)).apply(EXCEPTION);
235 | verify(consumer, times(1)).accept(VALUE);
236 | }
237 |
238 | @Test
239 | public void exceptionallyShouldPassValue() {
240 | CompletionStage completionStage = createCompletionStage(VALUE);
241 |
242 | Function function = mock(Function.class);
243 | when(function.apply(EXCEPTION)).thenReturn(VALUE);
244 | completionStage.exceptionally(function).thenAccept(consumer);
245 |
246 | finish(completionStage);
247 |
248 | verifyZeroInteractions(function);
249 | verify(consumer).accept(VALUE);
250 | }
251 |
252 | @Test
253 | public void exceptionFromThenApplyShouldBePassedToTheNextPhase() {
254 | CompletionStage completionStage = createCompletionStage(VALUE);
255 |
256 | Function conversion = mock(Function.class);
257 | Function errorHandler = mock(Function.class);
258 | when(errorHandler.apply(EXCEPTION)).thenReturn(null);
259 | when(conversion.apply(VALUE)).thenThrow(EXCEPTION);
260 | completionStage.thenApply(conversion).exceptionally(errorHandler);
261 |
262 | finish(completionStage);
263 |
264 | verify(errorHandler).apply(isACompletionException());
265 | verify(conversion).apply(VALUE);
266 | }
267 |
268 | @Test
269 | public void ifExceptionallyFunctionFailsItShouldBePassedFurther() {
270 | CompletionStage completionStage = createCompletionStage(EXCEPTION);
271 |
272 | Function errorHandler = mock(Function.class);
273 | when(errorHandler.apply(EXCEPTION)).thenReturn(VALUE);
274 | completionStage.exceptionally(e -> {
275 | throw EXCEPTION;
276 | }).exceptionally(errorHandler).thenAccept(consumer);
277 |
278 | finish(completionStage);
279 |
280 | verify(errorHandler).apply(isACompletionException());
281 | verify(consumer).accept(null);
282 | }
283 |
284 | @Test
285 | public void shouldCombineValues() {
286 | CompletionStage completionStage1 = createCompletionStage(VALUE);
287 | CompletionStage completionStage2 = createCompletionStage(VALUE2);
288 | finish(completionStage2);
289 |
290 | BiFunction combiner = mock(BiFunction.class);
291 | when(combiner.apply(VALUE, VALUE2)).thenReturn(5);
292 |
293 | completionStage1.thenCombine(completionStage2, combiner).thenAccept(intConsumer);
294 | finish(completionStage1);
295 |
296 | verify(combiner).apply(VALUE, VALUE2);
297 | verify(intConsumer).accept(5);
298 | }
299 |
300 | @Test
301 | public void shouldCombineValuesInOppositeOrder() {
302 | CompletionStage completionStage1 = createCompletionStage(VALUE);
303 | CompletionStage completionStage2 = createCompletionStage(VALUE2);
304 | finish(completionStage2);
305 |
306 | BiFunction combiner = mock(BiFunction.class);
307 |
308 | when(combiner.apply(VALUE2, VALUE)).thenReturn(5);
309 |
310 | completionStage2.thenCombine(completionStage1, combiner).thenAccept(intConsumer);
311 | finish(completionStage1);
312 |
313 | verify(combiner).apply(VALUE2, VALUE);
314 | verify(intConsumer).accept(5);
315 | }
316 |
317 | @Test
318 | public void combineShouldHandleExceptionCorrectly() {
319 | CompletionStage completionStage1 = createCompletionStage(VALUE);
320 | CompletionStage completionStage2 = createCompletionStage(VALUE2);
321 | finish(completionStage2);
322 |
323 |
324 | BiFunction handler = mock(BiFunction.class);
325 |
326 | completionStage1.thenCombine(completionStage2, (a, b) -> {
327 | throw EXCEPTION;
328 | }).handle(handler);
329 |
330 | finish(completionStage1);
331 |
332 | verify(handler).apply(isNull(String.class), isACompletionException());
333 | }
334 |
335 | @Test
336 | public void combineShouldHandlePreviousStageFailureCorrectly() {
337 | CompletionStage completionStage1 = createCompletionStage(EXCEPTION);
338 | CompletionStage completionStage2 = createCompletionStage(VALUE2);
339 | finish(completionStage2);
340 |
341 |
342 | BiFunction handler = mock(BiFunction.class);
343 | BiFunction combiner = mock(BiFunction.class);
344 |
345 | completionStage1.thenCombine(completionStage2, combiner).handle(handler);
346 |
347 | finish(completionStage1);
348 |
349 | verify(handler).apply(isNull(String.class), isACompletionException());
350 | verifyZeroInteractions(combiner);
351 | }
352 |
353 | @Test
354 | public void combineShouldHandleTheOtherPreviousStageFailureCorrectly() {
355 | CompletionStage completionStage1 = createCompletionStage(VALUE);
356 | CompletionStage completionStage2 = createCompletionStage(EXCEPTION);
357 | finish(completionStage2);
358 |
359 |
360 | BiFunction handler = mock(BiFunction.class);
361 | BiFunction combiner = mock(BiFunction.class);
362 |
363 | completionStage1.thenCombine(completionStage2, combiner).handle(handler);
364 |
365 | finish(completionStage1);
366 |
367 | verify(handler).apply(isNull(String.class), isACompletionException());
368 | verifyZeroInteractions(combiner);
369 | }
370 |
371 | @Test
372 | public void combineAsyncShouldExecuteFunctionInCorrectExecutor() throws ExecutionException, InterruptedException {
373 | CompletionStage completionStage1 = createCompletionStage(VALUE);
374 | CompletionStage completionStage2 = createCompletionStage(VALUE2);
375 |
376 |
377 | CompletableFuture completableFuture = completionStage1.thenCombineAsync(completionStage2, (r1, r2) -> {
378 | assertEquals(IN_EXECUTOR_THREAD_NAME, currentThread().getName());
379 | return null;
380 | }, executor).toCompletableFuture();
381 |
382 | finish(completionStage1);
383 | finish(completionStage2);
384 |
385 | completableFuture.get();
386 | }
387 |
388 | @Test
389 | public void shouldAcceptBothValues() {
390 | CompletionStage completionStage1 = createCompletionStage(VALUE);
391 | CompletionStage completionStage2 = createCompletionStage(VALUE2);
392 | finish(completionStage2);
393 |
394 | BiConsumer biConsumer = mock(BiConsumer.class);
395 |
396 | completionStage1.thenAcceptBoth(completionStage2, biConsumer);
397 | if (!finished()) {
398 | verifyZeroInteractions(biConsumer);
399 | }
400 |
401 | finish(completionStage1);
402 |
403 | verify(biConsumer).accept(VALUE, VALUE2);
404 | }
405 |
406 | @Test
407 | public void shouldRunAfterBothValues() {
408 | CompletionStage completionStage1 = createCompletionStage(VALUE);
409 | CompletionStage completionStage2 = createCompletionStage(VALUE2);
410 | finish(completionStage2);
411 |
412 | Runnable runnable = mock(Runnable.class);
413 |
414 | completionStage1.runAfterBoth(completionStage2, runnable);
415 | if (!finished()) {
416 | verifyZeroInteractions(runnable);
417 | }
418 |
419 | finish(completionStage1);
420 |
421 | verify(runnable).run();
422 | }
423 |
424 |
425 | @Test
426 | public void exceptionFromThenAcceptShouldBePassedToTheNextPhase() {
427 | CompletionStage completionStage = createCompletionStage(VALUE);
428 |
429 | Function errorHandler = mock(Function.class);
430 | when(errorHandler.apply(EXCEPTION)).thenReturn(null);
431 | doThrow(EXCEPTION).when(consumer).accept(VALUE);
432 | completionStage.thenAccept(consumer).exceptionally(errorHandler);
433 |
434 | finish(completionStage);
435 |
436 | verify(errorHandler).apply(isACompletionException());
437 | verify(consumer).accept(VALUE);
438 | }
439 |
440 | @Test
441 | public void thenApplyShouldTransformTheValue() {
442 | CompletionStage completionStage = createCompletionStage(VALUE);
443 |
444 | completionStage.thenApply(String::length).thenApply(i -> i * 2).thenAccept(intConsumer);
445 |
446 | finish(completionStage);
447 |
448 | verify(intConsumer).accept(8);
449 | }
450 |
451 | @Test
452 | public void exceptionFromTheNextPhaseShouldNotAffectPreviousPhases() {
453 | CompletionStage completionStage = createCompletionStage(VALUE);
454 | BiFunction handler = mock(BiFunction.class);
455 |
456 | completionStage.thenApply(String::length).thenApply(i -> {
457 | throw EXCEPTION;
458 | }).handle(handler);
459 | finish(completionStage);
460 |
461 | verify(handler, times(1)).apply(isNull(), isACompletionException());
462 | }
463 |
464 | @Test
465 | public void shouldNotFailOnException() {
466 | CompletionStage completionStage = createCompletionStage(EXCEPTION);
467 |
468 | Function errorFunction = mock(Function.class);
469 | completionStage.thenApply(String::length).thenApply(i -> i * 2).thenAccept(intConsumer).exceptionally(errorFunction);
470 |
471 | finish(completionStage);
472 |
473 | verifyZeroInteractions(intConsumer);
474 | verify(errorFunction, times(1)).apply(isACompletionException());
475 | }
476 |
477 | @Test
478 | public void handleShouldBeCalled() {
479 | CompletionStage completionStage = createCompletionStage(VALUE);
480 |
481 | BiFunction consumer = mock(BiFunction.class);
482 | completionStage.handle(consumer);
483 |
484 | finish(completionStage);
485 |
486 | verify(consumer).apply(VALUE, null);
487 | }
488 |
489 | @Test
490 | public void exceptionFromHandleShouldBePropagatedOnSuccess() {
491 | CompletionStage completionStage = createCompletionStage(VALUE);
492 |
493 | BiFunction consumer = (s, throwable) -> {
494 | throw EXCEPTION;
495 | };
496 |
497 | Function errorHandler = mock(Function.class);
498 | completionStage.handle(consumer).exceptionally(errorHandler);
499 |
500 | finish(completionStage);
501 |
502 | verify(errorHandler).apply(isACompletionException());
503 | }
504 |
505 | @Test
506 | public void exceptionFromHandleShouldBePropagatedOnError() {
507 | CompletionStage completionStage = createCompletionStage(EXCEPTION);
508 |
509 | BiFunction consumer = (s, throwable) -> {
510 | throw EXCEPTION;
511 | };
512 |
513 | Function errorHandler = mock(Function.class);
514 | completionStage.handle(consumer).exceptionally(errorHandler);
515 |
516 | finish(completionStage);
517 |
518 | verify(errorHandler).apply(isACompletionException());
519 | }
520 |
521 | @Test
522 | public void handleShouldNotPassException() {
523 | CompletionStage completionStage = createCompletionStage(EXCEPTION);
524 |
525 | BiFunction consumer = mock(BiFunction.class);
526 | Function errorHandler = mock(Function.class);
527 | completionStage.handle(consumer).exceptionally(errorHandler);
528 |
529 | finish(completionStage);
530 |
531 | verify(consumer).apply(null, EXCEPTION);
532 | verifyZeroInteractions(errorHandler);
533 | }
534 |
535 |
536 | @Test
537 | public void whenCompleteShouldAcceptValue() {
538 | CompletionStage completionStage = createCompletionStage(VALUE);
539 |
540 | BiConsumer consumer = mock(BiConsumer.class);
541 | completionStage.thenApply(String::length).thenApply(i -> i * 2).whenComplete(consumer).thenAccept(intConsumer);
542 |
543 | finish(completionStage);
544 |
545 | verify(consumer).accept(8, null);
546 | verify(intConsumer).accept(8);
547 | }
548 |
549 | @Test
550 | public void whenCompleteShouldPassException() {
551 | CompletionStage completionStage = createCompletionStage(EXCEPTION);
552 |
553 | BiConsumer consumer = mock(BiConsumer.class);
554 | Function errorHandler = mock(Function.class);
555 | completionStage.thenApply(String::length).thenApply(i -> i * 2).whenComplete(consumer).exceptionally(errorHandler);
556 |
557 | finish(completionStage);
558 |
559 | verify(consumer).accept((Integer) isNull(), isACompletionException());
560 | verify(errorHandler).apply(isACompletionException());
561 | }
562 |
563 | @Test
564 | public void whenCompleteShouldPassExceptionFromConsumerOnSuccess() {
565 | CompletionStage completionStage = createCompletionStage(VALUE);
566 |
567 | BiConsumer consumer = (r, e) -> {
568 | throw EXCEPTION;
569 | };
570 | Function errorHandler = mock(Function.class);
571 | completionStage.whenComplete(consumer).exceptionally(errorHandler);
572 |
573 | finish(completionStage);
574 |
575 | verify(errorHandler).apply(isACompletionException());
576 | }
577 |
578 | @Test
579 | public void whenCompleteShouldPassExceptionFromConsumerOnFailure() {
580 | CompletionStage completionStage = createCompletionStage(EXCEPTION);
581 |
582 | BiConsumer consumer = (r, e) -> {
583 | throw EXCEPTION;
584 | };
585 | Function errorHandler = mock(Function.class);
586 | completionStage.whenComplete(consumer).exceptionally(errorHandler);
587 |
588 | finish(completionStage);
589 |
590 | verify(errorHandler).apply(isACompletionException());
591 | }
592 |
593 | @Test
594 | public void thenRunShouldRun() {
595 | CompletionStage completionStage = createCompletionStage(VALUE);
596 |
597 | Runnable runnable = mock(Runnable.class);
598 | completionStage.thenRun(runnable);
599 |
600 | finish(completionStage);
601 |
602 | verify(runnable).run();
603 | }
604 |
605 | @Test
606 | public void thenComposeWaitsForTheOtherResult() {
607 | CompletionStage completionStage = createCompletionStage(VALUE);
608 | CompletionStage completionStage2 = createCompletionStage(VALUE2);
609 |
610 | completionStage.thenCompose(r -> completionStage2).thenAccept(consumer);
611 |
612 | finish(completionStage);
613 | finish(completionStage2);
614 |
615 | verify(consumer, times(1)).accept(VALUE2);
616 | }
617 |
618 | @Test
619 | public void thenComposePassesPreviousFailure() {
620 | CompletionStage completionStage = createCompletionStage(EXCEPTION);
621 | CompletionStage completionStage2 = createCompletionStage(VALUE2);
622 |
623 |
624 | BiFunction handler = mock(BiFunction.class);
625 | completionStage.thenCompose(r -> completionStage2).handle(handler);
626 |
627 | finish(completionStage);
628 | finish(completionStage2);
629 |
630 | verify(handler).apply(isNull(String.class), isACompletionException());
631 | }
632 |
633 | @Test
634 | public void thenComposePassesFailureFromTheOtherCompletionStage() {
635 | CompletionStage completionStage = createCompletionStage(VALUE);
636 | CompletionStage completionStage2 = createCompletionStage(EXCEPTION);
637 |
638 | completionStage.thenCompose(r -> completionStage2).whenComplete(biConsumer);
639 |
640 | finish(completionStage);
641 | finish(completionStage2);
642 |
643 |
644 | verify(biConsumer).accept(isNull(String.class), isACompletionException());
645 | }
646 |
647 |
648 | @Test
649 | public void toCompletableFutureShouldPassValue() throws ExecutionException, InterruptedException {
650 | CompletionStage completionStage = createCompletionStage(VALUE);
651 |
652 | CompletableFuture completableFuture = completionStage.toCompletableFuture();
653 |
654 | finish(completionStage);
655 |
656 | assertThat(completableFuture.get()).isEqualTo(VALUE);
657 | }
658 |
659 | @Test
660 | public void toCompletableFutureShouldPassException() throws ExecutionException, InterruptedException {
661 | CompletionStage completionStage = createCompletionStage(EXCEPTION);
662 |
663 | CompletableFuture completableFuture = completionStage.toCompletableFuture();
664 |
665 | finish(completionStage);
666 |
667 | try {
668 | completableFuture.get();
669 | } catch (ExecutionException e) {
670 | assertThat(e.getCause()).isSameAs(EXCEPTION);
671 | }
672 | }
673 |
674 | @Test
675 | public void thenComposeWrapsExceptionIfFunctionFails() {
676 | CompletionStage