From df9e418091de13d1a8092a0fc612bd970cd52d5d Mon Sep 17 00:00:00 2001 From: tommyskeff Date: Thu, 9 Jan 2025 09:59:19 +0000 Subject: [PATCH] add orDefault methods to promises & more factory overloads --- .../futur/joiner/CompletionJoiner.java | 4 +- .../futur/joiner/MappedResultJoiner.java | 16 +- .../tommyjs/futur/joiner/PromiseJoiner.java | 10 +- .../tommyjs/futur/joiner/ResultJoiner.java | 16 +- .../dev/tommyjs/futur/joiner/VoidJoiner.java | 5 +- .../futur/promise/AbstractPromise.java | 40 +++- .../futur/promise/AbstractPromiseFactory.java | 20 +- .../dev/tommyjs/futur/promise/Promise.java | 37 ++++ .../tommyjs/futur/promise/PromiseFactory.java | 197 +++++++++-------- .../java/dev/tommyjs/futur/PromiseTests.java | 49 ++++- .../java/dev/tommyjs/futur/lazy/Promises.java | 199 ++++++++++-------- 11 files changed, 346 insertions(+), 247 deletions(-) diff --git a/futur-api/src/main/java/dev/tommyjs/futur/joiner/CompletionJoiner.java b/futur-api/src/main/java/dev/tommyjs/futur/joiner/CompletionJoiner.java index 3165d05..8284805 100644 --- a/futur-api/src/main/java/dev/tommyjs/futur/joiner/CompletionJoiner.java +++ b/futur-api/src/main/java/dev/tommyjs/futur/joiner/CompletionJoiner.java @@ -5,7 +5,6 @@ import dev.tommyjs.futur.promise.PromiseCompletion; import dev.tommyjs.futur.promise.PromiseFactory; import dev.tommyjs.futur.util.ConcurrentResultArray; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import java.util.Iterator; import java.util.List; @@ -36,9 +35,8 @@ public class CompletionJoiner extends PromiseJoiner, Void, Void, List } @Override - protected @Nullable Throwable onChildComplete(int index, Void key, @NotNull PromiseCompletion res) { + protected void onChildComplete(int index, Void key, @NotNull PromiseCompletion res) { results.set(index, res); - return null; } @Override diff --git a/futur-api/src/main/java/dev/tommyjs/futur/joiner/MappedResultJoiner.java b/futur-api/src/main/java/dev/tommyjs/futur/joiner/MappedResultJoiner.java index fa61dbd..4fdcd65 100644 --- a/futur-api/src/main/java/dev/tommyjs/futur/joiner/MappedResultJoiner.java +++ b/futur-api/src/main/java/dev/tommyjs/futur/joiner/MappedResultJoiner.java @@ -5,24 +5,19 @@ import dev.tommyjs.futur.promise.PromiseCompletion; import dev.tommyjs.futur.promise.PromiseFactory; import dev.tommyjs.futur.util.ConcurrentResultArray; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import java.util.*; -import java.util.function.BiConsumer; public class MappedResultJoiner extends PromiseJoiner>, K, V, Map> { - private final @Nullable BiConsumer exceptionHandler; private final @NotNull ConcurrentResultArray> results; public MappedResultJoiner( @NotNull PromiseFactory factory, @NotNull Iterator>> promises, - @Nullable BiConsumer exceptionHandler, int expectedSize, boolean link ) { super(factory); - this.exceptionHandler = exceptionHandler; this.results = new ConcurrentResultArray<>(expectedSize); join(promises, link); } @@ -38,17 +33,8 @@ public class MappedResultJoiner extends PromiseJoiner res) { - if (res.isError()) { - if (exceptionHandler == null) { - return res.getException(); - } - - exceptionHandler.accept(key, res.getException()); - } - + protected void onChildComplete(int index, K key, @NotNull PromiseCompletion res) { results.set(index, new AbstractMap.SimpleImmutableEntry<>(key, res.getResult())); - return null; } @Override diff --git a/futur-api/src/main/java/dev/tommyjs/futur/joiner/PromiseJoiner.java b/futur-api/src/main/java/dev/tommyjs/futur/joiner/PromiseJoiner.java index e0bff07..222bc27 100644 --- a/futur-api/src/main/java/dev/tommyjs/futur/joiner/PromiseJoiner.java +++ b/futur-api/src/main/java/dev/tommyjs/futur/joiner/PromiseJoiner.java @@ -6,7 +6,6 @@ import dev.tommyjs.futur.promise.PromiseCompletion; import dev.tommyjs.futur.promise.PromiseFactory; import dev.tommyjs.futur.util.PromiseUtil; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import java.util.Iterator; import java.util.concurrent.atomic.AtomicInteger; @@ -23,7 +22,7 @@ public abstract class PromiseJoiner { protected abstract @NotNull Promise getChildPromise(V value); - protected abstract @Nullable Throwable onChildComplete(int index, K key, @NotNull PromiseCompletion completion); + protected abstract void onChildComplete(int index, K key, @NotNull PromiseCompletion completion); protected abstract R getResult(); @@ -45,9 +44,10 @@ public abstract class PromiseJoiner { int index = i++; p.addAsyncListener(res -> { - Throwable e = onChildComplete(index, key, res); - if (e != null) { - joined.completeExceptionally(e); + onChildComplete(index, key, res); + if (res.isError()) { + assert res.getException() != null; + joined.completeExceptionally(res.getException()); } else if (count.decrementAndGet() == -1) { joined.complete(getResult()); } diff --git a/futur-api/src/main/java/dev/tommyjs/futur/joiner/ResultJoiner.java b/futur-api/src/main/java/dev/tommyjs/futur/joiner/ResultJoiner.java index 28046fb..9cdfe6d 100644 --- a/futur-api/src/main/java/dev/tommyjs/futur/joiner/ResultJoiner.java +++ b/futur-api/src/main/java/dev/tommyjs/futur/joiner/ResultJoiner.java @@ -5,25 +5,20 @@ import dev.tommyjs.futur.promise.PromiseCompletion; import dev.tommyjs.futur.promise.PromiseFactory; import dev.tommyjs.futur.util.ConcurrentResultArray; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import java.util.Iterator; import java.util.List; -import java.util.function.BiConsumer; public class ResultJoiner extends PromiseJoiner, Void, T, List> { - private final @Nullable BiConsumer exceptionHandler; private final ConcurrentResultArray results; public ResultJoiner( @NotNull PromiseFactory factory, @NotNull Iterator> promises, - @Nullable BiConsumer exceptionHandler, int expectedSize, boolean link ) { super(factory); - this.exceptionHandler = exceptionHandler; this.results = new ConcurrentResultArray<>(expectedSize); join(promises, link); } @@ -39,17 +34,8 @@ public class ResultJoiner extends PromiseJoiner, Void, T, List> } @Override - protected @Nullable Throwable onChildComplete(int index, Void key, @NotNull PromiseCompletion res) { - if (res.isError()) { - if (exceptionHandler == null) { - return res.getException(); - } - - exceptionHandler.accept(index, res.getException()); - } - + protected void onChildComplete(int index, Void key, @NotNull PromiseCompletion res) { results.set(index, res.getResult()); - return null; } @Override diff --git a/futur-api/src/main/java/dev/tommyjs/futur/joiner/VoidJoiner.java b/futur-api/src/main/java/dev/tommyjs/futur/joiner/VoidJoiner.java index 195f9ab..673c2c7 100644 --- a/futur-api/src/main/java/dev/tommyjs/futur/joiner/VoidJoiner.java +++ b/futur-api/src/main/java/dev/tommyjs/futur/joiner/VoidJoiner.java @@ -4,7 +4,6 @@ import dev.tommyjs.futur.promise.Promise; import dev.tommyjs.futur.promise.PromiseCompletion; import dev.tommyjs.futur.promise.PromiseFactory; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import java.util.Iterator; @@ -27,8 +26,8 @@ public class VoidJoiner extends PromiseJoiner, Void, Void, Void> { } @Override - protected @Nullable Throwable onChildComplete(int index, Void key, @NotNull PromiseCompletion completion) { - return completion.getException(); + protected void onChildComplete(int index, Void key, @NotNull PromiseCompletion completion) { + } @Override diff --git a/futur-api/src/main/java/dev/tommyjs/futur/promise/AbstractPromise.java b/futur-api/src/main/java/dev/tommyjs/futur/promise/AbstractPromise.java index c0538e2..c66ba1d 100644 --- a/futur-api/src/main/java/dev/tommyjs/futur/promise/AbstractPromise.java +++ b/futur-api/src/main/java/dev/tommyjs/futur/promise/AbstractPromise.java @@ -11,7 +11,10 @@ import org.slf4j.Logger; import java.lang.invoke.MethodHandles; import java.lang.invoke.VarHandle; -import java.util.*; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.Objects; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.AbstractQueuedSynchronizer; @@ -355,7 +358,7 @@ public abstract class AbstractPromise implements CompletablePromise thenPopulateReference(@NotNull AtomicReference reference) { - return thenApplyAsync(result -> { + return thenApply(result -> { reference.set(result); return result; }); @@ -400,7 +403,7 @@ public abstract class AbstractPromise implements CompletablePromise addAnyListener(PromiseListener listener) { Collection> prev = listeners, next = null; - for (boolean haveNext = false;;) { + for (boolean haveNext = false; ; ) { if (!haveNext) { next = prev == Collections.EMPTY_LIST ? new ConcurrentLinkedQueue<>() : prev; if (next != null) next.add(listener); @@ -474,6 +477,32 @@ public abstract class AbstractPromise implements CompletablePromise orDefault(@Nullable T defaultValue) { + return orDefault(_ -> defaultValue); + } + + @Override + public @NotNull Promise orDefault(@NotNull ExceptionalSupplier supplier) { + return orDefault(_ -> supplier.get()); + } + + @Override + public @NotNull Promise orDefault(@NotNull ExceptionalFunction function) { + CompletablePromise promise = getFactory().unresolved(); + addDirectListener(promise::complete, e -> { + try { + T result = function.apply(e); + promise.complete(result); + } catch (Exception ex) { + promise.completeExceptionally(ex); + } + }); + + PromiseUtil.propagateCancel(promise, this); + return promise; + } + @Override public @NotNull Promise timeout(long time, @NotNull TimeUnit unit) { Exception e = new CancellationException("Promise timed out after " + time + " " + unit.toString().toLowerCase()); @@ -517,7 +546,8 @@ public abstract class AbstractPromise implements CompletablePromise listener, PromiseCompletion completion) { try { getFactory().getAsyncExecutor().run(() -> callListenerNow(listener, completion)); - } catch (Throwable ignored) {} + } catch (Throwable ignored) { + } } @Override @@ -582,7 +612,7 @@ public abstract class AbstractPromise implements CompletablePromise implements PromiseFactory { @@ -71,24 +69,18 @@ public abstract class AbstractPromiseFactory implements PromiseFactory { } @Override - public @NotNull Promise> combine( - @NotNull Map> promises, - @Nullable BiConsumer exceptionHandler, - boolean link - ) { - if (promises.isEmpty()) return resolve(Collections.emptyMap()); - return new MappedResultJoiner<>(this, - promises.entrySet().iterator(), exceptionHandler, promises.size(), link).joined(); + public @NotNull Promise> combineMapped(@NotNull Iterator>> promises, int expectedSize, boolean link) { + if (!promises.hasNext()) return resolve(Collections.emptyMap()); + return new MappedResultJoiner<>(this, promises, expectedSize, link).joined(); } @Override public @NotNull Promise> combine( - @NotNull Iterator> promises, int expectedSize, - @Nullable BiConsumer exceptionHandler, boolean link + @NotNull Iterator> promises, + int expectedSize, boolean link ) { if (!promises.hasNext()) return resolve(Collections.emptyList()); - return new ResultJoiner<>( - this, promises, exceptionHandler, expectedSize, link).joined(); + return new ResultJoiner<>(this, promises, expectedSize, link).joined(); } @Override diff --git a/futur-api/src/main/java/dev/tommyjs/futur/promise/Promise.java b/futur-api/src/main/java/dev/tommyjs/futur/promise/Promise.java index 13bd4b8..f4628ce 100644 --- a/futur-api/src/main/java/dev/tommyjs/futur/promise/Promise.java +++ b/futur-api/src/main/java/dev/tommyjs/futur/promise/Promise.java @@ -430,6 +430,43 @@ public interface Promise { */ @NotNull Promise onCancel(@NotNull Consumer listener); + /** + * Creates a new promise that will always complete successfully - either with the result of this + * promise, or with the specified default value if this promise completes exceptionally. Cancelling + * the returned promise will cancel this promise, and consequently any previous promises in the chain. + * + * @param defaultValue the default value to complete the promise with if this promise completes exceptionally + * @return a new promise that completes with the result of this promise, or with the default value if this + * promise completes exceptionally + */ + @NotNull Promise orDefault(@Nullable T defaultValue); + + /** + * Creates a new promise that will attempt to always complete successfully - either with the result + * of this promise, or with the result of the specified supplier if this promise completes exceptionally. + * If an exception is encountered while executing the supplier, the promise will complete exceptionally + * with that exception. Cancelling the returned promise will cancel this promise, and consequently any + * previous promises in the chain. + * + * @param supplier the supplier to complete the promise with if this promise completes exceptionally + * @return a new promise that completes with the result of this promise, or with the result of the + * supplier if this promise completes exceptionally + */ + @NotNull Promise orDefault(@NotNull ExceptionalSupplier supplier); + + /** + * Creates a new promise that will attempt to always complete successfully - either with the result + * of this promise, or with the result of the specified function if this promise completes + * exceptionally. If an exception is encountered while executing the function, the promise will + * complete exceptionally with that exception. Cancelling the returned promise will cancel this + * promise, and consequently any previous promises in the chain. + * + * @param function the function to complete the promise with if this promise completes exceptionally + * @return a new promise that completes with the result of this promise, or with the result of the + * function if this promise completes exceptionally + */ + @NotNull Promise orDefault(@NotNull ExceptionalFunction function); + /** * Cancels the promise if not already completed after the specified timeout. This will result in * an exceptional completion with a {@link CancellationException}. diff --git a/futur-api/src/main/java/dev/tommyjs/futur/promise/PromiseFactory.java b/futur-api/src/main/java/dev/tommyjs/futur/promise/PromiseFactory.java index 33ed594..095f8f1 100644 --- a/futur-api/src/main/java/dev/tommyjs/futur/promise/PromiseFactory.java +++ b/futur-api/src/main/java/dev/tommyjs/futur/promise/PromiseFactory.java @@ -3,14 +3,14 @@ package dev.tommyjs.futur.promise; import dev.tommyjs.futur.executor.PromiseExecutor; import dev.tommyjs.futur.util.PromiseUtil; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ScheduledExecutorService; -import java.util.function.BiConsumer; +import java.util.function.Function; import java.util.stream.Stream; +import java.util.stream.StreamSupport; public interface PromiseFactory { @@ -122,32 +122,26 @@ public interface PromiseFactory { * Combines key-value pairs of inputs to promises into a single promise that completes with key-value * pairs of inputs to outputs when all promises complete. If {@code link} is {@code true} * and any promise completes exceptionally, the other promises will be cancelled and the output - * promise will complete exceptionally. If an exception handler is present, promises that fail - * will not cause this behaviour, and instead the exception handler will be called with the key - * that failed and the exception. + * promise will complete exceptionally. * - * @param promises the input promises - * @param exceptionHandler the exception handler - * @param link whether to cancel all promises on any exceptional completions + * @param promises the input promises + * @param link whether to cancel all promises on any exceptional completions * @return the combined promise */ - @NotNull Promise> combine(@NotNull Map> promises, - @Nullable BiConsumer exceptionHandler, - boolean link); + @NotNull Promise> combineMapped(@NotNull Iterator>> promises, + int expectedSize, boolean link); /** * Combines key-value pairs of inputs to promises into a single promise that completes with key-value * pairs of inputs to outputs when all promises complete. If any promise completes exceptionally, - * the exception handler will be called with the key that failed and the exception. The output promise - * will always complete successfully regardless of whether input promises fail. + * the output promise will complete exceptionally. * - * @param promises the input promises - * @param exceptionHandler the exception handler + * @param promises the input promises * @return the combined promise */ - default @NotNull Promise> combine(@NotNull Map> promises, - @NotNull BiConsumer exceptionHandler) { - return combine(promises, exceptionHandler, true); + default @NotNull Promise> combineMapped(@NotNull Collection>> promises, + boolean link) { + return combineMapped(promises.iterator(), promises.size(), link); } /** @@ -160,8 +154,9 @@ public interface PromiseFactory { * @param link whether to cancel all promises on any exceptional completions * @return the combined promise */ - default @NotNull Promise> combine(@NotNull Map> promises, boolean link) { - return combine(promises, null, link); + default @NotNull Promise> combineMapped(@NotNull Map> promises, + boolean link) { + return combineMapped(promises.entrySet().iterator(), promises.size(), link); } /** @@ -172,8 +167,89 @@ public interface PromiseFactory { * @param promises the input promises * @return the combined promise */ + default @NotNull Promise> combineMapped(@NotNull Map> promises) { + return combineMapped(promises, true); + } + + /** + * Combines key-value pairs of inputs to promises into a single promise that completes with key-value + * pairs of inputs to outputs when all promises complete. If any promise completes exceptionally, + * the output promise will complete exceptionally. + * + * @param promises the input promises + * @return the combined promise + */ + default @NotNull Promise> combineMapped(@NotNull Collection>> promises) { + return combineMapped(promises, true); + } + + /** + * Combines key-value pairs of inputs to promises into a single promise that completes with key-value + * pairs of inputs to outputs when all promises complete. If {@code link} is {@code true} + * and any promise completes exceptionally, the other promises will be cancelled and the output + * promise will complete exceptionally. + * + * @param promises the input promises + * @param link whether to cancel all promises on any exceptional completions + * @return the combined promise + */ + default @NotNull Promise> combineMapped(@NotNull Stream>> promises, + boolean link) { + return combineMapped(promises.iterator(), PromiseUtil.estimateSize(promises), link); + } + + /** + * Combines key-value pairs of inputs to promises into a single promise that completes with key-value + * pairs of inputs to outputs when all promises complete. If any promise completes exceptionally, + * the output promise will complete exceptionally. + * + * @param promises the input promises + * @return the combined promise + */ + default @NotNull Promise> combineMapped(@NotNull Stream>> promises) { + return combineMapped(promises, true); + } + + /** + * Combines key-value pairs of inputs to promises into a single promise that completes with key-value + * pairs of inputs to outputs when all promises complete. If {@code link} is {@code true} + * and any promise completes exceptionally, the other promises will be cancelled and the output + * promise will complete exceptionally. + * + * @param keys the input keys + * @param mapper the function to map keys to value promises + * @param link whether to cancel all promises on any exceptional completions + * @return the combined promise + */ + default @NotNull Promise> combineMapped(@NotNull Iterable keys, + @NotNull Function> mapper, + boolean link) { + return combineMapped(StreamSupport.stream(keys.spliterator(), true) + .map(k -> new AbstractMap.SimpleImmutableEntry<>(k, mapper.apply(k))), link); + } + + /** + * Combines key-value pairs of inputs to promises into a single promise that completes with key-value + * pairs of inputs to outputs when all promises complete. If any promise completes exceptionally, + * the output promise will complete exceptionally. + * + * @param keys the input keys + * @param mapper the function to map keys to value promises + * @return the combined promise + */ + default @NotNull Promise> combineMapped(@NotNull Iterable keys, + @NotNull Function> mapper) { + return combineMapped(keys, mapper, true); + } + + @Deprecated + default @NotNull Promise> combine(@NotNull Map> promises, boolean link) { + return combineMapped(promises, link); + } + + @Deprecated default @NotNull Promise> combine(@NotNull Map> promises) { - return combine(promises, null, true); + return combineMapped(promises); } /** @@ -183,46 +259,13 @@ public interface PromiseFactory { * handler is present, promises that fail will not cause this behaviour, and instead the exception * handler will be called with the index that failed and the exception. * - * @param promises the input promises - * @param exceptionHandler the exception handler - * @param link whether to cancel all promises on any exceptional completions + * @param promises the input promises + * @param link whether to cancel all promises on any exceptional completions * @return the combined promise */ @NotNull Promise> combine(@NotNull Iterator> promises, int expectedSize, - @Nullable BiConsumer exceptionHandler, boolean link); - /** - * Combines a collection of promises into a single promise that completes with a list of results when all - * promises complete. If any promise completes exceptionally, the exception handler will be called with - * the index that failed and the exception. The output promise will always complete successfully regardless - * of whether input promises fail. - * - * @param promises the input promises - * @param exceptionHandler the exception handler - * @return the combined promise - */ - default @NotNull Promise> combine(@NotNull Collection> promises, - @NotNull BiConsumer exceptionHandler, - boolean link) { - return combine(promises.iterator(), promises.size(), exceptionHandler, link); - } - - /** - * Combines a collection of promises into a single promise that completes with a list of results when all - * promises complete. If any promise completes exceptionally, the exception handler will be called with - * the index that failed and the exception. The output promise will always complete successfully regardless - * of whether input promises fail. - * - * @param promises the input promises - * @param exceptionHandler the exception handler - * @return the combined promise - */ - default @NotNull Promise> combine(@NotNull Collection> promises, - @NotNull BiConsumer exceptionHandler) { - return combine(promises.iterator(), promises.size(), exceptionHandler, true); - } - /** * Combines a collection of promises into a single promise that completes with a list of results when all * promises complete. If {@code link} is {@code true} and any promise completes exceptionally, all @@ -233,7 +276,7 @@ public interface PromiseFactory { * @return the combined promise */ default @NotNull Promise> combine(@NotNull Collection> promises, boolean link) { - return combine(promises.iterator(), promises.size(), null, link); + return combine(promises.iterator(), promises.size(), link); } /** @@ -244,7 +287,7 @@ public interface PromiseFactory { * @return the combined promise */ default @NotNull Promise> combine(@NotNull Collection> promises) { - return combine(promises.iterator(), promises.size(), null, true); + return combine(promises, true); } /** @@ -254,54 +297,24 @@ public interface PromiseFactory { * handler is present, promises that fail will not cause this behaviour, and instead the exception * handler will be called with the index that failed and the exception. * - * @param promises the input promises - * @param exceptionHandler the exception handler - * @param link whether to cancel all promises on any exceptional completions - * @return the combined promise - */ - default @NotNull Promise> combine(@NotNull Stream> promises, - @NotNull BiConsumer exceptionHandler, - boolean link) { - return combine(promises.iterator(), PromiseUtil.estimateSize(promises), exceptionHandler, link); - } - - /** - * Combines a stream of promises into a single promise that completes with a list of results when all - * promises complete. If any promise completes exceptionally, the exception handler will be called with - * the index that failed and the exception. The output promise will always complete successfully regardless - * of whether input promises fail. - * - * @param promises the input promises - * @param exceptionHandler the exception handler - * @return the combined promise - */ - default @NotNull Promise> combine(@NotNull Stream> promises, - @NotNull BiConsumer exceptionHandler) { - return combine(promises.iterator(), PromiseUtil.estimateSize(promises), exceptionHandler, true); - } - - /** - * Combines a stream of promises into a single promise that completes with a list of results when all - * promises complete. If {@code link} is {@code true} and any promise completes exceptionally, all - * other promises will be cancelled and the output promise will complete exceptionally. - * * @param promises the input promises * @param link whether to cancel all promises on any exceptional completions * @return the combined promise */ default @NotNull Promise> combine(@NotNull Stream> promises, boolean link) { - return combine(promises.iterator(), PromiseUtil.estimateSize(promises), null, link); + return combine(promises.iterator(), PromiseUtil.estimateSize(promises), link); } /** * Combines a stream of promises into a single promise that completes with a list of results when all - * promises complete. If any promise completes exceptionally, the output promise will complete exceptionally. + * promises complete. The output promise will always complete successfully regardless of whether input + * promises fail. * * @param promises the input promises * @return the combined promise */ default @NotNull Promise> combine(@NotNull Stream> promises) { - return combine(promises.iterator(), PromiseUtil.estimateSize(promises), null, true); + return combine(promises.iterator(), PromiseUtil.estimateSize(promises), true); } /** diff --git a/futur-api/src/test/java/dev/tommyjs/futur/PromiseTests.java b/futur-api/src/test/java/dev/tommyjs/futur/PromiseTests.java index f9e245a..37ff3ad 100644 --- a/futur-api/src/test/java/dev/tommyjs/futur/PromiseTests.java +++ b/futur-api/src/test/java/dev/tommyjs/futur/PromiseTests.java @@ -1,5 +1,6 @@ package dev.tommyjs.futur; +import dev.tommyjs.futur.promise.CompletablePromise; import dev.tommyjs.futur.promise.Promise; import dev.tommyjs.futur.promise.PromiseFactory; import org.junit.jupiter.api.Test; @@ -10,6 +11,7 @@ import java.util.List; import java.util.Map; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; public final class PromiseTests { @@ -96,7 +98,7 @@ public final class PromiseTests { ) .get(100L, TimeUnit.MILLISECONDS); - promises.combine( + promises.combineMapped( Map.of( "a", promises.start().thenRunDelayedAsync(() -> {}, 50, TimeUnit.MILLISECONDS), "b", promises.start().thenRunDelayedAsync(() -> {}, 50, TimeUnit.MILLISECONDS) @@ -139,7 +141,7 @@ public final class PromiseTests { .cancel(); var finished5 = new AtomicBoolean(); - promises.combine( + promises.combineMapped( Map.of( "a", promises.start().thenRunDelayedAsync(() -> finished5.set(true), 50, TimeUnit.MILLISECONDS), "b", promises.start().thenRunDelayedAsync(() -> finished5.set(true), 50, TimeUnit.MILLISECONDS) @@ -165,4 +167,47 @@ public final class PromiseTests { ).await(); } + @Test + public void testOrDefault() { + CompletablePromise promise = promises.unresolved(); + AtomicReference res = new AtomicReference<>(); + promise.orDefault(10).thenPopulateReference(res); + promise.completeExceptionally(new IllegalStateException("Test")); + assert res.get() == 10; + } + + @Test + public void testOrDefaultSupplier() { + CompletablePromise promise = promises.unresolved(); + AtomicReference res = new AtomicReference<>(); + promise.orDefault(() -> 10).thenPopulateReference(res); + promise.completeExceptionally(new IllegalStateException("Test")); + assert res.get() == 10; + } + + @Test + public void testOrDefaultFunction() { + CompletablePromise promise = promises.unresolved(); + AtomicReference res = new AtomicReference<>(); + promise.orDefault(e -> { + assert e instanceof IllegalStateException; + return 10; + }).thenPopulateReference(res); + promise.completeExceptionally(new IllegalStateException("Test")); + assert res.get() == 10; + } + + @Test + public void testOrDefaultError() { + CompletablePromise promise = promises.unresolved(); + AtomicReference res = new AtomicReference<>(); + Promise promise2 = promise.orDefault(e -> { + throw new IllegalStateException("Test"); + }).thenPopulateReference(res); + promise.completeExceptionally(new IllegalStateException("Test")); + + assert res.get() == null; + assert promise2.getCompletion() != null && promise2.getCompletion().getException() instanceof IllegalStateException; + } + } diff --git a/futur-lazy/src/main/java/dev/tommyjs/futur/lazy/Promises.java b/futur-lazy/src/main/java/dev/tommyjs/futur/lazy/Promises.java index 263b836..633328f 100644 --- a/futur-lazy/src/main/java/dev/tommyjs/futur/lazy/Promises.java +++ b/futur-lazy/src/main/java/dev/tommyjs/futur/lazy/Promises.java @@ -16,6 +16,7 @@ import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.function.BiConsumer; +import java.util.function.Function; import java.util.stream.Stream; public final class Promises { @@ -91,7 +92,7 @@ public final class Promises { * @return the combined promise */ public static @NotNull Promise> combine(@NotNull Promise p1, @NotNull Promise p2, - boolean link) { + boolean link) { return factory.combine(p1, p2, link); } @@ -112,34 +113,28 @@ public final class Promises { * Combines key-value pairs of inputs to promises into a single promise that completes with key-value * pairs of inputs to outputs when all promises complete. If {@code link} is {@code true} * and any promise completes exceptionally, the other promises will be cancelled and the output - * promise will complete exceptionally. If an exception handler is present, promises that fail - * will not cause this behaviour, and instead the exception handler will be called with the key - * that failed and the exception. + * promise will complete exceptionally. * - * @param promises the input promises - * @param exceptionHandler the exception handler - * @param link whether to cancel all promises on any exceptional completions + * @param promises the input promises + * @param link whether to cancel all promises on any exceptional completions * @return the combined promise */ - public static @NotNull Promise> combine(@NotNull Map> promises, - @Nullable BiConsumer exceptionHandler, - boolean link) { - return factory.combine(promises, exceptionHandler, link); + public static @NotNull Promise> combineMapped(@NotNull Iterator>> promises, + int expectedSize, boolean link) { + return factory.combineMapped(promises, expectedSize, link); } /** * Combines key-value pairs of inputs to promises into a single promise that completes with key-value * pairs of inputs to outputs when all promises complete. If any promise completes exceptionally, - * the exception handler will be called with the key that failed and the exception. The output promise - * will always complete successfully regardless of whether input promises fail. + * the output promise will complete exceptionally. * - * @param promises the input promises - * @param exceptionHandler the exception handler + * @param promises the input promises * @return the combined promise */ - public static @NotNull Promise> combine(@NotNull Map> promises, - @NotNull BiConsumer exceptionHandler) { - return factory.combine(promises, exceptionHandler); + public static @NotNull Promise> combineMapped(@NotNull Collection>> promises, + boolean link) { + return factory.combineMapped(promises, link); } /** @@ -152,8 +147,9 @@ public final class Promises { * @param link whether to cancel all promises on any exceptional completions * @return the combined promise */ - public static @NotNull Promise> combine(@NotNull Map> promises, boolean link) { - return factory.combine(promises, link); + public static @NotNull Promise> combineMapped(@NotNull Map> promises, + boolean link) { + return factory.combineMapped(promises, link); } /** @@ -164,6 +160,86 @@ public final class Promises { * @param promises the input promises * @return the combined promise */ + public static @NotNull Promise> combineMapped(@NotNull Map> promises) { + return factory.combineMapped(promises); + } + + /** + * Combines key-value pairs of inputs to promises into a single promise that completes with key-value + * pairs of inputs to outputs when all promises complete. If any promise completes exceptionally, + * the output promise will complete exceptionally. + * + * @param promises the input promises + * @return the combined promise + */ + public static @NotNull Promise> combineMapped(@NotNull Collection>> promises) { + return factory.combineMapped(promises); + } + + /** + * Combines key-value pairs of inputs to promises into a single promise that completes with key-value + * pairs of inputs to outputs when all promises complete. If {@code link} is {@code true} + * and any promise completes exceptionally, the other promises will be cancelled and the output + * promise will complete exceptionally. + * + * @param promises the input promises + * @param link whether to cancel all promises on any exceptional completions + * @return the combined promise + */ + public static @NotNull Promise> combineMapped(@NotNull Stream>> promises, + boolean link) { + return factory.combineMapped(promises, link); + } + + /** + * Combines key-value pairs of inputs to promises into a single promise that completes with key-value + * pairs of inputs to outputs when all promises complete. If any promise completes exceptionally, + * the output promise will complete exceptionally. + * + * @param promises the input promises + * @return the combined promise + */ + public static @NotNull Promise> combineMapped(@NotNull Stream>> promises) { + return factory.combineMapped(promises); + } + + /** + * Combines key-value pairs of inputs to promises into a single promise that completes with key-value + * pairs of inputs to outputs when all promises complete. If {@code link} is {@code true} + * and any promise completes exceptionally, the other promises will be cancelled and the output + * promise will complete exceptionally. + * + * @param keys the input keys + * @param mapper the function to map keys to value promises + * @param link whether to cancel all promises on any exceptional completions + * @return the combined promise + */ + public static @NotNull Promise> combineMapped(@NotNull Iterable keys, + @NotNull Function> mapper, + boolean link) { + return factory.combineMapped(keys, mapper, link); + } + + /** + * Combines key-value pairs of inputs to promises into a single promise that completes with key-value + * pairs of inputs to outputs when all promises complete. If any promise completes exceptionally, + * the output promise will complete exceptionally. + * + * @param keys the input keys + * @param mapper the function to map keys to value promises + * @return the combined promise + */ + public static @NotNull Promise> combineMapped(@NotNull Iterable keys, + @NotNull Function> mapper) { + return factory.combineMapped(keys, mapper); + } + + @Deprecated + public static @NotNull Promise> combine(@NotNull Map> promises, boolean link) { + return factory.combine(promises, link); + } + + @Deprecated public static @NotNull Promise> combine(@NotNull Map> promises) { return factory.combine(promises); } @@ -175,46 +251,13 @@ public final class Promises { * handler is present, promises that fail will not cause this behaviour, and instead the exception * handler will be called with the index that failed and the exception. * - * @param promises the input promises - * @param exceptionHandler the exception handler - * @param link whether to cancel all promises on any exceptional completions + * @param promises the input promises + * @param link whether to cancel all promises on any exceptional completions * @return the combined promise */ public static @NotNull Promise> combine(@NotNull Iterator> promises, int expectedSize, - @Nullable BiConsumer exceptionHandler, - boolean link) { - return factory.combine(promises, expectedSize, exceptionHandler, link); - } - - /** - * Combines a collection of promises into a single promise that completes with a list of results when all - * promises complete. If any promise completes exceptionally, the exception handler will be called with - * the index that failed and the exception. The output promise will always complete successfully regardless - * of whether input promises fail. - * - * @param promises the input promises - * @param exceptionHandler the exception handler - * @return the combined promise - */ - public static @NotNull Promise> combine(@NotNull Collection> promises, - @NotNull BiConsumer exceptionHandler, - boolean link) { - return factory.combine(promises, exceptionHandler, link); - } - - /** - * Combines a collection of promises into a single promise that completes with a list of results when all - * promises complete. If any promise completes exceptionally, the exception handler will be called with - * the index that failed and the exception. The output promise will always complete successfully regardless - * of whether input promises fail. - * - * @param promises the input promises - * @param exceptionHandler the exception handler - * @return the combined promise - */ - public static @NotNull Promise> combine(@NotNull Collection> promises, - @NotNull BiConsumer exceptionHandler) { - return factory.combine(promises, exceptionHandler); + boolean link) { + return factory.combine(promises, expectedSize, link); } /** @@ -248,37 +291,6 @@ public final class Promises { * handler is present, promises that fail will not cause this behaviour, and instead the exception * handler will be called with the index that failed and the exception. * - * @param promises the input promises - * @param exceptionHandler the exception handler - * @param link whether to cancel all promises on any exceptional completions - * @return the combined promise - */ - public static @NotNull Promise> combine(@NotNull Stream> promises, - @NotNull BiConsumer exceptionHandler, - boolean link) { - return factory.combine(promises, exceptionHandler, link); - } - - /** - * Combines a stream of promises into a single promise that completes with a list of results when all - * promises complete. If any promise completes exceptionally, the exception handler will be called with - * the index that failed and the exception. The output promise will always complete successfully regardless - * of whether input promises fail. - * - * @param promises the input promises - * @param exceptionHandler the exception handler - * @return the combined promise - */ - public static @NotNull Promise> combine(@NotNull Stream> promises, - @NotNull BiConsumer exceptionHandler) { - return factory.combine(promises, exceptionHandler); - } - - /** - * Combines a stream of promises into a single promise that completes with a list of results when all - * promises complete. If {@code link} is {@code true} and any promise completes exceptionally, all - * other promises will be cancelled and the output promise will complete exceptionally. - * * @param promises the input promises * @param link whether to cancel all promises on any exceptional completions * @return the combined promise @@ -289,7 +301,8 @@ public final class Promises { /** * Combines a stream of promises into a single promise that completes with a list of results when all - * promises complete. If any promise completes exceptionally, the output promise will complete exceptionally. + * promises complete. The output promise will always complete successfully regardless of whether input + * promises fail. * * @param promises the input promises * @return the combined promise @@ -310,7 +323,7 @@ public final class Promises { * @return the combined promise */ public static @NotNull Promise>> allSettled(@NotNull Iterator> promises, - int expectedSize, boolean link) { + int expectedSize, boolean link) { return factory.allSettled(promises, expectedSize, link); } @@ -325,7 +338,7 @@ public final class Promises { * @return the combined promise */ public static @NotNull Promise>> allSettled(@NotNull Collection> promises, - boolean link) { + boolean link) { return factory.allSettled(promises, link); } @@ -351,7 +364,7 @@ public final class Promises { * @return the combined promise */ public static @NotNull Promise>> allSettled(@NotNull Stream> promises, - boolean link) { + boolean link) { return factory.allSettled(promises, link); } @@ -377,7 +390,7 @@ public final class Promises { * @return the combined promise */ public static @NotNull Promise>> allSettled(boolean link, - @NotNull Promise... promises) { + @NotNull Promise... promises) { return factory.allSettled(link, promises); }