io.atlassian.util.concurrent.Promises Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of atlassian-util-concurrent Show documentation
Show all versions of atlassian-util-concurrent Show documentation
This project contains utility classes that are used by
various products and projects inside Atlassian and may have some
utility to the world at large.
The newest version!
/**
* Copyright 2015 Atlassian Pty Ltd
*
* 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 io.atlassian.util.concurrent;
import javax.annotation.Nonnull;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
public final class Promises {
private Promises() {}
/**
* Create a promise for the input effect.
*
* @param type of the successful value.
* @return a new {@link SettablePromise} that can be fulfilled by calling the
* {@link Callback} methods it implements. Cancelling this Promise will have
* no consequence on the code that calls the callback.
*/
public static SettablePromise settablePromise() {
return settablePromise(Optional.empty());
}
/**
* @param type of the successful value.
* @param executor to be called to run callbacks and transformations attached
* to the returned Promise. If None, they will be executed on the caller
* thread.
* @return a new {@link SettablePromise} that can be fulfilled by calling the
* {@link Callback} methods it implements. Cancelling this Promise will have
* no consequence on the code that calls the callback.
*/
public static SettablePromise settablePromise(@Nonnull final Optional executor) {
Objects.requireNonNull(executor, "Executor");
return new Settable<>(executor);
}
/**
* Create a promise from the input completion stage.
*
* @param stage a {@link java.util.concurrent.CompletionStage} used as the
* precedent of the returned Promise.
* @param type of the successful value.
* @return a new {@link io.atlassian.util.concurrent.Promise} that will be
* fulfilled with the same result that the CompletionStage has. If it
* implements
* {@link java.util.concurrent.CompletionStage#toCompletableFuture()} then
* cancelling this Promise will cancel that CompletableFuture.
*/
public static Promise forCompletionStage(@Nonnull final CompletionStage stage) {
return forCompletionStage(stage, Optional.empty());
}
/**
* Create a promise from the input completion stage.
*
* @param stage a {@link java.util.concurrent.CompletionStage} used as the
* precedent of the returned Promise.
* @param type of the successful value.
* @param executor to be called to run callbacks and transformations attached
* to the returned Promise. If None, they will be executed on the caller
* thread.
* @return a new {@link io.atlassian.util.concurrent.Promise} that will be
* fulfilled with the same result that the CompletionStage has. If it
* implements
* {@link java.util.concurrent.CompletionStage#toCompletableFuture()} then
* cancelling this Promise will cancel that CompletableFuture.
*/
public static Promise forCompletionStage(@Nonnull final CompletionStage stage, @Nonnull final Optional executor) {
Objects.requireNonNull(stage, "CompletionStage");
Objects.requireNonNull(executor, "Executor");
return new OfStage<>(stage, executor);
}
/**
* Return a completable future based on the input promise
*
* @param promise a {@link io.atlassian.util.concurrent.Promise} used as the
* precedent of the returned CompletableFuture.
* @param any super type of A that may be inferred.
* @return a new {@link java.util.concurrent.CompletableFuture} that will be
* fulfilled with the same result that the Promise has.
*/
public static CompletableFuture toCompletableFuture(@Nonnull final Promise promise) {
if (promise instanceof OfStage) {
// shortcut
return ((OfStage) promise).future;
} else {
final CompletableFuture aCompletableFuture = new CompletableFuture<>();
promise.then(compose(aCompletableFuture::complete, t -> {
if (promise.isCancelled() && (!(t instanceof CancellationException))) {
aCompletableFuture.completeExceptionally(new CancellationException(t.getMessage()));
} else {
aCompletableFuture.completeExceptionally(getRealException(t));
}
}));
return aCompletableFuture;
}
}
/**
* Returns a new {@link io.atlassian.util.concurrent.Promise} representing the
* status of a list of other promises.
*
* @param promises The promises that the new promise should track
* @return The new, aggregate promise
* @param an A.
*/
public @SafeVarargs static Promise> when(@Nonnull final Promise extends A>... promises) {
return when(Stream.of(promises));
}
/**
* Returns a new {@link io.atlassian.util.concurrent.Promise} representing the
* status of a list of other promises. More generally this is known as
* {code}sequence{code} as both List and Promise are traversable monads.
*
* @param promises The promises that the new promise should track
* @return The new, aggregate promise
* @param an A.
*/
public static Promise> when(@Nonnull final Iterable extends Promise extends A>> promises) {
return when(StreamSupport.stream(promises.spliterator(), false).map(Function.identity()));
}
/**
* Returns a new {@link io.atlassian.util.concurrent.Promise} representing the
* status of a stream of other promises.
*
* @param promises The promises that the new promise should track
* @return The new, aggregate promise
* @param an A.
*/
public static Promise> when(@Nonnull final Stream extends Promise extends A>> promises) {
final List> futures = promises.map(Promises::toCompletableFuture).collect(Collectors.toList());
final CompletableFuture allFutures = CompletableFuture.allOf(futures.toArray(new CompletableFuture[futures.size()]));
// short-circuit: eagerly cancel the futures if any of the leaves fail. Do
// not wait for the others.
futures.forEach(cf -> cf.whenComplete((a, t) -> {
if (t != null)
futures.forEach(f -> f.cancel(true));
}));
final Function> gatherValues = o -> futures.stream().map(CompletableFuture::join).collect(Collectors.toList());
return Promises.forCompletionStage(allFutures.thenApply(gatherValues));
}
/**
* Creates a new, resolved promise for the specified concrete value.
*
* @return The new promise
* @since 2.7
*
* @deprecated use {@link Promises#promise(Object)} as a method reference.
*/
public @Deprecated static Function> toPromise() {
return Promises::promise;
}
/**
* Creates a new, resolved promise for the specified concrete value.
*
* @param value The value for which a promise should be created
* @return The new promise
* @param an A.
*/
public static Promise promise(final A value) {
final CompletableFuture future = new CompletableFuture<>();
future.complete(value);
return Promises.forCompletionStage(future);
}
/**
* Creates a new, rejected promise from the given {@link java.lang.Throwable}
* and result type.
*
* @param t The throwable
* @return The new promise
* @param an A.
*/
public static Promise rejected(@Nonnull final Throwable t) {
final CompletableFuture future = new CompletableFuture<>();
future.completeExceptionally(t);
return Promises.forCompletionStage(future);
}
/**
* Creates a promise from the given future.
*
* @param future The future delegate for the new promise
* @param executor an executor where a task will run that waits for the future
* to complete
* @param possible future value type
* @return The new promise
*/
public static Promise forFuture(@Nonnull final Future future, @Nonnull final Executor executor) {
final CompletableFuture newFuture = new CompletableFuture<>();
executor.execute(() -> {
try {
newFuture.complete(future.get());
} catch (final ExecutionException ee) {
newFuture.completeExceptionally(ee.getCause());
} catch (final InterruptedException ee) {
newFuture.cancel(true);
} catch (final Throwable t) {
newFuture.completeExceptionally(t);
}
});
newFuture.whenComplete((a, t) -> {
if (t instanceof CancellationException) {
future.cancel(true);
}
});
return forCompletionStage(newFuture, Optional.of(executor));
}
/**
* Create a {@link Promise.TryConsumer} by composing two {@link Consumer}.
*
* @param success To run if the Future is successful
* @param failure To run if the Future fails
* @return The composed Callback
* @param an A.
*/
public static Promise.TryConsumer compose(@Nonnull final Consumer super A> success, @Nonnull final Consumer failure) {
return new Promise.TryConsumer() {
public void accept(final A result) {
success.accept(result);
}
public void fail(@Nonnull final Throwable t) {
failure.accept(t);
}
};
}
static class OfStage implements Promise {
private final CompletableFuture future;
private final Optional executor;
public OfStage(@Nonnull final CompletionStage delegate, @Nonnull final Optional ex) {
future = buildCompletableFuture(delegate, ex);
executor = ex;
}
@Override public A claim() {
try {
return future.get();
} catch (InterruptedException e) {
throw new RuntimeInterruptedException(e);
} catch (CompletionException e) {
final Throwable cause = e.getCause();
if (cause instanceof RuntimeException) {
throw (RuntimeException) cause;
}
if (cause instanceof Error) {
throw (Error) cause;
}
throw e;
} catch (ExecutionException e) {
final Throwable cause = e.getCause();
if (cause instanceof RuntimeException) {
throw (RuntimeException) cause;
}
if (cause instanceof Error) {
throw (Error) cause;
}
throw new RuntimeException(cause);
}
}
@Override public Promise done(final Consumer super A> e) {
return then(e, t -> {});
}
@Override public Promise fail(final Consumer e) {
return then(a -> {}, e);
}
@Override public Promise then(final TryConsumer super A> callback) {
return then(callback::accept, callback::fail);
}
@Override public Promise map(final Function super A, ? extends B> function) {
return forCompletionStage(future.thenApply(function));
}
@Override public Promise flatMap(final Function super A, ? extends Promise extends B>> f) {
final Function> fn = a -> Promises.toCompletableFuture(f.apply(a));
return this.>, B> newPromise(future::thenCompose, future::thenComposeAsync).apply(fn);
}
@Override public Promise recover(final Function handleThrowable) {
return forCompletionStage(future.exceptionally(handleThrowable.compose(Promises::getRealException)));
}
@Override public Promise fold(final Function ft, final Function super A, ? extends B> fa) {
final Function super A, ? extends B> fn = a -> {
try {
return fa.apply(a);
} catch (final Throwable t) {
return ft.apply(t);
}
};
return this., B> newPromise(future::handle, future::handleAsync).apply(biFunction(fn, ft));
}
@Override public boolean cancel(final boolean mayInterruptIfRunning) {
return future.cancel(mayInterruptIfRunning);
}
@Override public boolean isCancelled() {
return future.isCancelled();
}
@Override public boolean isDone() {
return future.isDone();
}
@Override public A get() throws InterruptedException, ExecutionException {
return future.get();
}
@Override public A get(final long timeout, @Nonnull final TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
return future.get(timeout, unit);
}
private Promise then(final Consumer super A> onSuccess, final Consumer onFailure) {
return this.newPromise(future::whenComplete, future::whenCompleteAsync).apply(biConsumer(onSuccess, onFailure));
}
private Function> newPromise(final Function> f1, final BiFunction> f2) {
return i -> {
if (executor.isPresent()) {
return forCompletionStage(f2.apply(i, executor.get()));
} else {
return forCompletionStage(f1.apply(i));
}
};
}
private CompletableFuture buildCompletableFuture(final CompletionStage completionStage, final Optional executor) {
try {
return completionStage.toCompletableFuture();
} catch (final UnsupportedOperationException uoe) {
final CompletableFuture aCompletableFuture = new CompletableFuture<>();
BiConsumer action = biConsumer(aCompletableFuture::complete, aCompletableFuture::completeExceptionally);
if (executor.isPresent()) {
completionStage.whenCompleteAsync(action, executor.get());
} else {
completionStage.whenComplete(action);
}
return aCompletableFuture;
}
}
}
static class Settable extends OfStage implements SettablePromise {
private final CompletableFuture completableFuture;
public Settable(@Nonnull final Optional ex) {
this(new CompletableFuture<>(), ex);
}
private Settable(@Nonnull final CompletableFuture cf, @Nonnull final Optional ex) {
super(cf, ex);
completableFuture = cf;
}
public void set(final A result) {
completableFuture.complete(result);
}
public void exception(@Nonnull final Throwable t) {
completableFuture.completeExceptionally(t);
}
}
private static Throwable getRealException(@Nonnull final Throwable t) {
if (t instanceof CompletionException) {
return t.getCause();
}
return t;
}
private static BiFunction biFunction(final Function super A, ? extends B> f, final Function ft) {
return (a, t) -> {
if (t == null) {
return f.apply(a);
} else {
return ft.apply(getRealException(t));
}
};
}
private static BiConsumer biConsumer(final Consumer super A> c, final Consumer ct) {
return (a, t) -> {
if (t == null) {
c.accept(a);
} else {
ct.accept(getRealException(t));
}
};
}
/**
* A callback that can be completed with a successful value or a failed
* exception.
*
* @param type of the successful value.
*/
public interface Callback {
void set(A result);
void exception(@Nonnull Throwable t);
}
/**
* A promise that can be completed with a successful value or a failed
* exception.
*
* @param type of the successful value.
*/
public interface SettablePromise extends Promise, Callback {}
}