All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.florianschoppmann.java.futures.Futures Maven / Gradle / Ivy

Go to download

Bridge gaps and help overcome inconveniences with CompletableFuture. This module includes, among other features, methods to collect the results of multiple futures into a list, provides equivalent alternatives to CompletableFuture’s monadic methods (thenApply, thenCompose, etc.) that can deal with checked exceptions, and methods for asynchronous “try-with-resources” constructs.

The newest version!
package net.florianschoppmann.java.futures;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.Executor;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.function.Supplier;
import javax.annotation.Nullable;

/**
 * This class consists exclusively of static methods that operate on {@link CompletionStage} and
 * {@link CompletableFuture} instances.
 */
public final class Futures {
    private Futures() {
        throw new AssertionError(String.format("No %s instances for you!", getClass().getName()));
    }

    /**
     * Returns a new {@link CompletableFuture} that will normally be completed with the list of results of all given
     * completion stages. The future will fail early if any of the given stages are completed exceptionally.
     *
     * 

If any of the given completion stages completes exceptionally, then the returned future is completed with a * {@link CompletionException}. This is either the first encountered exception (in the order of the given * {@link Iterable}) if that is a {@link CompletionException}, or otherwise a fresh {@link CompletionException} * holding the first encountered exception as cause. * *

Unlike {@link #collect(Iterable)}, the returned future may be completed before all completion stages are * completed. Specifically, in case of exceptional completion of the returned future, some of the given completion * stages may not yet be completed. * *

The order of the given completion stages is preserved in the result list. * * @param completionStages completion stages supplying the elements of the list that the returned future will be * completed with * @param the element type of the list that the new future will be completed with * @return the new future */ public static CompletableFuture> shortCircuitCollect( Iterable> completionStages) { Objects.requireNonNull(completionStages); CompletableFuture> listFuture = CompletableFuture.completedFuture((List) new ArrayList()); for (CompletionStage completionStage: completionStages) { CompletableFuture> newListFuture = new CompletableFuture<>(); listFuture.whenComplete((list, listThrowable) -> { assert list != null || listThrowable != null; if (listThrowable != null) { newListFuture.completeExceptionally(listThrowable); } else { completionStage.whenComplete((element, throwable) -> { if (throwable != null) { newListFuture.completeExceptionally(wrapInCompletionException(throwable)); } else { list.add(element); newListFuture.complete(list); } }); } }); listFuture = newListFuture; } return listFuture; } /** * Returns a new {@link CompletableFuture} that will normally be completed with a list of results of all given * completion stages. * *

If any of the given completion stages completes exceptionally, then the returned future is completed with a * {@link CompletionException}. This is either the first encountered exception (in the order of the given * {@link Iterable}) if that is a {@link CompletionException}, or otherwise a fresh {@link CompletionException} * holding the first encountered exception as cause. * *

Similar to {@link CompletableFuture#allOf(CompletableFuture[])}, this method guarantees that the returned * future will only be completed after all given completion stages have completed. * *

The order of the given completion stages is preserved in the result list. * * @param completionStages completion stages supplying the elements of the list that the returned future will be * completed with * @param the element type of the list that the new future will be completed with * @return future that will be completed with a list of the values that the given futures will be completed with */ public static CompletableFuture> collect( Iterable> completionStages) { Objects.requireNonNull(completionStages); CompletableFuture> listFuture = CompletableFuture.completedFuture((List) new ArrayList()); for (CompletionStage completionStage: completionStages) { CompletableFuture> newListFuture = new CompletableFuture<>(); listFuture.whenComplete((list, listThrowable) -> { assert list != null || listThrowable != null; completionStage.whenComplete((element, throwable) -> { if (throwable != null) { if (listThrowable != null) { newListFuture.completeExceptionally(listThrowable); } else { newListFuture.completeExceptionally(wrapInCompletionException(throwable)); } } else { if (listThrowable != null) { newListFuture.completeExceptionally(listThrowable); } else { list.add(element); newListFuture.complete(list); } } }); }); listFuture = newListFuture; } return listFuture; } /** * Returns a new {@link CompletableFuture} that is already completed with the value obtained by calling the given * {@link CheckedSupplier}. * *

While calling this method is normally equivalent to calling {@link CompletableFuture#completedFuture(Object)} * with the value returned by the given supplier, this method can be helpful when chaining multiple futures, and the * exception handling for the initial computation stage is supposed to be identical with the one in later stages. * Just like {@link CompletableFuture#supplyAsync(Supplier, Executor)}, the returned future will be completed * with a {@link CompletionException} if the given supplier throws. * * @param supplier a function returning the value to be used to complete the returned {@link CompletableFuture} * @param the function's return type * @return the new future */ public static CompletableFuture supply(CheckedSupplier supplier) { return supplyAsync(supplier, Runnable::run); } private static CompletionException rewrap(CompletionException exception) { Throwable cause = exception.getCause(); return cause instanceof WrappedException ? new CompletionException(cause.getCause()) : exception; } /** * Returns a new {@link CompletableFuture} that is asynchronously completed by a task running in the given executor * with the value obtained by calling the given {@link CheckedSupplier}. * *

Just like {@link CompletableFuture#supplyAsync(Supplier, Executor)}, the returned future will be completed * with a {@link CompletionException} if the given supplier throws. * * @param supplier a function returning the value to be used to complete the returned {@link CompletableFuture} * @param executor the executor to use for asynchronous execution * @param the function's return type * @return the new future * * @see CompletableFuture#supplyAsync(Supplier, Executor) */ public static CompletableFuture supplyAsync(CheckedSupplier supplier, Executor executor) { CompletionStage completionStage = CompletableFuture.supplyAsync(() -> { try { return supplier.checkedGet(); } catch (Exception exception) { throw new WrappedException(exception); } }, executor); CompletableFuture future = new CompletableFuture<>(); completionStage.whenComplete((result, failure) -> { if (failure != null) { assert failure instanceof CompletionException; future.completeExceptionally(rewrap((CompletionException) failure)); } else { future.complete(result); } }); return future; } /** * Returns a new {@link CompletableFuture} that is already exceptionally completed with the given {@link Throwable}. * * @param throwable the failure * @param type of the value (if the future had completed normally) * @return the exceptionally completed future */ public static CompletableFuture completedExceptionally(Throwable throwable) { CompletableFuture future = new CompletableFuture<>(); future.completeExceptionally(throwable); return future; } /** * When the given {@link CompletionStage} completes either normally or exceptionally, completes the given * {@link CompletableFuture} with its result. * * @param completableFuture future that will be completed with the result of {@code completionStage} * @param completionStage completion stage providing the result for {@code completableFuture} * @param type of the result value if the stage completes normally */ public static void completeWith(CompletableFuture completableFuture, CompletionStage completionStage) { completionStage.whenComplete((result, throwable) -> { if (throwable != null) { completableFuture.completeExceptionally(throwable); } else { completableFuture.complete(result); } }); } /** * Returns a {@link CompletionException} containing the given {@link Throwable} as cause, unless the given * {@link Throwable} is a {@link CompletionException}, in which case it itself is returned. * *

This function exists to match the behavior of {@link CompletableFuture}. * * @param throwable the exception or error * @return the completion exception */ public static CompletionException wrapInCompletionException(Throwable throwable) { return throwable instanceof CompletionException ? (CompletionException) throwable : new CompletionException(throwable); } /** * If the given {@link Throwable} is a {@link CompletionException} with cause, returns this. Otherwise, returns the * given throwable. * *

See also {@link #wrapInCompletionException(Throwable)}. * * @param throwable the exception or error * @return the cause */ public static Throwable unwrapCompletionException(Throwable throwable) { if (throwable instanceof CompletionException) { @Nullable Throwable cause = throwable.getCause(); if (cause != null) { return cause; } } return throwable; } /** * Returns a new {@link CompletableFuture} that will be completed in the same way as the given stage. However, in * case of exceptional completion with a {@link CompletionException}, the returned future will be exceptionally * completed with the cause held in the {@link CompletionException}. * * @param completionStage the completion stage that may be completed with a {@link CompletionException} * @param the type of result of the completion stage * @return the new future */ public static CompletableFuture unwrapCompletionException(CompletionStage completionStage) { CompletableFuture future = new CompletableFuture<>(); completionStage.whenComplete((value, failure) -> { if (failure instanceof CompletionException) { @Nullable Throwable cause = failure.getCause(); if (cause != null) { future.completeExceptionally(cause); } else { future.completeExceptionally(failure); } } else if (failure != null) { future.completeExceptionally(failure); } else { future.complete(value); } }); return future; } private static final class WrappedException extends RuntimeException { private static final long serialVersionUID = -3891858012985643515L; /** * Constructs a new wrapped exception with the specified cause. * * @param cause the cause (which is saved for later retrieval by the {@link #getCause()} method) */ private WrappedException(Throwable cause) { super(cause); } } /** * Returns a new {@link CompletableFuture} that, when the given stage completes normally, is executed with that * stage's result as the argument to the supplied function. See the {@link CompletionStage} documentation for rules * covering exceptional completion. * *

If {@code function} was a {@link Function} instance identical to {@code checkedFunction}, except for not * throwing checked exceptions, then this method is equivalent to {@code completionStage.thenApply(function)}. * * @param completionStage the completion stage providing the result for the given function * @param checkedFunction the function to use to compute the value of the returned {@link CompletableFuture} * @param the function's argument type * @param the function's return type * @return the new future */ public static CompletableFuture thenApply(CompletionStage completionStage, CheckedFunction checkedFunction) { // Oddly, javac 1.8.0_73 fails with the type inference, so we need to do an explicit cast. return thenApplyInternal( (Function, CompletionStage>) completionStage::whenComplete, checkedFunction ); } /** * Returns a new {@link CompletableFuture} that, when the given stage completes normally, is executed using the * supplied Executor, with the given stage's result as the argument to the supplied function. See the * {@link CompletionStage} documentation for rules covering exceptional completion. * *

If {@code function} was a {@link Function} instance identical to {@code checkedFunction}, except for not * throwing checked exceptions, then this method is equivalent to * {@code completionStage.thenApplyAsync(function, executor)}. * * @param completionStage the completion stage providing the result for the given function * @param checkedFunction the function to use to compute the value of the returned {@link CompletableFuture} * @param executor the executor to use for asynchronous execution * @param the function's argument type * @param the function's return type * @return the new future */ public static CompletableFuture thenApplyAsync(CompletionStage completionStage, CheckedFunction checkedFunction, Executor executor) { return thenApplyInternal(action -> completionStage.whenCompleteAsync(action, executor), checkedFunction); } private static CompletableFuture thenApplyInternal( Function, CompletionStage> whenCompleteFunction, CheckedFunction checkedFunction) { return transformCompletionStage( whenCompleteFunction, (result, failure, future) -> { if (failure == null) { try { future.complete(checkedFunction.checkedApply(result)); } catch (Exception exception) { throw new WrappedException(exception); } } } ); } /** * Returns a new {@link CompletableFuture} that, when the given stage completes normally, is executed with that * stage as the argument to the supplied function. See the {@link CompletionStage} documentation for rules covering * exceptional completion. * *

If {@code function} was a {@link Function} instance identical to {@code checkedFunction}, except for not * throwing checked exceptions, then this method is equivalent to {@code completionStage.thenCompose(function)}. * * @param completionStage the completion stage providing the result for the given function * @param checkedFunction the function returning a new {@link CompletionStage} * @param the function's argument type * @param the type of the returned {@link CompletionStage}'s result * @return the new future */ public static CompletableFuture thenCompose(CompletionStage completionStage, CheckedFunction> checkedFunction) { // Oddly, javac 1.8.0_73 fails with the type inference, so we need to do an explicit cast. return thenComposeInternal( (Function, CompletionStage>) completionStage::whenComplete, checkedFunction ); } /** * Returns a new {@link CompletableFuture} that, when the given stage completes normally, is executed using the * supplied {@link Executor}, with the given stage's result as the argument to the supplied function. See the * {@link CompletionStage} documentation for rules covering exceptional completion. * *

If {@code function} was a {@link Function} instance identical to {@code checkedFunction}, except for not * throwing checked exceptions, then this method is equivalent to * {@code completionStage.thenComposeAsync(function, executor)}. * * @param completionStage the completion stage providing the result for the given function * @param checkedFunction the function returning a new {@link CompletionStage} * @param executor the executor to use for asynchronous execution * @param the function's argument type * @param the type of the returned {@link CompletionStage}'s result * @return the new future */ public static CompletableFuture thenComposeAsync(CompletionStage completionStage, CheckedFunction> checkedFunction, Executor executor) { return thenComposeInternal(action -> completionStage.whenCompleteAsync(action, executor), checkedFunction); } private static CompletableFuture thenComposeInternal( Function, CompletionStage> whenCompleteStage, CheckedFunction> checkedFunction) { return transformCompletionStage( whenCompleteStage, (result, failure, future) -> { if (failure == null) { try { CompletionStage nextStage = Objects.requireNonNull(checkedFunction.checkedApply(result)); nextStage.whenComplete((nextResult, nextFailure) -> { if (nextFailure != null) { future.completeExceptionally(wrapInCompletionException(nextFailure)); } else { future.complete(nextResult); } }); } catch (Exception exception) { throw new WrappedException(exception); } } } ); } /** * Returns a new {@link CompletableFuture} with the same result or exception as the given stage, that executes the * given action when the given stage completes. * *

When the given stage is complete, the given action is invoked with the result (or {@code null} if none) and * the exception (or {@code null} if none) of the given stage as arguments. The returned future is completed when * the action returns. If the supplied action itself encounters an exception, then the returned future exceptionally * completes with this exception unless the given stage also completed exceptionally. * * @param completionStage the completion stage providing the result or exception for the given action * @param action the action to perform * @param the action's first argument type * @return the new future */ public static CompletableFuture whenComplete(CompletionStage completionStage, CheckedBiConsumer action) { // Oddly, javac 1.8.0_73 fails with the type inference, so we need to do an explicit cast. return whenCompleteInternal( (Function, CompletionStage>) completionStage::whenComplete, action ); } /** * Returns a new {@link CompletableFuture} with the same result or exception as the given stage, that executes the * given action using the supplied {@link Executor} when the given stage completes. * *

When the given stage is complete, the given action is invoked with the result (or {@code null} if none) and * the exception (or {@code null} if none) of the given stage as arguments. The returned future is completed when * the action returns. If the supplied action itself encounters an exception, then the returned future exceptionally * completes with this exception unless the given stage also completed exceptionally. * * @param completionStage the completion stage providing the result or exception for the given action * @param executor the executor to use for asynchronous execution * @param action the action to perform * @param the action's first argument type * @return the new future */ public static CompletableFuture whenCompleteAsync(CompletionStage completionStage, CheckedBiConsumer action, Executor executor) { return whenCompleteInternal(actionArg -> completionStage.whenCompleteAsync(actionArg, executor), action); } private static CompletableFuture whenCompleteInternal( Function, CompletionStage> whenCompleteStage, CheckedBiConsumer action) { return transformCompletionStage( whenCompleteStage, (result, failure, future) -> { try { action.checkedAccept(result, failure); if (failure == null) { future.complete(result); } } catch (Exception exception) { throw new WrappedException(exception); } } ); } /** * Returns a new {@link CompletableFuture} that is normally completed with the same result as the given stage. If * the given stage completed exceptionally, the given function is called with the exception as argument and the * returned future is completed exceptionally with the result. * *

The exception returned by {@code function} is not wrapped in a {@link CompletionException}. However, * if an exception E occurs in the given function, the returned future will be completed exceptionally with * a {@link CompletionException} containing E as cause. * * @param completionStage the completion stage providing the exception for the given function * @param function the exception-translation function * @param the type of the completion stage * @return the new future */ public static CompletableFuture translateException(CompletionStage completionStage, Function function) { CompletableFuture future = new CompletableFuture<>(); completionStage .handle((result, failure) -> { if (failure != null) { future.completeExceptionally(Objects.requireNonNull(function.apply(failure))); } else { future.complete(result); } return null; }) .exceptionally(future::completeExceptionally); return future; } @FunctionalInterface interface TriConsumer { /** * Performs this operation on the given arguments. * * @param first the first input argument * @param second the second input argument * @param third the third input argument */ void accept(@Nullable T first, @Nullable U second, @Nullable V third); } /** * Returns a new {@link CompletableFuture} that is normally completed by the given {@code resultConsumer}, and that * is exceptionally completed if a failure occurs. * *

This method factors out common code in order to implement thenApply, thenCompose, etc. * methods that that have corresponding methods in {@link CompletionStage}. However, the static methods in this * class allow transformations that throw checked exceptions. */ static CompletableFuture transformCompletionStage( Function, CompletionStage> whenCompleteFunction, TriConsumer> resultConsumer ) { CompletableFuture future = new CompletableFuture<>(); whenCompleteFunction .apply((result, failure) -> resultConsumer.accept(result, failure, future)) .exceptionally(exception -> { // Assuming that whenCompleteFunction is indeed method whenComplete or whenCompleteAsync, then we expect // a CompletionException here. However, we are extra cautious here, because a failed assertion or a // ClassCastException here could potentially create a debugging nightmare (it would be caught in // CompletableFuture#exceptionally() and then ignored). future.completeExceptionally( exception instanceof CompletionException ? rewrap((CompletionException) exception) : exception ); // Result of the new completion stage created by exceptionally() is ignored return null; }); return future; } /** * Returns a new {@link CompletableFuture} that, when the given stage completes normally, is executed using the * supplied Executor, with the given stage's result as the argument to the supplied function. See the * {@link CompletionStage} documentation for rules covering exceptional completion. * *

This method behaves like {@link #thenApplyAsync(CompletionStage, CheckedFunction, Executor)}, but it ensures * that the resource passed to {@code function} is properly closed, even if an exception occurs at any stage. This * method may thus be regarded as an asynchronous try-with-resources implementation (with just one resource: The * {@code R} instance that {@code resourceStage} will is completed with). * *

Similar to try-with-resources, if an exception occurs after the {@link AutoCloseable} resource has been * opened, then any further exception while automatically closing the resource will be added as suppressed * exception. Suppressed exceptions are added to the original exception, not the {@link CompletionException} that * the returned future would be completed with and that contains the original exception as cause. * * @param resourceStage completion stage that will be completed with an {@link AutoCloseable} * @param function function computing the result for the new future * @param executor the executor to use for asynchronous execution * @param the type of the resource returned by the given completion stage * @param the type of the value returned by the given function * @return the new future */ public static CompletableFuture thenApplyWithResourceAsync( CompletionStage resourceStage, CheckedFunction function, Executor executor) { return thenApplyAsync(resourceStage, resource -> { try (R ignored = resource) { return function.checkedApply(resource); } }, executor); } /** * Returns a new {@link CompletableFuture} that, when the given stage completes normally, is executed with that * stage as the argument to the supplied function. See the {@link CompletionStage} documentation for rules covering * exceptional completion. * *

This method behaves like {@link #thenCompose(CompletionStage, CheckedFunction)}, but it ensures that the * resource passed to {@code function} is properly closed, even if an exception occurs at any stage. This method may * thus be regarded as an asynchronous try-with-resources implementation (with just one resource: The {@code R} * instance that {@code resourceStage} will is completed with). * *

Similar to try-with-resources, if an exception occurs after the {@link AutoCloseable} resource has been * opened, then any further exception while automatically closing the resource will be added as suppressed * exception. Suppressed exceptions are added to the original exception, not the {@link CompletionException} that * the returned future would be completed with and that contains the original exception as cause. * * @param resourceStage completion stage that will be completed with an {@link AutoCloseable} * @param function Function returning a completion stage that will compute the result for the new future. * @param the type of the resource returned by the given completion stage * @param the result type of the future returned by the given function * @return the new future */ public static CompletableFuture thenComposeWithResource( CompletionStage resourceStage, CheckedFunction> function) { return thenCompose(resourceStage, resource -> { try (CloseableWrapper closeableWrapper = new CloseableWrapper(resource)) { CompletionStage nextStage = Objects.requireNonNull(function.checkedApply(resource)); closeableWrapper.ignoreClose = true; return whenComplete(nextStage, (result, failure) -> { // According to JLS8 §14.20.3.1, if a primary exception has occurred, then whatever exception occurs // during close() is added as suppressed exception, even in case of a Throwable that is not an // Exception. if (resource != null) { if (failure != null) { try { resource.close(); } catch (Throwable throwable) { unwrapCompletionException(failure).addSuppressed(throwable); } } else { resource.close(); } } }); } }); } private static final class CloseableWrapper implements AutoCloseable { @Nullable private final AutoCloseable closeable; /** * No need to synchronize access to this field. The read from {@link #close()} here will always happen either * before or after it is written to, never concurrent to a write in * {@link #thenComposeWithResource(CompletionStage, CheckedFunction)}. However, the field has to be volatile * for cache coherence. */ private volatile boolean ignoreClose = false; private CloseableWrapper(@Nullable AutoCloseable closeable) { this.closeable = closeable; } @Override public void close() throws Exception { if (closeable != null && !ignoreClose) { closeable.close(); } } } }