com.spotify.futures.CompletableFuturesExtra Maven / Gradle / Ivy
/*
* Copyright (c) 2013-2019 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.spotify.futures;
import com.google.api.core.ApiFuture;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.Executor;
import java.util.function.BiFunction;
import java.util.function.Function;
public class CompletableFuturesExtra {
private CompletableFuturesExtra() {
throw new AssertionError();
}
/**
* Wrap a {@link CompletableFuture} in a {@link ListenableFuture}. The returned future will
* complete with the same result or failure as the original future.
*
* @param future The {@link CompletableFuture} to wrap in a {@link ListenableFuture}.
* @return A {@link ListenableFuture} that completes when the original future completes.
*/
public static ListenableFuture toListenableFuture(
CompletableFuture future) {
return toListenableFuture((CompletionStage) future);
}
/**
* Wrap a {@link CompletionStage} in a {@link ListenableFuture}. The returned future will
* complete with the same result or failure as the original future.
*
* @param future The {@link CompletionStage} to wrap in a {@link ListenableFuture}.
* @return A {@link ListenableFuture} that completes when the original future completes.
*/
public static ListenableFuture toListenableFuture(
CompletionStage future) {
if (future instanceof ListenableToCompletableFutureWrapper) {
return ((ListenableToCompletableFutureWrapper) future).unwrap();
}
return new CompletableToListenableFutureWrapper<>(future);
}
/**
* Wrap a {@link ListenableFuture} in a {@link CompletableFuture}. The returned future will
* complete with the same result or failure as the original future. Completing the returned
* future does not complete the original future.
*
* @deprecated - see {@link ListenableFuturesExtra#toCompletableFuture(ListenableFuture)}
*
* @param future The {@link ListenableFuture} to wrap in a {@link CompletableFuture}.
* @return A {@link CompletableFuture} that completes when the original future completes.
*/
@Deprecated
public static CompletableFuture toCompletableFuture(ListenableFuture future) {
return ListenableFuturesExtra.toCompletableFuture(future);
}
/**
* Converts an {@link ApiFuture} to a {@link CompletableFuture}.
*
* @deprecated - see {@link ApiFuturesExtra#toCompletableFuture(ApiFuture)}
*
* @param future the {@link ApiFuture} to wrap.
* @return a {@link CompletableFuture} that completes when the original future completes.
*/
@Deprecated
public static CompletableFuture toCompletableFuture(ApiFuture future) {
return ApiFuturesExtra.toCompletableFuture(future);
}
/**
* Converts an {@link ApiFuture} to a {@link CompletableFuture}.
*
* @deprecated - see {@link ApiFuturesExtra#toCompletableFuture(ApiFuture)}
*
* @param future the {@link ApiFuture} to wrap.
* @param executor the executor where the listener is running.
* @return a {@link CompletableFuture} that completes when the original future completes.
*/
@Deprecated
public static CompletableFuture toCompletableFuture(ApiFuture future,
Executor executor) {
return ApiFuturesExtra.toCompletableFuture(future, executor);
}
/**
* Wrap a {@link CompletionStage} in a {@link ApiFuture}. The returned future will
* complete with the same result or failure as the original future.
*
* @param future The {@link CompletionStage} to wrap in a {@link ApiFuture}.
* @return A {@link ApiFuture} that completes when the original future completes.
*/
public static ApiFuture toApiFuture(CompletionStage future) {
if (future instanceof ApiFutureToCompletableFutureWrapper) {
return ((ApiFutureToCompletableFutureWrapper) future).unwrap();
}
return new CompletableToApiFutureWrapper<>(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 stage 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 stage the {@link CompletionStage} to compose
* @param fn the function to use to compute the value of the
* returned {@link CompletionStage}
* @param the function's return type
* @return the new {@link CompletionStage}
*/
public static CompletionStage handleCompose(
CompletionStage stage,
BiFunction super T, Throwable, ? extends CompletionStage> fn) {
return dereference(stage.handle(fn));
}
/**
* Returns a new stage 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 stage the {@link CompletionStage} to compose
* @param fn the function to use to compute the value of the
* returned {@link CompletionStage} if this stage completed
* exceptionally
* @return the new {@link CompletionStage}
*/
public static CompletionStage exceptionallyCompose(
CompletionStage stage,
Function> fn) {
return dereference(wrap(stage).exceptionally(fn));
}
/**
* check that a stage is completed.
* @param stage a {@link CompletionStage}.
* @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 {@link CompletionStage}.
* @return the value of the stage if it has one.
* @throws IllegalStateException if the stage is not completed.
* @throws java.util.concurrent.CompletionException if the future completed exceptionally.
*/
public static T getCompleted(CompletionStage stage) {
CompletableFuture future = stage.toCompletableFuture();
checkCompleted(future);
return future.join();
}
/**
* Get the exception from an exceptionally completed stage.
*
* @param stage a completed {@link CompletionStage}.
* @return the exception of the stage if it has one.
* @throws IllegalStateException if the stage is not completed, or not completed exceptionally.
*/
public static Throwable getCompletedException(CompletionStage> stage) {
CompletableFuture> future = stage.toCompletableFuture();
checkCompleted(future);
if (!future.isCompletedExceptionally()) {
throw new IllegalStateException("future was not completed exceptionally");
}
try {
future.join();
throw new IllegalStateException("Unreachable");
} catch (CompletionException | CancellationException e) {
return e;
}
}
/**
* This takes a stage of a stage of a value and
* returns a plain stage of a value.
*
* @param stage a {@link CompletionStage} of a {@link CompletionStage} of a value
* @return the {@link CompletionStage} of the value
*/
public static CompletionStage dereference(
CompletionStage extends CompletionStage> stage) {
//noinspection unchecked
return stage.thenCompose(Identity.INSTANCE);
}
private static CompletionStage> wrap(CompletionStage future) {
//noinspection unchecked
return future.thenApply((Function>) WrapFunction.INSTANCE);
}
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);
}
}
}