From e648a15b691a042e4971791f3a77c20c5ec9c4c5 Mon Sep 17 00:00:00 2001 From: Kristofer Karlsson Date: Fri, 6 Nov 2015 09:45:19 +0100 Subject: [PATCH 1/9] Add more utilities for completable futures --- .../futures/CompletableFuturesExtra.java | 154 ++++++++++++++++ .../futures/CompletableFuturesExtraTest.java | 171 +++++++++++++++++- 2 files changed, 324 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/spotify/futures/CompletableFuturesExtra.java b/src/main/java/com/spotify/futures/CompletableFuturesExtra.java index b7825a2..492a950 100644 --- a/src/main/java/com/spotify/futures/CompletableFuturesExtra.java +++ b/src/main/java/com/spotify/futures/CompletableFuturesExtra.java @@ -15,9 +15,15 @@ */ package com.spotify.futures; +import com.google.common.base.Throwables; import com.google.common.util.concurrent.ListenableFuture; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.ExecutionException; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.Function; public class CompletableFuturesExtra { @@ -55,4 +61,152 @@ public static ListenableFuture toListenableFuture( } return new CompletableToListenableFutureWrapper(future); } + + /** + * Returns a new CompletableFuture that is already exceptionally completed with + * the given exception. + * + * @param throwable the exception + * @return the exceptionally completed CompletableFuture + */ + public static CompletableFuture exceptionallyCompletedFuture(Throwable throwable) { + final CompletableFuture future = new CompletableFuture(); + future.completeExceptionally(throwable); + return future; + } + + /** + * Returns a new CompletionStage that, when this stage completes + * either normally or exceptionally, is executed with this stage's + * result and exception as arguments to the supplied function. + * + *

When this stage is complete, the given function is invoked + * with the result (or {@code null} if none) and the exception (or + * {@code null} if none) of this stage as arguments, and the + * function's result is used to complete the returned stage. + * + * This differs from + * {@link java.util.concurrent.CompletionStage#handle(java.util.function.BiFunction)} + * in that the function should return a {@link java.util.concurrent.CompletionStage} rather than + * the value directly. + * + * @param fn the function to use to compute the value of the + * returned CompletionStage + * @param the function's return type + * @return the new CompletionStage + */ + public static CompletionStage handleCompose( + CompletionStage future, + final BiFunction> fn) { + + final CompletableFuture result = new CompletableFuture(); + + future.whenComplete(new BiConsumer() { + @Override + public void accept(T value, Throwable throwable) { + final CompletionStage newStage; + try { + newStage = fn.apply(value, throwable); + } catch (Throwable e) { + result.completeExceptionally(e); + return; + } + if (newStage == null) { + result.completeExceptionally(new NullPointerException("fn returned null")); + } else { + newStage.whenComplete(new BiConsumer() { + @Override + public void accept(U value, Throwable throwable) { + if (throwable != null) { + result.completeExceptionally(throwable); + } else { + result.complete(value); + } + } + }); + } + } + }); + return result; + } + + /** + * Returns a new CompletionStage that, when this stage completes + * exceptionally, is executed with this stage's exception as the + * argument to the supplied function. Otherwise, if this stage + * completes normally, then the returned stage also completes + * normally with the same value. + * + * This differs from + * {@link java.util.concurrent.CompletionStage#exceptionally(java.util.function.Function)} + * in that the function should return a {@link java.util.concurrent.CompletionStage} rather than + * the value directly. + * + * @param fn the function to use to compute the value of the + * returned CompletionStage if this CompletionStage completed + * exceptionally + * @return the new CompletionStage + */ + public static CompletionStage exceptionallyCompose( + CompletionStage future, + final Function> fn) { + return handleCompose(future, new BiFunction>() { + @Override + public CompletionStage apply(T value, Throwable throwable) { + if (throwable != null) { + return fn.apply(throwable); + } else { + return CompletableFuture.completedFuture(value); + } + } + }); + } + + /** + * This takes a stage of a stage of a value and returns a plain future of a value. + * + * @param future of a future of a value + * @return the completion stage of a value + */ + public static CompletionStage dereference( + CompletionStage> future) { + return handleCompose(future, + new BiFunction, Throwable, CompletionStage>() { + @Override + public CompletionStage apply(CompletionStage value, Throwable throwable) { + return value; + } + }); + } + + /** + * check that a stage is completed. + * @param stage the stage. + * @throws IllegalStateException if the stage is not completed. + */ + public static void checkCompleted(CompletionStage stage) { + if (!stage.toCompletableFuture().isDone()) { + throw new IllegalStateException("future was not completed"); + } + } + + /** + * Get the value of a completed stage. + * + * @param stage a completed stage. + * @return the value of the stage if it has one. + * @throws IllegalStateException if the stage is not completed. + * @throws com.google.common.util.concurrent.UncheckedExecutionException if the future has failed + */ + public static T getCompleted(CompletionStage stage) { + CompletableFuture future = stage.toCompletableFuture(); + checkCompleted(future); + try { + return future.get(); + } catch (InterruptedException e) { + throw Throwables.propagate(e); + } catch (ExecutionException e) { + throw Throwables.propagate(e.getCause()); + } + } } diff --git a/src/test/java/com/spotify/futures/CompletableFuturesExtraTest.java b/src/test/java/com/spotify/futures/CompletableFuturesExtraTest.java index f879cbb..d71ba15 100644 --- a/src/test/java/com/spotify/futures/CompletableFuturesExtraTest.java +++ b/src/test/java/com/spotify/futures/CompletableFuturesExtraTest.java @@ -4,7 +4,6 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.SettableFuture; - import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -14,14 +13,19 @@ import org.mockito.runners.MockitoJUnitRunner; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; import java.util.concurrent.ExecutionException; import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.Function; import static com.spotify.futures.CompletableFuturesExtra.toCompletableFuture; import static com.spotify.futures.CompletableFuturesExtra.toListenableFuture; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.sameInstance; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; import static org.junit.Assume.assumeThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -119,4 +123,169 @@ private static boolean hasCompletableFuture() { return false; } } + + @Test(expected = IllegalArgumentException.class) + public void testImmediateFailed() throws Exception { + final CompletionStage future = CompletableFuturesExtra.exceptionallyCompletedFuture(new IllegalArgumentException()); + CompletableFuturesExtra.getCompleted(future); + fail(); + } + + @Test + public void testGetCompleted() throws Exception { + final CompletionStage future = CompletableFuture.completedFuture("hello"); + assertEquals("hello", CompletableFuturesExtra.getCompleted(future)); + } + + @Test(expected = IllegalArgumentException.class) + public void testDereferenceFailure() throws Exception { + final CompletionStage future = CompletableFuturesExtra.exceptionallyCompletedFuture(new IllegalArgumentException()); + final CompletionStage> future2 = CompletableFuture.completedFuture(future); + final CompletionStage dereferenced = CompletableFuturesExtra.dereference(future2); + CompletableFuturesExtra.getCompleted(dereferenced.toCompletableFuture()); + fail(); + } + + @Test(expected = NullPointerException.class) + public void testDereferenceNull() throws Exception { + final CompletionStage> future2 = CompletableFuture.completedFuture(null); + final CompletionStage dereferenced = CompletableFuturesExtra.dereference(future2); + CompletableFuturesExtra.getCompleted(dereferenced); + fail(); + } + + @Test + public void testDereferenceSuccess() throws Exception { + final CompletionStage future = CompletableFuture.completedFuture("hello"); + final CompletionStage> future2 = CompletableFuture.completedFuture(future); + final CompletionStage dereferenced = CompletableFuturesExtra.dereference(future2); + assertEquals("hello", CompletableFuturesExtra.getCompleted(dereferenced)); + } + + @Test + public void testExceptionallyCompose() throws Exception { + final CompletionStage future = CompletableFuturesExtra.exceptionallyCompletedFuture(new IllegalArgumentException()); + + final CompletionStage composed = CompletableFuturesExtra.exceptionallyCompose(future, new Function>() { + @Override + public CompletionStage apply(Throwable throwable) { + return CompletableFuture.completedFuture("hello"); + } + }); + + assertEquals("hello", CompletableFuturesExtra.getCompleted(composed)); + + } + + @Test(expected = IllegalStateException.class) + public void testExceptionallyComposeFailure() throws Exception { + final CompletionStage future = CompletableFuturesExtra.exceptionallyCompletedFuture(new IllegalArgumentException()); + + final CompletionStage composed = CompletableFuturesExtra.exceptionallyCompose(future, new Function>() { + @Override + public CompletionStage apply(Throwable throwable) { + return CompletableFuturesExtra.exceptionallyCompletedFuture(new IllegalStateException()); + } + }); + CompletableFuturesExtra.getCompleted(composed); + fail(); + } + + @Test + public void testExceptionallyComposeUnused() throws Exception { + final CompletionStage future = CompletableFuture.completedFuture("hello"); + + final CompletionStage composed = CompletableFuturesExtra.exceptionallyCompose(future, new Function>() { + @Override + public CompletionStage apply(Throwable throwable) { + return CompletableFuturesExtra.exceptionallyCompletedFuture(new IllegalStateException()); + } + }); + assertEquals("hello", CompletableFuturesExtra.getCompleted(composed)); + } + + @Test(expected = IllegalStateException.class) + public void testExceptionallyComposeThrows() throws Exception { + final CompletionStage future = CompletableFuturesExtra.exceptionallyCompletedFuture(new IllegalArgumentException()); + + final CompletionStage composed = CompletableFuturesExtra.exceptionallyCompose(future, new Function>() { + @Override + public CompletionStage apply(Throwable throwable) { + throw new IllegalStateException(); + } + }); + CompletableFuturesExtra.getCompleted(composed); + fail(); + } + + @Test(expected = NullPointerException.class) + public void testExceptionallyComposeReturnsNull() throws Exception { + final CompletionStage future = CompletableFuturesExtra.exceptionallyCompletedFuture(new IllegalArgumentException()); + + final CompletionStage composed = CompletableFuturesExtra.exceptionallyCompose(future, new Function>() { + @Override + public CompletionStage apply(Throwable throwable) { + return null; + } + }); + CompletableFuturesExtra.getCompleted(composed); + fail(); + } + + @Test + public void testHandleCompose() throws Exception { + final CompletionStage future = CompletableFuturesExtra.exceptionallyCompletedFuture(new IllegalArgumentException()); + + final CompletionStage composed = CompletableFuturesExtra.handleCompose(future, new BiFunction>() { + @Override + public CompletionStage apply(String s, Throwable throwable) { + return CompletableFuture.completedFuture("hello"); + } + }); + + assertEquals("hello", CompletableFuturesExtra.getCompleted(composed)); + + } + + @Test(expected = IllegalStateException.class) + public void testHandleComposeFailure() throws Exception { + final CompletionStage future = CompletableFuturesExtra.exceptionallyCompletedFuture(new IllegalArgumentException()); + + final CompletionStage composed = CompletableFuturesExtra.handleCompose(future, new BiFunction>() { + @Override + public CompletionStage apply(String s, Throwable throwable) { + return CompletableFuturesExtra.exceptionallyCompletedFuture(new IllegalStateException()); + } + }); + CompletableFuturesExtra.getCompleted(composed); + fail(); + } + + @Test(expected = IllegalStateException.class) + public void testHandleComposeThrows() throws Exception { + final CompletionStage future = CompletableFuturesExtra.exceptionallyCompletedFuture(new IllegalArgumentException()); + + final CompletionStage composed = CompletableFuturesExtra.handleCompose(future, new BiFunction>() { + @Override + public CompletionStage apply(String s, Throwable throwable) { + throw new IllegalStateException(); + } + }); + CompletableFuturesExtra.getCompleted(composed); + fail(); + } + + @Test(expected = NullPointerException.class) + public void testHandleComposeReturnsNull() throws Exception { + final CompletionStage future = CompletableFuturesExtra.exceptionallyCompletedFuture(new IllegalArgumentException()); + + final CompletionStage composed = CompletableFuturesExtra.handleCompose(future, new BiFunction>() { + @Override + public CompletionStage apply(String s, Throwable throwable) { + return null; + } + }); + CompletableFuturesExtra.getCompleted(composed); + fail(); + } } From e88cf3b8e63457f6cbb3b04872c628e93a791c3d Mon Sep 17 00:00:00 2001 From: Kristofer Karlsson Date: Fri, 6 Nov 2015 11:55:16 +0100 Subject: [PATCH 2/9] Set junit includes --- pom.xml | 18 +++++++++-- .../futures/{ => jdk7}/AsyncRetrierTest.java | 3 +- .../futures/{ => jdk7}/FuturesExtraTest.java | 4 ++- .../futures/{ => jdk7}/JoinedResultsTest.java | 14 ++++----- .../futures/{ => jdk7}/TimeoutFutureTest.java | 3 +- .../CompletableFuturesExtraTest.java | 31 ++++++------------- 6 files changed, 39 insertions(+), 34 deletions(-) rename src/test/java/com/spotify/futures/{ => jdk7}/AsyncRetrierTest.java (98%) rename src/test/java/com/spotify/futures/{ => jdk7}/FuturesExtraTest.java (99%) rename src/test/java/com/spotify/futures/{ => jdk7}/JoinedResultsTest.java (95%) rename src/test/java/com/spotify/futures/{ => jdk7}/TimeoutFutureTest.java (97%) rename src/test/java/com/spotify/futures/{ => jdk8}/CompletableFuturesExtraTest.java (95%) diff --git a/pom.xml b/pom.xml index 38f2016..3025337 100644 --- a/pom.xml +++ b/pom.xml @@ -16,18 +16,20 @@ jdk7 - [1.6,1.7) + [1.6,1.8) + **/jdk7/*Test.class jdk8 - 1.8 + [1.8,) + **/*Test.class -Xdoclint:none @@ -95,7 +97,7 @@ junit junit - 4.11 + 4.12 test @@ -161,6 +163,16 @@ + + org.apache.maven.plugins + maven-surefire-plugin + 2.19 + + + ${junitincludes} + + + org.apache.maven.plugins maven-javadoc-plugin diff --git a/src/test/java/com/spotify/futures/AsyncRetrierTest.java b/src/test/java/com/spotify/futures/jdk7/AsyncRetrierTest.java similarity index 98% rename from src/test/java/com/spotify/futures/AsyncRetrierTest.java rename to src/test/java/com/spotify/futures/jdk7/AsyncRetrierTest.java index e870c74..138c80d 100644 --- a/src/test/java/com/spotify/futures/AsyncRetrierTest.java +++ b/src/test/java/com/spotify/futures/jdk7/AsyncRetrierTest.java @@ -14,11 +14,12 @@ * the License. */ -package com.spotify.futures; +package com.spotify.futures.jdk7; import com.google.common.base.Predicate; import com.google.common.base.Supplier; import com.google.common.util.concurrent.ListenableFuture; +import com.spotify.futures.AsyncRetrier; import org.jmock.lib.concurrent.DeterministicScheduler; import org.junit.Before; import org.junit.Test; diff --git a/src/test/java/com/spotify/futures/FuturesExtraTest.java b/src/test/java/com/spotify/futures/jdk7/FuturesExtraTest.java similarity index 99% rename from src/test/java/com/spotify/futures/FuturesExtraTest.java rename to src/test/java/com/spotify/futures/jdk7/FuturesExtraTest.java index 05c87c9..5ff447a 100644 --- a/src/test/java/com/spotify/futures/FuturesExtraTest.java +++ b/src/test/java/com/spotify/futures/jdk7/FuturesExtraTest.java @@ -13,7 +13,7 @@ * License for the specific language governing permissions and limitations under * the License. */ -package com.spotify.futures; +package com.spotify.futures.jdk7; import com.google.common.base.Function; import com.google.common.collect.Lists; @@ -22,7 +22,9 @@ import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.SettableFuture; import com.google.common.util.concurrent.UncheckedExecutionException; +import com.spotify.futures.FuturesExtra; import com.spotify.futures.FuturesExtra.Consumer; +import com.spotify.futures.Validator; import org.junit.Test; import java.util.Arrays; diff --git a/src/test/java/com/spotify/futures/JoinedResultsTest.java b/src/test/java/com/spotify/futures/jdk7/JoinedResultsTest.java similarity index 95% rename from src/test/java/com/spotify/futures/JoinedResultsTest.java rename to src/test/java/com/spotify/futures/jdk7/JoinedResultsTest.java index 99761c3..3a837cb 100644 --- a/src/test/java/com/spotify/futures/JoinedResultsTest.java +++ b/src/test/java/com/spotify/futures/jdk7/JoinedResultsTest.java @@ -13,19 +13,19 @@ * License for the specific language governing permissions and limitations under * the License. */ -package com.spotify.futures; +package com.spotify.futures.jdk7; -import static org.junit.Assert.assertEquals; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.spotify.futures.FuturesExtra; +import com.spotify.futures.JoinedResults; +import org.junit.Test; import java.util.Arrays; import java.util.Collections; import java.util.List; -import com.google.common.collect.Lists; -import org.junit.Test; - -import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; +import static org.junit.Assert.assertEquals; public class JoinedResultsTest { @Test diff --git a/src/test/java/com/spotify/futures/TimeoutFutureTest.java b/src/test/java/com/spotify/futures/jdk7/TimeoutFutureTest.java similarity index 97% rename from src/test/java/com/spotify/futures/TimeoutFutureTest.java rename to src/test/java/com/spotify/futures/jdk7/TimeoutFutureTest.java index efeb4ef..fe334e4 100644 --- a/src/test/java/com/spotify/futures/TimeoutFutureTest.java +++ b/src/test/java/com/spotify/futures/jdk7/TimeoutFutureTest.java @@ -13,10 +13,11 @@ * License for the specific language governing permissions and limitations under * the License. */ -package com.spotify.futures; +package com.spotify.futures.jdk7; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.SettableFuture; +import com.spotify.futures.FuturesExtra; import org.jmock.lib.concurrent.DeterministicScheduler; import org.junit.Before; import org.junit.Test; diff --git a/src/test/java/com/spotify/futures/CompletableFuturesExtraTest.java b/src/test/java/com/spotify/futures/jdk8/CompletableFuturesExtraTest.java similarity index 95% rename from src/test/java/com/spotify/futures/CompletableFuturesExtraTest.java rename to src/test/java/com/spotify/futures/jdk8/CompletableFuturesExtraTest.java index d71ba15..b679a2f 100644 --- a/src/test/java/com/spotify/futures/CompletableFuturesExtraTest.java +++ b/src/test/java/com/spotify/futures/jdk8/CompletableFuturesExtraTest.java @@ -1,16 +1,15 @@ -package com.spotify.futures; +package com.spotify.futures.jdk8; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.SettableFuture; +import com.spotify.futures.CompletableFuturesExtra; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.Mockito; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; @@ -26,26 +25,25 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; -import static org.junit.Assume.assumeThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; -@RunWith(MockitoJUnitRunner.class) public class CompletableFuturesExtraTest { @Rule public ExpectedException exception = ExpectedException.none(); - @Mock FutureCallback callback; - - private final SettableFuture settable = SettableFuture.create(); - private final ListenableFuture listenable = settable; + FutureCallback callback; @Before - public void setup() { - assumeThat(hasCompletableFuture(), is(true)); + public void setUp() throws Exception { + callback = Mockito.mock(FutureCallback.class); + } + private final SettableFuture settable = SettableFuture.create(); + private final ListenableFuture listenable = settable; + @Test public void testToCompletableFutureUnwrap() { final CompletableFuture wrapped = toCompletableFuture(listenable); @@ -115,15 +113,6 @@ public void testToListenableFutureFailure() throws ExecutionException, Interrupt wrapped.get(); } - private static boolean hasCompletableFuture() { - try { - Class.forName("java.util.concurrent.CompletableFuture"); - return true; - } catch (ClassNotFoundException e) { - return false; - } - } - @Test(expected = IllegalArgumentException.class) public void testImmediateFailed() throws Exception { final CompletionStage future = CompletableFuturesExtra.exceptionallyCompletedFuture(new IllegalArgumentException()); From 130302f6555447647cf58e064827ab381d484e23 Mon Sep 17 00:00:00 2001 From: Kristofer Karlsson Date: Fri, 6 Nov 2015 13:26:16 +0100 Subject: [PATCH 3/9] Fix missed coverage --- .../com/spotify/futures/CompletableFuturesExtra.java | 12 ++++++------ .../futures/jdk8/CompletableFuturesExtraTest.java | 7 +++++++ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/spotify/futures/CompletableFuturesExtra.java b/src/main/java/com/spotify/futures/CompletableFuturesExtra.java index 492a950..4452b1d 100644 --- a/src/main/java/com/spotify/futures/CompletableFuturesExtra.java +++ b/src/main/java/com/spotify/futures/CompletableFuturesExtra.java @@ -19,8 +19,8 @@ import com.google.common.util.concurrent.ListenableFuture; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; import java.util.concurrent.CompletionStage; -import java.util.concurrent.ExecutionException; import java.util.function.BiConsumer; import java.util.function.BiFunction; import java.util.function.Function; @@ -196,16 +196,16 @@ public static void checkCompleted(CompletionStage stage) { * @param stage a completed stage. * @return the value of the stage if it has one. * @throws IllegalStateException if the stage is not completed. - * @throws com.google.common.util.concurrent.UncheckedExecutionException if the future has failed + * @throws com.google.common.util.concurrent.UncheckedExecutionException + * if the future has failed with a non-runtime exception, otherwise + * the actual exception */ public static T getCompleted(CompletionStage stage) { CompletableFuture future = stage.toCompletableFuture(); checkCompleted(future); try { - return future.get(); - } catch (InterruptedException e) { - throw Throwables.propagate(e); - } catch (ExecutionException e) { + return future.join(); + } catch (CompletionException e) { throw Throwables.propagate(e.getCause()); } } diff --git a/src/test/java/com/spotify/futures/jdk8/CompletableFuturesExtraTest.java b/src/test/java/com/spotify/futures/jdk8/CompletableFuturesExtraTest.java index b679a2f..f3f51a5 100644 --- a/src/test/java/com/spotify/futures/jdk8/CompletableFuturesExtraTest.java +++ b/src/test/java/com/spotify/futures/jdk8/CompletableFuturesExtraTest.java @@ -126,6 +126,13 @@ public void testGetCompleted() throws Exception { assertEquals("hello", CompletableFuturesExtra.getCompleted(future)); } + @Test(expected = IllegalStateException.class) + public void testGetCompletedFails() throws Exception { + final CompletionStage future = new CompletableFuture(); + CompletableFuturesExtra.getCompleted(future); + fail(); + } + @Test(expected = IllegalArgumentException.class) public void testDereferenceFailure() throws Exception { final CompletionStage future = CompletableFuturesExtra.exceptionallyCompletedFuture(new IllegalArgumentException()); From d431d4178bd7292bb87e00407b6308c91f92dbff Mon Sep 17 00:00:00 2001 From: Kristofer Karlsson Date: Fri, 6 Nov 2015 13:29:38 +0100 Subject: [PATCH 4/9] Fix generic wildcard bug --- src/main/java/com/spotify/futures/CompletableFuturesExtra.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/spotify/futures/CompletableFuturesExtra.java b/src/main/java/com/spotify/futures/CompletableFuturesExtra.java index 4452b1d..d5021fd 100644 --- a/src/main/java/com/spotify/futures/CompletableFuturesExtra.java +++ b/src/main/java/com/spotify/futures/CompletableFuturesExtra.java @@ -97,7 +97,7 @@ public static CompletableFuture exceptionallyCompletedFuture(Throwable th */ public static CompletionStage handleCompose( CompletionStage future, - final BiFunction> fn) { + final BiFunction> fn) { final CompletableFuture result = new CompletableFuture(); From 7cfb7d53c3a0085d478d0eb1a3b99f1a8f75f5d5 Mon Sep 17 00:00:00 2001 From: Kristofer Karlsson Date: Fri, 6 Nov 2015 14:05:52 +0100 Subject: [PATCH 5/9] More generics fixes --- .../java/com/spotify/futures/CompletableFuturesExtra.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/spotify/futures/CompletableFuturesExtra.java b/src/main/java/com/spotify/futures/CompletableFuturesExtra.java index d5021fd..42e7094 100644 --- a/src/main/java/com/spotify/futures/CompletableFuturesExtra.java +++ b/src/main/java/com/spotify/futures/CompletableFuturesExtra.java @@ -149,7 +149,7 @@ public void accept(U value, Throwable throwable) { */ public static CompletionStage exceptionallyCompose( CompletionStage future, - final Function> fn) { + final Function> fn) { return handleCompose(future, new BiFunction>() { @Override public CompletionStage apply(T value, Throwable throwable) { @@ -169,11 +169,11 @@ public CompletionStage apply(T value, Throwable throwable) { * @return the completion stage of a value */ public static CompletionStage dereference( - CompletionStage> future) { + CompletionStage> future) { return handleCompose(future, - new BiFunction, Throwable, CompletionStage>() { + new BiFunction, Throwable, CompletionStage>() { @Override - public CompletionStage apply(CompletionStage value, Throwable throwable) { + public CompletionStage apply(CompletionStage value, Throwable throwable) { return value; } }); From 3e30cc3f94efdf19c18907b8afe579ef59f3ee19 Mon Sep 17 00:00:00 2001 From: Kristofer Karlsson Date: Fri, 6 Nov 2015 14:14:50 +0100 Subject: [PATCH 6/9] Fix checkstyle violation --- src/main/java/com/spotify/futures/CompletableFuturesExtra.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/spotify/futures/CompletableFuturesExtra.java b/src/main/java/com/spotify/futures/CompletableFuturesExtra.java index 42e7094..398405f 100644 --- a/src/main/java/com/spotify/futures/CompletableFuturesExtra.java +++ b/src/main/java/com/spotify/futures/CompletableFuturesExtra.java @@ -173,7 +173,8 @@ public static CompletionStage dereference( return handleCompose(future, new BiFunction, Throwable, CompletionStage>() { @Override - public CompletionStage apply(CompletionStage value, Throwable throwable) { + public CompletionStage apply( + CompletionStage value, Throwable throwable) { return value; } }); From ab6d452571bdf6f4d6a115ee6fdb38ded0d6eb09 Mon Sep 17 00:00:00 2001 From: Kristofer Karlsson Date: Fri, 6 Nov 2015 14:30:24 +0100 Subject: [PATCH 7/9] More generics fixes --- .../futures/CompletableFuturesExtra.java | 18 +++++++++--------- .../jdk8/CompletableFuturesExtraTest.java | 16 ++++++++-------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/main/java/com/spotify/futures/CompletableFuturesExtra.java b/src/main/java/com/spotify/futures/CompletableFuturesExtra.java index 398405f..c93b17e 100644 --- a/src/main/java/com/spotify/futures/CompletableFuturesExtra.java +++ b/src/main/java/com/spotify/futures/CompletableFuturesExtra.java @@ -97,14 +97,14 @@ public static CompletableFuture exceptionallyCompletedFuture(Throwable th */ public static CompletionStage handleCompose( CompletionStage future, - final BiFunction> fn) { + final BiFunction> fn) { final CompletableFuture result = new CompletableFuture(); future.whenComplete(new BiConsumer() { @Override public void accept(T value, Throwable throwable) { - final CompletionStage newStage; + final CompletionStage newStage; try { newStage = fn.apply(value, throwable); } catch (Throwable e) { @@ -149,10 +149,10 @@ public void accept(U value, Throwable throwable) { */ public static CompletionStage exceptionallyCompose( CompletionStage future, - final Function> fn) { - return handleCompose(future, new BiFunction>() { + final Function> fn) { + return handleCompose(future, new BiFunction>() { @Override - public CompletionStage apply(T value, Throwable throwable) { + public CompletionStage apply(T value, Throwable throwable) { if (throwable != null) { return fn.apply(throwable); } else { @@ -169,12 +169,12 @@ public CompletionStage apply(T value, Throwable throwable) { * @return the completion stage of a value */ public static CompletionStage dereference( - CompletionStage> future) { + CompletionStage> future) { return handleCompose(future, - new BiFunction, Throwable, CompletionStage>() { + new BiFunction, Throwable, CompletionStage>() { @Override - public CompletionStage apply( - CompletionStage value, Throwable throwable) { + public CompletionStage apply( + CompletionStage value, Throwable throwable) { return value; } }); diff --git a/src/test/java/com/spotify/futures/jdk8/CompletableFuturesExtraTest.java b/src/test/java/com/spotify/futures/jdk8/CompletableFuturesExtraTest.java index f3f51a5..1471bcb 100644 --- a/src/test/java/com/spotify/futures/jdk8/CompletableFuturesExtraTest.java +++ b/src/test/java/com/spotify/futures/jdk8/CompletableFuturesExtraTest.java @@ -232,9 +232,9 @@ public CompletionStage apply(Throwable throwable) { public void testHandleCompose() throws Exception { final CompletionStage future = CompletableFuturesExtra.exceptionallyCompletedFuture(new IllegalArgumentException()); - final CompletionStage composed = CompletableFuturesExtra.handleCompose(future, new BiFunction>() { + final CompletionStage composed = CompletableFuturesExtra.handleCompose(future, new BiFunction>() { @Override - public CompletionStage apply(String s, Throwable throwable) { + public CompletionStage apply(String s, Throwable throwable) { return CompletableFuture.completedFuture("hello"); } }); @@ -247,9 +247,9 @@ public CompletionStage apply(String s, Throwable throwable) { public void testHandleComposeFailure() throws Exception { final CompletionStage future = CompletableFuturesExtra.exceptionallyCompletedFuture(new IllegalArgumentException()); - final CompletionStage composed = CompletableFuturesExtra.handleCompose(future, new BiFunction>() { + final CompletionStage composed = CompletableFuturesExtra.handleCompose(future, new BiFunction>() { @Override - public CompletionStage apply(String s, Throwable throwable) { + public CompletionStage apply(String s, Throwable throwable) { return CompletableFuturesExtra.exceptionallyCompletedFuture(new IllegalStateException()); } }); @@ -261,9 +261,9 @@ public CompletionStage apply(String s, Throwable throwable) { public void testHandleComposeThrows() throws Exception { final CompletionStage future = CompletableFuturesExtra.exceptionallyCompletedFuture(new IllegalArgumentException()); - final CompletionStage composed = CompletableFuturesExtra.handleCompose(future, new BiFunction>() { + final CompletionStage composed = CompletableFuturesExtra.handleCompose(future, new BiFunction>() { @Override - public CompletionStage apply(String s, Throwable throwable) { + public CompletionStage apply(String s, Throwable throwable) { throw new IllegalStateException(); } }); @@ -275,9 +275,9 @@ public CompletionStage apply(String s, Throwable throwable) { public void testHandleComposeReturnsNull() throws Exception { final CompletionStage future = CompletableFuturesExtra.exceptionallyCompletedFuture(new IllegalArgumentException()); - final CompletionStage composed = CompletableFuturesExtra.handleCompose(future, new BiFunction>() { + final CompletionStage composed = CompletableFuturesExtra.handleCompose(future, new BiFunction>() { @Override - public CompletionStage apply(String s, Throwable throwable) { + public CompletionStage apply(String s, Throwable throwable) { return null; } }); From 2febcd54fda6f6f3e239c93a1ddee41259bfe98b Mon Sep 17 00:00:00 2001 From: Kristofer Karlsson Date: Fri, 6 Nov 2015 14:38:30 +0100 Subject: [PATCH 8/9] More generics fixes --- .../com/spotify/futures/CompletableFuturesExtra.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/spotify/futures/CompletableFuturesExtra.java b/src/main/java/com/spotify/futures/CompletableFuturesExtra.java index c93b17e..6270a4d 100644 --- a/src/main/java/com/spotify/futures/CompletableFuturesExtra.java +++ b/src/main/java/com/spotify/futures/CompletableFuturesExtra.java @@ -147,16 +147,16 @@ public void accept(U value, Throwable throwable) { * exceptionally * @return the new CompletionStage */ - public static CompletionStage exceptionallyCompose( + public static CompletionStage exceptionallyCompose( CompletionStage future, - final Function> fn) { - return handleCompose(future, new BiFunction>() { + final Function> fn) { + return handleCompose(future, new BiFunction>() { @Override - public CompletionStage apply(T value, Throwable throwable) { + public CompletionStage apply(T value, Throwable throwable) { if (throwable != null) { return fn.apply(throwable); } else { - return CompletableFuture.completedFuture(value); + return CompletableFuture.completedFuture(value); } } }); From 20b8d21178fde157a3c0aa47cdeac2ae6b870cee Mon Sep 17 00:00:00 2001 From: Kristofer Karlsson Date: Fri, 6 Nov 2015 15:24:20 +0100 Subject: [PATCH 9/9] Reimplemented futures with better primitives --- .../futures/CompletableFuturesExtra.java | 80 +++++++------------ 1 file changed, 29 insertions(+), 51 deletions(-) diff --git a/src/main/java/com/spotify/futures/CompletableFuturesExtra.java b/src/main/java/com/spotify/futures/CompletableFuturesExtra.java index 6270a4d..c2cf411 100644 --- a/src/main/java/com/spotify/futures/CompletableFuturesExtra.java +++ b/src/main/java/com/spotify/futures/CompletableFuturesExtra.java @@ -21,7 +21,6 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; import java.util.concurrent.CompletionStage; -import java.util.function.BiConsumer; import java.util.function.BiFunction; import java.util.function.Function; @@ -98,36 +97,7 @@ public static CompletableFuture exceptionallyCompletedFuture(Throwable th public static CompletionStage handleCompose( CompletionStage future, final BiFunction> fn) { - - final CompletableFuture result = new CompletableFuture(); - - future.whenComplete(new BiConsumer() { - @Override - public void accept(T value, Throwable throwable) { - final CompletionStage newStage; - try { - newStage = fn.apply(value, throwable); - } catch (Throwable e) { - result.completeExceptionally(e); - return; - } - if (newStage == null) { - result.completeExceptionally(new NullPointerException("fn returned null")); - } else { - newStage.whenComplete(new BiConsumer() { - @Override - public void accept(U value, Throwable throwable) { - if (throwable != null) { - result.completeExceptionally(throwable); - } else { - result.complete(value); - } - } - }); - } - } - }); - return result; + return dereference(future.handle(fn)); } /** @@ -147,19 +117,10 @@ public void accept(U value, Throwable throwable) { * exceptionally * @return the new CompletionStage */ - public static CompletionStage exceptionallyCompose( + public static CompletionStage exceptionallyCompose( CompletionStage future, - final Function> fn) { - return handleCompose(future, new BiFunction>() { - @Override - public CompletionStage apply(T value, Throwable throwable) { - if (throwable != null) { - return fn.apply(throwable); - } else { - return CompletableFuture.completedFuture(value); - } - } - }); + final Function> fn) { + return dereference(wrap(future).exceptionally(fn)); } /** @@ -170,14 +131,8 @@ public CompletionStage apply(T value, Throwable throwable) { */ public static CompletionStage dereference( CompletionStage> future) { - return handleCompose(future, - new BiFunction, Throwable, CompletionStage>() { - @Override - public CompletionStage apply( - CompletionStage value, Throwable throwable) { - return value; - } - }); + //noinspection unchecked + return future.thenCompose(Identity.INSTANCE); } /** @@ -210,4 +165,27 @@ public static T getCompleted(CompletionStage stage) { throw Throwables.propagate(e.getCause()); } } + + private enum Identity implements Function { + INSTANCE; + + @Override + public Object apply(Object o) { + return o; + } + } + + private enum WrapFunction implements Function { + INSTANCE; + + @Override + public Object apply(Object o) { + return CompletableFuture.completedFuture(o); + } + } + + private static CompletionStage> wrap(CompletionStage future) { + //noinspection unchecked + return future.thenApply((Function>) WrapFunction.INSTANCE); + } }