Skip to content

Instantly share code, notes, and snippets.

@brcolow
Last active January 23, 2018 03:40
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save brcolow/8f7107825c14bfe6723040695412d5f2 to your computer and use it in GitHub Desktop.
Save brcolow/8f7107825c14bfe6723040695412d5f2 to your computer and use it in GitHub Desktop.

Notice that in order to use the whenComplete wrapper, the generic types must be explicitly specified thusly:

Errors.<Boolean, Boolean>whenComplete(..).wrap(

Is there a way to alter the method signatures of "whenComplete" or "wrap" so that this explicitness is not required?

import java.util.concurrent.CompletableFuture;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
public abstract class Errors implements Consumer<Throwable> {
public static <T, V> Completing<T, V> whenComplete(CompletableFuture<T> completableFuture) {
return new Completing(completableFuture);
}
public static class Completing<T, V> extends Errors {
private final CompletableFuture<T> completableFuture;
protected Completing(CompletableFuture<T> completableFuture) {
super(error -> completableFuture.completeExceptionally(error));
this.completableFuture = completableFuture;
}
/**
* Returns a BiConsumer that completes the {@code completableFuture} exceptionally
* when either the given {@code BiConsumer}'s error is non-null or the {@code BiConsumer}
* itself throws an exception. This method only works with {@code BiConsumer}s that have
* a {@link Throwable} as their second generic type argument). This is useful when wrapping
* a lambda argument for {@link java.util.concurrent.CompletableFuture#whenComplete(BiConsumer)}.
* For example:
* <pre>{@code
*
* String process(Result result) throws CheckedException {
* // Process result, throwing an exception if it fails
* }
*
* CompletableFuture<String> httpRequest = Http.get("http://something.com/");
* CompletableFuture<String> wrappedFuture = Errors.whenComplete(someFuture).wrap((result, error) -> {
* // No need to deal with error, as if it's non-null "wrappedFuture" will be completed
* // exceptionally with it.
*
* // No need to surround this call with a try-catch even though it is declared
* // to throw a checked exception - "someFuture" will be completed exceptionally
* // with the exception thrown by calling "process", if any.
*
* if (error != null) {
* // Here we can inspect the error that was thrown during execution of someFuture and,
* // if "propagateException" is false, "swallow" it and still return a result or throw
* // a new/different exception.
* if (error instanceof HttpStatusException) {
* if (((HttpStatusException) error).getStatusCode() == 301) {
* // etc.
* }
* }
* }
* return process(result);
* });
* }</pre>
*/
public CompletableFuture<V> wrap(boolean propagateException, Throwing.BiFunction<T, Throwable, V> biFunction) {
CompletableFuture<V> completableFuture = new CompletableFuture<>();
this.completableFuture.whenComplete((val, error) -> {
if (error != null && propagateException) {
completableFuture.completeExceptionally(error);
return;
}
try {
completableFuture.complete(biFunction.apply(val, error));
}
catch (Throwable e) {
completableFuture.completeExceptionally(e);
}
});
return completableFuture;
}
}
}
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import org.junit.Test;
public class ErrorsTest {
@Test
public void wrappingWhenCompleteShouldPropagateExceptions() throws Exception {
String randomExceptionMessage = "Failed: " + ThreadLocalRandom.current().nextInt(500);
// Given
CompletableFuture<Boolean> completableFuture = new CompletableFuture<>();
// When (a future completes with an exception)
completableFuture.completeExceptionally(new IllegalArgumentException(randomExceptionMessage));
// Then (the error of the original exception is propagated to the wrapped future)
CompletableFuture<Boolean> wrappedFuture = Errors.<Boolean, Boolean>whenComplete(completableFuture).wrap(
(result, error) -> false);
assertThatThrownBy(() -> wrappedFuture.get(5, TimeUnit.SECONDS))
.hasCauseExactlyInstanceOf(IllegalArgumentException.class)
.hasMessageContaining(randomExceptionMessage);
}
@Test
public void wrappingWhenCompleteShouldNotPropagateExceptionsWhenPropagateIsFalse() throws Exception {
String randomExceptionMessage = "Failed: " + ThreadLocalRandom.current().nextInt(500);
// Given
CompletableFuture<Boolean> completableFuture = new CompletableFuture<>();
// When (a future completes with an exception)
completableFuture.completeExceptionally(new IllegalArgumentException(randomExceptionMessage));
// Then (the error of the original exception is propagated to the wrapped future)
CompletableFuture<Boolean> wrappedFuture = Errors.<Boolean, Boolean>whenComplete(completableFuture).wrap(
false, // explicitly tell the wrapper not to propagate the original exception
(result, error) -> false);
assertThat(wrappedFuture.get(5, TimeUnit.SECONDS)).isEqualTo(false);
}
@Test
public void wrappingWhenCompleteShouldAllowForInspectingError() throws Exception {
String randomExceptionMessage = "Failed: " + ThreadLocalRandom.current().nextInt(500);
// Given
CountDownLatch latch = new CountDownLatch(1);
CompletableFuture<Boolean> completableFuture = new CompletableFuture<>();
// When (a future completes with an exception)
completableFuture.completeExceptionally(new IllegalArgumentException(randomExceptionMessage));
// Then (the error of the original exception is propagated to the wrapped future)
CompletableFuture<Boolean> wrappedFuture = Errors.<Boolean, Boolean>whenComplete(completableFuture).wrap(
(result, error) -> {
assertThat(error).isNotNull()
.hasMessageContaining(randomExceptionMessage)
.isExactlyInstanceOf(IllegalArgumentException.class);
assertThat(result).isNull();
latch.countDown();
return false;
});
latch.await(5, TimeUnit.SECONDS);
}
@Test
public void wrappingWhenCompleteShouldProvideResult() throws Exception {
String randomMessage = "Success: " + ThreadLocalRandom.current().nextInt(500);
// Given
CountDownLatch latch = new CountDownLatch(1);
CompletableFuture<String> completableFuture = new CompletableFuture<>();
// When (a future completes with a given result)
completableFuture.complete(randomMessage);
// Then (the result is accessible to the wrapped future)
CompletableFuture<Boolean> wrappedFuture = Errors.<String, Boolean>whenComplete(completableFuture).wrap(
(result, error) -> {
assertThat(result).isEqualTo(randomMessage);
assertThat(error).isNull();
latch.countDown();
return false;
});
}
@Test
public void wrappedFutureShouldPropagateExceptions() {
String randomMessage = "Success: " + ThreadLocalRandom.current().nextInt(500);
String randomExceptionMessage = "Failed: " + ThreadLocalRandom.current().nextInt(500);
// Given
CompletableFuture<String> completableFuture = new CompletableFuture<>();
// When (a future completes with a given result and wrapped future throws exception)
completableFuture.complete(randomMessage);
CompletableFuture<Boolean> wrappedFuture = Errors.<String, Boolean>whenComplete(completableFuture).wrap(
(result, error) -> {
throw new NoSuchMethodException(randomExceptionMessage); // Checked exception
});
// Then (the error of the wrapped future is set to the wrapped future's result)
assertThatThrownBy(() -> wrappedFuture.get(5, TimeUnit.SECONDS))
.hasCauseExactlyInstanceOf(NoSuchMethodException.class)
.hasMessageContaining(randomExceptionMessage);
}
}
// From: https://github.com/diffplug/durian/blob/master/src/com/diffplug/common/base/Throwing.java
/**
* Variations on the standard functional interfaces which throw Throwable.
* <p>
* {@link Errors} can convert these into standard functional interfaces.
*/
public interface Throwing
{
/** Variations on the standard functional interfaces which throw a specific subclass of Throwable. */
public interface Specific {
@FunctionalInterface
public interface BiConsumer<T, U, E extends Throwable> {
void accept(T t, U u) throws E;
}
}
@FunctionalInterface
public interface BiConsumer<T, U> extends Specific.BiConsumer<T, U, Throwable> {}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment