package io.trane.future;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
/**
* `Future` is an abstraction to deal with asynchronicity without
* having to use callbacks directly or blocking threads. The
* primary usage for `Futures` on the JVM is to perform IO
* operations, which are asynchronous by nature.
*
* Although most IO APIs return synchronously, they do that by
* blocking the current `Thread`. For instance, the thread issues
* a request to a remote system and then waits until a response
* comes back. Considering that the JVM uses native threads, it
* is wasteful to block them since it leads to potential thread
* starvation and higher garbage collection pressure. It is hard
* to scale a JVM system vertically if the IO throughput is
* bounded by the number of threads.
*
* From the user perspective, a `Future` can be in three states:
*
* 1. Uncompleted
* 2. Completed with a value
* 3. Completed with an exception
*
* Instead of exposing this state to the user, `Future` provides
* combinators to express computations that run once the `Future`
* completes. The results of these combinators are `Future` instances
* that can be used to perform other transformations, giving the
* user a powerful tool to express complex chains of asynchronous
* transformations.
*
* Let's say that we need to call a remote service to get the
* username given an id:
*
* ```java
* Future<User> user = userService.get(userId);
* ```
*
* It's possible to apply the `map` transformation that produces
* a `Future` for the username string:
*
* ```java
* Future<String> username = user.map(user -> user.username);
* ```
*
* Note that we are using a lambda expression (`user -> user.username`)
* that takes a user and returns its username.
*
* Let's say that now we need to call a service to validate the
* username string. This is the result if we use the `map` combinator for it:
*
* ```java
* Future<Future<Boolean>> isValid =
* username.map(username -> usernameService.isValid(username));
* ```
*
* Given that the lambda expression calls another service and
* returns a `Future`, the produced result is a nested future
* (`Future<Future<Boolean>>`). One alternative to flatten this
* nested result is using `Future.flatten`:
*
* ```java
* Future<Boolean> isValidFlat = Future.flatten(isValid);
* ```
*
* There's a convenient combinator called `flatMap` that applies
* both `map` and `Future.flatten` at once:
*
* ```java
* Future<Boolean> isValid =
* username.flatMap(username -> usernameService.isValid(username));
* ```
*
* The `flatMap` combinator is very flexible and comes from the
* monad abstraction. Although useful, learning monads and category
* theory is not a requirement to use `Future`s. Strictly speaking,
* `Future` isn't a monad since it uses eager evaluation and thus
* breaks referential transparency. Once a `Future` is created, it
* is already running. See the "Execution Model" section for more
* information.
*
* There are many other useful operators to deal with exceptions,
* collections of futures, and others.
*
* @param <T> the type of the asynchronous computation result
*/
public interface Future<T> extends InterruptHandler {
/**
* A constant void future. Useful to represent completed side effects.
*/
static final Future<Void> VOID = Future.value((Void) null);
/**
* A constant `false` future.
*/
static final Future<Boolean> FALSE = Future.value(false);
/**
* A constant `true` future.
*/
static final Future<Boolean> TRUE = Future.value(true);
/**
* Returns a future that is never satisfied.
*
* @return the unsatisfied future.
* @param <T> the type of the never satisfied future.
*/
public static <T> Future<T> never() {
return FutureConstants.NEVER.unsafeCast();
}
/**
* Creates a future with the result of the supplier. Note that the supplier is
* executed by the current thread. Use FuturePool.async to execute the
* supplier on a separate thread.
*
* @param s a supplier that may throw an exception
* @param <T> the type of the value returned by the Supplier.
* @return a satisfied future if s doesn't throw or else a failed future with
* the supplier exception.
*/
public static <T> Future<T> apply(final Supplier<T> s) {
try {
return new ValueFuture<>(s.get());
} catch (final Throwable ex) {
return new ExceptionFuture<>(ex);
}
}
/**
* Applies the provided supplier and flattens the result. This method is useful
* to apply a supplier safely without having to catch possible synchronous exceptions
* that the supplier may throw.
*
* @param s a supplier that may throw an exception
* @return the future produced by the supplier or a failed future if the supplier
* throws a synchronous exception (not a failed Future)
*/
public static <T> Future<T> flatApply(final Supplier<Future<T>> s) {
try {
return s.get();
} catch (final Throwable ex) {
return new ExceptionFuture<>(ex);
}
}
/**
* Creates a successful future.
*
* @param v the value that satisfies the future.
* @param <T> the type of the value.
* @return the successful future.
*/
public static <T> Future<T> value(final T v) {
return new ValueFuture<>(v);
}
/**
* Creates a failed future.
*
* @param ex the failure.
* @param <T> the type of the failed future.
* @return the failed future.
*/
public static <T> Future<T> exception(final Throwable ex) {
return new ExceptionFuture<>(ex);
}
/**
* Flattens a nested future. The usage of this method indicates a code smell:
* a map may have been used instead of flatMap. There are genuine scenarios
* where flatten is required, though.
*
* @param fut the nested future
* @param <T> the type of the future result
* @return the flat future
*/
public static <T> Future<T> flatten(final Future<Future<T>> fut) {
return fut.flatMap(f -> f);
}
/**
* Returns a satisfied future with an immutable empty list.
*
* @return the empty list future.
* @param <T> the list type
*/
public static <T> Future<List<T>> emptyList() {
return FutureConstants.EMPTY_LIST.unsafeCast();
}
/**
* Returns a satisfied future with an empty optional.
*
* @return the empty optional future.
* @param <T> the optional type
*/
public static <T> Future<Optional<T>> emptyOptional() {
return FutureConstants.EMPTY_OPIONAL.unsafeCast();
}
/**
* Transforms a list of futures into a future of a list.
*
* @param list the futures to collect from
* @return a future that is satisfied with the future results.
*/
public static <T> Future<List<T>> collect(final List<? extends Future<T>> list) {
switch (list.size()) {
case 0:
return emptyList();
case 1:
return list.get(0).map(Arrays::asList);
case 2:
return list.get(0).biMap(list.get(1), Arrays::asList);
default:
final CollectPromise<T> p = new CollectPromise<>(list);
int i = 0;
for (final Future<T> f : list) {
if (f instanceof ExceptionFuture)
return f.unsafeCast();
if (f instanceof ValueFuture)
p.collect(((ValueFuture<T>) f).value, i);
else {
final int ii = i;
final Responder<T> responder = new Responder<T>() {
@Override
public final void onException(final Throwable ex) {
p.setException(ex);
}
@Override
public final void onValue(final T value) {
p.collect(value, ii);
}
};
f.respond(responder);
}
i++;
}
return p;
}
}
/**
* This method is similar to collect, but it discards the result of the
* futures. It's useful to wait for a list of pending future side effects.
*
* @param list the futures to wait for
* @return a void future that indicates that all futures are satisfied.
*/
public static <T> Future<Void> join(final List<? extends Future<T>> list) {
switch (list.size()) {
case 0:
return VOID;
case 1:
return list.get(0).voided();
default:
final JoinPromise<T> p = new JoinPromise<>(list);
for (final Future<T> f : list) {
if (f instanceof ExceptionFuture)
return f.voided();
f.respond(p);
}
return p;
}
}
/**
* Selects the index of the first satisfied future.
*
* @param list the list of futures to select from
* @return a future with the index of the first satisfied future of the list.
*/
public static <T> Future<Integer> selectIndex(final List<Future<T>> list) {
switch (list.size()) {
case 0:
return Future.exception(new IllegalArgumentException("Can't select from empty list."));
case 1:
return list.get(0).map(v -> 0);
default:
final Promise<Integer> p = Promise.apply(list);
int i = 0;
for (final Future<?> f : list) {
if (f instanceof SatisfiedFuture)
return Future.value(i);
final int ii = i;
f.ensure(() -> p.becomeIfEmpty(Future.value(ii)));
i++;
}
return p;
}
}
/**
* Finds the first future that completes.
*
* @param list futures to select from.
* @return a future that is satisfied with the result of the first future to
* complete.
*/
public static <T> Future<T> firstCompletedOf(final List<Future<T>> list) {
switch (list.size()) {
case 0:
return Future.exception(new IllegalArgumentException("Can't select first completed future from empty list."));
case 1:
return list.get(0);
default:
final FirstCompletedOfPromise<T> p = new FirstCompletedOfPromise<>(list);
for (final Future<T> f : list) {
if (f instanceof SatisfiedFuture)
return f;
f.respond(p);
}
return p;
}
}
/**
* Executes the supplier function while the condition is valid.
*
* @param cond a supplier that determines if the while should stop or not
* @param f the body of the while that is executed on each asynchronous
* iteration
* @return a void future that is satisfied when the while stops.
*/
public static <T> Future<Void> whileDo(final Supplier<Boolean> cond, final Supplier<Future<T>> f) {
return Tailrec.apply(() -> {
if (cond.get())
return f.get().flatMap(r -> whileDo(cond, f));
else
return VOID;
});
}
/**
* Returns a void future that is satisfied after the specified delay.
* It's a shortcut for `Future.VOID.delayed(delay, scheduler)`
*
* @param delay for how long the future must be delayed
* @param scheduler a scheduler for internal tasks
* @return the void future that is satisfied after the delay
*/
public static Future<Void> delay(final Duration delay, ScheduledExecutorService scheduler) {
return Future.VOID.delayed(delay, scheduler);
}
/**
* Maps the result of this future to another value.
*
* @param f the mapping function.
* @return a future transformed by the mapping function.
*/
<R> Future<R> map(Function<? super T, ? extends R> f);
/**
* Maps the result of this future to another future and flattens the result.
*
* @param f the mapping function that returns another future instance.
* @return a mapped and flattened future with the transformed result.
*/
<R> Future<R> flatMap(Function<? super T, ? extends Future<R>> f);
/**
* Maps the result of this future using the provided Transformer. If this
* future completes successfully, transformer.onValue is called. If this
* future completes with an exception, transformer.onException is called.
*
* @param t a Transformer that applies the transformation.
* @return a future tranformed by the Transformer.
*/
<R> Future<R> transform(Transformer<? super T, ? extends R> t);
/**
* Maps the result of this future using a Transformer that returns another
* future and flattens the result. If this future completes successfully,
* transformer.onValue is called. If this future completes with an exception,
* transformer.onException is called.
*
* @param t a Transformer that applies the transformation.
* @return a future tranformed by the Transformer.
*/
<R> Future<R> transformWith(Transformer<? super T, ? extends Future<R>> t);
/**
* Waits for this and other (running in parallel) and then maps the result
* with f.
*
* @param other The other future that becomes the second parameter of f.
* @param f the mapping function. The first parameter is the result of this
* and the second the result of other.
* @return the mapped future.
*/
<U, R> Future<R> biMap(Future<U> other, BiFunction<? super T, ? super U, ? extends R> f);
/**
* Waits for this and other (running in parallel), maps the result with f, and
* flattens the result.
*
* @param other The other future that becomes the second parameter of f.
* @param f the mapping function. The first parameter is the result of this
* and the second the result of other.
* @return the mapped and flattened future.
*/
<U, R> Future<R> biFlatMap(Future<U> other, BiFunction<? super T, ? super U, ? extends Future<R>> f);
/**
* Runs r when this future completes.
*
* @param r the Runnable to be executed.
* @return a future that completes with the result of this after r is
* executed.
*/
Future<T> ensure(Runnable r);
/**
* Executes the Consumer if this future completes successfully.
*
* @param c the Consumer to be executed.
* @return a future that completes with the result of this after c is
* executed, if applicable.
*/
Future<T> onSuccess(Consumer<? super T> c);
/**
* Executes the Consumer if this future completes with an exception.
*
* @param c the Consumer to be executed.
* @return a future that completes with the result of this after c is
* executed, if applicable.
*/
Future<T> onFailure(Consumer<Throwable> c);
/**
* Executes the Responder once this future completes. If this future completes
* successfully, responder.onValue is called. If this future completes with an
* exception, responder.onException is called.
*
* @param r the Responder to be executed.
* @return a future that completes with the result of this after r is
* executed.
*/
Future<T> respond(Responder<? super T> r);
/**
* If this future completes with an exception, applies the provided rescue
* function and flattens the result.
*
* Note that it's possible to return a Future.exception from f if the
* exception can't be recovered.
*
* @param f the function to be executed.
* @return a future with the result of f.
*/
Future<T> rescue(Function<Throwable, ? extends Future<T>> f);
/**
* Creates a future that will be completed with the interrupt exception if an
* interrupt is received.
*
* @return the interruptible future.
*/
Future<T> interruptible();
/**
* Checks if this future is completed.
*
* @return true if completed, false if not completed.
*/
boolean isDefined();
/**
* Blocks the current thread until this future is satisfied and gets its
* result. This method normally only useful for tests, **avoid it for
* production code**.
*
* @param timeout for how long the thread should wait for the result
* @return the result if the future completes successfully or an exception if
* the future completes with an exception.
* @throws CheckedFutureException
* wrapper exception used when the result is a checked exception. If
* the exception is unchecked, it's thrown without this wrapper.
*/
T get(Duration timeout) throws CheckedFutureException;
/**
* Blocks the current thread until this future is satisfied. This method
* normally only useful for tests, **avoid it for production code**.
*
* This method is similar to get but it doesn't return the future value nor
* throws if the future completes with an exception.
*
* @param timeout for how long the thread should wait for the result
* @throws CheckedFutureException
* wrapper exception used when the result is a checked exception. If
* the exception is unchecked, it's thrown without this wrapper.
*/
void join(Duration timeout) throws CheckedFutureException;
/**
* Creates a future that is satisfied with void when this future completes.
*
* @return the voided future.
*/
Future<Void> voided();
/**
* Delays the result of this future. It method doesn't take in consideration
* how long this future takes to be completed. It assumes the state of this
* future after delay, being it completed or not.
*
* @param delay for how long the future must be delayed
* @param timeUnit the time unit for delay
* @param scheduler used to schedule an internal task to be executed after delay.
* @return a future that assumes the state of this future after delay.
*/
Future<T> delayed(final Duration delay, final ScheduledExecutorService scheduler);
/**
* Proxies the result of this future, successful or not, to a Promise.
*
* @param p the Promise to be updated once this future completes.
*/
void proxyTo(final Promise<T> p);
/**
* Creates a future that fails with a TimeoutException if this future isn't
* completed within the timeout.
*
* @param timeout how long to wait for the result
* @param timeUnit the time unit of timeout.
* @param scheduler used to schedule an internal task after the timeout.
* @return a future that completes with the result of this future within the
* timeout, a failed future otherwise.
*/
default Future<T> within(final Duration timeout, final ScheduledExecutorService scheduler) {
return within(timeout, scheduler, TimeoutException.stackless);
}
/**
* Creates a future that fails with a exception if this future isn't completed
* within the timeout.
*
* @param timeout how long to wait for the result
* @param timeUnit the time unit of timeout.
* @param scheduler used to schedule an internal task after the timeout.
* @param exception the exception to be thrown when the future times out.
* @return a future that completes with the result of this future within the
* timeout, a failed future otherwise.
*/
Future<T> within(final Duration timeout, final ScheduledExecutorService scheduler,
final Throwable exception);
/**
* Casts the result of this future. **Avoid this method** since it's unsafe
* and can produce a future failed with a ClassCastException.
*
* @return the casted future.
*/
@SuppressWarnings("unchecked")
default <R> Future<R> unsafeCast() {
return (Future<R>) this;
}
}
final class FutureConstants {
private FutureConstants() {
}
static final Future<?> NEVER = new NoFuture<>();
static final Future<? extends List<?>> EMPTY_LIST = Future.value(Collections.unmodifiableList(new ArrayList<>(0)));
static final Future<? extends Optional<?>> EMPTY_OPIONAL = Future.value(Optional.empty());
}
final class CollectPromise<T> extends Promise<List<T>> {
private final Object[] results;
private final AtomicInteger count;
private final List<? extends Future<T>> list;
public CollectPromise(final List<? extends Future<T>> list) {
final int size = list.size();
this.results = new Object[size];
this.count = new AtomicInteger(size);
this.list = list;
}
@SuppressWarnings("unchecked")
public final void collect(final T value, final int i) {
results[i] = value;
if (count.decrementAndGet() == 0)
setValue((List<T>) Arrays.asList(results));
}
@Override
protected final InterruptHandler getInterruptHandler() {
return InterruptHandler.apply(list);
}
}
final class JoinPromise<T> extends Promise<Void> implements Responder<T> {
private final AtomicInteger count;
private final List<? extends Future<T>> list;
public JoinPromise(final List<? extends Future<T>> list) {
this.list = list;
this.count = new AtomicInteger(list.size());
}
@Override
public final void onException(final Throwable ex) {
setException(ex);
}
@Override
public final void onValue(final T value) {
if (count.decrementAndGet() == 0)
become(Future.VOID);
}
@Override
protected InterruptHandler getInterruptHandler() {
return InterruptHandler.apply(list);
}
}
final class FirstCompletedOfPromise<T> extends Promise<T> implements Responder<T> {
private final List<? extends Future<T>> list;
public FirstCompletedOfPromise(final List<? extends Future<T>> list) {
this.list = list;
}
@Override
public final void onException(final Throwable ex) {
becomeIfEmpty(Future.exception(ex));
}
@Override
public final void onValue(final T value) {
becomeIfEmpty(Future.value(value));
}
@Override
protected InterruptHandler getInterruptHandler() {
return InterruptHandler.apply(list);
}
}