package io.simplesource.data; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.function.Function; import java.util.function.Supplier; /** * Represents an operation that calculates an {@link Result} asynchronously. * * @param <E> on failure there will be a NonEmptyList of error instances with an error value of this type. * @param <T> when successful there will be a contained value of this type. */ public final class FutureResult<E, T> { private final CompletableFuture<Result<E, T>> run; public static <E, T> FutureResult<E, T> ofCompletableFuture(final CompletableFuture<Result<E, T>> run) { return new FutureResult<>(run); } public static <E, T> FutureResult<E, T> ofFuture(final Future<T> run, final Function<Exception, E> f) { return new FutureResult<>(CompletableFuture.supplyAsync(() -> { try { return Result.success(run.get()); } catch (final InterruptedException | ExecutionException e) { return Result.failure(f.apply(e)); } })); } public static <E, T> FutureResult<E, T> ofFutureResult(final Future<Result<E, T>> future, final Function<Exception, E> f) { return new FutureResult<>(CompletableFuture.supplyAsync(() -> { try { return future.get(); } catch (final InterruptedException | ExecutionException e) { return Result.failure(f.apply(e)); } })); } public static <E, T> FutureResult<E, T> ofResult(final Result<E, T> result) { return new FutureResult<>(() -> result); } public static <E, T> FutureResult<E, T> ofSupplier(final Supplier<Result<E, T>> supplier) { return new FutureResult<>(supplier); } public static <E, T> FutureResult<E, T> of(final T t) { return new FutureResult<>(() -> Result.success(t)); } @SafeVarargs public static <E, T> FutureResult<E, T> fail(final E error, final E... errors) { return new FutureResult<>(() -> Result.failure(error, errors)); } public static <E, T> FutureResult<E, T> fail(final NonEmptyList<E> errors) { return new FutureResult<>(() -> Result.failure(errors)); } // TEMP public Result<E, T> unsafePerform(final Function<Exception, E> f) { try { return run.get(); } catch (final InterruptedException | ExecutionException e) { E error = f.apply(e); return Result.failure(error); } } private FutureResult(final CompletableFuture<Result<E, T>> run) { this.run = run; } private FutureResult(final Supplier<Result<E, T>> supplier) { run = CompletableFuture.supplyAsync(supplier); } public Result<E, T> getOrElse(final Supplier<Result<E, T>> resultSupplier, final Function<Exception, E> f) { try { return run.handle((tResult, throwable) -> tResult != null ? tResult : resultSupplier.get()).get(); } catch (final InterruptedException | ExecutionException e) { return Result.failure(f.apply(e)); } } public CompletableFuture<Result<E, T>> future() { return run; } public <R> FutureResult<E, R> map(Function<T, R> f) { // CompletableFuture thenApply === map return new FutureResult<>(run.thenApply(r -> r.map(f))); } public <F> FutureResult<F, T> errorMap(Function<E, F> f) { return new FutureResult<>(run.thenApply(r -> r.errorMap(f))); } public <R> CompletableFuture<R> fold(Function<NonEmptyList<E>, R> e, Function<T, R> f) { return run.thenApply(r -> r.fold(e, f)); } public <R> FutureResult<E, R> flatMap(Function<T, FutureResult<E, R>> f) { // CompletableFuture thenCompose / thenComposeAsync === flatMap final CompletableFuture<Result<E, R>> future = run.thenComposeAsync( (Result<E, T> r) -> r.fold( reasons -> CompletableFuture.completedFuture(Result.failure(reasons)), value -> f.apply(value).run ) ); return new FutureResult<>(future); } }