From 9e392c91ba2bb2a001c6e65b5c0540c26141ed41 Mon Sep 17 00:00:00 2001 From: WhatCats Date: Mon, 6 Jan 2025 14:06:39 +0100 Subject: [PATCH 01/16] optimizations, more comfortable PromiseFactory api and support virtual threaded executors --- build.gradle | 11 +- .../futur/executor/DualPoolExecutor.java | 39 --- .../futur/executor/ExecutorServiceImpl.java | 32 ++ .../futur/executor/PromiseExecutor.java | 26 +- .../futur/executor/SinglePoolExecutor.java | 18 - .../futur/executor/VirtualThreadImpl.java | 31 ++ .../futur/function/ExceptionalConsumer.java | 2 +- .../futur/function/ExceptionalFunction.java | 2 +- .../futur/function/ExceptionalRunnable.java | 2 +- .../futur/function/ExceptionalSupplier.java | 2 +- .../dev/tommyjs/futur/impl/SimplePromise.java | 27 -- .../futur/impl/SimplePromiseFactory.java | 34 -- .../futur/joiner/CompletionJoiner.java | 48 +++ .../futur/joiner/ConcurrentResultArray.java | 32 ++ .../futur/joiner/MappedResultJoiner.java | 60 ++++ .../tommyjs/futur/joiner/PromiseJoiner.java | 66 ++++ .../tommyjs/futur/joiner/ResultJoiner.java | 56 +++ .../dev/tommyjs/futur/joiner/VoidJoiner.java | 39 +++ .../futur/promise/AbstractPromise.java | 327 +++++++++--------- .../futur/promise/AbstractPromiseFactory.java | 186 ++++------ .../futur/promise/CompletablePromise.java | 12 + .../promise/DeferredExecutionException.java | 11 + .../dev/tommyjs/futur/promise/Promise.java | 76 ++-- .../futur/promise/PromiseCompletion.java | 8 +- .../tommyjs/futur/promise/PromiseFactory.java | 173 ++++++--- .../futur/promise/PromiseFactoryImpl.java | 43 +++ .../tommyjs/futur/promise/PromiseImpl.java | 18 + .../dev/tommyjs/futur/promise/Promises.java | 90 ----- .../java/dev/tommyjs/futur/PromiseTests.java | 125 +++---- futur-lazy/build.gradle | 3 + .../java/dev/tommyjs/futur/lazy/Promises.java | 208 +++++++++++ futur-static/build.gradle | 6 - .../dev/tommyjs/futur/lazy/PromiseUtil.java | 127 ------- .../futur/lazy/StaticPromiseFactory.java | 38 -- gradle/wrapper/gradle-wrapper.jar | Bin 61608 -> 43583 bytes gradle/wrapper/gradle-wrapper.properties | 3 +- gradlew | 33 +- gradlew.bat | 22 +- settings.gradle | 2 +- 39 files changed, 1205 insertions(+), 833 deletions(-) delete mode 100644 futur-api/src/main/java/dev/tommyjs/futur/executor/DualPoolExecutor.java create mode 100644 futur-api/src/main/java/dev/tommyjs/futur/executor/ExecutorServiceImpl.java delete mode 100644 futur-api/src/main/java/dev/tommyjs/futur/executor/SinglePoolExecutor.java create mode 100644 futur-api/src/main/java/dev/tommyjs/futur/executor/VirtualThreadImpl.java delete mode 100644 futur-api/src/main/java/dev/tommyjs/futur/impl/SimplePromise.java delete mode 100644 futur-api/src/main/java/dev/tommyjs/futur/impl/SimplePromiseFactory.java create mode 100644 futur-api/src/main/java/dev/tommyjs/futur/joiner/CompletionJoiner.java create mode 100644 futur-api/src/main/java/dev/tommyjs/futur/joiner/ConcurrentResultArray.java create mode 100644 futur-api/src/main/java/dev/tommyjs/futur/joiner/MappedResultJoiner.java create mode 100644 futur-api/src/main/java/dev/tommyjs/futur/joiner/PromiseJoiner.java create mode 100644 futur-api/src/main/java/dev/tommyjs/futur/joiner/ResultJoiner.java create mode 100644 futur-api/src/main/java/dev/tommyjs/futur/joiner/VoidJoiner.java create mode 100644 futur-api/src/main/java/dev/tommyjs/futur/promise/CompletablePromise.java create mode 100644 futur-api/src/main/java/dev/tommyjs/futur/promise/DeferredExecutionException.java create mode 100644 futur-api/src/main/java/dev/tommyjs/futur/promise/PromiseFactoryImpl.java create mode 100644 futur-api/src/main/java/dev/tommyjs/futur/promise/PromiseImpl.java delete mode 100644 futur-api/src/main/java/dev/tommyjs/futur/promise/Promises.java create mode 100644 futur-lazy/build.gradle create mode 100644 futur-lazy/src/main/java/dev/tommyjs/futur/lazy/Promises.java delete mode 100644 futur-static/build.gradle delete mode 100644 futur-static/src/main/java/dev/tommyjs/futur/lazy/PromiseUtil.java delete mode 100644 futur-static/src/main/java/dev/tommyjs/futur/lazy/StaticPromiseFactory.java diff --git a/build.gradle b/build.gradle index efb73d0..9b2d364 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ plugins { - id 'java' + id 'java-library' id 'com.github.johnrengelman.shadow' version '8.1.1' - id 'io.github.gradle-nexus.publish-plugin' version '1.3.0' + id 'io.github.gradle-nexus.publish-plugin' version '2.0.0' } nexusPublishing { @@ -14,9 +14,9 @@ nexusPublishing { subprojects { group = 'dev.tommyjs' - version = '2.3.4' + version = '2.4' - apply plugin: 'java' + apply plugin: 'java-library' apply plugin: 'com.github.johnrengelman.shadow' tasks { @@ -30,8 +30,9 @@ subprojects { } dependencies { - implementation 'org.jetbrains:annotations:24.1.0' + compileOnly 'org.jetbrains:annotations:24.1.0' implementation 'org.slf4j:slf4j-api:2.0.12' +2 testRuntimeOnly 'org.junit.platform:junit-platform-launcher' testImplementation 'io.projectreactor:reactor-core:3.6.4' testImplementation 'org.junit.jupiter:junit-jupiter:5.8.1' diff --git a/futur-api/src/main/java/dev/tommyjs/futur/executor/DualPoolExecutor.java b/futur-api/src/main/java/dev/tommyjs/futur/executor/DualPoolExecutor.java deleted file mode 100644 index df0d998..0000000 --- a/futur-api/src/main/java/dev/tommyjs/futur/executor/DualPoolExecutor.java +++ /dev/null @@ -1,39 +0,0 @@ -package dev.tommyjs.futur.executor; - -import org.jetbrains.annotations.NotNull; - -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; - -public class DualPoolExecutor implements PromiseExecutor> { - - private final @NotNull ScheduledExecutorService syncSvc; - private final @NotNull ScheduledExecutorService asyncSvc; - - public DualPoolExecutor(@NotNull ScheduledExecutorService syncSvc, @NotNull ScheduledExecutorService asyncSvc) { - this.syncSvc = syncSvc; - this.asyncSvc = asyncSvc; - } - - public static @NotNull DualPoolExecutor create(int asyncPoolSize) { - return new DualPoolExecutor(Executors.newSingleThreadScheduledExecutor(), Executors.newScheduledThreadPool(asyncPoolSize)); - } - - @Override - public Future runSync(@NotNull Runnable task, long delay, @NotNull TimeUnit unit) { - return syncSvc.schedule(task, delay, unit); - } - - @Override - public Future runAsync(@NotNull Runnable task, long delay, @NotNull TimeUnit unit) { - return asyncSvc.schedule(task, delay, unit); - } - - @Override - public void cancel(Future task) { - task.cancel(true); - } - -} diff --git a/futur-api/src/main/java/dev/tommyjs/futur/executor/ExecutorServiceImpl.java b/futur-api/src/main/java/dev/tommyjs/futur/executor/ExecutorServiceImpl.java new file mode 100644 index 0000000..52e2065 --- /dev/null +++ b/futur-api/src/main/java/dev/tommyjs/futur/executor/ExecutorServiceImpl.java @@ -0,0 +1,32 @@ +package dev.tommyjs.futur.executor; + +import org.jetbrains.annotations.NotNull; + +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +class ExecutorServiceImpl implements PromiseExecutor> { + + private final ScheduledExecutorService service; + + public ExecutorServiceImpl(@NotNull ScheduledExecutorService service) { + this.service = service; + } + + @Override + public Future run(@NotNull Runnable task) { + return service.submit(task); + } + + @Override + public Future run(@NotNull Runnable task, long delay, @NotNull TimeUnit unit) { + return service.schedule(task, delay, unit); + } + + @Override + public void cancel(Future task) { + task.cancel(true); + } + +} diff --git a/futur-api/src/main/java/dev/tommyjs/futur/executor/PromiseExecutor.java b/futur-api/src/main/java/dev/tommyjs/futur/executor/PromiseExecutor.java index caeaad8..4ab7637 100644 --- a/futur-api/src/main/java/dev/tommyjs/futur/executor/PromiseExecutor.java +++ b/futur-api/src/main/java/dev/tommyjs/futur/executor/PromiseExecutor.java @@ -2,22 +2,32 @@ package dev.tommyjs.futur.executor; import org.jetbrains.annotations.NotNull; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; public interface PromiseExecutor { - T runSync(@NotNull Runnable task, long delay, @NotNull TimeUnit unit); - - T runAsync(@NotNull Runnable task, long delay, @NotNull TimeUnit unit); - - default T runSync(@NotNull Runnable task) { - return runSync(task, 0L, TimeUnit.MILLISECONDS); + static PromiseExecutor virtualThreaded() { + return new VirtualThreadImpl(); } - default T runAsync(@NotNull Runnable task) { - return runAsync(task, 0L, TimeUnit.MILLISECONDS); + static PromiseExecutor singleThreaded() { + return of(Executors.newSingleThreadScheduledExecutor()); } + static PromiseExecutor multiThreaded(int threads) { + return of(Executors.newScheduledThreadPool(threads)); + } + + static PromiseExecutor of(@NotNull ScheduledExecutorService service) { + return new ExecutorServiceImpl(service); + } + + T run(@NotNull Runnable task) throws Exception; + + T run(@NotNull Runnable task, long delay, @NotNull TimeUnit unit) throws Exception; + void cancel(T task); } diff --git a/futur-api/src/main/java/dev/tommyjs/futur/executor/SinglePoolExecutor.java b/futur-api/src/main/java/dev/tommyjs/futur/executor/SinglePoolExecutor.java deleted file mode 100644 index c63dab6..0000000 --- a/futur-api/src/main/java/dev/tommyjs/futur/executor/SinglePoolExecutor.java +++ /dev/null @@ -1,18 +0,0 @@ -package dev.tommyjs.futur.executor; - -import org.jetbrains.annotations.NotNull; - -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; - -public class SinglePoolExecutor extends DualPoolExecutor { - - public SinglePoolExecutor(@NotNull ScheduledExecutorService service) { - super(service, service); - } - - public static @NotNull SinglePoolExecutor create(int threadPoolSize) { - return new SinglePoolExecutor(Executors.newScheduledThreadPool(threadPoolSize)); - } - -} diff --git a/futur-api/src/main/java/dev/tommyjs/futur/executor/VirtualThreadImpl.java b/futur-api/src/main/java/dev/tommyjs/futur/executor/VirtualThreadImpl.java new file mode 100644 index 0000000..95faa7c --- /dev/null +++ b/futur-api/src/main/java/dev/tommyjs/futur/executor/VirtualThreadImpl.java @@ -0,0 +1,31 @@ +package dev.tommyjs.futur.executor; + +import org.jetbrains.annotations.NotNull; + +import java.util.concurrent.TimeUnit; + +class VirtualThreadImpl implements PromiseExecutor { + + @Override + public Thread run(@NotNull Runnable task) { + return Thread.ofVirtual().start(task); + } + + @Override + public Thread run(@NotNull Runnable task, long delay, @NotNull TimeUnit unit) { + return Thread.ofVirtual().start(() -> { + try { + Thread.sleep(unit.toMillis(delay)); + } catch (InterruptedException e) { + return; + } + task.run(); + }); + } + + @Override + public void cancel(Thread task) { + task.interrupt(); + } + +} \ No newline at end of file diff --git a/futur-api/src/main/java/dev/tommyjs/futur/function/ExceptionalConsumer.java b/futur-api/src/main/java/dev/tommyjs/futur/function/ExceptionalConsumer.java index 3998e57..cb84eff 100644 --- a/futur-api/src/main/java/dev/tommyjs/futur/function/ExceptionalConsumer.java +++ b/futur-api/src/main/java/dev/tommyjs/futur/function/ExceptionalConsumer.java @@ -3,6 +3,6 @@ package dev.tommyjs.futur.function; @FunctionalInterface public interface ExceptionalConsumer { - void accept(T value) throws Throwable; + void accept(T value) throws Exception; } diff --git a/futur-api/src/main/java/dev/tommyjs/futur/function/ExceptionalFunction.java b/futur-api/src/main/java/dev/tommyjs/futur/function/ExceptionalFunction.java index ebebf5f..9d32b45 100644 --- a/futur-api/src/main/java/dev/tommyjs/futur/function/ExceptionalFunction.java +++ b/futur-api/src/main/java/dev/tommyjs/futur/function/ExceptionalFunction.java @@ -3,6 +3,6 @@ package dev.tommyjs.futur.function; @FunctionalInterface public interface ExceptionalFunction { - V apply(K value) throws Throwable; + V apply(K value) throws Exception; } diff --git a/futur-api/src/main/java/dev/tommyjs/futur/function/ExceptionalRunnable.java b/futur-api/src/main/java/dev/tommyjs/futur/function/ExceptionalRunnable.java index c4b8002..d426a9c 100644 --- a/futur-api/src/main/java/dev/tommyjs/futur/function/ExceptionalRunnable.java +++ b/futur-api/src/main/java/dev/tommyjs/futur/function/ExceptionalRunnable.java @@ -3,6 +3,6 @@ package dev.tommyjs.futur.function; @FunctionalInterface public interface ExceptionalRunnable { - void run() throws Throwable; + void run() throws Exception; } diff --git a/futur-api/src/main/java/dev/tommyjs/futur/function/ExceptionalSupplier.java b/futur-api/src/main/java/dev/tommyjs/futur/function/ExceptionalSupplier.java index e47f977..7e62218 100644 --- a/futur-api/src/main/java/dev/tommyjs/futur/function/ExceptionalSupplier.java +++ b/futur-api/src/main/java/dev/tommyjs/futur/function/ExceptionalSupplier.java @@ -3,6 +3,6 @@ package dev.tommyjs.futur.function; @FunctionalInterface public interface ExceptionalSupplier { - T get() throws Throwable; + T get() throws Exception; } diff --git a/futur-api/src/main/java/dev/tommyjs/futur/impl/SimplePromise.java b/futur-api/src/main/java/dev/tommyjs/futur/impl/SimplePromise.java deleted file mode 100644 index 8427c8a..0000000 --- a/futur-api/src/main/java/dev/tommyjs/futur/impl/SimplePromise.java +++ /dev/null @@ -1,27 +0,0 @@ -package dev.tommyjs.futur.impl; - -import dev.tommyjs.futur.executor.PromiseExecutor; -import dev.tommyjs.futur.promise.AbstractPromise; -import dev.tommyjs.futur.promise.AbstractPromiseFactory; -import org.jetbrains.annotations.NotNull; -import org.slf4j.Logger; - -public class SimplePromise extends AbstractPromise { - - private final @NotNull AbstractPromiseFactory factory; - - public SimplePromise(@NotNull AbstractPromiseFactory factory) { - this.factory = factory; - } - - @Deprecated - public SimplePromise(@NotNull PromiseExecutor executor, @NotNull Logger logger, @NotNull AbstractPromiseFactory factory) { - this(factory); - } - - @Override - public @NotNull AbstractPromiseFactory getFactory() { - return factory; - } - -} diff --git a/futur-api/src/main/java/dev/tommyjs/futur/impl/SimplePromiseFactory.java b/futur-api/src/main/java/dev/tommyjs/futur/impl/SimplePromiseFactory.java deleted file mode 100644 index 1bc6ecb..0000000 --- a/futur-api/src/main/java/dev/tommyjs/futur/impl/SimplePromiseFactory.java +++ /dev/null @@ -1,34 +0,0 @@ -package dev.tommyjs.futur.impl; - -import dev.tommyjs.futur.executor.PromiseExecutor; -import dev.tommyjs.futur.promise.AbstractPromiseFactory; -import dev.tommyjs.futur.promise.Promise; -import org.jetbrains.annotations.NotNull; -import org.slf4j.Logger; - -public class SimplePromiseFactory extends AbstractPromiseFactory { - - private final PromiseExecutor executor; - private final Logger logger; - - public SimplePromiseFactory(PromiseExecutor executor, Logger logger) { - this.executor = executor; - this.logger = logger; - } - - @Override - public @NotNull Promise unresolved() { - return new SimplePromise<>(this); - } - - @Override - public @NotNull Logger getLogger() { - return logger; - } - - @Override - public @NotNull PromiseExecutor getExecutor() { - return executor; - } - -} 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 new file mode 100644 index 0000000..3f75d55 --- /dev/null +++ b/futur-api/src/main/java/dev/tommyjs/futur/joiner/CompletionJoiner.java @@ -0,0 +1,48 @@ +package dev.tommyjs.futur.joiner; + +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; +import java.util.List; + +public class CompletionJoiner extends PromiseJoiner, Void, Void, List>> { + + private final ConcurrentResultArray> results; + + public CompletionJoiner( + @NotNull PromiseFactory factory, + @NotNull Iterator> promises, + int expectedSize, boolean link + ) { + super(factory); + results = new ConcurrentResultArray<>(expectedSize); + join(promises, link); + } + + @Override + protected Void getKey(Promise value) { + return null; + } + + @Override + protected @NotNull Promise getPromise(Promise value) { + //noinspection unchecked + return (Promise) value; + } + + @Override + protected @Nullable Throwable onFinish(int index, Void key, @NotNull PromiseCompletion res) { + results.set(index, res); + return null; + } + + @Override + protected List> getResult() { + return results.toList(); + } + +} diff --git a/futur-api/src/main/java/dev/tommyjs/futur/joiner/ConcurrentResultArray.java b/futur-api/src/main/java/dev/tommyjs/futur/joiner/ConcurrentResultArray.java new file mode 100644 index 0000000..af34f33 --- /dev/null +++ b/futur-api/src/main/java/dev/tommyjs/futur/joiner/ConcurrentResultArray.java @@ -0,0 +1,32 @@ +package dev.tommyjs.futur.joiner; + +import org.jetbrains.annotations.NotNull; + +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; + +class ConcurrentResultArray { + + private final AtomicReference ref; + + public ConcurrentResultArray(int expectedSize) { + //noinspection unchecked + this.ref = new AtomicReference<>((T[]) new Object[expectedSize]); + } + + public void set(int index, T element) { + ref.updateAndGet(array -> { + if (array.length <= index) + return Arrays.copyOf(array, index + 6); + + array[index] = element; + return array; + }); + } + + public @NotNull List toList() { + return Arrays.asList(ref.get()); + } + +} 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 new file mode 100644 index 0000000..1145da9 --- /dev/null +++ b/futur-api/src/main/java/dev/tommyjs/futur/joiner/MappedResultJoiner.java @@ -0,0 +1,60 @@ +package dev.tommyjs.futur.joiner; + +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.*; +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); + } + + @Override + protected K getKey(Map.Entry> entry) { + return entry.getKey(); + } + + @Override + protected @NotNull Promise getPromise(Map.Entry> entry) { + return entry.getValue(); + } + + @Override + protected @Nullable Throwable onFinish(int index, K key, @NotNull PromiseCompletion res) { + if (res.isError()) { + if (exceptionHandler == null) return res.getException(); + exceptionHandler.accept(key, res.getException()); + } + + results.set(index, new AbstractMap.SimpleImmutableEntry<>(key, res.getResult())); + return null; + } + + @Override + protected Map getResult() { + List> list = results.toList(); + Map map = new HashMap<>(list.size()); + for (Map.Entry entry : list) { + map.put(entry.getKey(), entry.getValue()); + } + return map; + } + +} \ No newline at end of file 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 new file mode 100644 index 0000000..31319ba --- /dev/null +++ b/futur-api/src/main/java/dev/tommyjs/futur/joiner/PromiseJoiner.java @@ -0,0 +1,66 @@ +package dev.tommyjs.futur.joiner; + +import dev.tommyjs.futur.promise.*; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Iterator; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + +abstract class PromiseJoiner { + + private final CompletablePromise joined; + + protected PromiseJoiner(@NotNull PromiseFactory factory) { + this.joined = factory.unresolved(); + } + + public @NotNull Promise joined() { + return joined; + } + + protected abstract K getKey(V value); + + protected abstract @NotNull Promise getPromise(V value); + + protected abstract @Nullable Throwable onFinish(int index, K key, @NotNull PromiseCompletion completion); + + protected abstract R getResult(); + + protected void join(@NotNull Iterator promises, boolean link) { + AtomicBoolean waiting = new AtomicBoolean(); + AtomicInteger count = new AtomicInteger(); + + int i = 0; + do { + V value = promises.next(); + Promise p = getPromise(value); + if (link) { + AbstractPromise.cancelOnFinish(p, joined); + } + + if (!joined.isCompleted()) { + count.incrementAndGet(); + K key = getKey(value); + int index = i++; + + p.addListener((res) -> { + Throwable e = onFinish(index, key, res); + if (e != null) { + joined.completeExceptionally(e); + } else if (count.decrementAndGet() == 0 && waiting.get()) { + joined.complete(getResult()); + } + }); + } + } while (promises.hasNext()); + + count.updateAndGet((v) -> { + if (v == 0) joined.complete(getResult()); + else waiting.set(true); + return v; + }); + } + +} 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 new file mode 100644 index 0000000..d69ad33 --- /dev/null +++ b/futur-api/src/main/java/dev/tommyjs/futur/joiner/ResultJoiner.java @@ -0,0 +1,56 @@ +package dev.tommyjs.futur.joiner; + +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; +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); + } + + @Override + protected Void getKey(Promise value) { + return null; + } + + @Override + protected @NotNull Promise getPromise(Promise value) { + return value; + } + + @Override + protected @Nullable Throwable onFinish(int index, Void key, @NotNull PromiseCompletion res) { + if (res.isError()) { + if (exceptionHandler == null) return res.getException(); + exceptionHandler.accept(index, res.getException()); + } + + results.set(index, res.getResult()); + return null; + } + + @Override + protected List getResult() { + return results.toList(); + } + +} 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 new file mode 100644 index 0000000..8998956 --- /dev/null +++ b/futur-api/src/main/java/dev/tommyjs/futur/joiner/VoidJoiner.java @@ -0,0 +1,39 @@ +package dev.tommyjs.futur.joiner; + +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; + +public class VoidJoiner extends PromiseJoiner, Void, Void, Void> { + + public VoidJoiner(@NotNull PromiseFactory factory, @NotNull Iterator> promises, boolean link) { + super(factory); + join(promises, link); + } + + @Override + protected Void getKey(Promise value) { + return null; + } + + @Override + protected @NotNull Promise getPromise(Promise value) { + //noinspection unchecked + return (Promise) value; + } + + @Override + protected @Nullable Throwable onFinish(int index, Void key, @NotNull PromiseCompletion completion) { + return completion.getException(); + } + + @Override + protected Void getResult() { + return null; + } + +} 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 bb823eb..5e673c4 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 @@ -1,6 +1,5 @@ package dev.tommyjs.futur.promise; -import dev.tommyjs.futur.executor.PromiseExecutor; import dev.tommyjs.futur.function.ExceptionalConsumer; import dev.tommyjs.futur.function.ExceptionalFunction; import dev.tommyjs.futur.function.ExceptionalRunnable; @@ -10,102 +9,110 @@ import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import java.util.Collection; -import java.util.LinkedList; +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.Lock; -import java.util.concurrent.locks.ReentrantLock; import java.util.function.Consumer; -public abstract class AbstractPromise implements Promise { +public abstract class AbstractPromise implements CompletablePromise { - private Collection> listeners; - private final AtomicReference> completion; - private final CountDownLatch latch; - private final Lock lock; - - public AbstractPromise() { - this.completion = new AtomicReference<>(); - this.latch = new CountDownLatch(1); - this.lock = new ReentrantLock(); - } - - protected static void propagateResult(Promise from, Promise to) { + public static void propagateResult(Promise from, CompletablePromise to) { from.addDirectListener(to::complete, to::completeExceptionally); } - protected static void propagateCancel(Promise from, Promise to) { - from.onCancel(to::completeExceptionally); + public static void propagateCancel(Promise from, Promise to) { + from.onCancel(to::cancel); } - private @NotNull Runnable createRunnable(T result, @NotNull Promise promise, @NotNull ExceptionalFunction task) { + public static void cancelOnFinish(Promise toCancel, Promise toFinish) { + toFinish.addDirectListener(_ -> toCancel.cancel()); + } + + private final AtomicReference>> listeners; + private final AtomicReference> completion; + private final CountDownLatch latch; + + public AbstractPromise() { + this.listeners = new AtomicReference<>(Collections.emptyList()); + this.completion = new AtomicReference<>(); + this.latch = new CountDownLatch(1); + } + + private void runCompleter(@NotNull CompletablePromise promise, @NotNull ExceptionalRunnable completer) { + try { + completer.run(); + } catch (Error e) { + promise.completeExceptionally(e); + throw e; + } catch (Throwable e) { + promise.completeExceptionally(e); + } + } + + private @NotNull Runnable createCompleter( + T result, + @NotNull CompletablePromise promise, + @NotNull ExceptionalFunction completer + ) { return () -> { if (promise.isCompleted()) return; - - try { - V nextResult = task.apply(result); - promise.complete(nextResult); - } catch (Throwable e) { - promise.completeExceptionally(e); - } + runCompleter(promise, () -> promise.complete(completer.apply(result))); }; } - public abstract @NotNull AbstractPromiseFactory getFactory(); - - protected @NotNull PromiseExecutor getExecutor() { - return getFactory().getExecutor(); - } + public abstract @NotNull AbstractPromiseFactory getFactory(); protected @NotNull Logger getLogger() { return getFactory().getLogger(); } @Override - public T awaitInterruptibly() throws InterruptedException { + public T get() throws InterruptedException, ExecutionException { this.latch.await(); - return joinCompletion(Objects.requireNonNull(getCompletion())); + return joinCompletion(); } @Override - public T awaitInterruptibly(long timeoutMillis) throws TimeoutException, InterruptedException { - boolean success = this.latch.await(timeoutMillis, TimeUnit.MILLISECONDS); + public T get(long time, @NotNull TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { + boolean success = this.latch.await(time, unit); if (!success) { - throw new TimeoutException("Promise stopped waiting after " + timeoutMillis + "ms"); + throw new TimeoutException("Promise stopped waiting after " + time + " " + unit); } - return joinCompletion(Objects.requireNonNull(getCompletion())); + return joinCompletion(); } @Override public T await() { try { - return awaitInterruptibly(); + this.latch.await(); } catch (InterruptedException e) { throw new RuntimeException(e); } + + PromiseCompletion completion = Objects.requireNonNull(getCompletion()); + if (completion.isSuccess()) return completion.getResult(); + throw new CompletionException(completion.getException()); + } + + private T joinCompletion() throws ExecutionException { + PromiseCompletion completion = Objects.requireNonNull(getCompletion()); + if (completion.isSuccess()) return completion.getResult(); + throw new ExecutionException(completion.getException()); } @Override - public T await(long timeoutMillis) throws TimeoutException { - try { - return awaitInterruptibly(timeoutMillis); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - } - - private T joinCompletion(PromiseCompletion completion) { - if (completion.isError()) - throw new RuntimeException(completion.getException()); - - return completion.getResult(); + public @NotNull Promise fork() { + CompletablePromise fork = getFactory().unresolved(); + propagateResult(this, fork); + return fork; } @Override public @NotNull Promise thenRun(@NotNull ExceptionalRunnable task) { - return thenApply(result -> { + return thenApply(_ -> { task.run(); return null; }); @@ -121,14 +128,14 @@ public abstract class AbstractPromise implements Promise { @Override public @NotNull Promise thenSupply(@NotNull ExceptionalSupplier task) { - return thenApply(result -> task.get()); + return thenApply(_ -> task.get()); } @Override public @NotNull Promise thenApply(@NotNull ExceptionalFunction task) { - Promise promise = getFactory().unresolved(); + CompletablePromise promise = getFactory().unresolved(); addDirectListener( - res -> createRunnable(res, promise, task).run(), + res -> createCompleter(res, promise, task).run(), promise::completeExceptionally ); @@ -138,7 +145,7 @@ public abstract class AbstractPromise implements Promise { @Override public @NotNull Promise thenCompose(@NotNull ExceptionalFunction> task) { - Promise promise = getFactory().unresolved(); + CompletablePromise promise = getFactory().unresolved(); thenApply(task).addDirectListener( nestedPromise -> { if (nestedPromise == null) { @@ -157,7 +164,7 @@ public abstract class AbstractPromise implements Promise { @Override public @NotNull Promise thenRunSync(@NotNull ExceptionalRunnable task) { - return thenApplySync(result -> { + return thenApplySync(_ -> { task.run(); return null; }); @@ -165,7 +172,7 @@ public abstract class AbstractPromise implements Promise { @Override public @NotNull Promise thenRunDelayedSync(@NotNull ExceptionalRunnable task, long delay, @NotNull TimeUnit unit) { - return thenApplyDelayedSync(result -> { + return thenApplyDelayedSync(_ -> { task.run(); return null; }, delay, unit); @@ -189,27 +196,23 @@ public abstract class AbstractPromise implements Promise { @Override public @NotNull Promise thenSupplySync(@NotNull ExceptionalSupplier task) { - return thenApplySync(result -> task.get()); + return thenApplySync(_ -> task.get()); } @Override public @NotNull Promise thenSupplyDelayedSync(@NotNull ExceptionalSupplier task, long delay, @NotNull TimeUnit unit) { - return thenApplyDelayedSync(result -> task.get(), delay, unit); + return thenApplyDelayedSync(_ -> task.get(), delay, unit); } @Override public @NotNull Promise thenApplySync(@NotNull ExceptionalFunction task) { - Promise promise = getFactory().unresolved(); + CompletablePromise promise = getFactory().unresolved(); addDirectListener( - res -> { - try { - Runnable runnable = createRunnable(res, promise, task); - F future = getExecutor().runSync(runnable); - promise.onCancel((e) -> getExecutor().cancel(future)); - } catch (RejectedExecutionException e) { - promise.completeExceptionally(e); - } - }, + res -> runCompleter(promise, () -> { + Runnable runnable = createCompleter(res, promise, task); + FS future = getFactory().getSyncExecutor().run(runnable); + promise.addDirectListener(_ -> getFactory().getSyncExecutor().cancel(future)); + }), promise::completeExceptionally ); @@ -219,17 +222,13 @@ public abstract class AbstractPromise implements Promise { @Override public @NotNull Promise thenApplyDelayedSync(@NotNull ExceptionalFunction task, long delay, @NotNull TimeUnit unit) { - Promise promise = getFactory().unresolved(); + CompletablePromise promise = getFactory().unresolved(); addDirectListener( - res -> { - try { - Runnable runnable = createRunnable(res, promise, task); - F future = getExecutor().runSync(runnable, delay, unit); - promise.onCancel((e) -> getExecutor().cancel(future)); - } catch (RejectedExecutionException e) { - promise.completeExceptionally(e); - } - }, + res -> runCompleter(promise, () -> { + Runnable runnable = createCompleter(res, promise, task); + FS future = getFactory().getSyncExecutor().run(runnable, delay, unit); + promise.addDirectListener(_ -> getFactory().getSyncExecutor().cancel(future)); + }), promise::completeExceptionally ); @@ -239,7 +238,7 @@ public abstract class AbstractPromise implements Promise { @Override public @NotNull Promise thenComposeSync(@NotNull ExceptionalFunction> task) { - Promise promise = getFactory().unresolved(); + CompletablePromise promise = getFactory().unresolved(); thenApplySync(task).addDirectListener( nestedPromise -> { if (nestedPromise == null) { @@ -258,7 +257,7 @@ public abstract class AbstractPromise implements Promise { @Override public @NotNull Promise thenRunAsync(@NotNull ExceptionalRunnable task) { - return thenApplyAsync(result -> { + return thenApplyAsync(_ -> { task.run(); return null; }); @@ -266,7 +265,7 @@ public abstract class AbstractPromise implements Promise { @Override public @NotNull Promise thenRunDelayedAsync(@NotNull ExceptionalRunnable task, long delay, @NotNull TimeUnit unit) { - return thenApplyDelayedAsync(result -> { + return thenApplyDelayedAsync(_ -> { task.run(); return null; }, delay, unit); @@ -290,17 +289,17 @@ public abstract class AbstractPromise implements Promise { @Override public @NotNull Promise thenSupplyAsync(@NotNull ExceptionalSupplier task) { - return thenApplyAsync(result -> task.get()); + return thenApplyAsync(_ -> task.get()); } @Override public @NotNull Promise thenSupplyDelayedAsync(@NotNull ExceptionalSupplier task, long delay, @NotNull TimeUnit unit) { - return thenApplyDelayedAsync(result -> task.get(), delay, unit); + return thenApplyDelayedAsync(_ -> task.get(), delay, unit); } @Override public @NotNull Promise thenPopulateReference(@NotNull AtomicReference reference) { - return thenApplyAsync((result) -> { + return thenApplyAsync(result -> { reference.set(result); return result; }); @@ -308,17 +307,13 @@ public abstract class AbstractPromise implements Promise { @Override public @NotNull Promise thenApplyAsync(@NotNull ExceptionalFunction task) { - Promise promise = getFactory().unresolved(); + CompletablePromise promise = getFactory().unresolved(); addDirectListener( - (res) -> { - try { - Runnable runnable = createRunnable(res, promise, task); - F future = getExecutor().runAsync(runnable); - promise.onCancel((e) -> getExecutor().cancel(future)); - } catch (RejectedExecutionException e) { - promise.completeExceptionally(e); - } - }, + (res) -> runCompleter(promise, () -> { + Runnable runnable = createCompleter(res, promise, task); + FA future = getFactory().getAsyncExecutor().run(runnable); + promise.addDirectListener(_ -> getFactory().getAsyncExecutor().cancel(future)); + }), promise::completeExceptionally ); @@ -328,17 +323,13 @@ public abstract class AbstractPromise implements Promise { @Override public @NotNull Promise thenApplyDelayedAsync(@NotNull ExceptionalFunction task, long delay, @NotNull TimeUnit unit) { - Promise promise = getFactory().unresolved(); + CompletablePromise promise = getFactory().unresolved(); addDirectListener( - res -> { - try { - Runnable runnable = createRunnable(res, promise, task); - F future = getExecutor().runAsync(runnable, delay, unit); - promise.onCancel((e) -> getExecutor().cancel(future)); - } catch (RejectedExecutionException e) { - promise.completeExceptionally(e); - } - }, + res -> runCompleter(promise, () -> { + Runnable runnable = createCompleter(res, promise, task); + FA future = getFactory().getAsyncExecutor().run(runnable, delay, unit); + promise.addDirectListener(_ -> getFactory().getAsyncExecutor().cancel(future)); + }), promise::completeExceptionally ); @@ -348,7 +339,7 @@ public abstract class AbstractPromise implements Promise { @Override public @NotNull Promise thenComposeAsync(@NotNull ExceptionalFunction> task) { - Promise promise = getFactory().unresolved(); + CompletablePromise promise = getFactory().unresolved(); thenApplyAsync(task).addDirectListener( nestedPromise -> { if (nestedPromise == null) { @@ -367,7 +358,7 @@ public abstract class AbstractPromise implements Promise { @Override public @NotNull Promise erase() { - return thenSupplyAsync(() -> null); + return thenSupply(() -> null); } @Override @@ -378,10 +369,10 @@ public abstract class AbstractPromise implements Promise { @Override public @NotNull Promise addAsyncListener(@Nullable Consumer successListener, @Nullable Consumer errorListener) { return addAsyncListener((res) -> { - if (res.isError()) { - if (errorListener != null) errorListener.accept(res.getException()); - } else { + if (res.isSuccess()) { if (successListener != null) successListener.accept(res.getResult()); + } else { + if (errorListener != null) errorListener.accept(res.getException()); } }); } @@ -394,49 +385,47 @@ public abstract class AbstractPromise implements Promise { @Override public @NotNull Promise addDirectListener(@Nullable Consumer successListener, @Nullable Consumer errorListener) { return addDirectListener((res) -> { - if (res.isError()) { - if (errorListener != null) errorListener.accept(res.getException()); - } else { + if (res.isSuccess()) { if (successListener != null) successListener.accept(res.getResult()); + } else { + if (errorListener != null) errorListener.accept(res.getException()); } }); } private @NotNull Promise addAnyListener(PromiseListener listener) { - PromiseCompletion completion; + Collection> res = listeners.updateAndGet(v -> { + if (v == Collections.EMPTY_LIST) v = new ConcurrentLinkedQueue<>(); + if (v != null) v.add(listener); + return v; + }); - lock.lock(); - try { - completion = getCompletion(); - if (completion == null) { - if (listeners == null) listeners = new LinkedList<>(); - listeners.add(listener); - return this; + if (res == null) { + if (listener instanceof AsyncPromiseListener) { + callListenerAsync(listener, Objects.requireNonNull(getCompletion())); + } else { + callListenerNow(listener, Objects.requireNonNull(getCompletion())); } - } finally { - lock.unlock(); } - callListener(listener, completion); return this; } - private void callListener(PromiseListener listener, PromiseCompletion ctx) { - if (listener instanceof AsyncPromiseListener) { - try { - getExecutor().runAsync(() -> callListenerNow(listener, ctx)); - } catch (RejectedExecutionException ignored) { - - } - } else { - callListenerNow(listener, ctx); + private void callListenerAsync(PromiseListener listener, PromiseCompletion res) { + try { + getFactory().getAsyncExecutor().run(() -> callListenerNow(listener, res)); + } catch (Exception e) { + getLogger().warn("Exception caught while running promise listener", e); } } - private void callListenerNow(PromiseListener listener, PromiseCompletion ctx) { + private void callListenerNow(PromiseListener listener, PromiseCompletion res) { try { - listener.handle(ctx); - } catch (Exception e) { + listener.handle(res); + } catch (Error e) { + getLogger().error("Error caught in promise listener", e); + throw e; + } catch (Throwable e) { getLogger().error("Exception caught in promise listener", e); } } @@ -453,13 +442,15 @@ public abstract class AbstractPromise implements Promise { @Override public @NotNull Promise logExceptions(@NotNull String message) { - return onError(e -> getLogger().error(message, e)); + Exception wrapper = new DeferredExecutionException(); + return onError(e -> getLogger().error(message, wrapper.initCause(e))); } @Override public @NotNull Promise onError(@NotNull Class clazz, @NotNull Consumer listener) { return onError((e) -> { if (clazz.isAssignableFrom(e.getClass())) { + getLogger().info("On Error {}", e.getClass()); //noinspection unchecked listener.accept((E) e); } @@ -471,37 +462,51 @@ public abstract class AbstractPromise implements Promise { return onError(CancellationException.class, listener); } - @Deprecated @Override public @NotNull Promise timeout(long time, @NotNull TimeUnit unit) { - return maxWaitTime(time, unit); + Exception e = new CancellationException("Promise timed out after " + time + " " + unit); + return completeExceptionallyDelayed(e, time, unit); } @Override public @NotNull Promise maxWaitTime(long time, @NotNull TimeUnit unit) { - try { - Exception e = new TimeoutException("Promise stopped waiting after " + time + " " + unit); - F future = getExecutor().runAsync(() -> completeExceptionally(e), time, unit); - return addDirectListener((_v) -> getExecutor().cancel(future)); - } catch (RejectedExecutionException e) { - completeExceptionally(e); - return this; - } + Exception e = new TimeoutException("Promise stopped waiting after " + time + " " + unit); + return completeExceptionallyDelayed(e, time, unit); + } + + private Promise completeExceptionallyDelayed(Throwable e, long delay, TimeUnit unit) { + runCompleter(this, () -> { + FA future = getFactory().getAsyncExecutor().run(() -> completeExceptionally(e), delay, unit); + addDirectListener(_ -> getFactory().getAsyncExecutor().cancel(future)); + }); + return this; } private void handleCompletion(@NotNull PromiseCompletion ctx) { - lock.lock(); - try { - if (!setCompletion(ctx)) return; + if (!setCompletion(ctx)) return; + latch.countDown(); - this.latch.countDown(); - if (listeners != null) { - for (PromiseListener listener : listeners) { - callListener(listener, ctx); + Iterator> iter = listeners.getAndSet(null).iterator(); + while (iter.hasNext()) { + PromiseListener listener = iter.next(); + + if (listener instanceof AsyncPromiseListener) { + callListenerAsync(listener, ctx); + } else { + try { + callListenerNow(listener, ctx); + } finally { + iter.forEachRemaining(v -> callListenerAsyncLastResort(v, ctx)); } } - } finally { - lock.unlock(); + } + } + + private void callListenerAsyncLastResort(PromiseListener listener, PromiseCompletion ctx) { + try { + getFactory().getAsyncExecutor().run(() -> callListenerNow(listener, ctx)); + } catch (Throwable ignored) { + } } @@ -510,8 +515,8 @@ public abstract class AbstractPromise implements Promise { } @Override - public void cancel(@Nullable String message) { - completeExceptionally(new CancellationException(message)); + public void cancel(@NotNull CancellationException e) { + completeExceptionally(e); } @Override @@ -538,7 +543,7 @@ public abstract class AbstractPromise implements Promise { public @NotNull CompletableFuture toFuture() { CompletableFuture future = new CompletableFuture<>(); this.addDirectListener(future::complete, future::completeExceptionally); - future.whenComplete((res, e) -> { + future.whenComplete((_, e) -> { if (e instanceof CancellationException) { this.cancel(); } diff --git a/futur-api/src/main/java/dev/tommyjs/futur/promise/AbstractPromiseFactory.java b/futur-api/src/main/java/dev/tommyjs/futur/promise/AbstractPromiseFactory.java index 80a4e52..c1c78ff 100644 --- a/futur-api/src/main/java/dev/tommyjs/futur/promise/AbstractPromiseFactory.java +++ b/futur-api/src/main/java/dev/tommyjs/futur/promise/AbstractPromiseFactory.java @@ -1,6 +1,10 @@ package dev.tommyjs.futur.promise; import dev.tommyjs.futur.executor.PromiseExecutor; +import dev.tommyjs.futur.joiner.CompletionJoiner; +import dev.tommyjs.futur.joiner.MappedResultJoiner; +import dev.tommyjs.futur.joiner.ResultJoiner; +import dev.tommyjs.futur.joiner.VoidJoiner; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -8,142 +12,84 @@ import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.concurrent.Future; -import java.util.concurrent.atomic.AtomicInteger; import java.util.function.BiConsumer; -import java.util.stream.Collectors; -import java.util.stream.StreamSupport; +import java.util.stream.Stream; -public abstract class AbstractPromiseFactory implements PromiseFactory { +public abstract class AbstractPromiseFactory implements PromiseFactory { - public abstract @NotNull PromiseExecutor getExecutor(); + public abstract @NotNull PromiseExecutor getSyncExecutor(); + + public abstract @NotNull PromiseExecutor getAsyncExecutor(); @Override - public @NotNull Promise> combine(boolean propagateCancel, @NotNull Promise p1, @NotNull Promise p2) { - List> promises = List.of(p1, p2); - return all(propagateCancel, promises) - .thenApplyAsync((res) -> new AbstractMap.SimpleImmutableEntry<>( - Objects.requireNonNull(p1.getCompletion()).getResult(), - Objects.requireNonNull(p2.getCompletion()).getResult() - )); + public @NotNull Promise> combine( + @NotNull Promise p1, + @NotNull Promise p2, + boolean dontFork + ) { + return all(dontFork, p1, p2).thenApply((_) -> new AbstractMap.SimpleImmutableEntry<>( + Objects.requireNonNull(p1.getCompletion()).getResult(), + Objects.requireNonNull(p2.getCompletion()).getResult() + )); } @Override - public @NotNull Promise> combine(boolean propagateCancel, @NotNull Map> promises, @Nullable BiConsumer exceptionHandler) { + 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(); + } - Map map = new HashMap<>(); - Promise> promise = unresolved(); - for (Map.Entry> entry : promises.entrySet()) { - if (propagateCancel) { - AbstractPromise.propagateCancel(promise, entry.getValue()); - } + @Override + public @NotNull Promise> combine( + @NotNull Iterator> promises, int expectedSize, + @Nullable BiConsumer exceptionHandler, boolean link + ) { + if (!promises.hasNext()) return resolve(Collections.emptyList()); + return new ResultJoiner<>( + this, promises, exceptionHandler, expectedSize, link).joined(); + } - entry.getValue().addDirectListener((ctx) -> { - synchronized (map) { - if (ctx.getException() != null) { - if (exceptionHandler == null) { - promise.completeExceptionally(ctx.getException()); - } else { - exceptionHandler.accept(entry.getKey(), ctx.getException()); - map.put(entry.getKey(), null); - } - } else { - map.put(entry.getKey(), ctx.getResult()); - } + @Override + public @NotNull Promise>> allSettled( + @NotNull Iterator> promises, + int expectedSize, + boolean link + ) { + if (!promises.hasNext()) return resolve(Collections.emptyList()); + return new CompletionJoiner(this, promises, expectedSize, link).joined(); + } - if (map.size() == promises.size()) { - promise.complete(map); - } - } - }); - } + @Override + public @NotNull Promise all(@NotNull Iterator> promises, boolean link) { + if (!promises.hasNext()) return resolve(null); + return new VoidJoiner(this, promises, link).joined(); + } + + @Override + public @NotNull Promise race(@NotNull Iterator> promises, boolean link) { + CompletablePromise promise = unresolved(); + promises.forEachRemaining(p -> { + if (link) AbstractPromise.cancelOnFinish(p, promise); + if (!promise.isCompleted()) + AbstractPromise.propagateResult(p, promise); + }); return promise; } @Override - public @NotNull Promise> combine(boolean propagateCancel, @NotNull Iterable> promises, @Nullable BiConsumer exceptionHandler) { - AtomicInteger index = new AtomicInteger(); - return this.combine( - propagateCancel, - StreamSupport.stream(promises.spliterator(), false) - .collect(Collectors.toMap(k -> index.getAndIncrement(), v -> v)), - exceptionHandler - ).thenApplyAsync(v -> - v.entrySet().stream() - .sorted(Map.Entry.comparingByKey()) - .map(Map.Entry::getValue) - .collect(Collectors.toList()) - ); + public @NotNull Promise race(@NotNull Iterable> promises, boolean link) { + return race(promises.iterator(), link); } @Override - public @NotNull Promise>> allSettled(boolean propagateCancel, @NotNull Iterable> promiseIterable) { - List> promises = new ArrayList<>(); - promiseIterable.iterator().forEachRemaining(promises::add); - - if (promises.isEmpty()) return resolve(Collections.emptyList()); - PromiseCompletion[] results = new PromiseCompletion[promises.size()]; - - Promise>> promise = unresolved(); - var iter = promises.listIterator(); - - while (iter.hasNext()) { - int index = iter.nextIndex(); - var p = iter.next(); - - if (propagateCancel) { - AbstractPromise.propagateCancel(promise, p); - } - - p.addDirectListener((res) -> { - synchronized (results) { - results[index] = res; - if (Arrays.stream(results).allMatch(Objects::nonNull)) - promise.complete(Arrays.asList(results)); - } - }); - } - - return promise; - } - - @Override - public @NotNull Promise all(boolean propagateCancel, @NotNull Iterable> promiseIterable) { - List> promises = new ArrayList<>(); - promiseIterable.iterator().forEachRemaining(promises::add); - - if (promises.isEmpty()) return resolve(null); - AtomicInteger completed = new AtomicInteger(); - Promise promise = unresolved(); - - for (Promise p : promises) { - if (propagateCancel) { - AbstractPromise.propagateCancel(promise, p); - } - - p.addDirectListener((res) -> { - if (res.getException() != null) { - promise.completeExceptionally(res.getException()); - } else if (completed.incrementAndGet() == promises.size()) { - promise.complete(null); - } - }); - } - - return promise; - } - - @Override - public @NotNull Promise race(boolean cancelRaceLosers, @NotNull Iterable> promises) { - Promise promise = unresolved(); - for (Promise p : promises) { - if (cancelRaceLosers) { - promise.addListener((res) -> p.cancel()); - } - AbstractPromise.propagateResult(p, promise); - } - return promise; + public @NotNull Promise race(@NotNull Stream> promises, boolean link) { + return race(promises.iterator(), link); } @Override @@ -152,7 +98,7 @@ public abstract class AbstractPromiseFactory implements PromiseFactory { } private @NotNull Promise wrap(@NotNull CompletionStage completion, Future future) { - Promise promise = unresolved(); + CompletablePromise promise = unresolved(); completion.whenComplete((v, e) -> { if (e != null) { @@ -162,20 +108,20 @@ public abstract class AbstractPromiseFactory implements PromiseFactory { } }); - promise.onCancel((e) -> future.cancel(true)); + promise.onCancel(_ -> future.cancel(true)); return promise; } @Override public @NotNull Promise resolve(T value) { - Promise promise = unresolved(); + CompletablePromise promise = unresolved(); promise.complete(value); return promise; } @Override public @NotNull Promise error(@NotNull Throwable error) { - Promise promise = unresolved(); + CompletablePromise promise = unresolved(); promise.completeExceptionally(error); return promise; } diff --git a/futur-api/src/main/java/dev/tommyjs/futur/promise/CompletablePromise.java b/futur-api/src/main/java/dev/tommyjs/futur/promise/CompletablePromise.java new file mode 100644 index 0000000..101bc55 --- /dev/null +++ b/futur-api/src/main/java/dev/tommyjs/futur/promise/CompletablePromise.java @@ -0,0 +1,12 @@ +package dev.tommyjs.futur.promise; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public interface CompletablePromise extends Promise { + + void complete(@Nullable T result); + + void completeExceptionally(@NotNull Throwable result); + +} diff --git a/futur-api/src/main/java/dev/tommyjs/futur/promise/DeferredExecutionException.java b/futur-api/src/main/java/dev/tommyjs/futur/promise/DeferredExecutionException.java new file mode 100644 index 0000000..c1f58f8 --- /dev/null +++ b/futur-api/src/main/java/dev/tommyjs/futur/promise/DeferredExecutionException.java @@ -0,0 +1,11 @@ +package dev.tommyjs.futur.promise; + +import java.util.concurrent.ExecutionException; + +class DeferredExecutionException extends ExecutionException { + + public DeferredExecutionException() { + super(); + } + +} 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 1fc93f4..8883019 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 @@ -8,16 +8,13 @@ import org.jetbrains.annotations.Blocking; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.concurrent.CancellationException; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; +import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; public interface Promise { - PromiseFactory getFactory(); + @NotNull PromiseFactory getFactory(); @NotNull Promise thenRun(@NotNull ExceptionalRunnable task); @@ -80,6 +77,9 @@ public interface Promise { */ @NotNull Promise addDirectListener(@NotNull PromiseListener listener); + /** + * @apiNote Direct listeners run on the same thread as the completion. + */ @NotNull Promise addDirectListener(@Nullable Consumer successHandler, @Nullable Consumer errorHandler); /** @@ -94,6 +94,9 @@ public interface Promise { return addAsyncListener(listener); } + /** + * @apiNote Async listeners are run in parallel. + */ @NotNull Promise addAsyncListener(@Nullable Consumer successHandler, @Nullable Consumer errorHandler); @NotNull Promise onSuccess(@NotNull Consumer listener); @@ -105,55 +108,70 @@ public interface Promise { @NotNull Promise onCancel(@NotNull Consumer listener); /** - * @deprecated Use maxWaitTime instead + * Cancels the promise with a TimeoutException after the specified time. */ - @Deprecated @NotNull Promise timeout(long time, @NotNull TimeUnit unit); /** - * @deprecated Use maxWaitTime instead + * Cancels the promise with a TimeoutException after the specified time. */ - @Deprecated default @NotNull Promise timeout(long ms) { return timeout(ms, TimeUnit.MILLISECONDS); } + /** + * Completes the promise exceptionally with a TimeoutException after the specified time. + */ @NotNull Promise maxWaitTime(long time, @NotNull TimeUnit unit); + /** + * Completes the promise exceptionally with a TimeoutException after the specified time. + */ default @NotNull Promise maxWaitTime(long ms) { return maxWaitTime(ms, TimeUnit.MILLISECONDS); } - void cancel(@Nullable String reason); + void cancel(@NotNull CancellationException exception); + + default void cancel(@NotNull String reason) { + cancel(new CancellationException(reason)); + }; default void cancel() { - cancel(null); + cancel(new CancellationException()); } - void complete(@Nullable T result); - - void completeExceptionally(@NotNull Throwable result); - - @Blocking - T awaitInterruptibly() throws InterruptedException; - - @Blocking - T awaitInterruptibly(long timeout) throws TimeoutException, InterruptedException; - + /** + * Waits if necessary for this promise to complete, and then returns its result. + * @throws CancellationException if the computation was cancelled + * @throws CompletionException if this promise completed exceptionally + */ @Blocking T await(); - @Blocking - T await(long timeout) throws TimeoutException; - /** - * @deprecated Use await instead. + * Waits if necessary for this promise to complete, and then returns its result. + * @throws CancellationException if the computation was cancelled + * @throws ExecutionException if this promise completed exceptionally + * @throws InterruptedException if the current thread was interrupted while waiting */ @Blocking - @Deprecated - default T join(long timeout) throws TimeoutException { - return await(timeout); - }; + T get() throws InterruptedException, ExecutionException; + + /** + * Waits if necessary for at most the given time for this future to complete, and then returns its result, if available. + * @throws CancellationException if the computation was cancelled + * @throws ExecutionException if this promise completed exceptionally + * @throws InterruptedException if the current thread was interrupted while waiting + * @throws TimeoutException if the wait timed out + */ + @Blocking + T get(long timeout, @NotNull TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException; + + /** + * Stops this promise from propagating up cancellations. + */ + @NotNull Promise fork(); @Nullable PromiseCompletion getCompletion(); diff --git a/futur-api/src/main/java/dev/tommyjs/futur/promise/PromiseCompletion.java b/futur-api/src/main/java/dev/tommyjs/futur/promise/PromiseCompletion.java index de7e276..865b66c 100644 --- a/futur-api/src/main/java/dev/tommyjs/futur/promise/PromiseCompletion.java +++ b/futur-api/src/main/java/dev/tommyjs/futur/promise/PromiseCompletion.java @@ -22,12 +22,16 @@ public class PromiseCompletion { this.result = null; } + public boolean isSuccess() { + return exception == null; + } + public boolean isError() { - return getException() != null; + return exception != null; } public boolean wasCanceled() { - return getException() instanceof CancellationException; + return exception instanceof CancellationException; } public @Nullable T getResult() { 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 d7a3d45..80d6963 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 @@ -1,90 +1,183 @@ package dev.tommyjs.futur.promise; +import dev.tommyjs.futur.executor.PromiseExecutor; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; -import java.util.Arrays; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ScheduledExecutorService; import java.util.function.BiConsumer; +import java.util.stream.Stream; public interface PromiseFactory { + static @NotNull PromiseFactory of(@NotNull Logger logger, @NotNull PromiseExecutor syncExecutor, @NotNull PromiseExecutor asyncExecutor) { + return new PromiseFactoryImpl<>(logger, syncExecutor, asyncExecutor); + } + + static @NotNull PromiseFactory of(@NotNull Logger logger, @NotNull PromiseExecutor executor) { + return new PromiseFactoryImpl<>(logger, executor, executor); + } + + static @NotNull PromiseFactory of(@NotNull Logger logger, @NotNull ScheduledExecutorService executor) { + return of(logger, PromiseExecutor.of(executor)); + } + + private static int size(@NotNull Stream stream) { + long estimate = stream.spliterator().estimateSize(); + return estimate == Long.MAX_VALUE ? 10 : (int) estimate; + } + @NotNull Logger getLogger(); - @NotNull Promise unresolved(); + @NotNull CompletablePromise unresolved(); - @NotNull Promise> combine(boolean propagateCancel, @NotNull Promise p1, @NotNull Promise p2); + @NotNull Promise> combine(@NotNull Promise p1, @NotNull Promise p2, boolean cancelOnError); default @NotNull Promise> combine(@NotNull Promise p1, @NotNull Promise p2) { - return combine(false, p1, p2); + return combine(p1, p2, true); } - @NotNull Promise> combine(boolean propagateCancel, @NotNull Map> promises, @Nullable BiConsumer exceptionHandler); + @NotNull Promise> combine( + @NotNull Map> promises, + @Nullable BiConsumer exceptionHandler, + boolean propagateCancel + ); - default @NotNull Promise> combine(boolean propagateCancel, @NotNull Map> promises) { - return combine(propagateCancel, promises, null); + default @NotNull Promise> combine(@NotNull Map> promises, @NotNull BiConsumer exceptionHandler) { + return combine(promises, exceptionHandler, true); } - default @NotNull Promise> combine(@NotNull Map> promises, @Nullable BiConsumer exceptionHandler) { - return combine(false, promises, exceptionHandler); + default @NotNull Promise> combine(@NotNull Map> promises, boolean cancelOnError) { + return combine(promises, null, cancelOnError); } default @NotNull Promise> combine(@NotNull Map> promises) { - return combine(promises, null); + return combine(promises, null, true); } - @NotNull Promise> combine(boolean propagateCancel, @NotNull Iterable> promises, @Nullable BiConsumer exceptionHandler); + @NotNull Promise> combine( + @NotNull Iterator> promises, int expectedSize, + @Nullable BiConsumer exceptionHandler, boolean propagateCancel + ); - default @NotNull Promise> combine(boolean propagateCancel, @NotNull Iterable> promises) { - return combine(propagateCancel, promises, null); + default @NotNull Promise> combine( + @NotNull Collection> promises, + @NotNull BiConsumer exceptionHandler, + boolean propagateCancel + ) { + return combine(promises.iterator(), promises.size(), exceptionHandler, propagateCancel); } - default @NotNull Promise> combine(@NotNull Iterable> promises, @Nullable BiConsumer exceptionHandler) { - return combine(false, promises, exceptionHandler); + default @NotNull Promise> combine( + @NotNull Collection> promises, + @NotNull BiConsumer exceptionHandler + ) { + return combine(promises.iterator(), promises.size(), exceptionHandler, true); } - default @NotNull Promise> combine(@NotNull Iterable> promises) { - return combine(promises, null); + default @NotNull Promise> combine(@NotNull Collection> promises, boolean cancelOnError) { + return combine(promises.iterator(), promises.size(), null, cancelOnError); } - @NotNull Promise>> allSettled(boolean propagateCancel, @NotNull Iterable> promiseIterable); - - default @NotNull Promise>> allSettled(@NotNull Iterable> promiseIterable) { - return allSettled(false, promiseIterable); + default @NotNull Promise> combine(@NotNull Collection> promises) { + return combine(promises.iterator(), promises.size(), null, true); } - default @NotNull Promise>> allSettled(boolean propagateCancel, @NotNull Promise... promiseArray) { - return allSettled(propagateCancel, Arrays.asList(promiseArray)); + default @NotNull Promise> combine( + @NotNull Stream> promises, + @NotNull BiConsumer exceptionHandler, + boolean propagateCancel + ) { + return combine(promises.iterator(), size(promises), exceptionHandler, propagateCancel); } - default @NotNull Promise>> allSettled(@NotNull Promise... promiseArray) { - return allSettled(false, promiseArray); + default @NotNull Promise> combine( + @NotNull Stream> promises, + @NotNull BiConsumer exceptionHandler + ) { + return combine(promises.iterator(), size(promises), exceptionHandler, true); } - @NotNull Promise all(boolean propagateCancel, @NotNull Iterable> promiseIterable); - - default @NotNull Promise all(@NotNull Iterable> promiseIterable) { - return all(false, promiseIterable); + default @NotNull Promise> combine(@NotNull Stream> promises, boolean cancelOnError) { + return combine(promises.iterator(), size(promises), null, cancelOnError); } - default @NotNull Promise all(boolean propagateCancel, @NotNull Promise... promiseArray) { - return all(propagateCancel, Arrays.asList(promiseArray)); + default @NotNull Promise> combine(@NotNull Stream> promises) { + return combine(promises.iterator(), size(promises), null, true); } - default @NotNull Promise all(@NotNull Promise... promiseArray) { - return all(false, promiseArray); + @NotNull Promise>> allSettled( + @NotNull Iterator> promises, int estimatedSize, boolean propagateCancel); + + default @NotNull Promise>> allSettled(@NotNull Collection> promises, boolean propagateCancel) { + return allSettled(promises.iterator(), promises.size(), propagateCancel); } - /** - * @apiNote Even with cancelRaceLosers, it is not guaranteed that only one promise will complete. - */ - @NotNull Promise race(boolean cancelRaceLosers, @NotNull Iterable> promises); + default @NotNull Promise>> allSettled(@NotNull Collection> promises) { + return allSettled(promises.iterator(), promises.size(), true); + } + + default @NotNull Promise>> allSettled(@NotNull Stream> promises, boolean propagateCancel) { + return allSettled(promises.iterator(), size(promises), propagateCancel); + } + + default @NotNull Promise>> allSettled(@NotNull Stream> promises) { + return allSettled(promises.iterator(), size(promises), true); + } + + default @NotNull Promise>> allSettled(boolean propagateCancel, @NotNull Promise... promises) { + return allSettled(Arrays.asList(promises).iterator(), promises.length, propagateCancel); + } + + default @NotNull Promise>> allSettled(@NotNull Promise... promises) { + return allSettled(Arrays.asList(promises).iterator(), promises.length, true); + } + + @NotNull Promise all(@NotNull Iterator> promises, boolean cancelAllOnError); + + default @NotNull Promise all(@NotNull Iterable> promises, boolean cancelAllOnError) { + return all(promises.iterator(), cancelAllOnError); + } + + default @NotNull Promise all(@NotNull Iterable> promises) { + return all(promises.iterator(), true); + } + + default @NotNull Promise all(@NotNull Stream> promises, boolean cancelAllOnError) { + return all(promises.iterator(), cancelAllOnError); + } + + default @NotNull Promise all(@NotNull Stream> promises) { + return all(promises.iterator(), true); + } + + default @NotNull Promise all(boolean cancelAllOnError, @NotNull Promise... promises) { + return all(Arrays.asList(promises).iterator(), cancelAllOnError); + } + + default @NotNull Promise all(@NotNull Promise... promises) { + return all(Arrays.asList(promises).iterator(), true); + } + + @NotNull Promise race(@NotNull Iterator> promises, boolean cancelLosers); + + default @NotNull Promise race(@NotNull Iterable> promises, boolean cancelLosers) { + return race(promises.iterator(), cancelLosers); + } default @NotNull Promise race(@NotNull Iterable> promises) { - return race(false, promises); + return race(promises.iterator(), true); + } + + default @NotNull Promise race(@NotNull Stream> promises, boolean cancelLosers) { + return race(promises.iterator(), cancelLosers); + } + + default @NotNull Promise race(@NotNull Stream> promises) { + return race(promises.iterator(), true); } @NotNull Promise wrap(@NotNull CompletableFuture future); diff --git a/futur-api/src/main/java/dev/tommyjs/futur/promise/PromiseFactoryImpl.java b/futur-api/src/main/java/dev/tommyjs/futur/promise/PromiseFactoryImpl.java new file mode 100644 index 0000000..7feaf36 --- /dev/null +++ b/futur-api/src/main/java/dev/tommyjs/futur/promise/PromiseFactoryImpl.java @@ -0,0 +1,43 @@ +package dev.tommyjs.futur.promise; + +import dev.tommyjs.futur.executor.PromiseExecutor; +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; + +public class PromiseFactoryImpl extends AbstractPromiseFactory { + + private final @NotNull Logger logger; + private final @NotNull PromiseExecutor syncExecutor; + private final @NotNull PromiseExecutor asyncExecutor; + + public PromiseFactoryImpl( + @NotNull Logger logger, + @NotNull PromiseExecutor syncExecutor, + @NotNull PromiseExecutor asyncExecutor + ) { + this.logger = logger; + this.syncExecutor = syncExecutor; + this.asyncExecutor = asyncExecutor; + } + + @Override + public @NotNull CompletablePromise unresolved() { + return new PromiseImpl<>(this); + } + + @Override + public @NotNull Logger getLogger() { + return logger; + } + + @Override + public @NotNull PromiseExecutor getSyncExecutor() { + return syncExecutor; + } + + @Override + public @NotNull PromiseExecutor getAsyncExecutor() { + return asyncExecutor; + } + +} diff --git a/futur-api/src/main/java/dev/tommyjs/futur/promise/PromiseImpl.java b/futur-api/src/main/java/dev/tommyjs/futur/promise/PromiseImpl.java new file mode 100644 index 0000000..ad752dc --- /dev/null +++ b/futur-api/src/main/java/dev/tommyjs/futur/promise/PromiseImpl.java @@ -0,0 +1,18 @@ +package dev.tommyjs.futur.promise; + +import org.jetbrains.annotations.NotNull; + +public class PromiseImpl extends AbstractPromise { + + private final @NotNull AbstractPromiseFactory factory; + + public PromiseImpl(@NotNull AbstractPromiseFactory factory) { + this.factory = factory; + } + + @Override + public @NotNull AbstractPromiseFactory getFactory() { + return factory; + } + +} diff --git a/futur-api/src/main/java/dev/tommyjs/futur/promise/Promises.java b/futur-api/src/main/java/dev/tommyjs/futur/promise/Promises.java deleted file mode 100644 index ff1f0b8..0000000 --- a/futur-api/src/main/java/dev/tommyjs/futur/promise/Promises.java +++ /dev/null @@ -1,90 +0,0 @@ -package dev.tommyjs.futur.promise; - -import dev.tommyjs.futur.function.ExceptionalFunction; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.CompletableFuture; -import java.util.function.BiConsumer; - -/** - * @deprecated Use PromiseFactory instance methods instead. - */ -@Deprecated -public class Promises { - - public static @NotNull Promise> combine(@NotNull Promise p1, @NotNull Promise p2) { - return combine(p1, p2, p1.getFactory()); - } - - public static @NotNull Promise> combine(@NotNull Promise p1, @NotNull Promise p2, PromiseFactory factory) { - return factory.combine(p1, p2); - } - - public static @NotNull Promise> combine(@NotNull Map> promises, long timeout, PromiseFactory factory) { - return combine(promises, timeout, true, factory); - } - - public static @NotNull Promise> combine(@NotNull Map> promises, long timeout, boolean strict, PromiseFactory factory) { - return combine(promises, timeout, strict ? null : (_k, _v) -> {}, factory); - } - - public static @NotNull Promise> combine(@NotNull Map> promises, long timeout, @Nullable BiConsumer exceptionHandler, PromiseFactory factory) { - return factory.combine(promises, exceptionHandler).timeout(timeout); - } - - public static @NotNull Promise> combine(@NotNull Map> promises, PromiseFactory factory) { - return combine(promises, 1500L, true, factory); - } - - public static @NotNull Promise> combine(@NotNull List> promises, long timeout, PromiseFactory factory) { - return combine(promises, timeout, true, factory); - } - - public static @NotNull Promise> combine(@NotNull List> promises, long timeout, boolean strict, PromiseFactory factory) { - return factory.combine(promises, strict ? null : (_i, _v) -> {}).timeout(timeout); - } - - public static @NotNull Promise> combine(@NotNull List> promises, PromiseFactory factory) { - return combine(promises, 1500L, true, factory); - } - - public static @NotNull Promise all(@NotNull List> promises, PromiseFactory factory) { - return factory.all(promises); - } - - public static @NotNull Promise> combine(@NotNull Collection keys, @NotNull ExceptionalFunction mapper, long timeout, PromiseFactory factory) { - return combine(keys, mapper, timeout, true, factory); - } - - public static @NotNull Promise> combine(@NotNull Collection keys, @NotNull ExceptionalFunction mapper, long timeout, boolean strict, PromiseFactory factory) { - Map> promises = new HashMap<>(); - for (K key : keys) { - Promise promise = factory.resolve(key).thenApplyAsync(mapper); - promises.put(key, promise); - } - - return combine(promises, timeout, strict, factory); - } - - public static @NotNull Promise> combine(@NotNull Collection keys, @NotNull ExceptionalFunction mapper, PromiseFactory factory) { - return combine(keys, mapper, 1500L, true, factory); - } - - public static @NotNull Promise erase(@NotNull Promise p) { - return erase(p, p.getFactory()); - } - - public static @NotNull Promise erase(@NotNull Promise p, PromiseFactory factory) { - return p.erase(); - } - - public static @NotNull Promise wrap(@NotNull CompletableFuture future, PromiseFactory factory) { - return factory.wrap(future); - } - -} \ No newline at end of file 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 6195924..f9e245a 100644 --- a/futur-api/src/test/java/dev/tommyjs/futur/PromiseTests.java +++ b/futur-api/src/test/java/dev/tommyjs/futur/PromiseTests.java @@ -1,7 +1,5 @@ package dev.tommyjs.futur; -import dev.tommyjs.futur.executor.SinglePoolExecutor; -import dev.tommyjs.futur.impl.SimplePromiseFactory; import dev.tommyjs.futur.promise.Promise; import dev.tommyjs.futur.promise.PromiseFactory; import org.junit.jupiter.api.Test; @@ -17,24 +15,36 @@ public final class PromiseTests { private final Logger logger = LoggerFactory.getLogger(PromiseTests.class); private final ScheduledExecutorService executor = Executors.newScheduledThreadPool(5); - private final PromiseFactory pfac = new SimplePromiseFactory<>(new SinglePoolExecutor(executor), logger); + private final PromiseFactory promises = PromiseFactory.of(logger, executor); + + @Test + public void testErrors() { + Promise promise = promises.start().thenSupplyAsync(() -> { + throw new StackOverflowError(); + }); + + try { + promise.await(); + } catch (CompletionException e) { + assert e.getCause() instanceof StackOverflowError; + } + } @Test public void testShutdown() { - executor.shutdown(); - Promise promise = pfac.resolve(null).thenSupplyAsync(() -> null); + executor.close(); + Promise promise = promises.resolve(null).thenSupplyAsync(() -> null); try { promise.await(); - } catch (RuntimeException e) { + } catch (CompletionException e) { assert e.getCause() instanceof RejectedExecutionException; } } @Test - public void testErrorCancellation() throws InterruptedException { + public void testCancellation() throws InterruptedException { var finished = new AtomicBoolean(); - pfac.start() - .thenRunDelayedAsync(() -> finished.set(true), 50, TimeUnit.MILLISECONDS) + promises.start().thenRunDelayedAsync(() -> finished.set(true), 50, TimeUnit.MILLISECONDS) .thenRunAsync(() -> {}) .cancel(); @@ -44,11 +54,11 @@ public final class PromiseTests { @Test public void testToFuture() throws InterruptedException { - assert pfac.resolve(true).toFuture().getNow(false); - assert pfac.error(new Exception("Test")).toFuture().isCompletedExceptionally(); + assert promises.resolve(true).toFuture().getNow(false); + assert promises.error(new Exception("Test")).toFuture().isCompletedExceptionally(); var finished = new AtomicBoolean(); - pfac.start() + promises.start() .thenRunDelayedAsync(() -> finished.set(true), 50, TimeUnit.MILLISECONDS) .toFuture() .cancel(true); @@ -58,86 +68,81 @@ public final class PromiseTests { } @Test - public void testCombineUtil() throws TimeoutException { - pfac.all( - pfac.start().thenRunDelayedAsync(() -> {}, 50, TimeUnit.MILLISECONDS), - pfac.start().thenRunDelayedAsync(() -> {}, 50, TimeUnit.MILLISECONDS) + public void testCombineUtil() throws TimeoutException, ExecutionException, InterruptedException { + promises.all( + promises.start().thenRunDelayedAsync(() -> {}, 50, TimeUnit.MILLISECONDS), + promises.start().thenRunDelayedAsync(() -> {}, 50, TimeUnit.MILLISECONDS) ) - .join(100L); + .get(100L, TimeUnit.MILLISECONDS); - pfac.allSettled( - pfac.start().thenRunDelayedAsync(() -> {}, 50, TimeUnit.MILLISECONDS), - pfac.start().thenRunDelayedAsync(() -> {}, 50, TimeUnit.MILLISECONDS) + promises.allSettled( + promises.start().thenRunDelayedAsync(() -> {}, 50, TimeUnit.MILLISECONDS), + promises.start().thenRunDelayedAsync(() -> {}, 50, TimeUnit.MILLISECONDS) ) - .join(100L); + .get(100L, TimeUnit.MILLISECONDS); - pfac.combine( - pfac.start().thenRunDelayedAsync(() -> {}, 50, TimeUnit.MILLISECONDS), - pfac.start().thenRunDelayedAsync(() -> {}, 50, TimeUnit.MILLISECONDS) + promises.combine( + promises.start().thenRunDelayedAsync(() -> {}, 50, TimeUnit.MILLISECONDS), + promises.start().thenRunDelayedAsync(() -> {}, 50, TimeUnit.MILLISECONDS) ) - .join(100L); + .get(100L, TimeUnit.MILLISECONDS); - pfac.combine( + promises.combine( List.of( - pfac.start().thenRunDelayedAsync(() -> {}, 49, TimeUnit.MILLISECONDS), - pfac.start().thenRunDelayedAsync(() -> {}, 50, TimeUnit.MILLISECONDS), - pfac.start().thenRunDelayedAsync(() -> {}, 51, TimeUnit.MILLISECONDS) + promises.start().thenRunDelayedAsync(() -> {}, 49, TimeUnit.MILLISECONDS), + promises.start().thenRunDelayedAsync(() -> {}, 50, TimeUnit.MILLISECONDS), + promises.start().thenRunDelayedAsync(() -> {}, 51, TimeUnit.MILLISECONDS) ) ) - .join(100L); + .get(100L, TimeUnit.MILLISECONDS); - pfac.combine( + promises.combine( Map.of( - "a", pfac.start().thenRunDelayedAsync(() -> {}, 50, TimeUnit.MILLISECONDS), - "b", pfac.start().thenRunDelayedAsync(() -> {}, 50, TimeUnit.MILLISECONDS) + "a", promises.start().thenRunDelayedAsync(() -> {}, 50, TimeUnit.MILLISECONDS), + "b", promises.start().thenRunDelayedAsync(() -> {}, 50, TimeUnit.MILLISECONDS) ) ) - .join(100L); + .get(100L, TimeUnit.MILLISECONDS); } @Test public void testCombineUtilPropagation() throws InterruptedException { var finished1 = new AtomicBoolean(); - pfac.all( - true, - pfac.start().thenRunDelayedAsync(() -> finished1.set(true), 50, TimeUnit.MILLISECONDS), - pfac.start().thenRunDelayedAsync(() -> finished1.set(true), 50, TimeUnit.MILLISECONDS) + promises.all( + promises.start().thenRunDelayedAsync(() -> finished1.set(true), 50, TimeUnit.MILLISECONDS), + promises.start().thenRunDelayedAsync(() -> finished1.set(true), 50, TimeUnit.MILLISECONDS) ) .cancel(); var finished2 = new AtomicBoolean(); - pfac.allSettled( - true, - pfac.start().thenRunDelayedAsync(() -> finished2.set(true), 50, TimeUnit.MILLISECONDS), - pfac.start().thenRunDelayedAsync(() -> finished2.set(true), 50, TimeUnit.MILLISECONDS) + promises.allSettled( + promises.start().thenRunDelayedAsync(() -> finished2.set(true), 50, TimeUnit.MILLISECONDS), + promises.start().thenRunDelayedAsync(() -> finished2.set(true), 50, TimeUnit.MILLISECONDS) ) .cancel(); var finished3 = new AtomicBoolean(); - pfac.combine( - true, - pfac.start().thenRunDelayedAsync(() -> finished3.set(true), 50, TimeUnit.MILLISECONDS), - pfac.start().thenRunDelayedAsync(() -> finished3.set(true), 50, TimeUnit.MILLISECONDS) + promises.combine( + promises.start().thenRunDelayedAsync(() -> finished3.set(true), 50, TimeUnit.MILLISECONDS), + promises.start().thenRunDelayedAsync(() -> finished3.set(true), 50, TimeUnit.MILLISECONDS) ) .cancel(); var finished4 = new AtomicBoolean(); - pfac.combine( - true, + promises.combine( List.of( - pfac.start().thenRunDelayedAsync(() -> finished4.set(true), 50, TimeUnit.MILLISECONDS), - pfac.start().thenRunDelayedAsync(() -> finished4.set(true), 50, TimeUnit.MILLISECONDS), - pfac.start().thenRunDelayedAsync(() -> finished4.set(true), 50, TimeUnit.MILLISECONDS) + promises.start().thenRunDelayedAsync(() -> finished4.set(true), 50, TimeUnit.MILLISECONDS), + promises.start().thenRunDelayedAsync(() -> finished4.set(true), 50, TimeUnit.MILLISECONDS), + promises.start().thenRunDelayedAsync(() -> finished4.set(true), 50, TimeUnit.MILLISECONDS) ) ) .cancel(); var finished5 = new AtomicBoolean(); - pfac.combine( - true, + promises.combine( Map.of( - "a", pfac.start().thenRunDelayedAsync(() -> finished5.set(true), 50, TimeUnit.MILLISECONDS), - "b", pfac.start().thenRunDelayedAsync(() -> finished5.set(true), 50, TimeUnit.MILLISECONDS) + "a", promises.start().thenRunDelayedAsync(() -> finished5.set(true), 50, TimeUnit.MILLISECONDS), + "b", promises.start().thenRunDelayedAsync(() -> finished5.set(true), 50, TimeUnit.MILLISECONDS) ) ) .cancel(); @@ -151,13 +156,13 @@ public final class PromiseTests { } @Test - public void testRace() throws TimeoutException { - assert pfac.race( + public void testRace() { + assert promises.race( List.of( - pfac.start().thenSupplyDelayedAsync(() -> true, 150, TimeUnit.MILLISECONDS), - pfac.start().thenSupplyDelayedAsync(() -> false, 200, TimeUnit.MILLISECONDS) + promises.start().thenSupplyDelayedAsync(() -> true, 150, TimeUnit.MILLISECONDS), + promises.start().thenSupplyDelayedAsync(() -> false, 200, TimeUnit.MILLISECONDS) ) - ).join(300L); + ).await(); } } diff --git a/futur-lazy/build.gradle b/futur-lazy/build.gradle new file mode 100644 index 0000000..78d5335 --- /dev/null +++ b/futur-lazy/build.gradle @@ -0,0 +1,3 @@ +dependencies { + api project(':futur-api') +} \ No newline at end of file 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 new file mode 100644 index 0000000..5c64399 --- /dev/null +++ b/futur-lazy/src/main/java/dev/tommyjs/futur/lazy/Promises.java @@ -0,0 +1,208 @@ +package dev.tommyjs.futur.lazy; + +import dev.tommyjs.futur.executor.PromiseExecutor; +import dev.tommyjs.futur.promise.CompletablePromise; +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 org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.function.BiConsumer; +import java.util.stream.Stream; + +public final class Promises { + + private static final Logger LOGGER = LoggerFactory.getLogger(Promises.class); + private static PromiseFactory factory = PromiseFactory.of(LOGGER, PromiseExecutor.virtualThreaded()); + + public static void useFactory(@NotNull PromiseFactory factory) { + Promises.factory = factory; + } + + public static @NotNull CompletablePromise unresolved() { + return factory.unresolved(); + } + + public static @NotNull Promise> combine(@NotNull Promise p1, @NotNull Promise p2, boolean cancelOnError) { + return factory.combine(p1, p2, cancelOnError); + } + + public static @NotNull Promise> combine(@NotNull Promise p1, @NotNull Promise p2) { + return factory.combine(p1, p2); + } + + public static @NotNull Promise> combine( + @NotNull Map> promises, + @Nullable BiConsumer exceptionHandler, + boolean propagateCancel + ) { + return factory.combine(promises, exceptionHandler, propagateCancel); + } + + public static @NotNull Promise> combine(@NotNull Map> promises, @NotNull BiConsumer exceptionHandler) { + return factory.combine(promises, exceptionHandler); + } + + public static @NotNull Promise> combine(@NotNull Map> promises, boolean cancelOnError) { + return factory.combine(promises, cancelOnError); + } + + public static @NotNull Promise> combine(@NotNull Map> promises) { + return factory.combine(promises); + } + + public static @NotNull Promise> combine( + @NotNull Iterator> promises, int expectedSize, + @Nullable BiConsumer exceptionHandler, boolean propagateCancel + ) { + return factory.combine(promises, expectedSize, exceptionHandler, propagateCancel); + } + + public static @NotNull Promise> combine( + @NotNull Collection> promises, + @NotNull BiConsumer exceptionHandler, + boolean propagateCancel + ) { + return factory.combine(promises, exceptionHandler, propagateCancel); + } + + public static @NotNull Promise> combine( + @NotNull Collection> promises, + @NotNull BiConsumer exceptionHandler + ) { + return factory.combine(promises, exceptionHandler); + } + + public static @NotNull Promise> combine(@NotNull Collection> promises, boolean cancelOnError) { + return factory.combine(promises, cancelOnError); + } + + public static @NotNull Promise> combine(@NotNull Collection> promises) { + return factory.combine(promises); + } + + public static @NotNull Promise> combine( + @NotNull Stream> promises, + @NotNull BiConsumer exceptionHandler, + boolean propagateCancel + ) { + return factory.combine(promises, exceptionHandler, propagateCancel); + } + + public static @NotNull Promise> combine( + @NotNull Stream> promises, + @NotNull BiConsumer exceptionHandler + ) { + return factory.combine(promises, exceptionHandler); + } + + public static @NotNull Promise> combine(@NotNull Stream> promises, boolean cancelOnError) { + return factory.combine(promises, cancelOnError); + } + + public static @NotNull Promise> combine(@NotNull Stream> promises) { + return factory.combine(promises); + } + + public static @NotNull Promise>> allSettled( + @NotNull Iterator> promises, int estimatedSize, boolean propagateCancel) { + return factory.allSettled(promises, estimatedSize, propagateCancel); + } + + public static @NotNull Promise>> allSettled(@NotNull Collection> promises, boolean propagateCancel) { + return factory.allSettled(promises, propagateCancel); + } + + public static @NotNull Promise>> allSettled(@NotNull Collection> promises) { + return factory.allSettled(promises); + } + + public static @NotNull Promise>> allSettled(@NotNull Stream> promises, boolean propagateCancel) { + return factory.allSettled(promises, propagateCancel); + } + + public static @NotNull Promise>> allSettled(@NotNull Stream> promises) { + return factory.allSettled(promises); + } + + public static @NotNull Promise>> allSettled(boolean propagateCancel, @NotNull Promise... promises) { + return factory.allSettled(propagateCancel, promises); + } + + public static @NotNull Promise>> allSettled(@NotNull Promise... promises) { + return factory.allSettled(promises); + } + + public static @NotNull Promise all(@NotNull Iterator> promises, boolean cancelAllOnError) { + return factory.all(promises, cancelAllOnError); + } + + public static @NotNull Promise all(@NotNull Iterable> promises, boolean cancelAllOnError) { + return factory.all(promises, cancelAllOnError); + } + + public static @NotNull Promise all(@NotNull Iterable> promises) { + return factory.all(promises); + } + + public static @NotNull Promise all(@NotNull Stream> promises, boolean cancelAllOnError) { + return factory.all(promises, cancelAllOnError); + } + + public static @NotNull Promise all(@NotNull Stream> promises) { + return factory.all(promises); + } + + public static @NotNull Promise all(boolean cancelAllOnError, @NotNull Promise... promises) { + return factory.all(cancelAllOnError, promises); + } + + public static @NotNull Promise all(@NotNull Promise... promises) { + return factory.all(promises); + } + + public static @NotNull Promise race(@NotNull Iterator> promises, boolean cancelLosers) { + return factory.race(promises, cancelLosers); + } + + public static @NotNull Promise race(@NotNull Iterable> promises, boolean cancelLosers) { + return factory.race(promises, cancelLosers); + } + + public static @NotNull Promise race(@NotNull Iterable> promises) { + return factory.race(promises); + } + + public static @NotNull Promise race(@NotNull Stream> promises, boolean cancelLosers) { + return factory.race(promises, cancelLosers); + } + + public static @NotNull Promise race(@NotNull Stream> promises) { + return factory.race(promises); + } + + public static @NotNull Promise wrap(@NotNull CompletableFuture future) { + return factory.wrap(future); + } + + public static @NotNull Promise start() { + return factory.start(); + } + + public static @NotNull Promise resolve(T value) { + return factory.resolve(value); + } + + public static @NotNull Promise error(@NotNull Throwable error) { + return factory.error(error); + } + +} diff --git a/futur-static/build.gradle b/futur-static/build.gradle deleted file mode 100644 index a60942c..0000000 --- a/futur-static/build.gradle +++ /dev/null @@ -1,6 +0,0 @@ -apply plugin: 'java-library' - -dependencies { - api project(':futur-api') - testImplementation project(':futur-api') -} \ No newline at end of file diff --git a/futur-static/src/main/java/dev/tommyjs/futur/lazy/PromiseUtil.java b/futur-static/src/main/java/dev/tommyjs/futur/lazy/PromiseUtil.java deleted file mode 100644 index abd2bdd..0000000 --- a/futur-static/src/main/java/dev/tommyjs/futur/lazy/PromiseUtil.java +++ /dev/null @@ -1,127 +0,0 @@ -package dev.tommyjs.futur.lazy; - -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 org.slf4j.Logger; - -import java.util.List; -import java.util.Map; -import java.util.concurrent.CompletableFuture; -import java.util.function.BiConsumer; - -public final class PromiseUtil { - - private static PromiseFactory pfac = StaticPromiseFactory.INSTANCE; - - public static @NotNull Logger getLogger() { - return pfac.getLogger(); - } - - public static void setPromiseFactory(PromiseFactory pfac) { - PromiseUtil.pfac = pfac; - } - - public static @NotNull Promise unresolved() { - return pfac.unresolved(); - } - - public static @NotNull Promise> combine(boolean propagateCancel, @NotNull Promise p1, @NotNull Promise p2) { - return pfac.combine(propagateCancel, p1, p2); - } - - public static @NotNull Promise> combine(@NotNull Promise p1, @NotNull Promise p2) { - return pfac.combine(p1, p2); - } - - public static @NotNull Promise> combine(boolean propagateCancel, @NotNull Map> promises, @Nullable BiConsumer exceptionHandler) { - return pfac.combine(propagateCancel, promises, exceptionHandler); - } - - public static @NotNull Promise> combine(@NotNull Map> promises, @Nullable BiConsumer exceptionHandler) { - return pfac.combine(promises, exceptionHandler); - } - - public static @NotNull Promise> combine(boolean propagateCancel, @NotNull Map> promises) { - return pfac.combine(propagateCancel, promises); - } - - public static @NotNull Promise> combine(@NotNull Map> promises) { - return pfac.combine(promises); - } - - public static @NotNull Promise> combine(boolean propagateCancel, @NotNull Iterable> promises, @Nullable BiConsumer exceptionHandler) { - return pfac.combine(propagateCancel, promises, exceptionHandler); - } - - public static @NotNull Promise> combine(@NotNull Iterable> promises, @Nullable BiConsumer exceptionHandler) { - return pfac.combine(promises, exceptionHandler); - } - - public static @NotNull Promise> combine(boolean propagateCancel, @NotNull Iterable> promises) { - return pfac.combine(propagateCancel, promises); - } - - public static @NotNull Promise> combine(@NotNull Iterable> promises) { - return pfac.combine(promises); - } - - public static @NotNull Promise>> allSettled(boolean propagateCancel, @NotNull Iterable> promiseIterable) { - return pfac.allSettled(propagateCancel, promiseIterable); - } - - public static @NotNull Promise>> allSettled(@NotNull Iterable> promiseIterable) { - return pfac.allSettled(promiseIterable); - } - - public static @NotNull Promise>> allSettled(boolean propagateCancel, @NotNull Promise... promiseArray) { - return pfac.allSettled(propagateCancel, promiseArray); - } - - public static @NotNull Promise>> allSettled(@NotNull Promise... promiseArray) { - return pfac.allSettled(promiseArray); - } - - public static @NotNull Promise all(boolean propagateCancel, @NotNull Iterable> promiseIterable) { - return pfac.all(propagateCancel, promiseIterable); - } - - public static @NotNull Promise all(@NotNull Iterable> promiseIterable) { - return pfac.all(promiseIterable); - } - - public static @NotNull Promise all(boolean propagateCancel, @NotNull Promise... promiseArray) { - return pfac.all(propagateCancel, promiseArray); - } - - public static @NotNull Promise all(@NotNull Promise... promiseArray) { - return pfac.all(promiseArray); - } - - public static @NotNull Promise race(@NotNull Iterable> promises) { - return pfac.race(promises); - } - - public static @NotNull Promise race(boolean cancelRaceLosers, @NotNull Iterable> promises) { - return pfac.race(cancelRaceLosers, promises); - } - - public static @NotNull Promise wrap(@NotNull CompletableFuture future) { - return pfac.wrap(future); - } - - public static @NotNull Promise resolve(T value) { - return pfac.resolve(value); - } - - public static @NotNull Promise start() { - return pfac.start(); - } - - public static @NotNull Promise error(@NotNull Throwable error) { - return pfac.error(error); - } - -} diff --git a/futur-static/src/main/java/dev/tommyjs/futur/lazy/StaticPromiseFactory.java b/futur-static/src/main/java/dev/tommyjs/futur/lazy/StaticPromiseFactory.java deleted file mode 100644 index 340b92b..0000000 --- a/futur-static/src/main/java/dev/tommyjs/futur/lazy/StaticPromiseFactory.java +++ /dev/null @@ -1,38 +0,0 @@ -package dev.tommyjs.futur.lazy; - -import dev.tommyjs.futur.executor.PromiseExecutor; -import dev.tommyjs.futur.executor.SinglePoolExecutor; -import dev.tommyjs.futur.impl.SimplePromise; -import dev.tommyjs.futur.promise.AbstractPromiseFactory; -import dev.tommyjs.futur.promise.Promise; -import org.jetbrains.annotations.NotNull; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.concurrent.Future; - -public final class StaticPromiseFactory extends AbstractPromiseFactory> { - - public final static StaticPromiseFactory INSTANCE = new StaticPromiseFactory(); - private final static @NotNull SinglePoolExecutor EXECUTOR = SinglePoolExecutor.create(1); - private final static @NotNull Logger LOGGER = LoggerFactory.getLogger(StaticPromiseFactory.class); - - private StaticPromiseFactory() { - } - - @Override - public @NotNull Logger getLogger() { - return LOGGER; - } - - @Override - public @NotNull Promise unresolved() { - return new SimplePromise<>(this); - } - - @Override - public @NotNull PromiseExecutor> getExecutor() { - return EXECUTOR; - } - -} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index ccebba7710deaf9f98673a68957ea02138b60d0a..a4b76b9530d66f5e68d973ea569d8e19de379189 100644 GIT binary patch literal 43583 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 yu?e%!_ptzGB|8GrCfu%p?(_ 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 GZ~q4|mN%#X literal 61608 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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 20db9ad..cea7a79 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 79a61d4..f3b75f3 100644 --- a/gradlew +++ b/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -83,10 +85,8 @@ done # This is normally unused # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -133,10 +133,13 @@ location of your Java installation." fi else JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. @@ -144,7 +147,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac @@ -152,7 +155,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then '' | soft) :;; #( *) # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -197,11 +200,15 @@ if "$cygwin" || "$msys" ; then done fi -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ diff --git a/gradlew.bat b/gradlew.bat index 93e3f59..9d21a21 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,11 +59,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail diff --git a/settings.gradle b/settings.gradle index 9d69470..a0e7d05 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,4 +1,4 @@ rootProject.name = 'futur' include 'futur-api' -include 'futur-static' +include 'futur-lazy' From 4236adbd9e64fe50c318c72fd08e1182d9b22a1c Mon Sep 17 00:00:00 2001 From: tommyskeff Date: Mon, 6 Jan 2025 17:53:33 +0000 Subject: [PATCH 02/16] documentation and small changes - added docs for `Promise` and `PromiseFactory` - removed outdated README docs - moved some common utilities to `PromiseUtil` - improved efficiency of result array resizing - added cancellation result to promise executors - changed visibility of `PromiseJoiner` to public, and made some method names more verbose - inlined `DeferredExecutionException` to inside `AbstractPromise` - inlined default promise implementation to inner class in the factory - removed necessity for base factories to provide a logger --- README.md | 123 +--- build.gradle | 3 +- .../futur/executor/ExecutorServiceImpl.java | 4 +- .../futur/executor/PromiseExecutor.java | 41 +- .../futur/executor/VirtualThreadImpl.java | 9 +- .../futur/function/ExceptionalConsumer.java | 14 +- .../futur/function/ExceptionalFunction.java | 16 +- .../futur/function/ExceptionalRunnable.java | 11 +- .../futur/function/ExceptionalSupplier.java | 14 +- .../futur/joiner/CompletionJoiner.java | 7 +- .../futur/joiner/MappedResultJoiner.java | 13 +- .../tommyjs/futur/joiner/PromiseJoiner.java | 45 +- .../tommyjs/futur/joiner/ResultJoiner.java | 12 +- .../dev/tommyjs/futur/joiner/VoidJoiner.java | 6 +- .../futur/promise/AbstractPromise.java | 78 +-- .../futur/promise/AbstractPromiseFactory.java | 90 +-- .../futur/promise/AsyncPromiseListener.java | 5 +- .../futur/promise/CompletablePromise.java | 11 + .../promise/DeferredExecutionException.java | 11 - .../dev/tommyjs/futur/promise/Promise.java | 397 ++++++++++- .../futur/promise/PromiseCompletion.java | 43 +- .../tommyjs/futur/promise/PromiseFactory.java | 625 +++++++++++++----- .../futur/promise/PromiseFactoryImpl.java | 11 +- .../tommyjs/futur/promise/PromiseImpl.java | 18 - .../futur/promise/PromiseListener.java | 9 +- .../ConcurrentResultArray.java | 12 +- .../dev/tommyjs/futur/util/PromiseUtil.java | 48 ++ 27 files changed, 1210 insertions(+), 466 deletions(-) delete mode 100644 futur-api/src/main/java/dev/tommyjs/futur/promise/DeferredExecutionException.java delete mode 100644 futur-api/src/main/java/dev/tommyjs/futur/promise/PromiseImpl.java rename futur-api/src/main/java/dev/tommyjs/futur/{joiner => util}/ConcurrentResultArray.java (64%) create mode 100644 futur-api/src/main/java/dev/tommyjs/futur/util/PromiseUtil.java diff --git a/README.md b/README.md index 80ea3a6..1b09a96 100644 --- a/README.md +++ b/README.md @@ -15,8 +15,8 @@ repositories { } dependencies { - compile 'dev.tommyjs:futur-api:2.1.3' - compile 'dev.tommyjs:futur-reactor:2.1.3' + compile 'dev.tommyjs:futur-api:2.4.0' + compile 'dev.tommyjs:futur-lazy:2.4.0' } ``` ### Gradle DSL @@ -26,8 +26,8 @@ repositories { } dependencies { - implementation("dev.tommyjs:futur-api:2.1.3") - implementation("dev.tommyjs:futur-reactor:2.1.3") + implementation("dev.tommyjs:futur-api:2.4.0") + implementation("dev.tommyjs:futur-lazy:2.4.0") } ``` ### Maven @@ -43,121 +43,12 @@ dependencies { dev.tommyjs futur-api - 2.1.3 + 2.4.0 dev.tommyjs - futur-reactor - 2.1.3 + futur-lazy + 2.4.0 ``` - - -## Getting started -Futur4J uses an underlying `Scheduler` instance to power both synchronous and asynchronous task execution. - -The library was originally built for use in Minecraft servers, and therefore has native support for a "main" thread with can be seamlessly switched to in a `Promise` chain (read more below). - -It is recommended, but not required, to manually create a `Scheduler`. If this is not done, a single-threaded default `Scheduler` will be used. -```java -// check if scheduler has already been loaded -if (!Schedulers.isLoaded()) { - Scheduler scheduler = ThreadPoolScheduler.create(6); // create a scheduler using an underlying thread pool (6 threads) - Schedulers.setScheduler(scheduler); // initialize the scheduling -} -``` - -The `futur-standalone` module has two scheduler implementations available. The `ExclusiveThreadPoolScheduler` operates one thread pool, and throws an exception when sync tasks are attempted to be executed. -The `ThreadPoolScheduler` uses a single-threaded "main" pool and a multi-threaded async pool. - -For Minecraft, Bukkit, Velocity and BungeeCord support is coming soon. Feel free to make PRs for other external library support as modules. - -### Using the scheduler -You can invoke tasks using the synchronous and asynchronous scheduler `Scheduler` methods. It is worth noting that exceptions in these tasks are swallowed silently, and it is therefore better to -use promise chains, or use the `Schedulers` utility class, which will wrap tasks in a try-catch and log to the standard error. - -```java -scheduler.runDelayedSync(() -> { - System.out.println("I was scheduled to execute 10 seconds later!"); -}, 10L, TimeUnit.SECONDS); - -Schedulers.runAsync(() -> { - throw new RuntimeException("I will be caught by the exception wrapper in the Schedulers class!"); -}); -``` - -### Reacting to task completions -You may have recieved some form of "future" instance (a task that will complete at an unspecified time in the future). This may be a future4j-native `Promise` instance, or another form of future. In the case of the latter, you may obtain a `Promise` - instance through the various available wrappers (e.g. `ReactorTransformer` for use with reactor core). - -You can then add listeners to the `Promise`, which will be executed asynchronously on promise "completion". A promise is deemed completed once the task has either concluded successfully or an exception has been thrown. You can access this information in a `PromiseCompletion`, -which is passed to promise listeners. Promise listeners should not throw exceptions, and will print a stack trace if they do to avoid silent failure. However, callbacks in promise chain methods are permitted to throw exceptions as they will be passed down the chain. -```java -Promise promise = doSomeAsyncProcessing(); -promise.addListener(ctx -> { - if (ctx.isError()) { - System.out.println("Error! Oh no!"); - } else { - String result = ctx.getResult(); - System.out.println(result); - } -}); -``` - -### Promise chains -`Promise` also contains convenience wrapper methods to avoid checking the completion for exceptions every time. These methods also return a new promise which will resolve once the original promise, and then handler callback have finished execution. We can use this feature to create -"promise chains", which are a sequence of promise handlers. These handlers are permitted to throw exceptions, which will be passed down promise chains until they are handled. If exceptions are not handled properly, they will be silently swallowed. It is recommend to append `logExceptions()` -to chains unless exceptions are handled explicitly with a custom listener. This will simply log exceptions in the promise chain to the standard error. - -You can also use the sync and async methods to choose which executor is chosen for task execution. The ability to seamlessly switch between executors comes in handy with applications where you must be on the "main thread" to perform non-threadsafe operations. - -`Promise.start()` can be used to create a "resolved" promise. This is useful when starting a promise chain or returning an already-completed promise (common in compose callbacks, mentioned below). - -```java -Promise.start().thenSupplyAsync(() -> { - // do some async processing - return "Hello World!"; -}).thenApplyAsync(input -> { - // convert string to upper case - return input.toUpperCase(); -}).thenConsumeSync(result -> { - // display result - System.out.println("Result: " + result); -}).logExceptions(); // log exceptions to the standard error -``` - -The promise chain methods follow Java convention of `Supplier`, `Consumer` and `Runnable` through the `thenSupply`, `thenConsume` and `thenRun` methods. There is also `thenApply` (which acts as a Java `Function`), and `thenCompose`, which is explained below. Futur4J has its own implementations of the Java concepts though to allow for -exceptions within handlers. - -The `thenCompose` method is similar to the concept in the Java `CompletableFuture` API. You are able to return another `Promise` within a promise handler, and a `Promise>` will be wrapped up into just a `Promise`. This is often useful when using an external library that returns some sort of "future" inside a handler. - -```java -String username = "abc123"; -mySuperSecretApi.usernameToId(username) // returns Promise - .thenComposeAsync(id -> { - return userManager.fetchFromDatabase(id); // returns Promise - }).thenConsumeSync(playerData -> { - System.out.println(username + " has " + playerData.getCoins() + " coins!"); - }).logExceptions(); -``` - -### Utility methods -The `Promises` utility class provides many helpful methods for working with promises and groups of promises. - -`Promises.combine(Promise, Promise)` combines two promises into one `Promise>`. - -`Promises.erase(Promise)` erases the type on a `Promise` instance and returns a `Promise`. This is also supported for lists of promises with `Promises.all(List)`, which will return a `Promise` representing the future whereby all promises have completed. - -If all promises are of identical type, you can use `Promises.combine(List>)` which will return one `Promise>`, similarly representing the future whereby all promises have completed. - -This can also be applied to key-value scenarios in the same way, where you can provide a mapper function to be applied to all elements and combine into a single promise. - - - - - -### Future wrappers -External libaries provide asynchronous ways to interact with their output in all shapes and sizes. Futur4J currently has wrappers for the following libraries/frameworks: -- Reactor Core (via futur-reactor) -- Java CompletableFuture (via `Promises.wrap` in futur-api) diff --git a/build.gradle b/build.gradle index 9b2d364..bf38bbf 100644 --- a/build.gradle +++ b/build.gradle @@ -14,7 +14,7 @@ nexusPublishing { subprojects { group = 'dev.tommyjs' - version = '2.4' + version = '2.4.0' apply plugin: 'java-library' apply plugin: 'com.github.johnrengelman.shadow' @@ -43,6 +43,7 @@ subprojects { useJUnitPlatform() testLogging { exceptionFormat = 'full' + showStandardStreams = true } } } \ No newline at end of file diff --git a/futur-api/src/main/java/dev/tommyjs/futur/executor/ExecutorServiceImpl.java b/futur-api/src/main/java/dev/tommyjs/futur/executor/ExecutorServiceImpl.java index 52e2065..acefe38 100644 --- a/futur-api/src/main/java/dev/tommyjs/futur/executor/ExecutorServiceImpl.java +++ b/futur-api/src/main/java/dev/tommyjs/futur/executor/ExecutorServiceImpl.java @@ -25,8 +25,8 @@ class ExecutorServiceImpl implements PromiseExecutor> { } @Override - public void cancel(Future task) { - task.cancel(true); + public boolean cancel(Future task) { + return task.cancel(true); } } diff --git a/futur-api/src/main/java/dev/tommyjs/futur/executor/PromiseExecutor.java b/futur-api/src/main/java/dev/tommyjs/futur/executor/PromiseExecutor.java index 4ab7637..bb9fe03 100644 --- a/futur-api/src/main/java/dev/tommyjs/futur/executor/PromiseExecutor.java +++ b/futur-api/src/main/java/dev/tommyjs/futur/executor/PromiseExecutor.java @@ -8,26 +8,65 @@ import java.util.concurrent.TimeUnit; public interface PromiseExecutor { + /** + * Creates a new {@link PromiseExecutor} that runs tasks on virtual threads. + * @return the new executor + */ static PromiseExecutor virtualThreaded() { return new VirtualThreadImpl(); } + /** + * Creates a new {@link PromiseExecutor} that runs tasks on a single thread. + * @return the new executor + */ static PromiseExecutor singleThreaded() { return of(Executors.newSingleThreadScheduledExecutor()); } + /** + * Creates a new {@link PromiseExecutor} that runs tasks on multiple threads. + * @param threads the number of threads + * @return the new executor + */ static PromiseExecutor multiThreaded(int threads) { return of(Executors.newScheduledThreadPool(threads)); } + /** + * Creates a new {@link PromiseExecutor} that runs tasks on the given executor service. + * @param service the executor service + * @return the new executor + */ static PromiseExecutor of(@NotNull ScheduledExecutorService service) { return new ExecutorServiceImpl(service); } + /** + * Runs the given task. + * @param task the task + * @return the task + * @throws Exception if scheduling the task failed + */ T run(@NotNull Runnable task) throws Exception; + /** + * Runs the given task after the given delay. + * @param task the task + * @param delay the delay + * @param unit the time unit + * @return the task + * @throws Exception if scheduling the task failed + */ T run(@NotNull Runnable task, long delay, @NotNull TimeUnit unit) throws Exception; - void cancel(T task); + /** + * Cancels the given task if possible. This may interrupt the task mid-execution. + * + * @param task the task + * @return {@code true} if the task was cancelled. {@code false} if the task was already completed + * or could not be cancelled. + */ + boolean cancel(T task); } diff --git a/futur-api/src/main/java/dev/tommyjs/futur/executor/VirtualThreadImpl.java b/futur-api/src/main/java/dev/tommyjs/futur/executor/VirtualThreadImpl.java index 95faa7c..397efc3 100644 --- a/futur-api/src/main/java/dev/tommyjs/futur/executor/VirtualThreadImpl.java +++ b/futur-api/src/main/java/dev/tommyjs/futur/executor/VirtualThreadImpl.java @@ -24,8 +24,13 @@ class VirtualThreadImpl implements PromiseExecutor { } @Override - public void cancel(Thread task) { - task.interrupt(); + public boolean cancel(Thread task) { + if (task.isAlive()) { + task.interrupt(); + return true; + } else { + return false; + } } } \ No newline at end of file diff --git a/futur-api/src/main/java/dev/tommyjs/futur/function/ExceptionalConsumer.java b/futur-api/src/main/java/dev/tommyjs/futur/function/ExceptionalConsumer.java index cb84eff..8d96919 100644 --- a/futur-api/src/main/java/dev/tommyjs/futur/function/ExceptionalConsumer.java +++ b/futur-api/src/main/java/dev/tommyjs/futur/function/ExceptionalConsumer.java @@ -1,8 +1,20 @@ package dev.tommyjs.futur.function; +/** + * Represents an operation that accepts a single input argument and returns no result, + * and may throw an exception. This is a functional interface whose functional method is {@link #accept(Object)}. + * + * @param the type of the input to the operation + */ @FunctionalInterface public interface ExceptionalConsumer { + /** + * Performs this operation on the given argument, potentially throwing an exception. + * + * @param value the input argument + * @throws Exception if unable to compute a result + */ void accept(T value) throws Exception; -} +} \ No newline at end of file diff --git a/futur-api/src/main/java/dev/tommyjs/futur/function/ExceptionalFunction.java b/futur-api/src/main/java/dev/tommyjs/futur/function/ExceptionalFunction.java index 9d32b45..403da2b 100644 --- a/futur-api/src/main/java/dev/tommyjs/futur/function/ExceptionalFunction.java +++ b/futur-api/src/main/java/dev/tommyjs/futur/function/ExceptionalFunction.java @@ -1,8 +1,22 @@ package dev.tommyjs.futur.function; +/** + * Represents a function that accepts one argument and produces a result, + * and may throw an exception. This is a functional interface whose functional method is {@link #apply(Object)}. + * + * @param the type of the input to the function + * @param the type of the result of the function + */ @FunctionalInterface public interface ExceptionalFunction { + /** + * Applies this function to the given argument, potentially throwing an exception. + * + * @param value the input argument + * @return the function result + * @throws Exception if unable to compute a result + */ V apply(K value) throws Exception; -} +} \ No newline at end of file diff --git a/futur-api/src/main/java/dev/tommyjs/futur/function/ExceptionalRunnable.java b/futur-api/src/main/java/dev/tommyjs/futur/function/ExceptionalRunnable.java index d426a9c..fc130cb 100644 --- a/futur-api/src/main/java/dev/tommyjs/futur/function/ExceptionalRunnable.java +++ b/futur-api/src/main/java/dev/tommyjs/futur/function/ExceptionalRunnable.java @@ -1,8 +1,17 @@ package dev.tommyjs.futur.function; +/** + * Represents a runnable task that may throw an exception. + * This is a functional interface whose functional method is {@link #run()}. + */ @FunctionalInterface public interface ExceptionalRunnable { + /** + * Performs this runnable task, potentially throwing an exception. + * + * @throws Exception if unable to complete the task + */ void run() throws Exception; -} +} \ No newline at end of file diff --git a/futur-api/src/main/java/dev/tommyjs/futur/function/ExceptionalSupplier.java b/futur-api/src/main/java/dev/tommyjs/futur/function/ExceptionalSupplier.java index 7e62218..ba39049 100644 --- a/futur-api/src/main/java/dev/tommyjs/futur/function/ExceptionalSupplier.java +++ b/futur-api/src/main/java/dev/tommyjs/futur/function/ExceptionalSupplier.java @@ -1,8 +1,20 @@ package dev.tommyjs.futur.function; +/** + * Represents a supplier of results that may throw an exception. + * This is a functional interface whose functional method is {@link #get()}. + * + * @param the type of results supplied by this supplier + */ @FunctionalInterface public interface ExceptionalSupplier { + /** + * Gets a result, potentially throwing an exception. + * + * @return a result + * @throws Exception if unable to supply a result + */ T get() throws Exception; -} +} \ No newline at end of file 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 3f75d55..3165d05 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 @@ -3,6 +3,7 @@ package dev.tommyjs.futur.joiner; import dev.tommyjs.futur.promise.Promise; 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; @@ -24,18 +25,18 @@ public class CompletionJoiner extends PromiseJoiner, Void, Void, List } @Override - protected Void getKey(Promise value) { + protected Void getChildKey(Promise value) { return null; } @Override - protected @NotNull Promise getPromise(Promise value) { + protected @NotNull Promise getChildPromise(Promise value) { //noinspection unchecked return (Promise) value; } @Override - protected @Nullable Throwable onFinish(int index, Void key, @NotNull PromiseCompletion res) { + protected @Nullable Throwable onChildComplete(int index, Void key, @NotNull PromiseCompletion res) { results.set(index, res); return null; } 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 1145da9..fa61dbd 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 @@ -3,6 +3,7 @@ package dev.tommyjs.futur.joiner; import dev.tommyjs.futur.promise.Promise; 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; @@ -27,19 +28,22 @@ public class MappedResultJoiner extends PromiseJoiner> entry) { + protected K getChildKey(Map.Entry> entry) { return entry.getKey(); } @Override - protected @NotNull Promise getPromise(Map.Entry> entry) { + protected @NotNull Promise getChildPromise(Map.Entry> entry) { return entry.getValue(); } @Override - protected @Nullable Throwable onFinish(int index, K key, @NotNull PromiseCompletion res) { + protected @Nullable Throwable onChildComplete(int index, K key, @NotNull PromiseCompletion res) { if (res.isError()) { - if (exceptionHandler == null) return res.getException(); + if (exceptionHandler == null) { + return res.getException(); + } + exceptionHandler.accept(key, res.getException()); } @@ -54,6 +58,7 @@ public class MappedResultJoiner extends PromiseJoiner entry : list) { map.put(entry.getKey(), entry.getValue()); } + return map; } 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 31319ba..39997c4 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 @@ -1,6 +1,10 @@ package dev.tommyjs.futur.joiner; -import dev.tommyjs.futur.promise.*; +import dev.tommyjs.futur.promise.CompletablePromise; +import dev.tommyjs.futur.promise.Promise; +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; @@ -8,7 +12,7 @@ import java.util.Iterator; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; -abstract class PromiseJoiner { +public abstract class PromiseJoiner { private final CompletablePromise joined; @@ -16,15 +20,11 @@ abstract class PromiseJoiner { this.joined = factory.unresolved(); } - public @NotNull Promise joined() { - return joined; - } + protected abstract K getChildKey(V value); - protected abstract K getKey(V value); + protected abstract @NotNull Promise getChildPromise(V value); - protected abstract @NotNull Promise getPromise(V value); - - protected abstract @Nullable Throwable onFinish(int index, K key, @NotNull PromiseCompletion completion); + protected abstract @Nullable Throwable onChildComplete(int index, K key, @NotNull PromiseCompletion completion); protected abstract R getResult(); @@ -35,18 +35,19 @@ abstract class PromiseJoiner { int i = 0; do { V value = promises.next(); - Promise p = getPromise(value); + Promise p = getChildPromise(value); + if (link) { - AbstractPromise.cancelOnFinish(p, joined); + PromiseUtil.cancelOnComplete(joined, p); } if (!joined.isCompleted()) { count.incrementAndGet(); - K key = getKey(value); + K key = getChildKey(value); int index = i++; - p.addListener((res) -> { - Throwable e = onFinish(index, key, res); + p.addAsyncListener(res -> { + Throwable e = onChildComplete(index, key, res); if (e != null) { joined.completeExceptionally(e); } else if (count.decrementAndGet() == 0 && waiting.get()) { @@ -56,11 +57,17 @@ abstract class PromiseJoiner { } } while (promises.hasNext()); - count.updateAndGet((v) -> { - if (v == 0) joined.complete(getResult()); - else waiting.set(true); - return v; - }); + if (!joined.isCompleted()) { + count.updateAndGet(v -> { + if (v == 0) joined.complete(getResult()); + else waiting.set(true); + return v; + }); + } + } + + public @NotNull Promise joined() { + return joined; } } 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 d69ad33..28046fb 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 @@ -3,6 +3,7 @@ package dev.tommyjs.futur.joiner; import dev.tommyjs.futur.promise.Promise; 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; @@ -28,19 +29,22 @@ public class ResultJoiner extends PromiseJoiner, Void, T, List> } @Override - protected Void getKey(Promise value) { + protected Void getChildKey(Promise value) { return null; } @Override - protected @NotNull Promise getPromise(Promise value) { + protected @NotNull Promise getChildPromise(Promise value) { return value; } @Override - protected @Nullable Throwable onFinish(int index, Void key, @NotNull PromiseCompletion res) { + protected @Nullable Throwable onChildComplete(int index, Void key, @NotNull PromiseCompletion res) { if (res.isError()) { - if (exceptionHandler == null) return res.getException(); + if (exceptionHandler == null) { + return res.getException(); + } + exceptionHandler.accept(index, res.getException()); } 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 8998956..195f9ab 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 @@ -16,18 +16,18 @@ public class VoidJoiner extends PromiseJoiner, Void, Void, Void> { } @Override - protected Void getKey(Promise value) { + protected Void getChildKey(Promise value) { return null; } @Override - protected @NotNull Promise getPromise(Promise value) { + protected @NotNull Promise getChildPromise(Promise value) { //noinspection unchecked return (Promise) value; } @Override - protected @Nullable Throwable onFinish(int index, Void key, @NotNull PromiseCompletion completion) { + protected @Nullable Throwable onChildComplete(int index, Void key, @NotNull PromiseCompletion completion) { return completion.getException(); } 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 5e673c4..fe55aa1 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 @@ -4,6 +4,7 @@ import dev.tommyjs.futur.function.ExceptionalConsumer; import dev.tommyjs.futur.function.ExceptionalFunction; import dev.tommyjs.futur.function.ExceptionalRunnable; import dev.tommyjs.futur.function.ExceptionalSupplier; +import dev.tommyjs.futur.util.PromiseUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; @@ -18,18 +19,6 @@ import java.util.function.Consumer; public abstract class AbstractPromise implements CompletablePromise { - public static void propagateResult(Promise from, CompletablePromise to) { - from.addDirectListener(to::complete, to::completeExceptionally); - } - - public static void propagateCancel(Promise from, Promise to) { - from.onCancel(to::cancel); - } - - public static void cancelOnFinish(Promise toCancel, Promise toFinish) { - toFinish.addDirectListener(_ -> toCancel.cancel()); - } - private final AtomicReference>> listeners; private final AtomicReference> completion; private final CountDownLatch latch; @@ -40,6 +29,8 @@ public abstract class AbstractPromise implements CompletablePromise getFactory(); + private void runCompleter(@NotNull CompletablePromise promise, @NotNull ExceptionalRunnable completer) { try { completer.run(); @@ -62,8 +53,6 @@ public abstract class AbstractPromise implements CompletablePromise getFactory(); - protected @NotNull Logger getLogger() { return getFactory().getLogger(); } @@ -106,7 +95,7 @@ public abstract class AbstractPromise implements CompletablePromise fork() { CompletablePromise fork = getFactory().unresolved(); - propagateResult(this, fork); + PromiseUtil.propagateCompletion(this, fork); return fork; } @@ -139,7 +128,7 @@ public abstract class AbstractPromise implements CompletablePromise implements CompletablePromise implements CompletablePromise implements CompletablePromise implements CompletablePromise implements CompletablePromise task.get(), delay, unit); } - @Override - public @NotNull Promise thenPopulateReference(@NotNull AtomicReference reference) { - return thenApplyAsync(result -> { - reference.set(result); - return result; - }); - } - @Override public @NotNull Promise thenApplyAsync(@NotNull ExceptionalFunction task) { CompletablePromise promise = getFactory().unresolved(); @@ -317,7 +298,7 @@ public abstract class AbstractPromise implements CompletablePromise implements CompletablePromise implements CompletablePromise thenPopulateReference(@NotNull AtomicReference reference) { + return thenApplyAsync(result -> { + reference.set(result); + return result; + }); + } + @Override public @NotNull Promise erase() { return thenSupply(() -> null); @@ -368,7 +357,7 @@ public abstract class AbstractPromise implements CompletablePromise addAsyncListener(@Nullable Consumer successListener, @Nullable Consumer errorListener) { - return addAsyncListener((res) -> { + return addAsyncListener(res -> { if (res.isSuccess()) { if (successListener != null) successListener.accept(res.getResult()); } else { @@ -384,7 +373,7 @@ public abstract class AbstractPromise implements CompletablePromise addDirectListener(@Nullable Consumer successListener, @Nullable Consumer errorListener) { - return addDirectListener((res) -> { + return addDirectListener(res -> { if (res.isSuccess()) { if (successListener != null) successListener.accept(res.getResult()); } else { @@ -414,6 +403,7 @@ public abstract class AbstractPromise implements CompletablePromise listener, PromiseCompletion res) { try { getFactory().getAsyncExecutor().run(() -> callListenerNow(listener, res)); + } catch (RejectedExecutionException ignored) { } catch (Exception e) { getLogger().warn("Exception caught while running promise listener", e); } @@ -447,10 +437,9 @@ public abstract class AbstractPromise implements CompletablePromise @NotNull Promise onError(@NotNull Class clazz, @NotNull Consumer listener) { - return onError((e) -> { - if (clazz.isAssignableFrom(e.getClass())) { - getLogger().info("On Error {}", e.getClass()); + public @NotNull Promise onError(@NotNull Class type, @NotNull Consumer listener) { + return onError(e -> { + if (type.isAssignableFrom(e.getClass())) { //noinspection unchecked listener.accept((E) e); } @@ -551,4 +540,7 @@ public abstract class AbstractPromise implements CompletablePromise implements PromiseFactory { + public abstract @NotNull Logger getLogger(); + public abstract @NotNull PromiseExecutor getSyncExecutor(); public abstract @NotNull PromiseExecutor getAsyncExecutor(); + @Override + public @NotNull Promise resolve(T value) { + CompletablePromise promise = unresolved(); + promise.complete(value); + return promise; + } + + @Override + public @NotNull Promise error(@NotNull Throwable error) { + CompletablePromise promise = unresolved(); + promise.completeExceptionally(error); + return promise; + } + + @Override + public @NotNull Promise wrap(@NotNull CompletableFuture future) { + return wrap(future, future); + } + + private @NotNull Promise wrap(@NotNull CompletionStage completion, Future future) { + CompletablePromise promise = unresolved(); + completion.whenComplete((v, e) -> { + if (e != null) { + promise.completeExceptionally(e); + } else { + promise.complete(v); + } + }); + + promise.onCancel(_ -> future.cancel(true)); + return promise; + } + @Override public @NotNull Promise> combine( @NotNull Promise p1, @NotNull Promise p2, - boolean dontFork + boolean link ) { - return all(dontFork, p1, p2).thenApply((_) -> new AbstractMap.SimpleImmutableEntry<>( + return all(link, p1, p2).thenApply(_ -> new AbstractMap.SimpleImmutableEntry<>( Objects.requireNonNull(p1.getCompletion()).getResult(), Objects.requireNonNull(p2.getCompletion()).getResult() )); @@ -71,59 +108,26 @@ public abstract class AbstractPromiseFactory implements PromiseFactory { } @Override - public @NotNull Promise race(@NotNull Iterator> promises, boolean link) { + public @NotNull Promise race(@NotNull Iterator> promises, boolean cancelLosers) { CompletablePromise promise = unresolved(); promises.forEachRemaining(p -> { - if (link) AbstractPromise.cancelOnFinish(p, promise); - if (!promise.isCompleted()) - AbstractPromise.propagateResult(p, promise); - }); - - return promise; - } - - @Override - public @NotNull Promise race(@NotNull Iterable> promises, boolean link) { - return race(promises.iterator(), link); - } - - @Override - public @NotNull Promise race(@NotNull Stream> promises, boolean link) { - return race(promises.iterator(), link); - } - - @Override - public @NotNull Promise wrap(@NotNull CompletableFuture future) { - return wrap(future, future); - } - - private @NotNull Promise wrap(@NotNull CompletionStage completion, Future future) { - CompletablePromise promise = unresolved(); - - completion.whenComplete((v, e) -> { - if (e != null) { - promise.completeExceptionally(e); - } else { - promise.complete(v); + if (cancelLosers) PromiseUtil.cancelOnComplete(promise, p); + if (!promise.isCompleted()) { + PromiseUtil.propagateCompletion(p, promise); } }); - promise.onCancel(_ -> future.cancel(true)); return promise; } @Override - public @NotNull Promise resolve(T value) { - CompletablePromise promise = unresolved(); - promise.complete(value); - return promise; + public @NotNull Promise race(@NotNull Iterable> promises, boolean cancelLosers) { + return race(promises.iterator(), cancelLosers); } @Override - public @NotNull Promise error(@NotNull Throwable error) { - CompletablePromise promise = unresolved(); - promise.completeExceptionally(error); - return promise; + public @NotNull Promise race(@NotNull Stream> promises, boolean cancelLosers) { + return race(promises.iterator(), cancelLosers); } } diff --git a/futur-api/src/main/java/dev/tommyjs/futur/promise/AsyncPromiseListener.java b/futur-api/src/main/java/dev/tommyjs/futur/promise/AsyncPromiseListener.java index 7e0343b..799b6be 100644 --- a/futur-api/src/main/java/dev/tommyjs/futur/promise/AsyncPromiseListener.java +++ b/futur-api/src/main/java/dev/tommyjs/futur/promise/AsyncPromiseListener.java @@ -1,5 +1,8 @@ package dev.tommyjs.futur.promise; +/** + * A listener for a {@link Promise} that is called when the promise is resolved. This listener is + * executed asynchronously by the {@link PromiseFactory} that created the completed promise. + */ public interface AsyncPromiseListener extends PromiseListener { - } diff --git a/futur-api/src/main/java/dev/tommyjs/futur/promise/CompletablePromise.java b/futur-api/src/main/java/dev/tommyjs/futur/promise/CompletablePromise.java index 101bc55..3baff14 100644 --- a/futur-api/src/main/java/dev/tommyjs/futur/promise/CompletablePromise.java +++ b/futur-api/src/main/java/dev/tommyjs/futur/promise/CompletablePromise.java @@ -3,10 +3,21 @@ package dev.tommyjs.futur.promise; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +/** + * A {@link Promise} that can be completed. + */ public interface CompletablePromise extends Promise { + /** + * Completes the promise successfully with the given result. + * @param result the result + */ void complete(@Nullable T result); + /** + * Completes the promise exceptionally with the given exception. + * @param result the exception + */ void completeExceptionally(@NotNull Throwable result); } diff --git a/futur-api/src/main/java/dev/tommyjs/futur/promise/DeferredExecutionException.java b/futur-api/src/main/java/dev/tommyjs/futur/promise/DeferredExecutionException.java deleted file mode 100644 index c1f58f8..0000000 --- a/futur-api/src/main/java/dev/tommyjs/futur/promise/DeferredExecutionException.java +++ /dev/null @@ -1,11 +0,0 @@ -package dev.tommyjs.futur.promise; - -import java.util.concurrent.ExecutionException; - -class DeferredExecutionException extends ExecutionException { - - public DeferredExecutionException() { - super(); - } - -} 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 8883019..1cab047 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 @@ -12,171 +12,516 @@ import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; +/** + *

+ * A promise represents the result of an asynchronous computation. A promise will transition from a + * pending state to a completed state at most once, but may remain in a pending state indefinitely. + *

+ * + *

+ * Promises are created by a {@link PromiseFactory} and support chaining operations to be executed + * upon completion. These operations can be synchronous or asynchronous, and can be composed in a + * variety of ways. Promises can be listened to for completions, either with a result or with an + * exception. Promises can be cancelled, which will propagate a cancellation signal through the + * chain, but a promise can also be forked, which will prevent propagation of cancellations. + *

+ * + * @see #cancel() + * @see #fork() + */ public interface Promise { + /** + * Returns the factory that created this promise. This factory can be used to create new promises. + */ @NotNull PromiseFactory getFactory(); + /** + * Chains a task to be executed after this promise completes. The task will be executed immediately + * when this promise completes. + * + * @param task the task to execute + * @return a new promise that completes after the task is executed + */ @NotNull Promise thenRun(@NotNull ExceptionalRunnable task); + /** + * Chains a task to be executed after this promise completes. The task will be executed immediately + * when this promise completes and will be passed the result of this promise. + * + * @param task the task to execute + * @return a new promise that completes after the task is executed + */ @NotNull Promise thenConsume(@NotNull ExceptionalConsumer task); + /** + * Chains a task to be executed after this promise completes. The task will be executed immediately + * when this promise completes, and will supply a value to the next promise in the chain. + * + * @param task the task to execute + * @return a new promise that completes, after the task is executed, with the task result + */ @NotNull Promise thenSupply(@NotNull ExceptionalSupplier task); + /** + * Chains a task to be executed after this promise completes. The task will be executed immediately + * when this promise completes, and will apply the specified function to the result of this promise + * in order to supply a value to the next promise in the chain. + * + * @param task the task to execute + * @return a new promise that completes, after the task is executed, with the task result + */ @NotNull Promise thenApply(@NotNull ExceptionalFunction task); + /** + * Chains a task to be executed after this promise completes. The task will be executed immediately + * when this promise completes, and will compose the next promise in the chainfrom the result of + * this promise. + * + * @param task the task to execute + * @return a new promise that completes, once this promise and the promise returned by + * the task are complete, with the result of the task promise + */ @NotNull Promise thenCompose(@NotNull ExceptionalFunction> task); + /** + * Chains a task to be executed after this promise completes. The task will be executed by the + * sync executor of the factory that created this promise, immediately after this promise completes. + * + * @param task the task to execute + * @return a new promise that completes after the task is executed + */ @NotNull Promise thenRunSync(@NotNull ExceptionalRunnable task); + /** + * Chains a task to be executed after this promise completes. The task will be executed by the + * sync executor of the factory that created this promise, after the specified delay after this + * promise completes. + * + * @param task the task to execute + * @param delay the amount of time to wait before executing the task + * @param unit the time unit of the delay + * @return a new promise that completes after the task is executed + */ @NotNull Promise thenRunDelayedSync(@NotNull ExceptionalRunnable task, long delay, @NotNull TimeUnit unit); + /** + * Chains a task to be executed after this promise completes. The task will be executed by the + * sync executor of the factory that created this promise immediately after this promise completes, + * and will be passed the result of this promise. + * + * @param task the task to execute + * @return a new promise that completes after the task is executed + */ @NotNull Promise thenConsumeSync(@NotNull ExceptionalConsumer task); + /** + * Chains a task to be executed after this promise completes. The task will be executed by the + * sync executor of the factory that created this promise after the specified delay after this + * promise completes, and will be passed the result of this promise. + * + * @param task the task to execute + * @param delay the amount of time to wait before executing the task + * @param unit the time unit of the delay + * @return a new promise that completes after the task is executed + */ @NotNull Promise thenConsumeDelayedSync(@NotNull ExceptionalConsumer task, long delay, @NotNull TimeUnit unit); + /** + * Chains a task to be executed after this promise completes. The task will be executed immediately + * by the sync executor of the factory that created this promise when this promise completes, and + * will supply a value to the next promise in the chain. + * + * @param task the task to execute + * @return a new promise that completes, after the task is executed, with the task result + */ @NotNull Promise thenSupplySync(@NotNull ExceptionalSupplier task); + /** + * Chains a task to be executed after this promise completes. The task will be executed by the sync + * executor of the factory that created this promise after the specified delay after this promise + * completes, and will supply a value to the next promise in the chain. + * + * @param task the task to execute + * @param delay the amount of time to wait before executing the task + * @param unit the time unit of the delay + * @return a new promise that completes, after the task is executed, with the task result + */ @NotNull Promise thenSupplyDelayedSync(@NotNull ExceptionalSupplier task, long delay, @NotNull TimeUnit unit); + /** + * Chains a task to be executed after this promise completes. The task will be executed by the sync + * executor of the factory that created this promise immediately after this promise completes, and + * will apply the specified function to the result of this promise in order to supply a value to the + * next promise in the chain. + * + * @param task the task to execute + * @return a new promise that completes, after the task is executed, with the task result + */ @NotNull Promise thenApplySync(@NotNull ExceptionalFunction task); + /** + * Chains a task to be executed after this promise completes. The task will be executed by the sync + * executor of the factory that created this promise after the specified delay after this promise + * completes, and will apply the specified function to the result of this promise in order to supply + * a value to the next promise in the chain. + * + * @param task the task to execute + * @param delay the amount of time to wait before executing the task + * @param unit the time unit of the delay + * @return a new promise that completes, after the task is executed, with the task result + */ @NotNull Promise thenApplyDelayedSync(@NotNull ExceptionalFunction task, long delay, @NotNull TimeUnit unit); + /** + * Chains a task to be executed after this promise completes. The task will be executed by the sync + * executor of the factory that created this promise immediately after this promise completes, and + * will compose the next promise in the chain from the result of this promise. + * + * @param task the task to execute + * @return a new promise that completes, once this promise and the promise returned by the task are + * complete, with the result of the task promise + */ @NotNull Promise thenComposeSync(@NotNull ExceptionalFunction> task); + /** + * Chains a task to be executed after this promise completes. The task will be executed by the + * async executor of the factory that created this promise, immediately after this promise completes. + * + * @param task the task to execute + * @return a new promise that completes after the task is executed + */ @NotNull Promise thenRunAsync(@NotNull ExceptionalRunnable task); + /** + * Chains a task to be executed after this promise completes. The task will be executed by the + * async executor of the factory that created this promise after the specified delay after this + * promise completes. + * + * @param task the task to execute + * @param delay the amount of time to wait before executing the task + * @param unit the time unit of the delay + * @return a new promise that completes after the task is executed + */ @NotNull Promise thenRunDelayedAsync(@NotNull ExceptionalRunnable task, long delay, @NotNull TimeUnit unit); + /** + * Chains a task to be executed after this promise completes. The task will be executed by the + * async executor of the factory that created this promise immediately after this promise completes, + * and will be passed the result of this promise. + * + * @param task the task to execute + * @return a new promise that completes after the task is executed + */ @NotNull Promise thenConsumeAsync(@NotNull ExceptionalConsumer task); + /** + * Chains a task to be executed after this promise completes. The task will be executed by the + * async executor of the factory that created this promise after the specified delay after this + * promise completes, and will be passed the result of this promise. + * + * @param task the task to execute + * @param delay the amount of time to wait before executing the task + * @param unit the time unit of the delay + * @return a new promise that completes after the task is executed + */ @NotNull Promise thenConsumeDelayedAsync(@NotNull ExceptionalConsumer task, long delay, @NotNull TimeUnit unit); + /** + * Chains a task to be executed after this promise completes. The task will be executed by the + * async executor of the factory that created this promise immediately after this promise completes, + * and will supply a value to the next promise in the chain. + * + * @param task the task to execute + * @return a new promise that completes, after the task is executed, with the task result + */ @NotNull Promise thenSupplyAsync(@NotNull ExceptionalSupplier task); + /** + * Chains a task to be executed after this promise completes. The task will be executed by the async + * executor of the factory that created this promise after the specified delay after this promise + * completes, and will supply a value to the next promise in the chain. + * + * @param task the task to execute + * @param delay the amount of time to wait before executing the task + * @param unit the time unit of the delay + * @return a new promise that completes, after the task is executed, with the task result + */ @NotNull Promise thenSupplyDelayedAsync(@NotNull ExceptionalSupplier task, long delay, @NotNull TimeUnit unit); - @NotNull Promise thenPopulateReference(@NotNull AtomicReference reference); - + /** + * Chains a task to be executed after this promise completes. The task will be executed by the async + * executor of the factory that created this promise immediately after this promise completes, and + * will apply the specified function to the result of this promise in order to supply a value to the + * next promise in the chain. + * + * @param task the task to execute + * @return a new promise that completes, after the task is executed, with the task result + */ @NotNull Promise thenApplyAsync(@NotNull ExceptionalFunction task); + /** + * Chains a task to be executed after this promise completes. The task will be executed by the async + * executor of the factory that created this promise after the specified delay after this promise + * completes, and will apply the specified function to the result of this promise in order to supply + * a value to the next promise in the chain. + * + * @param task the task to execute + * @param delay the amount of time to wait before executing the task + * @param unit the time unit of the delay + * @return a new promise that completes, after the task is executed, with the task result + */ @NotNull Promise thenApplyDelayedAsync(@NotNull ExceptionalFunction task, long delay, @NotNull TimeUnit unit); + /** + * Chains a task to be executed after this promise completes. The task will be executed by the async + * executor of the factory that created this promise immediately after this promise completes, and + * will compose the next promise in the chain from the result of this promise. + * + * @param task the task to execute + * @return a new promise that completes, once this promise and the promise returned by the task are + * complete, with the result of the task promise + */ @NotNull Promise thenComposeAsync(@NotNull ExceptionalFunction> task); + /** + * Adds a listener to this promise that will populate the specified reference with the result of this + * promise upon successful completion. + * + * @param reference the reference to populate + * @return continuation of the promise chain + */ + @NotNull Promise thenPopulateReference(@NotNull AtomicReference reference); + + /** + * Returns a promise backed by this promise that will complete with {@code null} if this promise + * completes successfully, or with the exception if this promise completes exceptionally. + */ @NotNull Promise erase(); + /** + * Logs any exceptions that occur in the promise chain. + * + * @return continuation of the promise chain + */ default @NotNull Promise logExceptions() { return logExceptions("Exception caught in promise chain"); } + /** + * Logs any exceptions that occur in the promise chain with the specified message. + * + * @param message the message to log + * @return continuation of the promise chain + */ @NotNull Promise logExceptions(@NotNull String message); /** - * @apiNote Direct listeners run on the same thread as the completion. + * Adds a listener to this promise that will be executed immediately when this promise completes, + * on the same thread as the completion call. + * + * @param listener the listener to add + * @return continuation of the promise chain */ @NotNull Promise addDirectListener(@NotNull PromiseListener listener); /** - * @apiNote Direct listeners run on the same thread as the completion. + * Adds a listener to this promise that will be executed immediately when this promise completes, + * on the same thread as the completion call. One of {@code successHandler} and {@code errorHandler} will be + * called when the promise completes successfully or exceptionally, respectively. + * + * @param successHandler the function to call on success + * @param errorHandler the function to call on error + * @return continuation of the promise chain */ @NotNull Promise addDirectListener(@Nullable Consumer successHandler, @Nullable Consumer errorHandler); /** - * @apiNote Async listeners are run in parallel. + * Adds a listener to this promise that will be executed immediately when this promise completes, + * by the async executor of the factory that created this promise. + * + * @param listener the listener to add + * @return continuation of the promise chain */ @NotNull Promise addAsyncListener(@NotNull AsyncPromiseListener listener); /** - * @apiNote Same as addAsyncListener. + * Adds a listener to this promise that will be executed immediately when this promise completes. + * + * @param listener the listener to add + * @return continuation of the promise chain */ default @NotNull Promise addListener(@NotNull AsyncPromiseListener listener) { return addAsyncListener(listener); } /** - * @apiNote Async listeners are run in parallel. + * Adds a listener to this promise that will be executed immediately when this promise completes, + * by the async executor of the factory that created this promise. One of {@code successHandler} and + * {@code errorHandler} will be called when the promise completes successfully or exceptionally, respectively. + * + * @param successHandler the function to call on success + * @param errorHandler the function to call on error */ @NotNull Promise addAsyncListener(@Nullable Consumer successHandler, @Nullable Consumer errorHandler); + /** + * Adds a listener to this promise that will be called if the promise is completed successfully. + * + * @param listener the listener to add + * @return continuation of the promise chain + */ @NotNull Promise onSuccess(@NotNull Consumer listener); + /** + * Adds a listener to this promise that will be called if the promise is completed exceptionally. + * + * @param listener the listener to add + * @return continuation of the promise chain + */ @NotNull Promise onError(@NotNull Consumer listener); - @NotNull Promise onError(@NotNull Class clazz, @NotNull Consumer listener); + /** + * Adds a listener to this promise that will be called if the promise is completed exceptionally + * with an exception of the specified type. + * + * @param listener the listener to add + * @param type the class of the exception to listen for + * @return continuation of the promise chain + */ + @NotNull Promise onError(@NotNull Class type, @NotNull Consumer listener); + /** + * Adds a listener to this promise that will be called if the promise is cancelled. + * + * @param listener the listener to add + * @return continuation of the promise chain + */ @NotNull Promise onCancel(@NotNull Consumer listener); /** - * Cancels the promise with a TimeoutException after the specified time. + * Cancels the promise if not already completed after the specified timeout. This will result in + * an exceptional completion with a {@link CancellationException}. + * + * @param time the amount of time to wait before cancelling the promise + * @param unit the time unit of the delay + * @return continuation of the promise chain */ @NotNull Promise timeout(long time, @NotNull TimeUnit unit); /** - * Cancels the promise with a TimeoutException after the specified time. + * Cancels the promise if not already completed after the specified timeout. This will result in + * an exceptional completion with a {@link CancellationException}. + * @param ms the amount of time to wait before cancelling the promise (in milliseconds) + * @return continuation of the promise chain */ default @NotNull Promise timeout(long ms) { return timeout(ms, TimeUnit.MILLISECONDS); } /** - * Completes the promise exceptionally with a TimeoutException after the specified time. + * Times out the promise if not already completed after the specified timeout. This will result + * in an exceptional completion with a {@link TimeoutException}. This will not result in the + * promise being cancelled. + * + * @param time the amount of time to wait before timing out the promise + * @param unit the time unit of the delay + * @return continuation of the promise chain */ @NotNull Promise maxWaitTime(long time, @NotNull TimeUnit unit); /** - * Completes the promise exceptionally with a TimeoutException after the specified time. + * Times out the promise if not already completed after the specified timeout. This will result + * in an exceptional completion with a {@link TimeoutException}. This will not result in the + * promise being cancelled. + * @param ms the amount of time to wait before timing out the promise (in milliseconds) + * @return continuation of the promise chain */ default @NotNull Promise maxWaitTime(long ms) { return maxWaitTime(ms, TimeUnit.MILLISECONDS); } + /** + * Cancels the promise if not already completed after the specified timeout. This will result in + * an exceptional completion with the specified cancellation. + * @param exception the cancellation exception to complete the promise with + */ void cancel(@NotNull CancellationException exception); + /** + * Cancels the promise if not already completed after the specified timeout. This will result in + * an exceptional completion with a {@link CancellationException}. + * @param reason the reason for the cancellation + */ default void cancel(@NotNull String reason) { cancel(new CancellationException(reason)); - }; + } + /** + * Cancels the promise if not already completed after the specified timeout. This will result in + * an exceptional completion with a {@link CancellationException}. + */ default void cancel() { cancel(new CancellationException()); } /** - * Waits if necessary for this promise to complete, and then returns its result. - * @throws CancellationException if the computation was cancelled - * @throws CompletionException if this promise completed exceptionally + * Blocks until this promise has completed, and then returns its result. + * @throws CancellationException if the promise was cancelled + * @throws CompletionException if the promise completed exceptionally + * @return the result of the promise */ @Blocking T await(); /** - * Waits if necessary for this promise to complete, and then returns its result. - * @throws CancellationException if the computation was cancelled - * @throws ExecutionException if this promise completed exceptionally - * @throws InterruptedException if the current thread was interrupted while waiting + * Blocks until this promise has completed, and then returns its result. + * @throws CancellationException if the promise was cancelled + * @throws ExecutionException if the promise completed exceptionally + * @throws InterruptedException if the current thread was interrupted while waiting + * @return the result of the promise */ @Blocking T get() throws InterruptedException, ExecutionException; /** - * Waits if necessary for at most the given time for this future to complete, and then returns its result, if available. - * @throws CancellationException if the computation was cancelled - * @throws ExecutionException if this promise completed exceptionally - * @throws InterruptedException if the current thread was interrupted while waiting - * @throws TimeoutException if the wait timed out + * Blocks until either this promise has completed or the timeout has been exceeded, and then + * returns its result, if available. + * @throws CancellationException if the promise was cancelled + * @throws ExecutionException if the promise completed exceptionally + * @throws InterruptedException if the current thread was interrupted while waiting + * @throws TimeoutException if the timeout was exceeded + * @return the result of the promise */ @Blocking T get(long timeout, @NotNull TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException; /** - * Stops this promise from propagating up cancellations. + * Returns a new promise, backed by this promise, that will not propagate cancellations. This means + * that if the returned promise is cancelled, the cancellation will not be propagated to this promise + * or any other promises that share this promise as a parent. + * @return continuation the promise chain that will not propagate cancellations */ @NotNull Promise fork(); + /** + * Returns the current completion state of this promise. If the promise has not completed, this method + * will return {@code null}. + * @return the completion state of this promise, or {@code null} if the promise has not completed + */ @Nullable PromiseCompletion getCompletion(); + /** + * Returns whether this promise has completed. + * @return {@code true} if the promise has completed, {@code false} otherwise + */ boolean isCompleted(); + /** + * Converts this promise to a {@link CompletableFuture}. The returned future will complete with the + * result of this promise when it completes. + * @return a future that will complete with the result of this promise + */ @NotNull CompletableFuture toFuture(); } diff --git a/futur-api/src/main/java/dev/tommyjs/futur/promise/PromiseCompletion.java b/futur-api/src/main/java/dev/tommyjs/futur/promise/PromiseCompletion.java index 865b66c..2af014f 100644 --- a/futur-api/src/main/java/dev/tommyjs/futur/promise/PromiseCompletion.java +++ b/futur-api/src/main/java/dev/tommyjs/futur/promise/PromiseCompletion.java @@ -5,39 +5,78 @@ import org.jetbrains.annotations.Nullable; import java.util.concurrent.CancellationException; +/** + * Represents the result of a {@link Promise}, containing either an optional result or an exception. + */ public class PromiseCompletion { private @Nullable T result; private @Nullable Throwable exception; + /** + * Creates a new successful completion. + * @param result the result + */ public PromiseCompletion(@Nullable T result) { this.result = result; } + /** + * Creates a new exceptional completion. + * @param exception the exception + */ public PromiseCompletion(@NotNull Throwable exception) { this.exception = exception; } + /** + * Creates a new successful completion with a result of {@code null}. + */ public PromiseCompletion() { - this.result = null; + this((T) null); } + /** + * Checks if the completion was successful. + * @return {@code true} if the completion was successful, {@code false} otherwise + */ public boolean isSuccess() { return exception == null; } + /** + * Checks if the completion was exceptional. + * @return {@code true} if the completion was exceptional, {@code false} otherwise + */ public boolean isError() { return exception != null; } - public boolean wasCanceled() { + /** + * Checks if the completion was cancelled. + * @return {@code true} if the completion was cancelled, {@code false} otherwise + */ + public boolean wasCancelled() { return exception instanceof CancellationException; } + @Deprecated + public boolean wasCanceled() { + return wasCancelled(); + } + + /** + * Gets the result of the completion. + * @return the result, or {@code null} if the completion was exceptional + */ public @Nullable T getResult() { return result; } + /** + * Gets the exception of the completion. + * @return the exception, or {@code null} if the completion was successful + */ public @Nullable Throwable getException() { return exception; } 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 80d6963..8aac38f 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 @@ -1,6 +1,7 @@ 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; @@ -13,181 +14,491 @@ import java.util.stream.Stream; public interface PromiseFactory { - static @NotNull PromiseFactory of(@NotNull Logger logger, @NotNull PromiseExecutor syncExecutor, @NotNull PromiseExecutor asyncExecutor) { + /** + * Creates a new {@link PromiseFactory} with the given logger and executors. + * @param logger the logger + * @param syncExecutor the synchronous executor + * @param asyncExecutor the asynchronous executor + * @return the new promise factory + */ + static @NotNull PromiseFactory of(@NotNull Logger logger, @NotNull PromiseExecutor syncExecutor, + @NotNull PromiseExecutor asyncExecutor) { return new PromiseFactoryImpl<>(logger, syncExecutor, asyncExecutor); } + /** + * Creates a new {@link PromiseFactory} with the given logger and dual executor. + * @param logger the logger + * @param executor the executor + * @return the new promise factory + */ static @NotNull PromiseFactory of(@NotNull Logger logger, @NotNull PromiseExecutor executor) { return new PromiseFactoryImpl<>(logger, executor, executor); } + /** + * Creates a new {@link PromiseFactory} with the given logger and executor. + * @param logger the logger + * @param executor the executor + * @return the new promise factory + */ static @NotNull PromiseFactory of(@NotNull Logger logger, @NotNull ScheduledExecutorService executor) { return of(logger, PromiseExecutor.of(executor)); } - private static int size(@NotNull Stream stream) { - long estimate = stream.spliterator().estimateSize(); - return estimate == Long.MAX_VALUE ? 10 : (int) estimate; - } - - @NotNull Logger getLogger(); - + /** + * Creates a new uncompleted promise. + * @return the new promise + */ @NotNull CompletablePromise unresolved(); - @NotNull Promise> combine(@NotNull Promise p1, @NotNull Promise p2, boolean cancelOnError); - - default @NotNull Promise> combine(@NotNull Promise p1, @NotNull Promise p2) { - return combine(p1, p2, true); - } - - @NotNull Promise> combine( - @NotNull Map> promises, - @Nullable BiConsumer exceptionHandler, - boolean propagateCancel - ); - - default @NotNull Promise> combine(@NotNull Map> promises, @NotNull BiConsumer exceptionHandler) { - return combine(promises, exceptionHandler, true); - } - - default @NotNull Promise> combine(@NotNull Map> promises, boolean cancelOnError) { - return combine(promises, null, cancelOnError); - } - - default @NotNull Promise> combine(@NotNull Map> promises) { - return combine(promises, null, true); - } - - @NotNull Promise> combine( - @NotNull Iterator> promises, int expectedSize, - @Nullable BiConsumer exceptionHandler, boolean propagateCancel - ); - - default @NotNull Promise> combine( - @NotNull Collection> promises, - @NotNull BiConsumer exceptionHandler, - boolean propagateCancel - ) { - return combine(promises.iterator(), promises.size(), exceptionHandler, propagateCancel); - } - - default @NotNull Promise> combine( - @NotNull Collection> promises, - @NotNull BiConsumer exceptionHandler - ) { - return combine(promises.iterator(), promises.size(), exceptionHandler, true); - } - - default @NotNull Promise> combine(@NotNull Collection> promises, boolean cancelOnError) { - return combine(promises.iterator(), promises.size(), null, cancelOnError); - } - - default @NotNull Promise> combine(@NotNull Collection> promises) { - return combine(promises.iterator(), promises.size(), null, true); - } - - default @NotNull Promise> combine( - @NotNull Stream> promises, - @NotNull BiConsumer exceptionHandler, - boolean propagateCancel - ) { - return combine(promises.iterator(), size(promises), exceptionHandler, propagateCancel); - } - - default @NotNull Promise> combine( - @NotNull Stream> promises, - @NotNull BiConsumer exceptionHandler - ) { - return combine(promises.iterator(), size(promises), exceptionHandler, true); - } - - default @NotNull Promise> combine(@NotNull Stream> promises, boolean cancelOnError) { - return combine(promises.iterator(), size(promises), null, cancelOnError); - } - - default @NotNull Promise> combine(@NotNull Stream> promises) { - return combine(promises.iterator(), size(promises), null, true); - } - - @NotNull Promise>> allSettled( - @NotNull Iterator> promises, int estimatedSize, boolean propagateCancel); - - default @NotNull Promise>> allSettled(@NotNull Collection> promises, boolean propagateCancel) { - return allSettled(promises.iterator(), promises.size(), propagateCancel); - } - - default @NotNull Promise>> allSettled(@NotNull Collection> promises) { - return allSettled(promises.iterator(), promises.size(), true); - } - - default @NotNull Promise>> allSettled(@NotNull Stream> promises, boolean propagateCancel) { - return allSettled(promises.iterator(), size(promises), propagateCancel); - } - - default @NotNull Promise>> allSettled(@NotNull Stream> promises) { - return allSettled(promises.iterator(), size(promises), true); - } - - default @NotNull Promise>> allSettled(boolean propagateCancel, @NotNull Promise... promises) { - return allSettled(Arrays.asList(promises).iterator(), promises.length, propagateCancel); - } - - default @NotNull Promise>> allSettled(@NotNull Promise... promises) { - return allSettled(Arrays.asList(promises).iterator(), promises.length, true); - } - - @NotNull Promise all(@NotNull Iterator> promises, boolean cancelAllOnError); - - default @NotNull Promise all(@NotNull Iterable> promises, boolean cancelAllOnError) { - return all(promises.iterator(), cancelAllOnError); - } - - default @NotNull Promise all(@NotNull Iterable> promises) { - return all(promises.iterator(), true); - } - - default @NotNull Promise all(@NotNull Stream> promises, boolean cancelAllOnError) { - return all(promises.iterator(), cancelAllOnError); - } - - default @NotNull Promise all(@NotNull Stream> promises) { - return all(promises.iterator(), true); - } - - default @NotNull Promise all(boolean cancelAllOnError, @NotNull Promise... promises) { - return all(Arrays.asList(promises).iterator(), cancelAllOnError); - } - - default @NotNull Promise all(@NotNull Promise... promises) { - return all(Arrays.asList(promises).iterator(), true); - } - - @NotNull Promise race(@NotNull Iterator> promises, boolean cancelLosers); - - default @NotNull Promise race(@NotNull Iterable> promises, boolean cancelLosers) { - return race(promises.iterator(), cancelLosers); - } - - default @NotNull Promise race(@NotNull Iterable> promises) { - return race(promises.iterator(), true); - } - - default @NotNull Promise race(@NotNull Stream> promises, boolean cancelLosers) { - return race(promises.iterator(), cancelLosers); - } - - default @NotNull Promise race(@NotNull Stream> promises) { - return race(promises.iterator(), true); - } - - @NotNull Promise wrap(@NotNull CompletableFuture future); + /** + * Creates a new promise, completed with the given value. + * @param value the value to complete the promise with + * @return the new promise + */ + @NotNull Promise resolve(T value); + /** + * Creates a new promise, completed with {@code null}. + * @apiNote This method is often useful for starting promise chains. + * @return the new promise + */ default @NotNull Promise start() { return resolve(null); } - @NotNull Promise resolve(T value); - + /** + * Creates a new promise, completed exceptionally with the given error. + * @param error the error to complete the promise with + * @return the new promise + */ @NotNull Promise error(@NotNull Throwable error); + /** + * Creates a new promise backed by the given future. The promise will be completed upon completion + * of the future. + * @param future the future to wrap + * @return the new promise + */ + @NotNull Promise wrap(@NotNull CompletableFuture future); + + /** + * Combines two promises into a single promise that completes when both promises complete. If + * {@code link} is {@code true} and either input promise completes exceptionally (including + * cancellation), the other promise will be cancelled and the output promise will complete + * exceptionally. + * @param p1 the first promise + * @param p2 the second promise + * @param link whether to cancel the other promise on error + * @return the combined promise + */ + @NotNull Promise> combine(@NotNull Promise p1, @NotNull Promise p2, + boolean link); + + /** + * Combines two promises into a single promise that completes when both promises complete. If either + * input promise completes exceptionally, the other promise will be cancelled and the output promise + * will complete exceptionally. + * @param p1 the first promise + * @param p2 the second promise + * @return the combined promise + */ + default @NotNull Promise> combine(@NotNull Promise p1, @NotNull Promise p2) { + return combine(p1, p2, 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. 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. + * @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 + */ + @NotNull Promise> combine(@NotNull Map> promises, + @Nullable BiConsumer exceptionHandler, + 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. + * @param promises the input promises + * @param exceptionHandler the exception handler + * @return the combined promise + */ + default @NotNull Promise> combine(@NotNull Map> promises, + @NotNull BiConsumer exceptionHandler) { + return combine(promises, exceptionHandler, 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> combine(@NotNull Map> promises, boolean link) { + return combine(promises, null, 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> combine(@NotNull Map> promises) { + return combine(promises, null, true); + } + + /** + * Combines an iterator 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. If an exception + * 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 + */ + @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 + * 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 Collection> promises, boolean link) { + return combine(promises.iterator(), promises.size(), null, 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 output promise will complete exceptionally. + * @param promises the input promises + * @return the combined promise + */ + default @NotNull Promise> combine(@NotNull Collection> promises) { + return combine(promises.iterator(), promises.size(), null, 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. If an exception + * 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); + } + + /** + * 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. + * @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); + } + + /** + * Combines an iterator 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. The output promise will always complete successfully regardless + * of whether input promises fail. + * @param promises the input promises + * @param expectedSize the expected size of the list (used for optimization) + * @param link whether to cancel all promises on any exceptional completions + * @return the combined promise + */ + @NotNull Promise>> allSettled(@NotNull Iterator> promises, + int expectedSize, boolean link); + + /** + * 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 + * other promises will be cancelled. The output promise will always complete successfully regardless + * of whether input promises fail. + * @param promises the input promises + * @param link whether to cancel all promises on any exceptional completions + * @return the combined promise + */ + default @NotNull Promise>> allSettled(@NotNull Collection> promises, + boolean link) { + return allSettled(promises.iterator(), promises.size(), 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, all other promises will be cancelled. + * @param promises the input promises + * @return the combined promise + */ + default @NotNull Promise>> allSettled(@NotNull Collection> promises) { + return allSettled(promises.iterator(), promises.size(), 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. The output promise will always complete successfully regardless + * of whether input promises fail. + * @param promises the input promises + * @param link whether to cancel all promises on any exceptional completions + * @return the combined promise + */ + default @NotNull Promise>> allSettled(@NotNull Stream> promises, + boolean link) { + return allSettled(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, all other promises will be cancelled. + * @param promises the input promises + * @return the combined promise + */ + default @NotNull Promise>> allSettled(@NotNull Stream> promises) { + return allSettled(promises.iterator(), PromiseUtil.estimateSize(promises), true); + } + + /** + * Combines an array 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. The output promise will always complete successfully regardless + * of whether input promises fail. + * @param link whether to cancel all promises on any exceptional completions + * @param promises the input promises + * @return the combined promise + */ + default @NotNull Promise>> allSettled(boolean link, + @NotNull Promise... promises) { + return allSettled(Arrays.asList(promises).iterator(), promises.length, link); + } + + /** + * Combines an array of promises into a single promise that completes with a list of results when all + * promises complete. If any promise completes exceptionally, all other promises will be cancelled. + * @param promises the input promises + * @return the combined promise + */ + default @NotNull Promise>> allSettled(@NotNull Promise... promises) { + return allSettled(Arrays.asList(promises).iterator(), promises.length, true); + } + + /** + * Combines an iterator of promises into a single promise that completes 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 + */ + @NotNull Promise all(@NotNull Iterator> promises, boolean link); + + /** + * Combines an iterable of promises into a single promise that completes 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 all(@NotNull Iterable> promises, boolean link) { + return all(promises.iterator(), link); + } + + /** + * Combines an iterable of promises into a single promise that completes when all promises complete. + * If any promise completes exceptionally, all other promises will be cancelled and the output + * promise will complete exceptionally. + * @param promises the input promises + * @return the combined promise + */ + default @NotNull Promise all(@NotNull Iterable> promises) { + return all(promises.iterator(), true); + } + + /** + * Combines a stream of promises into a single promise that completes 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 all(@NotNull Stream> promises, boolean link) { + return all(promises.iterator(), link); + } + + /** + * Combines a stream of promises into a single promise that completes when all promises complete. + * If any promise completes exceptionally, all other promises will be cancelled and the output + * promise willcomplete exceptionally. + * @param promises the input promises + * @return the combined promise + */ + default @NotNull Promise all(@NotNull Stream> promises) { + return all(promises.iterator(), true); + } + + /** + * Combines an array of promises into a single promise that completes 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 link whether to cancel all promises on any exceptional completions + * @param promises the input promises + * @return the combined promise + */ + default @NotNull Promise all(boolean link, @NotNull Promise... promises) { + return all(Arrays.asList(promises).iterator(), link); + } + + /** + * Combines an array of promises into a single promise that completes when all promises complete. + * If any promise completes exceptionally, all other promises will be cancelled and the output + * promise will complete exceptionally. + * @param promises the input promises + * @return the combined promise + */ + default @NotNull Promise all(@NotNull Promise... promises) { + return all(Arrays.asList(promises).iterator(), true); + } + + /** + * Combines an iterator of promises into a single promise that completes when the first promise + * completes (successfully or exceptionally). If {@code cancelLosers} is {@code true}, all other + * promises will be cancelled when the first promise + * completes. + * @param promises the input promises + * @param cancelLosers whether to cancel the other promises when the first completes + * @return the combined promise + */ + @NotNull Promise race(@NotNull Iterator> promises, boolean cancelLosers); + + /** + * Combines an iterable of promises into a single promise that completes when the first promise + * completes (successfully or exceptionally). All other promises will be cancelled when the first + * promise completes. + * @param promises the input promises + * @return the combined promise + */ + default @NotNull Promise race(@NotNull Iterable> promises, boolean cancelLosers) { + return race(promises.iterator(), cancelLosers); + } + + /** + * Combines an iterable of promises into a single promise that completes when the first promise + * completes (successfully or exceptionally). All other promises will be cancelled when the first + * promise completes. + * @param promises the input promises + */ + default @NotNull Promise race(@NotNull Iterable> promises) { + return race(promises.iterator(), true); + } + + /** + * Combines a stream of promises into a single promise that completes when the first promise + * completes (successfully or exceptionally). If {@code cancelLosers} is {@code true}, all other + * promises will be cancelled when the first promise completes. + * @param promises the input promises + * @param cancelLosers whether to cancel the other promises when the first completes + * @return the combined promise + */ + default @NotNull Promise race(@NotNull Stream> promises, boolean cancelLosers) { + return race(promises.iterator(), cancelLosers); + } + + /** + * Combines a stream of promises into a single promise that completes when the first promise + * completes (successfully or exceptionally). All other promises will be cancelled when the first + * promise completes. + * @param promises the input promises + * @return the combined promise + */ + default @NotNull Promise race(@NotNull Stream> promises) { + return race(promises.iterator(), true); + } + } diff --git a/futur-api/src/main/java/dev/tommyjs/futur/promise/PromiseFactoryImpl.java b/futur-api/src/main/java/dev/tommyjs/futur/promise/PromiseFactoryImpl.java index 7feaf36..f5a2153 100644 --- a/futur-api/src/main/java/dev/tommyjs/futur/promise/PromiseFactoryImpl.java +++ b/futur-api/src/main/java/dev/tommyjs/futur/promise/PromiseFactoryImpl.java @@ -22,7 +22,7 @@ public class PromiseFactoryImpl extends AbstractPromiseFactory { @Override public @NotNull CompletablePromise unresolved() { - return new PromiseImpl<>(this); + return new PromiseImpl<>(); } @Override @@ -40,4 +40,13 @@ public class PromiseFactoryImpl extends AbstractPromiseFactory { return asyncExecutor; } + public class PromiseImpl extends AbstractPromise { + + @Override + public @NotNull AbstractPromiseFactory getFactory() { + return PromiseFactoryImpl.this; + } + + } + } diff --git a/futur-api/src/main/java/dev/tommyjs/futur/promise/PromiseImpl.java b/futur-api/src/main/java/dev/tommyjs/futur/promise/PromiseImpl.java deleted file mode 100644 index ad752dc..0000000 --- a/futur-api/src/main/java/dev/tommyjs/futur/promise/PromiseImpl.java +++ /dev/null @@ -1,18 +0,0 @@ -package dev.tommyjs.futur.promise; - -import org.jetbrains.annotations.NotNull; - -public class PromiseImpl extends AbstractPromise { - - private final @NotNull AbstractPromiseFactory factory; - - public PromiseImpl(@NotNull AbstractPromiseFactory factory) { - this.factory = factory; - } - - @Override - public @NotNull AbstractPromiseFactory getFactory() { - return factory; - } - -} diff --git a/futur-api/src/main/java/dev/tommyjs/futur/promise/PromiseListener.java b/futur-api/src/main/java/dev/tommyjs/futur/promise/PromiseListener.java index 7ec1a26..85b09af 100644 --- a/futur-api/src/main/java/dev/tommyjs/futur/promise/PromiseListener.java +++ b/futur-api/src/main/java/dev/tommyjs/futur/promise/PromiseListener.java @@ -2,8 +2,15 @@ package dev.tommyjs.futur.promise; import org.jetbrains.annotations.NotNull; +/** + * A listener for a {@link Promise} that is called when the promise is resolved. + */ public interface PromiseListener { - void handle(@NotNull PromiseCompletion ctx); + /** + * Handles the completion of the promise. + * @param completion the promise completion + */ + void handle(@NotNull PromiseCompletion completion); } diff --git a/futur-api/src/main/java/dev/tommyjs/futur/joiner/ConcurrentResultArray.java b/futur-api/src/main/java/dev/tommyjs/futur/util/ConcurrentResultArray.java similarity index 64% rename from futur-api/src/main/java/dev/tommyjs/futur/joiner/ConcurrentResultArray.java rename to futur-api/src/main/java/dev/tommyjs/futur/util/ConcurrentResultArray.java index af34f33..ec678a8 100644 --- a/futur-api/src/main/java/dev/tommyjs/futur/joiner/ConcurrentResultArray.java +++ b/futur-api/src/main/java/dev/tommyjs/futur/util/ConcurrentResultArray.java @@ -1,4 +1,4 @@ -package dev.tommyjs.futur.joiner; +package dev.tommyjs.futur.util; import org.jetbrains.annotations.NotNull; @@ -6,7 +6,10 @@ import java.util.Arrays; import java.util.List; import java.util.concurrent.atomic.AtomicReference; -class ConcurrentResultArray { +public class ConcurrentResultArray { + + private static final float RESIZE_THRESHOLD = 0.75F; + private static final float RESIZE_FACTOR = 1.2F; private final AtomicReference ref; @@ -17,8 +20,9 @@ class ConcurrentResultArray { public void set(int index, T element) { ref.updateAndGet(array -> { - if (array.length <= index) - return Arrays.copyOf(array, index + 6); + if (array.length * RESIZE_THRESHOLD <= index) { + array = Arrays.copyOf(array, (int) (array.length * RESIZE_FACTOR)); + } array[index] = element; return array; diff --git a/futur-api/src/main/java/dev/tommyjs/futur/util/PromiseUtil.java b/futur-api/src/main/java/dev/tommyjs/futur/util/PromiseUtil.java new file mode 100644 index 0000000..152c67c --- /dev/null +++ b/futur-api/src/main/java/dev/tommyjs/futur/util/PromiseUtil.java @@ -0,0 +1,48 @@ +package dev.tommyjs.futur.util; + +import dev.tommyjs.futur.promise.CompletablePromise; +import dev.tommyjs.futur.promise.Promise; +import org.jetbrains.annotations.NotNull; + +import java.util.stream.Stream; + +public class PromiseUtil { + + /** + * Propagates the completion, once completed, of the given promise to the given promise. + * @param from the promise to propagate the completion from + * @param to the completable promise to propagate the completion to + */ + public static void propagateCompletion(@NotNull Promise from, @NotNull CompletablePromise to) { + from.addDirectListener(to::complete, to::completeExceptionally); + } + + /** + * Propagates the cancellation, once cancelled, of the given promise to the given promise. + * @param from the promise to propagate the cancellation from + * @param to the promise to propagate the cancellation to + */ + public static void propagateCancel(@NotNull Promise from, @NotNull Promise to) { + from.onCancel(to::cancel); + } + + /** + * Cancels the given promise once the given promise is completed. + * @param from the promise to propagate the completion from + * @param to the promise to cancel upon completion + */ + public static void cancelOnComplete(@NotNull Promise from, @NotNull Promise to) { + from.addDirectListener(_ -> to.cancel()); + } + + /** + * Estimates the size of the given stream. + * @param stream the stream + * @return the estimated size + */ + public static int estimateSize(@NotNull Stream stream) { + long estimate = stream.spliterator().estimateSize(); + return estimate == Long.MAX_VALUE ? 10 : (int) estimate; + } + +} From 61741931450cfd47f08168a5d43eb3b6a1dd7681 Mon Sep 17 00:00:00 2001 From: WhatCats Date: Mon, 6 Jan 2025 23:08:59 +0100 Subject: [PATCH 03/16] add generator for futur-lazy --- .gitignore | 1 + build.gradle | 2 +- .../tommyjs/futur/joiner/PromiseJoiner.java | 12 +- .../futur/promise/AbstractPromise.java | 21 +- .../futur/promise/AsyncPromiseListener.java | 1 + .../futur/util/ConcurrentResultArray.java | 3 +- futur-lazy/generator/bun.lockb | Bin 0 -> 3142 bytes futur-lazy/generator/index.ts | 33 + futur-lazy/generator/package.json | 11 + futur-lazy/generator/tsconfig.json | 27 + .../java/dev/tommyjs/futur/lazy/Promises.java | 672 +++++++++++++----- 11 files changed, 595 insertions(+), 188 deletions(-) create mode 100644 futur-lazy/generator/bun.lockb create mode 100644 futur-lazy/generator/index.ts create mode 100644 futur-lazy/generator/package.json create mode 100644 futur-lazy/generator/tsconfig.json diff --git a/.gitignore b/.gitignore index f35ca9e..918bf19 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ .gradle build/ +node_modules/ !gradle/wrapper/gradle-wrapper.jar !**/src/main/**/build/ !**/src/test/**/build/ diff --git a/build.gradle b/build.gradle index bf38bbf..180556c 100644 --- a/build.gradle +++ b/build.gradle @@ -32,7 +32,7 @@ subprojects { dependencies { compileOnly 'org.jetbrains:annotations:24.1.0' implementation 'org.slf4j:slf4j-api:2.0.12' -2 + testRuntimeOnly 'org.junit.platform:junit-platform-launcher' testImplementation 'io.projectreactor:reactor-core:3.6.4' testImplementation 'org.junit.jupiter:junit-jupiter:5.8.1' 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 39997c4..e0bff07 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 @@ -9,7 +9,6 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Iterator; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; public abstract class PromiseJoiner { @@ -29,7 +28,6 @@ public abstract class PromiseJoiner { protected abstract R getResult(); protected void join(@NotNull Iterator promises, boolean link) { - AtomicBoolean waiting = new AtomicBoolean(); AtomicInteger count = new AtomicInteger(); int i = 0; @@ -50,19 +48,15 @@ public abstract class PromiseJoiner { Throwable e = onChildComplete(index, key, res); if (e != null) { joined.completeExceptionally(e); - } else if (count.decrementAndGet() == 0 && waiting.get()) { + } else if (count.decrementAndGet() == -1) { joined.complete(getResult()); } }); } } while (promises.hasNext()); - if (!joined.isCompleted()) { - count.updateAndGet(v -> { - if (v == 0) joined.complete(getResult()); - else waiting.set(true); - return v; - }); + if (count.decrementAndGet() == -1) { + joined.complete(getResult()); } } 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 fe55aa1..845069f 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 @@ -48,8 +48,9 @@ public abstract class AbstractPromise implements CompletablePromise completer ) { return () -> { - if (promise.isCompleted()) return; - runCompleter(promise, () -> promise.complete(completer.apply(result))); + if (!promise.isCompleted()) { + runCompleter(promise, () -> promise.complete(completer.apply(result))); + } }; } @@ -479,14 +480,14 @@ public abstract class AbstractPromise implements CompletablePromise listener = iter.next(); - if (listener instanceof AsyncPromiseListener) { - callListenerAsync(listener, ctx); - } else { - try { + try { + if (listener instanceof AsyncPromiseListener) { + callListenerAsync(listener, ctx); + } else { callListenerNow(listener, ctx); - } finally { - iter.forEachRemaining(v -> callListenerAsyncLastResort(v, ctx)); } + } finally { + iter.forEachRemaining(v -> callListenerAsyncLastResort(v, ctx)); } } } @@ -531,16 +532,18 @@ public abstract class AbstractPromise implements CompletablePromise toFuture() { CompletableFuture future = new CompletableFuture<>(); - this.addDirectListener(future::complete, future::completeExceptionally); + addDirectListener(future::complete, future::completeExceptionally); future.whenComplete((_, e) -> { if (e instanceof CancellationException) { this.cancel(); } }); + return future; } private static class DeferredExecutionException extends ExecutionException { + } } diff --git a/futur-api/src/main/java/dev/tommyjs/futur/promise/AsyncPromiseListener.java b/futur-api/src/main/java/dev/tommyjs/futur/promise/AsyncPromiseListener.java index 799b6be..c53f86d 100644 --- a/futur-api/src/main/java/dev/tommyjs/futur/promise/AsyncPromiseListener.java +++ b/futur-api/src/main/java/dev/tommyjs/futur/promise/AsyncPromiseListener.java @@ -5,4 +5,5 @@ package dev.tommyjs.futur.promise; * executed asynchronously by the {@link PromiseFactory} that created the completed promise. */ public interface AsyncPromiseListener extends PromiseListener { + } diff --git a/futur-api/src/main/java/dev/tommyjs/futur/util/ConcurrentResultArray.java b/futur-api/src/main/java/dev/tommyjs/futur/util/ConcurrentResultArray.java index ec678a8..ec202f1 100644 --- a/futur-api/src/main/java/dev/tommyjs/futur/util/ConcurrentResultArray.java +++ b/futur-api/src/main/java/dev/tommyjs/futur/util/ConcurrentResultArray.java @@ -8,7 +8,6 @@ import java.util.concurrent.atomic.AtomicReference; public class ConcurrentResultArray { - private static final float RESIZE_THRESHOLD = 0.75F; private static final float RESIZE_FACTOR = 1.2F; private final AtomicReference ref; @@ -20,7 +19,7 @@ public class ConcurrentResultArray { public void set(int index, T element) { ref.updateAndGet(array -> { - if (array.length * RESIZE_THRESHOLD <= index) { + if (array.length <= index) { array = Arrays.copyOf(array, (int) (array.length * RESIZE_FACTOR)); } diff --git a/futur-lazy/generator/bun.lockb b/futur-lazy/generator/bun.lockb new file mode 100644 index 0000000000000000000000000000000000000000..265b64a90c9e5d89166c3205afd891de89686f77 GIT binary patch literal 3142 zcmd5;4^R|k6yL)`^hgAwKO-_6k~E38_ZN=1^GKs{5>pX|RKPrry<_EW@7P^Ht`eBB z29YK-5kiacPl_5UrDZgX7Jn+y3C(eW3Zj3eMwsbD(+It9*$=kHkhB?lGvB=ZcHi%} z?|bj_G2j>CLn{E*cm(6Kz}O_g zBfjwn#=i~5aexm74e|Nc@BbYz^8oJ)dO9qm$0PXtad2n@;8BJVcFK6bV1w~qz?;SQ zqhi4K$3uf`(L!;bAYLOM(0(E;BuJyZw9sx3H#l_=5&f3}d@^E~S4u74S-HG4sorF- zJ=1yK_k8Ns+5QvDm+|`d_HUS;qw77;+Hu65W-D9Xb2Zo2mQgg2a3W_L^Lk5t<&VZ^ zc6{;hn=KMvX*}ir2dU$GOAfK0&a$rP^4FYfc=cD>w!J8#GWmLAcJhonfiP!KvB$zE;zz_}=UJ`~hQKL--F%D;J-BNZqu)dPSjcy1Mka zw>F3W)_XkhKKmu_ZV9inuH`$M<4aa2o9q0ZZvAfY#S32fnVIdam&zlHwp$`Q!b}0C z&MsHJD|AcFKueT2L>}Z(Ri#|+PgruzsR;BmRa8iLah)Nz|M}s$H?D>R(dnU8%0+u_%zts? zI^exqg5!?$?f7vQ-M<}o?-u5;_%CV^wZhD?Hj-vI-s*IcxZ{#3EJazAI*pQ}y_DNN zM`=)6owS2-+o3-SGB`9WXoKqTibyy~Y_|$?o?d4G+)Ws8uF(GhucyL&9rx)#*{Wz#Luv>DPX@Tt zPx5hEd5Yr+0?!J#_xs4Sq?Xhh2?9?GxcmFa*2R+1q*g=Jfj?-jz;gmT(P>Dnj*P{V z2A&+?Ni?a8Aq_Xd%off?5T)V?KOQ-$G^HHn%BNWJiM2e%GFE3Y&(e&;qSTHCA$BAP zJZBR)Py7-52D_yq7!73T-qh}0=AT1z8+neSSYXTeOj)wbo;bijBGFv5J~sN4Y;C~c zfat+NaRP%HfOu>FDcppEVkj2ksPeFG7tKLD3q2HPV`&fnm~g3vF)*9wJzShx%~B4U zi9M~&1fWU4};*BVIP;YB-vVhs3*jffWP1rEItaftKwv#2rX zBvwF(j8(%~9rh@X$)auaEuE5@0gZI45=tQ&xpr$e3OGO)a4MnX^g>94Yz}8|4E$&g TfM9e3FE^4p#3Pp7|4#i0z6479 literal 0 HcmV?d00001 diff --git a/futur-lazy/generator/index.ts b/futur-lazy/generator/index.ts new file mode 100644 index 0000000..ea74d6d --- /dev/null +++ b/futur-lazy/generator/index.ts @@ -0,0 +1,33 @@ +const PACKAGE = "src/main/java/dev/tommyjs/futur" +const SIGNAL = "// Generated delegates to static factory" + +const regex = /( {4}\/\*\*.+?)?((?:\S| )+? )(\S+)(\(.*?\))(?: {.+?}|;)/gs +const content = await Bun.file(`../../futur-api/${PACKAGE}/promise/PromiseFactory.java`).text() + +const methods = [""] +for (const match of content.matchAll(regex)) { + let [_, docs, head, name, params] = match + + head = head.trimStart() + if (head.startsWith("static")) continue + if (head.startsWith("default")) head = head.slice(8); + + const args = Array.from(params.matchAll(/ ([a-zA-Z1-9]+)[,)]/gs)) + .map(v => v[1]).join(", ") + + methods.push( + [ + `${docs} public static ${head}${name}${params} {`, + ` return factory.${name}(${args});`, + " }" + ].join("\n") + ) +} + +const output = Bun.file(`../${PACKAGE}/lazy/Promises.java`) + +const existing = await output.text() +const cutIndex = existing.indexOf(SIGNAL) + SIGNAL.length; +const newContent = existing.slice(0, cutIndex) + methods.join("\n\n") + "\n\n}" + +await Bun.write(output, newContent) \ No newline at end of file diff --git a/futur-lazy/generator/package.json b/futur-lazy/generator/package.json new file mode 100644 index 0000000..56136ad --- /dev/null +++ b/futur-lazy/generator/package.json @@ -0,0 +1,11 @@ +{ + "name": "generate-promises", + "module": "index.ts", + "type": "module", + "devDependencies": { + "@types/bun": "latest" + }, + "peerDependencies": { + "typescript": "latest" + } +} \ No newline at end of file diff --git a/futur-lazy/generator/tsconfig.json b/futur-lazy/generator/tsconfig.json new file mode 100644 index 0000000..238655f --- /dev/null +++ b/futur-lazy/generator/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + // Enable latest features + "lib": ["ESNext", "DOM"], + "target": "ESNext", + "module": "ESNext", + "moduleDetection": "force", + "jsx": "react-jsx", + "allowJs": true, + + // Bundler mode + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, + + // Best practices + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + + // Some stricter flags (disabled by default) + "noUnusedLocals": false, + "noUnusedParameters": false, + "noPropertyAccessFromIndexSignature": false + } +} 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 5c64399..263b836 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 @@ -27,182 +27,520 @@ public final class Promises { Promises.factory = factory; } + // Generated delegates to static factory + + /** + * Creates a new uncompleted promise. + * + * @return the new promise + */ public static @NotNull CompletablePromise unresolved() { return factory.unresolved(); } - public static @NotNull Promise> combine(@NotNull Promise p1, @NotNull Promise p2, boolean cancelOnError) { - return factory.combine(p1, p2, cancelOnError); - } - - public static @NotNull Promise> combine(@NotNull Promise p1, @NotNull Promise p2) { - return factory.combine(p1, p2); - } - - public static @NotNull Promise> combine( - @NotNull Map> promises, - @Nullable BiConsumer exceptionHandler, - boolean propagateCancel - ) { - return factory.combine(promises, exceptionHandler, propagateCancel); - } - - public static @NotNull Promise> combine(@NotNull Map> promises, @NotNull BiConsumer exceptionHandler) { - return factory.combine(promises, exceptionHandler); - } - - public static @NotNull Promise> combine(@NotNull Map> promises, boolean cancelOnError) { - return factory.combine(promises, cancelOnError); - } - - public static @NotNull Promise> combine(@NotNull Map> promises) { - return factory.combine(promises); - } - - public static @NotNull Promise> combine( - @NotNull Iterator> promises, int expectedSize, - @Nullable BiConsumer exceptionHandler, boolean propagateCancel - ) { - return factory.combine(promises, expectedSize, exceptionHandler, propagateCancel); - } - - public static @NotNull Promise> combine( - @NotNull Collection> promises, - @NotNull BiConsumer exceptionHandler, - boolean propagateCancel - ) { - return factory.combine(promises, exceptionHandler, propagateCancel); - } - - public static @NotNull Promise> combine( - @NotNull Collection> promises, - @NotNull BiConsumer exceptionHandler - ) { - return factory.combine(promises, exceptionHandler); - } - - public static @NotNull Promise> combine(@NotNull Collection> promises, boolean cancelOnError) { - return factory.combine(promises, cancelOnError); - } - - public static @NotNull Promise> combine(@NotNull Collection> promises) { - return factory.combine(promises); - } - - public static @NotNull Promise> combine( - @NotNull Stream> promises, - @NotNull BiConsumer exceptionHandler, - boolean propagateCancel - ) { - return factory.combine(promises, exceptionHandler, propagateCancel); - } - - public static @NotNull Promise> combine( - @NotNull Stream> promises, - @NotNull BiConsumer exceptionHandler - ) { - return factory.combine(promises, exceptionHandler); - } - - public static @NotNull Promise> combine(@NotNull Stream> promises, boolean cancelOnError) { - return factory.combine(promises, cancelOnError); - } - - public static @NotNull Promise> combine(@NotNull Stream> promises) { - return factory.combine(promises); - } - - public static @NotNull Promise>> allSettled( - @NotNull Iterator> promises, int estimatedSize, boolean propagateCancel) { - return factory.allSettled(promises, estimatedSize, propagateCancel); - } - - public static @NotNull Promise>> allSettled(@NotNull Collection> promises, boolean propagateCancel) { - return factory.allSettled(promises, propagateCancel); - } - - public static @NotNull Promise>> allSettled(@NotNull Collection> promises) { - return factory.allSettled(promises); - } - - public static @NotNull Promise>> allSettled(@NotNull Stream> promises, boolean propagateCancel) { - return factory.allSettled(promises, propagateCancel); - } - - public static @NotNull Promise>> allSettled(@NotNull Stream> promises) { - return factory.allSettled(promises); - } - - public static @NotNull Promise>> allSettled(boolean propagateCancel, @NotNull Promise... promises) { - return factory.allSettled(propagateCancel, promises); - } - - public static @NotNull Promise>> allSettled(@NotNull Promise... promises) { - return factory.allSettled(promises); - } - - public static @NotNull Promise all(@NotNull Iterator> promises, boolean cancelAllOnError) { - return factory.all(promises, cancelAllOnError); - } - - public static @NotNull Promise all(@NotNull Iterable> promises, boolean cancelAllOnError) { - return factory.all(promises, cancelAllOnError); - } - - public static @NotNull Promise all(@NotNull Iterable> promises) { - return factory.all(promises); - } - - public static @NotNull Promise all(@NotNull Stream> promises, boolean cancelAllOnError) { - return factory.all(promises, cancelAllOnError); - } - - public static @NotNull Promise all(@NotNull Stream> promises) { - return factory.all(promises); - } - - public static @NotNull Promise all(boolean cancelAllOnError, @NotNull Promise... promises) { - return factory.all(cancelAllOnError, promises); - } - - public static @NotNull Promise all(@NotNull Promise... promises) { - return factory.all(promises); - } - - public static @NotNull Promise race(@NotNull Iterator> promises, boolean cancelLosers) { - return factory.race(promises, cancelLosers); - } - - public static @NotNull Promise race(@NotNull Iterable> promises, boolean cancelLosers) { - return factory.race(promises, cancelLosers); - } - - public static @NotNull Promise race(@NotNull Iterable> promises) { - return factory.race(promises); - } - - public static @NotNull Promise race(@NotNull Stream> promises, boolean cancelLosers) { - return factory.race(promises, cancelLosers); - } - - public static @NotNull Promise race(@NotNull Stream> promises) { - return factory.race(promises); - } - - public static @NotNull Promise wrap(@NotNull CompletableFuture future) { - return factory.wrap(future); - } - - public static @NotNull Promise start() { - return factory.start(); - } - + /** + * Creates a new promise, completed with the given value. + * + * @param value the value to complete the promise with + * @return the new promise + */ public static @NotNull Promise resolve(T value) { return factory.resolve(value); } + /** + * Creates a new promise, completed with {@code null}. + * + * @return the new promise + * @apiNote This method is often useful for starting promise chains. + */ + public static @NotNull Promise start() { + return factory.start(); + } + + /** + * Creates a new promise, completed exceptionally with the given error. + * + * @param error the error to complete the promise with + * @return the new promise + */ public static @NotNull Promise error(@NotNull Throwable error) { return factory.error(error); } -} + /** + * Creates a new promise backed by the given future. The promise will be completed upon completion + * of the future. + * + * @param future the future to wrap + * @return the new promise + */ + public static @NotNull Promise wrap(@NotNull CompletableFuture future) { + return factory.wrap(future); + } + + /** + * Combines two promises into a single promise that completes when both promises complete. If + * {@code link} is {@code true} and either input promise completes exceptionally (including + * cancellation), the other promise will be cancelled and the output promise will complete + * exceptionally. + * + * @param p1 the first promise + * @param p2 the second promise + * @param link whether to cancel the other promise on error + * @return the combined promise + */ + public static @NotNull Promise> combine(@NotNull Promise p1, @NotNull Promise p2, + boolean link) { + return factory.combine(p1, p2, link); + } + + /** + * Combines two promises into a single promise that completes when both promises complete. If either + * input promise completes exceptionally, the other promise will be cancelled and the output promise + * will complete exceptionally. + * + * @param p1 the first promise + * @param p2 the second promise + * @return the combined promise + */ + public static @NotNull Promise> combine(@NotNull Promise p1, @NotNull Promise p2) { + return factory.combine(p1, p2); + } + + /** + * 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. + * + * @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 Map> promises, + @Nullable BiConsumer exceptionHandler, + boolean link) { + return factory.combine(promises, exceptionHandler, 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. + * + * @param promises the input promises + * @param exceptionHandler the exception handler + * @return the combined promise + */ + public static @NotNull Promise> combine(@NotNull Map> promises, + @NotNull BiConsumer exceptionHandler) { + return factory.combine(promises, exceptionHandler); + } + + /** + * 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> combine(@NotNull Map> promises, boolean link) { + return factory.combine(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> combine(@NotNull Map> promises) { + return factory.combine(promises); + } + + /** + * Combines an iterator 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. If an exception + * 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 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); + } + + /** + * 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 + * 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> combine(@NotNull Collection> promises, boolean link) { + return factory.combine(promises, 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 output promise will complete exceptionally. + * + * @param promises the input promises + * @return the combined promise + */ + public static @NotNull Promise> combine(@NotNull Collection> promises) { + return factory.combine(promises); + } + + /** + * 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. If an exception + * 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 + */ + public static @NotNull Promise> combine(@NotNull Stream> promises, boolean link) { + return factory.combine(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. + * + * @param promises the input promises + * @return the combined promise + */ + public static @NotNull Promise> combine(@NotNull Stream> promises) { + return factory.combine(promises); + } + + /** + * Combines an iterator 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. The output promise will always complete successfully regardless + * of whether input promises fail. + * + * @param promises the input promises + * @param expectedSize the expected size of the list (used for optimization) + * @param link whether to cancel all promises on any exceptional completions + * @return the combined promise + */ + public static @NotNull Promise>> allSettled(@NotNull Iterator> promises, + int expectedSize, boolean link) { + return factory.allSettled(promises, expectedSize, link); + } + + /** + * 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 + * other promises will be cancelled. The output promise will always complete successfully regardless + * of whether input promises fail. + * + * @param promises the input promises + * @param link whether to cancel all promises on any exceptional completions + * @return the combined promise + */ + public static @NotNull Promise>> allSettled(@NotNull Collection> promises, + boolean link) { + return factory.allSettled(promises, 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, all other promises will be cancelled. + * + * @param promises the input promises + * @return the combined promise + */ + public static @NotNull Promise>> allSettled(@NotNull Collection> promises) { + return factory.allSettled(promises); + } + + /** + * 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. The output promise will always complete successfully regardless + * of whether input promises fail. + * + * @param promises the input promises + * @param link whether to cancel all promises on any exceptional completions + * @return the combined promise + */ + public static @NotNull Promise>> allSettled(@NotNull Stream> promises, + boolean link) { + return factory.allSettled(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, all other promises will be cancelled. + * + * @param promises the input promises + * @return the combined promise + */ + public static @NotNull Promise>> allSettled(@NotNull Stream> promises) { + return factory.allSettled(promises); + } + + /** + * Combines an array 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. The output promise will always complete successfully regardless + * of whether input promises fail. + * + * @param link whether to cancel all promises on any exceptional completions + * @param promises the input promises + * @return the combined promise + */ + public static @NotNull Promise>> allSettled(boolean link, + @NotNull Promise... promises) { + return factory.allSettled(link, promises); + } + + /** + * Combines an array of promises into a single promise that completes with a list of results when all + * promises complete. If any promise completes exceptionally, all other promises will be cancelled. + * + * @param promises the input promises + * @return the combined promise + */ + public static @NotNull Promise>> allSettled(@NotNull Promise... promises) { + return factory.allSettled(promises); + } + + /** + * Combines an iterator of promises into a single promise that completes 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 + */ + public static @NotNull Promise all(@NotNull Iterator> promises, boolean link) { + return factory.all(promises, link); + } + + /** + * Combines an iterable of promises into a single promise that completes 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 + */ + public static @NotNull Promise all(@NotNull Iterable> promises, boolean link) { + return factory.all(promises, link); + } + + /** + * Combines an iterable of promises into a single promise that completes when all promises complete. + * If any promise completes exceptionally, all other promises will be cancelled and the output + * promise will complete exceptionally. + * + * @param promises the input promises + * @return the combined promise + */ + public static @NotNull Promise all(@NotNull Iterable> promises) { + return factory.all(promises); + } + + /** + * Combines a stream of promises into a single promise that completes 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 + */ + public static @NotNull Promise all(@NotNull Stream> promises, boolean link) { + return factory.all(promises, link); + } + + /** + * Combines a stream of promises into a single promise that completes when all promises complete. + * If any promise completes exceptionally, all other promises will be cancelled and the output + * promise willcomplete exceptionally. + * + * @param promises the input promises + * @return the combined promise + */ + public static @NotNull Promise all(@NotNull Stream> promises) { + return factory.all(promises); + } + + /** + * Combines an array of promises into a single promise that completes 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 link whether to cancel all promises on any exceptional completions + * @param promises the input promises + * @return the combined promise + */ + public static @NotNull Promise all(boolean link, @NotNull Promise... promises) { + return factory.all(link, promises); + } + + /** + * Combines an array of promises into a single promise that completes when all promises complete. + * If any promise completes exceptionally, all other promises will be cancelled and the output + * promise will complete exceptionally. + * + * @param promises the input promises + * @return the combined promise + */ + public static @NotNull Promise all(@NotNull Promise... promises) { + return factory.all(promises); + } + + /** + * Combines an iterator of promises into a single promise that completes when the first promise + * completes (successfully or exceptionally). If {@code cancelLosers} is {@code true}, all other + * promises will be cancelled when the first promise + * completes. + * + * @param promises the input promises + * @param cancelLosers whether to cancel the other promises when the first completes + * @return the combined promise + */ + public static @NotNull Promise race(@NotNull Iterator> promises, boolean cancelLosers) { + return factory.race(promises, cancelLosers); + } + + /** + * Combines an iterable of promises into a single promise that completes when the first promise + * completes (successfully or exceptionally). All other promises will be cancelled when the first + * promise completes. + * + * @param promises the input promises + * @return the combined promise + */ + public static @NotNull Promise race(@NotNull Iterable> promises, boolean cancelLosers) { + return factory.race(promises, cancelLosers); + } + + /** + * Combines an iterable of promises into a single promise that completes when the first promise + * completes (successfully or exceptionally). All other promises will be cancelled when the first + * promise completes. + * + * @param promises the input promises + */ + public static @NotNull Promise race(@NotNull Iterable> promises) { + return factory.race(promises); + } + + /** + * Combines a stream of promises into a single promise that completes when the first promise + * completes (successfully or exceptionally). If {@code cancelLosers} is {@code true}, all other + * promises will be cancelled when the first promise completes. + * + * @param promises the input promises + * @param cancelLosers whether to cancel the other promises when the first completes + * @return the combined promise + */ + public static @NotNull Promise race(@NotNull Stream> promises, boolean cancelLosers) { + return factory.race(promises, cancelLosers); + } + + /** + * Combines a stream of promises into a single promise that completes when the first promise + * completes (successfully or exceptionally). All other promises will be cancelled when the first + * promise completes. + * + * @param promises the input promises + * @return the combined promise + */ + public static @NotNull Promise race(@NotNull Stream> promises) { + return factory.race(promises); + } + +} \ No newline at end of file From f2ec06fba7d48bfe8d01d1b49a97103b90336b59 Mon Sep 17 00:00:00 2001 From: tommyskeff Date: Mon, 6 Jan 2025 22:44:45 +0000 Subject: [PATCH 04/16] a few documentation improvements --- README.md | 7 +- .../futur/executor/PromiseExecutor.java | 12 +- .../futur/promise/CompletablePromise.java | 2 + .../dev/tommyjs/futur/promise/Promise.java | 120 ++++++++++++------ .../futur/promise/PromiseCompletion.java | 7 + .../tommyjs/futur/promise/PromiseFactory.java | 104 ++++++++++----- .../futur/promise/PromiseListener.java | 1 + .../dev/tommyjs/futur/util/PromiseUtil.java | 10 +- 8 files changed, 183 insertions(+), 80 deletions(-) diff --git a/README.md b/README.md index 1b09a96..8e94b11 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,9 @@ Futur4J is a powerful and intuitive open-source Java library that simplifies asynchronous task scheduling, inspired by the concept of JavaScript promises. -**This documentation is outdated. Please don't read it.** - ## Dependency -The Futur4J project is composed of multiple modules. It is required to include the `futur-api` module, and the other modules depend on it at runtime, however the others are optional and dependent on your use case. +The Futur4J project has a `futur-api` module that provides the core functionality, and a `futur-lazy` module that provides additional static versions of factory methods. It is +recommended to use the main module for customization of logging and execution. ### Gradle ```gradle repositories { @@ -51,4 +50,4 @@ dependencies { 2.4.0 -``` +``` \ No newline at end of file diff --git a/futur-api/src/main/java/dev/tommyjs/futur/executor/PromiseExecutor.java b/futur-api/src/main/java/dev/tommyjs/futur/executor/PromiseExecutor.java index bb9fe03..a66d07f 100644 --- a/futur-api/src/main/java/dev/tommyjs/futur/executor/PromiseExecutor.java +++ b/futur-api/src/main/java/dev/tommyjs/futur/executor/PromiseExecutor.java @@ -10,6 +10,7 @@ public interface PromiseExecutor { /** * Creates a new {@link PromiseExecutor} that runs tasks on virtual threads. + * * @return the new executor */ static PromiseExecutor virtualThreaded() { @@ -18,6 +19,7 @@ public interface PromiseExecutor { /** * Creates a new {@link PromiseExecutor} that runs tasks on a single thread. + * * @return the new executor */ static PromiseExecutor singleThreaded() { @@ -26,6 +28,7 @@ public interface PromiseExecutor { /** * Creates a new {@link PromiseExecutor} that runs tasks on multiple threads. + * * @param threads the number of threads * @return the new executor */ @@ -35,6 +38,7 @@ public interface PromiseExecutor { /** * Creates a new {@link PromiseExecutor} that runs tasks on the given executor service. + * * @param service the executor service * @return the new executor */ @@ -44,6 +48,7 @@ public interface PromiseExecutor { /** * Runs the given task. + * * @param task the task * @return the task * @throws Exception if scheduling the task failed @@ -52,9 +57,10 @@ public interface PromiseExecutor { /** * Runs the given task after the given delay. - * @param task the task + * + * @param task the task * @param delay the delay - * @param unit the time unit + * @param unit the time unit * @return the task * @throws Exception if scheduling the task failed */ @@ -65,7 +71,7 @@ public interface PromiseExecutor { * * @param task the task * @return {@code true} if the task was cancelled. {@code false} if the task was already completed - * or could not be cancelled. + * or could not be cancelled. */ boolean cancel(T task); diff --git a/futur-api/src/main/java/dev/tommyjs/futur/promise/CompletablePromise.java b/futur-api/src/main/java/dev/tommyjs/futur/promise/CompletablePromise.java index 3baff14..998566a 100644 --- a/futur-api/src/main/java/dev/tommyjs/futur/promise/CompletablePromise.java +++ b/futur-api/src/main/java/dev/tommyjs/futur/promise/CompletablePromise.java @@ -10,12 +10,14 @@ public interface CompletablePromise extends Promise { /** * Completes the promise successfully with the given result. + * * @param result the result */ void complete(@Nullable T result); /** * Completes the promise exceptionally with the given exception. + * * @param result the exception */ void completeExceptionally(@NotNull Throwable result); 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 1cab047..4cbba54 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 @@ -38,7 +38,8 @@ public interface Promise { /** * Chains a task to be executed after this promise completes. The task will be executed immediately - * when this promise completes. + * when this promise completes. Cancelling the returned promise will cancel this promise, and + * consequently any previous promises in the chain. * * @param task the task to execute * @return a new promise that completes after the task is executed @@ -47,7 +48,8 @@ public interface Promise { /** * Chains a task to be executed after this promise completes. The task will be executed immediately - * when this promise completes and will be passed the result of this promise. + * when this promise completes and will be passed the result of this promise. Cancelling the returned + * promise will cancel this promise, and consequently any previous promises in the chain. * * @param task the task to execute * @return a new promise that completes after the task is executed @@ -56,7 +58,8 @@ public interface Promise { /** * Chains a task to be executed after this promise completes. The task will be executed immediately - * when this promise completes, and will supply a value to the next promise in the chain. + * when this promise completes, and will supply a value to the next promise in the chain. Cancelling + * the returned promise will cancel this promise, and consequently any previous promises in the chain. * * @param task the task to execute * @return a new promise that completes, after the task is executed, with the task result @@ -66,7 +69,8 @@ public interface Promise { /** * Chains a task to be executed after this promise completes. The task will be executed immediately * when this promise completes, and will apply the specified function to the result of this promise - * in order to supply a value to the next promise in the chain. + * in order to supply a value to the next promise in the chain. Cancelling the returned promise will + * cancel this promise, and consequently any previous promises in the chain. * * @param task the task to execute * @return a new promise that completes, after the task is executed, with the task result @@ -76,7 +80,8 @@ public interface Promise { /** * Chains a task to be executed after this promise completes. The task will be executed immediately * when this promise completes, and will compose the next promise in the chainfrom the result of - * this promise. + * this promise. Cancelling the returned promise will cancel this promise, and consequently any + * previous promises in the chain. * * @param task the task to execute * @return a new promise that completes, once this promise and the promise returned by @@ -87,6 +92,8 @@ public interface Promise { /** * Chains a task to be executed after this promise completes. The task will be executed by the * sync executor of the factory that created this promise, immediately after this promise completes. + * Cancelling the returned promise will cancel this promise, and consequently any previous promises + * in the chain. * * @param task the task to execute * @return a new promise that completes after the task is executed @@ -96,7 +103,8 @@ public interface Promise { /** * Chains a task to be executed after this promise completes. The task will be executed by the * sync executor of the factory that created this promise, after the specified delay after this - * promise completes. + * promise completes. Cancelling the returned promise will cancel this promise, and consequently + * any previous promises in the chain. * * @param task the task to execute * @param delay the amount of time to wait before executing the task @@ -108,7 +116,8 @@ public interface Promise { /** * Chains a task to be executed after this promise completes. The task will be executed by the * sync executor of the factory that created this promise immediately after this promise completes, - * and will be passed the result of this promise. + * and will be passed the result of this promise. Cancelling the returned promise will cancel this + * promise, and consequently any previous promises in the chain. * * @param task the task to execute * @return a new promise that completes after the task is executed @@ -118,7 +127,8 @@ public interface Promise { /** * Chains a task to be executed after this promise completes. The task will be executed by the * sync executor of the factory that created this promise after the specified delay after this - * promise completes, and will be passed the result of this promise. + * promise completes, and will be passed the result of this promise. Cancelling the returned promise + * will cancel this promise, and consequently any previous promises in the chain. * * @param task the task to execute * @param delay the amount of time to wait before executing the task @@ -130,7 +140,8 @@ public interface Promise { /** * Chains a task to be executed after this promise completes. The task will be executed immediately * by the sync executor of the factory that created this promise when this promise completes, and - * will supply a value to the next promise in the chain. + * will supply a value to the next promise in the chain. Cancelling the returned promise will cancel + * this promise, and consequently any previous promises in the chain. * * @param task the task to execute * @return a new promise that completes, after the task is executed, with the task result @@ -140,7 +151,8 @@ public interface Promise { /** * Chains a task to be executed after this promise completes. The task will be executed by the sync * executor of the factory that created this promise after the specified delay after this promise - * completes, and will supply a value to the next promise in the chain. + * completes, and will supply a value to the next promise in the chain. Cancelling the returned promise + * will cancel this promise, and consequently any previous promises in the chain. * * @param task the task to execute * @param delay the amount of time to wait before executing the task @@ -153,7 +165,8 @@ public interface Promise { * Chains a task to be executed after this promise completes. The task will be executed by the sync * executor of the factory that created this promise immediately after this promise completes, and * will apply the specified function to the result of this promise in order to supply a value to the - * next promise in the chain. + * next promise in the chain. Cancelling the returned promise will cancel this promise, and consequently + * any previous promises in the chain. * * @param task the task to execute * @return a new promise that completes, after the task is executed, with the task result @@ -164,7 +177,8 @@ public interface Promise { * Chains a task to be executed after this promise completes. The task will be executed by the sync * executor of the factory that created this promise after the specified delay after this promise * completes, and will apply the specified function to the result of this promise in order to supply - * a value to the next promise in the chain. + * a value to the next promise in the chain. Cancelling the returned promise will cancel this promise, + * and consequently any previous promises in the chain. * * @param task the task to execute * @param delay the amount of time to wait before executing the task @@ -176,7 +190,8 @@ public interface Promise { /** * Chains a task to be executed after this promise completes. The task will be executed by the sync * executor of the factory that created this promise immediately after this promise completes, and - * will compose the next promise in the chain from the result of this promise. + * will compose the next promise in the chain from the result of this promise. Cancelling the returned + * promise will cancel this promise, and consequently any previous promises in the chain. * * @param task the task to execute * @return a new promise that completes, once this promise and the promise returned by the task are @@ -187,6 +202,8 @@ public interface Promise { /** * Chains a task to be executed after this promise completes. The task will be executed by the * async executor of the factory that created this promise, immediately after this promise completes. + * Cancelling the returned promise will cancel this promise, and consequently any previous promises + * in the chain. * * @param task the task to execute * @return a new promise that completes after the task is executed @@ -196,7 +213,8 @@ public interface Promise { /** * Chains a task to be executed after this promise completes. The task will be executed by the * async executor of the factory that created this promise after the specified delay after this - * promise completes. + * promise completes. Cancelling the returned promise will cancel this promise, and consequently + * any previous promises in the chain. * * @param task the task to execute * @param delay the amount of time to wait before executing the task @@ -208,7 +226,8 @@ public interface Promise { /** * Chains a task to be executed after this promise completes. The task will be executed by the * async executor of the factory that created this promise immediately after this promise completes, - * and will be passed the result of this promise. + * and will be passed the result of this promise. Cancelling the returned promise will cancel this + * promise, and consequently any previous promises in the chain. * * @param task the task to execute * @return a new promise that completes after the task is executed @@ -218,7 +237,8 @@ public interface Promise { /** * Chains a task to be executed after this promise completes. The task will be executed by the * async executor of the factory that created this promise after the specified delay after this - * promise completes, and will be passed the result of this promise. + * promise completes, and will be passed the result of this promise. Cancelling the returned promise + * will cancel this promise, and consequently any previous promises in the chain. * * @param task the task to execute * @param delay the amount of time to wait before executing the task @@ -230,7 +250,8 @@ public interface Promise { /** * Chains a task to be executed after this promise completes. The task will be executed by the * async executor of the factory that created this promise immediately after this promise completes, - * and will supply a value to the next promise in the chain. + * and will supply a value to the next promise in the chain. Cancelling the returned promise will + * cancel this promise, and consequently any previous promises in the chain. * * @param task the task to execute * @return a new promise that completes, after the task is executed, with the task result @@ -240,7 +261,8 @@ public interface Promise { /** * Chains a task to be executed after this promise completes. The task will be executed by the async * executor of the factory that created this promise after the specified delay after this promise - * completes, and will supply a value to the next promise in the chain. + * completes, and will supply a value to the next promise in the chain. Cancelling the returned promise + * will cancel this promise, and consequently any previous promises in the chain. * * @param task the task to execute * @param delay the amount of time to wait before executing the task @@ -253,7 +275,8 @@ public interface Promise { * Chains a task to be executed after this promise completes. The task will be executed by the async * executor of the factory that created this promise immediately after this promise completes, and * will apply the specified function to the result of this promise in order to supply a value to the - * next promise in the chain. + * next promise in the chain. Cancelling the returned promise will cancel this promise, and consequently + * any previous promises in the chain. * * @param task the task to execute * @return a new promise that completes, after the task is executed, with the task result @@ -264,7 +287,8 @@ public interface Promise { * Chains a task to be executed after this promise completes. The task will be executed by the async * executor of the factory that created this promise after the specified delay after this promise * completes, and will apply the specified function to the result of this promise in order to supply - * a value to the next promise in the chain. + * a value to the next promise in the chain. Cancelling the returned promise will cancel this promise, + * and consequently any previous promises in the chain. * * @param task the task to execute * @param delay the amount of time to wait before executing the task @@ -276,7 +300,8 @@ public interface Promise { /** * Chains a task to be executed after this promise completes. The task will be executed by the async * executor of the factory that created this promise immediately after this promise completes, and - * will compose the next promise in the chain from the result of this promise. + * will compose the next promise in the chain from the result of this promise. Cancelling the returned + * promise will cancel this promise, and consequently any previous promises in the chain. * * @param task the task to execute * @return a new promise that completes, once this promise and the promise returned by the task are @@ -286,7 +311,8 @@ public interface Promise { /** * Adds a listener to this promise that will populate the specified reference with the result of this - * promise upon successful completion. + * promise upon successful completion. The reference will not be populated if this promise completes + * exceptionally. * * @param reference the reference to populate * @return continuation of the promise chain @@ -295,12 +321,25 @@ public interface Promise { /** * Returns a promise backed by this promise that will complete with {@code null} if this promise - * completes successfully, or with the exception if this promise completes exceptionally. + * completes successfully, or with the exception if this promise completes exceptionally. Cancelling + * the returned promise will cancel this promise, and consequently any previous promises in the chain. */ @NotNull Promise erase(); /** - * Logs any exceptions that occur in the promise chain. + * Logs any exceptions that occur in the promise chain with the specified message. The stack trace + * will be captured immediately when invoking this method, and logged alongside an exception if + * encountered, to allow for easier debugging. + * + * @param message the message to log + * @return continuation of the promise chain + */ + @NotNull Promise logExceptions(@NotNull String message); + + /** + * Logs any exceptions that occur in the promise chain. The stack trace will be captured immediately + * when invoking this method, and logged alongside an exception if encountered, to allow for easier + * debugging. * * @return continuation of the promise chain */ @@ -308,14 +347,6 @@ public interface Promise { return logExceptions("Exception caught in promise chain"); } - /** - * Logs any exceptions that occur in the promise chain with the specified message. - * - * @param message the message to log - * @return continuation of the promise chain - */ - @NotNull Promise logExceptions(@NotNull String message); - /** * Adds a listener to this promise that will be executed immediately when this promise completes, * on the same thread as the completion call. @@ -412,6 +443,7 @@ public interface Promise { /** * Cancels the promise if not already completed after the specified timeout. This will result in * an exceptional completion with a {@link CancellationException}. + * * @param ms the amount of time to wait before cancelling the promise (in milliseconds) * @return continuation of the promise chain */ @@ -434,6 +466,7 @@ public interface Promise { * Times out the promise if not already completed after the specified timeout. This will result * in an exceptional completion with a {@link TimeoutException}. This will not result in the * promise being cancelled. + * * @param ms the amount of time to wait before timing out the promise (in milliseconds) * @return continuation of the promise chain */ @@ -444,6 +477,7 @@ public interface Promise { /** * Cancels the promise if not already completed after the specified timeout. This will result in * an exceptional completion with the specified cancellation. + * * @param exception the cancellation exception to complete the promise with */ void cancel(@NotNull CancellationException exception); @@ -451,6 +485,7 @@ public interface Promise { /** * Cancels the promise if not already completed after the specified timeout. This will result in * an exceptional completion with a {@link CancellationException}. + * * @param reason the reason for the cancellation */ default void cancel(@NotNull String reason) { @@ -467,39 +502,43 @@ public interface Promise { /** * Blocks until this promise has completed, and then returns its result. + * + * @return the result of the promise * @throws CancellationException if the promise was cancelled * @throws CompletionException if the promise completed exceptionally - * @return the result of the promise */ @Blocking T await(); /** * Blocks until this promise has completed, and then returns its result. + * + * @return the result of the promise * @throws CancellationException if the promise was cancelled * @throws ExecutionException if the promise completed exceptionally * @throws InterruptedException if the current thread was interrupted while waiting - * @return the result of the promise */ @Blocking T get() throws InterruptedException, ExecutionException; /** - * Blocks until either this promise has completed or the timeout has been exceeded, and then - * returns its result, if available. + * Blocks until either this promise has completed or the timeout has been exceeded, and then + * returns its result, if available. + * + * @return the result of the promise * @throws CancellationException if the promise was cancelled * @throws ExecutionException if the promise completed exceptionally * @throws InterruptedException if the current thread was interrupted while waiting * @throws TimeoutException if the timeout was exceeded - * @return the result of the promise */ @Blocking T get(long timeout, @NotNull TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException; /** * Returns a new promise, backed by this promise, that will not propagate cancellations. This means - * that if the returned promise is cancelled, the cancellation will not be propagated to this promise - * or any other promises that share this promise as a parent. + * that if the returned promise is cancelled, the cancellation will not be propagated to this promise, + * and consequently any previous promises in the chain. + * * @return continuation the promise chain that will not propagate cancellations */ @NotNull Promise fork(); @@ -507,12 +546,14 @@ public interface Promise { /** * Returns the current completion state of this promise. If the promise has not completed, this method * will return {@code null}. + * * @return the completion state of this promise, or {@code null} if the promise has not completed */ @Nullable PromiseCompletion getCompletion(); /** * Returns whether this promise has completed. + * * @return {@code true} if the promise has completed, {@code false} otherwise */ boolean isCompleted(); @@ -520,6 +561,7 @@ public interface Promise { /** * Converts this promise to a {@link CompletableFuture}. The returned future will complete with the * result of this promise when it completes. + * * @return a future that will complete with the result of this promise */ @NotNull CompletableFuture toFuture(); diff --git a/futur-api/src/main/java/dev/tommyjs/futur/promise/PromiseCompletion.java b/futur-api/src/main/java/dev/tommyjs/futur/promise/PromiseCompletion.java index 2af014f..7ca6e9f 100644 --- a/futur-api/src/main/java/dev/tommyjs/futur/promise/PromiseCompletion.java +++ b/futur-api/src/main/java/dev/tommyjs/futur/promise/PromiseCompletion.java @@ -15,6 +15,7 @@ public class PromiseCompletion { /** * Creates a new successful completion. + * * @param result the result */ public PromiseCompletion(@Nullable T result) { @@ -23,6 +24,7 @@ public class PromiseCompletion { /** * Creates a new exceptional completion. + * * @param exception the exception */ public PromiseCompletion(@NotNull Throwable exception) { @@ -38,6 +40,7 @@ public class PromiseCompletion { /** * Checks if the completion was successful. + * * @return {@code true} if the completion was successful, {@code false} otherwise */ public boolean isSuccess() { @@ -46,6 +49,7 @@ public class PromiseCompletion { /** * Checks if the completion was exceptional. + * * @return {@code true} if the completion was exceptional, {@code false} otherwise */ public boolean isError() { @@ -54,6 +58,7 @@ public class PromiseCompletion { /** * Checks if the completion was cancelled. + * * @return {@code true} if the completion was cancelled, {@code false} otherwise */ public boolean wasCancelled() { @@ -67,6 +72,7 @@ public class PromiseCompletion { /** * Gets the result of the completion. + * * @return the result, or {@code null} if the completion was exceptional */ public @Nullable T getResult() { @@ -75,6 +81,7 @@ public class PromiseCompletion { /** * Gets the exception of the completion. + * * @return the exception, or {@code null} if the completion was successful */ public @Nullable Throwable getException() { 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 8aac38f..33ed594 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 @@ -16,8 +16,9 @@ public interface PromiseFactory { /** * Creates a new {@link PromiseFactory} with the given logger and executors. - * @param logger the logger - * @param syncExecutor the synchronous executor + * + * @param logger the logger + * @param syncExecutor the synchronous executor * @param asyncExecutor the asynchronous executor * @return the new promise factory */ @@ -28,7 +29,8 @@ public interface PromiseFactory { /** * Creates a new {@link PromiseFactory} with the given logger and dual executor. - * @param logger the logger + * + * @param logger the logger * @param executor the executor * @return the new promise factory */ @@ -38,7 +40,8 @@ public interface PromiseFactory { /** * Creates a new {@link PromiseFactory} with the given logger and executor. - * @param logger the logger + * + * @param logger the logger * @param executor the executor * @return the new promise factory */ @@ -48,12 +51,14 @@ public interface PromiseFactory { /** * Creates a new uncompleted promise. + * * @return the new promise */ @NotNull CompletablePromise unresolved(); /** * Creates a new promise, completed with the given value. + * * @param value the value to complete the promise with * @return the new promise */ @@ -61,8 +66,9 @@ public interface PromiseFactory { /** * Creates a new promise, completed with {@code null}. - * @apiNote This method is often useful for starting promise chains. + * * @return the new promise + * @apiNote This method is often useful for starting promise chains. */ default @NotNull Promise start() { return resolve(null); @@ -70,6 +76,7 @@ public interface PromiseFactory { /** * Creates a new promise, completed exceptionally with the given error. + * * @param error the error to complete the promise with * @return the new promise */ @@ -78,6 +85,7 @@ public interface PromiseFactory { /** * Creates a new promise backed by the given future. The promise will be completed upon completion * of the future. + * * @param future the future to wrap * @return the new promise */ @@ -88,8 +96,9 @@ public interface PromiseFactory { * {@code link} is {@code true} and either input promise completes exceptionally (including * cancellation), the other promise will be cancelled and the output promise will complete * exceptionally. - * @param p1 the first promise - * @param p2 the second promise + * + * @param p1 the first promise + * @param p2 the second promise * @param link whether to cancel the other promise on error * @return the combined promise */ @@ -100,6 +109,7 @@ public interface PromiseFactory { * Combines two promises into a single promise that completes when both promises complete. If either * input promise completes exceptionally, the other promise will be cancelled and the output promise * will complete exceptionally. + * * @param p1 the first promise * @param p2 the second promise * @return the combined promise @@ -115,9 +125,10 @@ public interface PromiseFactory { * 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. - * @param promises the input promises + * + * @param promises the input promises * @param exceptionHandler the exception handler - * @param link whether to cancel all promises on any exceptional completions + * @param link whether to cancel all promises on any exceptional completions * @return the combined promise */ @NotNull Promise> combine(@NotNull Map> promises, @@ -129,7 +140,8 @@ public interface PromiseFactory { * 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. - * @param promises the input promises + * + * @param promises the input promises * @param exceptionHandler the exception handler * @return the combined promise */ @@ -143,8 +155,9 @@ public interface PromiseFactory { * 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 + * @param link whether to cancel all promises on any exceptional completions * @return the combined promise */ default @NotNull Promise> combine(@NotNull Map> promises, boolean link) { @@ -155,6 +168,7 @@ 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 any promise completes exceptionally, * the output promise will complete exceptionally. + * * @param promises the input promises * @return the combined promise */ @@ -168,9 +182,10 @@ public interface PromiseFactory { * 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 index that failed and the exception. - * @param promises the input promises + * + * @param promises the input promises * @param exceptionHandler the exception handler - * @param link whether to cancel all promises on any exceptional completions + * @param link whether to cancel all promises on any exceptional completions * @return the combined promise */ @NotNull Promise> combine(@NotNull Iterator> promises, int expectedSize, @@ -182,7 +197,8 @@ public interface PromiseFactory { * 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 promises the input promises * @param exceptionHandler the exception handler * @return the combined promise */ @@ -197,7 +213,8 @@ public interface PromiseFactory { * 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 promises the input promises * @param exceptionHandler the exception handler * @return the combined promise */ @@ -210,8 +227,9 @@ public interface PromiseFactory { * 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 * 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 + * @param link whether to cancel all promises on any exceptional completions * @return the combined promise */ default @NotNull Promise> combine(@NotNull Collection> promises, boolean link) { @@ -221,6 +239,7 @@ public interface PromiseFactory { /** * 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 output promise will complete exceptionally. + * * @param promises the input promises * @return the combined promise */ @@ -234,9 +253,10 @@ public interface PromiseFactory { * 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 index that failed and the exception. - * @param promises the input promises + * + * @param promises the input promises * @param exceptionHandler the exception handler - * @param link whether to cancel all promises on any exceptional completions + * @param link whether to cancel all promises on any exceptional completions * @return the combined promise */ default @NotNull Promise> combine(@NotNull Stream> promises, @@ -250,7 +270,8 @@ public interface PromiseFactory { * 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 promises the input promises * @param exceptionHandler the exception handler * @return the combined promise */ @@ -263,8 +284,9 @@ public interface PromiseFactory { * 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 + * @param link whether to cancel all promises on any exceptional completions * @return the combined promise */ default @NotNull Promise> combine(@NotNull Stream> promises, boolean link) { @@ -274,6 +296,7 @@ public interface PromiseFactory { /** * 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. + * * @param promises the input promises * @return the combined promise */ @@ -286,9 +309,10 @@ public interface PromiseFactory { * promises complete. If {@code link} is {@code true} and any promise completes exceptionally, all * other promises will be cancelled. The output promise will always complete successfully regardless * of whether input promises fail. - * @param promises the input promises + * + * @param promises the input promises * @param expectedSize the expected size of the list (used for optimization) - * @param link whether to cancel all promises on any exceptional completions + * @param link whether to cancel all promises on any exceptional completions * @return the combined promise */ @NotNull Promise>> allSettled(@NotNull Iterator> promises, @@ -299,8 +323,9 @@ public interface PromiseFactory { * promises complete. If {@code link} is {@code true} and any promise completes exceptionally, all * other promises will be cancelled. The output promise will always complete successfully regardless * of whether input promises fail. + * * @param promises the input promises - * @param link whether to cancel all promises on any exceptional completions + * @param link whether to cancel all promises on any exceptional completions * @return the combined promise */ default @NotNull Promise>> allSettled(@NotNull Collection> promises, @@ -311,6 +336,7 @@ public interface PromiseFactory { /** * 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, all other promises will be cancelled. + * * @param promises the input promises * @return the combined promise */ @@ -323,8 +349,9 @@ public interface PromiseFactory { * promises complete. If {@code link} is {@code true} and any promise completes exceptionally, all * other promises will be cancelled. The output promise will always complete successfully regardless * of whether input promises fail. + * * @param promises the input promises - * @param link whether to cancel all promises on any exceptional completions + * @param link whether to cancel all promises on any exceptional completions * @return the combined promise */ default @NotNull Promise>> allSettled(@NotNull Stream> promises, @@ -335,6 +362,7 @@ public interface PromiseFactory { /** * 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, all other promises will be cancelled. + * * @param promises the input promises * @return the combined promise */ @@ -347,7 +375,8 @@ public interface PromiseFactory { * promises complete. If {@code link} is {@code true} and any promise completes exceptionally, all * other promises will be cancelled. The output promise will always complete successfully regardless * of whether input promises fail. - * @param link whether to cancel all promises on any exceptional completions + * + * @param link whether to cancel all promises on any exceptional completions * @param promises the input promises * @return the combined promise */ @@ -359,6 +388,7 @@ public interface PromiseFactory { /** * Combines an array of promises into a single promise that completes with a list of results when all * promises complete. If any promise completes exceptionally, all other promises will be cancelled. + * * @param promises the input promises * @return the combined promise */ @@ -370,8 +400,9 @@ public interface PromiseFactory { * Combines an iterator of promises into a single promise that completes 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 + * @param link whether to cancel all promises on any exceptional completions * @return the combined promise */ @NotNull Promise all(@NotNull Iterator> promises, boolean link); @@ -380,8 +411,9 @@ public interface PromiseFactory { * Combines an iterable of promises into a single promise that completes 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 + * @param link whether to cancel all promises on any exceptional completions * @return the combined promise */ default @NotNull Promise all(@NotNull Iterable> promises, boolean link) { @@ -392,6 +424,7 @@ public interface PromiseFactory { * Combines an iterable of promises into a single promise that completes when all promises complete. * If any promise completes exceptionally, all other promises will be cancelled and the output * promise will complete exceptionally. + * * @param promises the input promises * @return the combined promise */ @@ -403,8 +436,9 @@ public interface PromiseFactory { * Combines a stream of promises into a single promise that completes 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 + * @param link whether to cancel all promises on any exceptional completions * @return the combined promise */ default @NotNull Promise all(@NotNull Stream> promises, boolean link) { @@ -415,6 +449,7 @@ public interface PromiseFactory { * Combines a stream of promises into a single promise that completes when all promises complete. * If any promise completes exceptionally, all other promises will be cancelled and the output * promise willcomplete exceptionally. + * * @param promises the input promises * @return the combined promise */ @@ -427,7 +462,8 @@ public interface PromiseFactory { * 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 link whether to cancel all promises on any exceptional completions + * + * @param link whether to cancel all promises on any exceptional completions * @param promises the input promises * @return the combined promise */ @@ -439,6 +475,7 @@ public interface PromiseFactory { * Combines an array of promises into a single promise that completes when all promises complete. * If any promise completes exceptionally, all other promises will be cancelled and the output * promise will complete exceptionally. + * * @param promises the input promises * @return the combined promise */ @@ -451,7 +488,8 @@ public interface PromiseFactory { * completes (successfully or exceptionally). If {@code cancelLosers} is {@code true}, all other * promises will be cancelled when the first promise * completes. - * @param promises the input promises + * + * @param promises the input promises * @param cancelLosers whether to cancel the other promises when the first completes * @return the combined promise */ @@ -461,6 +499,7 @@ public interface PromiseFactory { * Combines an iterable of promises into a single promise that completes when the first promise * completes (successfully or exceptionally). All other promises will be cancelled when the first * promise completes. + * * @param promises the input promises * @return the combined promise */ @@ -472,6 +511,7 @@ public interface PromiseFactory { * Combines an iterable of promises into a single promise that completes when the first promise * completes (successfully or exceptionally). All other promises will be cancelled when the first * promise completes. + * * @param promises the input promises */ default @NotNull Promise race(@NotNull Iterable> promises) { @@ -482,7 +522,8 @@ public interface PromiseFactory { * Combines a stream of promises into a single promise that completes when the first promise * completes (successfully or exceptionally). If {@code cancelLosers} is {@code true}, all other * promises will be cancelled when the first promise completes. - * @param promises the input promises + * + * @param promises the input promises * @param cancelLosers whether to cancel the other promises when the first completes * @return the combined promise */ @@ -494,6 +535,7 @@ public interface PromiseFactory { * Combines a stream of promises into a single promise that completes when the first promise * completes (successfully or exceptionally). All other promises will be cancelled when the first * promise completes. + * * @param promises the input promises * @return the combined promise */ diff --git a/futur-api/src/main/java/dev/tommyjs/futur/promise/PromiseListener.java b/futur-api/src/main/java/dev/tommyjs/futur/promise/PromiseListener.java index 85b09af..18666f1 100644 --- a/futur-api/src/main/java/dev/tommyjs/futur/promise/PromiseListener.java +++ b/futur-api/src/main/java/dev/tommyjs/futur/promise/PromiseListener.java @@ -9,6 +9,7 @@ public interface PromiseListener { /** * Handles the completion of the promise. + * * @param completion the promise completion */ void handle(@NotNull PromiseCompletion completion); diff --git a/futur-api/src/main/java/dev/tommyjs/futur/util/PromiseUtil.java b/futur-api/src/main/java/dev/tommyjs/futur/util/PromiseUtil.java index 152c67c..25895b8 100644 --- a/futur-api/src/main/java/dev/tommyjs/futur/util/PromiseUtil.java +++ b/futur-api/src/main/java/dev/tommyjs/futur/util/PromiseUtil.java @@ -10,8 +10,9 @@ public class PromiseUtil { /** * Propagates the completion, once completed, of the given promise to the given promise. + * * @param from the promise to propagate the completion from - * @param to the completable promise to propagate the completion to + * @param to the completable promise to propagate the completion to */ public static void propagateCompletion(@NotNull Promise from, @NotNull CompletablePromise to) { from.addDirectListener(to::complete, to::completeExceptionally); @@ -19,8 +20,9 @@ public class PromiseUtil { /** * Propagates the cancellation, once cancelled, of the given promise to the given promise. + * * @param from the promise to propagate the cancellation from - * @param to the promise to propagate the cancellation to + * @param to the promise to propagate the cancellation to */ public static void propagateCancel(@NotNull Promise from, @NotNull Promise to) { from.onCancel(to::cancel); @@ -28,8 +30,9 @@ public class PromiseUtil { /** * Cancels the given promise once the given promise is completed. + * * @param from the promise to propagate the completion from - * @param to the promise to cancel upon completion + * @param to the promise to cancel upon completion */ public static void cancelOnComplete(@NotNull Promise from, @NotNull Promise to) { from.addDirectListener(_ -> to.cancel()); @@ -37,6 +40,7 @@ public class PromiseUtil { /** * Estimates the size of the given stream. + * * @param stream the stream * @return the estimated size */ From 08bf5eed1d57de17847dc844cefe9d6dea9f557e Mon Sep 17 00:00:00 2001 From: tommyskeff Date: Tue, 7 Jan 2025 08:23:59 +0000 Subject: [PATCH 05/16] change timeout unit format to lowercase --- .../main/java/dev/tommyjs/futur/promise/AbstractPromise.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 845069f..f451c7d 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 @@ -454,13 +454,13 @@ public abstract class AbstractPromise implements CompletablePromise timeout(long time, @NotNull TimeUnit unit) { - Exception e = new CancellationException("Promise timed out after " + time + " " + unit); + Exception e = new CancellationException("Promise timed out after " + time + " " + unit.toString().toLowerCase()); return completeExceptionallyDelayed(e, time, unit); } @Override public @NotNull Promise maxWaitTime(long time, @NotNull TimeUnit unit) { - Exception e = new TimeoutException("Promise stopped waiting after " + time + " " + unit); + Exception e = new TimeoutException("Promise stopped waiting after " + time + " " + unit.toString().toLowerCase()); return completeExceptionallyDelayed(e, time, unit); } From 8dbbc66de476138f90bd7e31f58b52cc91ab9bf6 Mon Sep 17 00:00:00 2001 From: tommyskeff Date: Tue, 7 Jan 2025 08:37:02 +0000 Subject: [PATCH 06/16] small docs improvements --- .../dev/tommyjs/futur/promise/Promise.java | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) 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 4cbba54..13bd4b8 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 @@ -501,17 +501,8 @@ public interface Promise { } /** - * Blocks until this promise has completed, and then returns its result. - * - * @return the result of the promise - * @throws CancellationException if the promise was cancelled - * @throws CompletionException if the promise completed exceptionally - */ - @Blocking - T await(); - - /** - * Blocks until this promise has completed, and then returns its result. + * Blocks until this promise has completed, and then returns its result. This method will throw + * checked exceptions if the promise completes exceptionally or the thread is interrupted. * * @return the result of the promise * @throws CancellationException if the promise was cancelled @@ -523,7 +514,8 @@ public interface Promise { /** * Blocks until either this promise has completed or the timeout has been exceeded, and then - * returns its result, if available. + * returns its result, if available. This method will throw checked exceptions if the promise + * completes exceptionally or the thread is interrupted, or the timeout is exceeded. * * @return the result of the promise * @throws CancellationException if the promise was cancelled @@ -534,6 +526,18 @@ public interface Promise { @Blocking T get(long timeout, @NotNull TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException; + /** + * Blocks until this promise has completed, and then returns its result. This method is similar + * to {@link #get()}, but will throw unchecked exceptions instead of checked exceptions if the + * promise completes exceptionally or the thread is interrupted. + * + * @return the result of the promise + * @throws CancellationException if the promise was cancelled + * @throws CompletionException if the promise completed exceptionally + */ + @Blocking + T await(); + /** * Returns a new promise, backed by this promise, that will not propagate cancellations. This means * that if the returned promise is cancelled, the cancellation will not be propagated to this promise, From fbeef9833bbceb04f099aa50cfcec331b1fb446b Mon Sep 17 00:00:00 2001 From: tommyskeff Date: Tue, 7 Jan 2025 14:22:33 +0000 Subject: [PATCH 07/16] small promise optimizations --- .../futur/promise/AbstractPromise.java | 101 ++++++++++++------ 1 file changed, 68 insertions(+), 33 deletions(-) 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 f451c7d..526f242 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 @@ -9,24 +9,40 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.VarHandle; 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; import java.util.function.Consumer; public abstract class AbstractPromise implements CompletablePromise { + private static final VarHandle COMPLETION_HANDLE; + + static { + try { + MethodHandles.Lookup lookup = MethodHandles.lookup(); + COMPLETION_HANDLE = lookup.findVarHandle(AbstractPromise.class, "completion", PromiseCompletion.class); + } catch (ReflectiveOperationException e) { + throw new ExceptionInInitializerError(e); + } + } + private final AtomicReference>> listeners; - private final AtomicReference> completion; - private final CountDownLatch latch; + private final Sync sync; + + @SuppressWarnings("FieldMayBeFinal") + private volatile PromiseCompletion completion; public AbstractPromise() { this.listeners = new AtomicReference<>(Collections.emptyList()); - this.completion = new AtomicReference<>(); - this.latch = new CountDownLatch(1); + this.sync = new Sync(); + this.completion = null; } public abstract @NotNull AbstractPromiseFactory getFactory(); @@ -60,13 +76,13 @@ public abstract class AbstractPromise implements CompletablePromise implements CompletablePromise implements CompletablePromise ctx) { - if (!setCompletion(ctx)) return; - latch.countDown(); + private void handleCompletion(@NotNull PromiseCompletion cmp) { + if (!COMPLETION_HANDLE.compareAndSet(this, null, cmp)) return; + sync.releaseShared(1); Iterator> iter = listeners.getAndSet(null).iterator(); - while (iter.hasNext()) { - PromiseListener listener = iter.next(); - - try { - if (listener instanceof AsyncPromiseListener) { - callListenerAsync(listener, ctx); - } else { - callListenerNow(listener, ctx); - } - } finally { - iter.forEachRemaining(v -> callListenerAsyncLastResort(v, ctx)); - } - } - } - - private void callListenerAsyncLastResort(PromiseListener listener, PromiseCompletion ctx) { try { - getFactory().getAsyncExecutor().run(() -> callListenerNow(listener, ctx)); - } catch (Throwable ignored) { - + while (iter.hasNext()) { + PromiseListener listener = iter.next(); + if (listener instanceof AsyncPromiseListener) { + callListenerAsync(listener, cmp); + } else { + callListenerNow(listener, cmp); + } + } + } finally { + iter.forEachRemaining(v -> callListenerAsyncLastResort(v, cmp)); } } - private boolean setCompletion(PromiseCompletion completion) { - return this.completion.compareAndSet(null, completion); + private void callListenerAsyncLastResort(PromiseListener listener, PromiseCompletion completion) { + try { + getFactory().getAsyncExecutor().run(() -> callListenerNow(listener, completion)); + } catch (Throwable ignored) {} } @Override @@ -521,12 +530,12 @@ public abstract class AbstractPromise implements CompletablePromise getCompletion() { - return completion.get(); + return completion; } @Override @@ -543,7 +552,33 @@ public abstract class AbstractPromise implements CompletablePromise Date: Tue, 7 Jan 2025 14:51:37 +0000 Subject: [PATCH 08/16] final promise optimization --- .../futur/promise/AbstractPromise.java | 35 +++++++++++-------- 1 file changed, 21 insertions(+), 14 deletions(-) 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 526f242..c0538e2 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,37 +11,36 @@ import org.slf4j.Logger; import java.lang.invoke.MethodHandles; import java.lang.invoke.VarHandle; -import java.util.Collection; -import java.util.Collections; -import java.util.Iterator; -import java.util.Objects; +import java.util.*; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.AbstractQueuedSynchronizer; import java.util.function.Consumer; +@SuppressWarnings({"FieldMayBeFinal", "unchecked"}) public abstract class AbstractPromise implements CompletablePromise { private static final VarHandle COMPLETION_HANDLE; + private static final VarHandle LISTENERS_HANDLE; static { try { MethodHandles.Lookup lookup = MethodHandles.lookup(); COMPLETION_HANDLE = lookup.findVarHandle(AbstractPromise.class, "completion", PromiseCompletion.class); + LISTENERS_HANDLE = lookup.findVarHandle(AbstractPromise.class, "listeners", Collection.class); } catch (ReflectiveOperationException e) { throw new ExceptionInInitializerError(e); } } - private final AtomicReference>> listeners; private final Sync sync; - @SuppressWarnings("FieldMayBeFinal") + private volatile Collection> listeners; private volatile PromiseCompletion completion; public AbstractPromise() { - this.listeners = new AtomicReference<>(Collections.emptyList()); this.sync = new Sync(); + this.listeners = Collections.EMPTY_LIST; this.completion = null; } @@ -400,13 +399,20 @@ public abstract class AbstractPromise implements CompletablePromise addAnyListener(PromiseListener listener) { - Collection> res = listeners.updateAndGet(v -> { - if (v == Collections.EMPTY_LIST) v = new ConcurrentLinkedQueue<>(); - if (v != null) v.add(listener); - return v; - }); + Collection> prev = listeners, next = null; + for (boolean haveNext = false;;) { + if (!haveNext) { + next = prev == Collections.EMPTY_LIST ? new ConcurrentLinkedQueue<>() : prev; + if (next != null) next.add(listener); + } - if (res == null) { + if (LISTENERS_HANDLE.weakCompareAndSet(this, prev, next)) + break; + + haveNext = (prev == (prev = listeners)); + } + + if (next == null) { if (listener instanceof AsyncPromiseListener) { callListenerAsync(listener, Objects.requireNonNull(getCompletion())); } else { @@ -492,7 +498,8 @@ public abstract class AbstractPromise implements CompletablePromise> iter = listeners.getAndSet(null).iterator(); + + Iterator> iter = ((Iterable>) LISTENERS_HANDLE.getAndSet(this, null)).iterator(); try { while (iter.hasNext()) { PromiseListener listener = iter.next(); From df9e418091de13d1a8092a0fc612bd970cd52d5d Mon Sep 17 00:00:00 2001 From: tommyskeff Date: Thu, 9 Jan 2025 09:59:19 +0000 Subject: [PATCH 09/16] 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); } From 18dff51617909804aafeecb3206c3165f4640e58 Mon Sep 17 00:00:00 2001 From: tommyskeff Date: Thu, 9 Jan 2025 10:17:13 +0000 Subject: [PATCH 10/16] fix stream closed issue --- .../tommyjs/futur/promise/PromiseFactory.java | 13 ++++++++----- .../dev/tommyjs/futur/util/PromiseUtil.java | 6 +++--- .../java/dev/tommyjs/futur/PromiseTests.java | 19 +++++++++++++++++++ .../java/dev/tommyjs/futur/lazy/Promises.java | 2 -- 4 files changed, 30 insertions(+), 10 deletions(-) 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 095f8f1..f44c919 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 @@ -195,7 +195,8 @@ public interface PromiseFactory { */ default @NotNull Promise> combineMapped(@NotNull Stream>> promises, boolean link) { - return combineMapped(promises.iterator(), PromiseUtil.estimateSize(promises), link); + Spliterator>> spliterator = promises.spliterator(); + return combineMapped(Spliterators.iterator(spliterator), PromiseUtil.estimateSize(spliterator), link); } /** @@ -302,7 +303,8 @@ public interface PromiseFactory { * @return the combined promise */ default @NotNull Promise> combine(@NotNull Stream> promises, boolean link) { - return combine(promises.iterator(), PromiseUtil.estimateSize(promises), link); + Spliterator> spliterator = promises.spliterator(); + return combine(Spliterators.iterator(spliterator), PromiseUtil.estimateSize(spliterator), link); } /** @@ -314,7 +316,7 @@ public interface PromiseFactory { * @return the combined promise */ default @NotNull Promise> combine(@NotNull Stream> promises) { - return combine(promises.iterator(), PromiseUtil.estimateSize(promises), true); + return combine(promises, true); } /** @@ -369,7 +371,8 @@ public interface PromiseFactory { */ default @NotNull Promise>> allSettled(@NotNull Stream> promises, boolean link) { - return allSettled(promises.iterator(), PromiseUtil.estimateSize(promises), link); + Spliterator> spliterator = promises.spliterator(); + return allSettled(Spliterators.iterator(spliterator), PromiseUtil.estimateSize(spliterator), link); } /** @@ -380,7 +383,7 @@ public interface PromiseFactory { * @return the combined promise */ default @NotNull Promise>> allSettled(@NotNull Stream> promises) { - return allSettled(promises.iterator(), PromiseUtil.estimateSize(promises), true); + return allSettled(promises, true); } /** diff --git a/futur-api/src/main/java/dev/tommyjs/futur/util/PromiseUtil.java b/futur-api/src/main/java/dev/tommyjs/futur/util/PromiseUtil.java index 25895b8..f2a87bd 100644 --- a/futur-api/src/main/java/dev/tommyjs/futur/util/PromiseUtil.java +++ b/futur-api/src/main/java/dev/tommyjs/futur/util/PromiseUtil.java @@ -4,7 +4,7 @@ import dev.tommyjs.futur.promise.CompletablePromise; import dev.tommyjs.futur.promise.Promise; import org.jetbrains.annotations.NotNull; -import java.util.stream.Stream; +import java.util.Spliterator; public class PromiseUtil { @@ -44,8 +44,8 @@ public class PromiseUtil { * @param stream the stream * @return the estimated size */ - public static int estimateSize(@NotNull Stream stream) { - long estimate = stream.spliterator().estimateSize(); + public static int estimateSize(@NotNull Spliterator stream) { + long estimate = stream.estimateSize(); return estimate == Long.MAX_VALUE ? 10 : (int) estimate; } 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 37ff3ad..9885398 100644 --- a/futur-api/src/test/java/dev/tommyjs/futur/PromiseTests.java +++ b/futur-api/src/test/java/dev/tommyjs/futur/PromiseTests.java @@ -12,6 +12,7 @@ import java.util.Map; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Stream; public final class PromiseTests { @@ -210,4 +211,22 @@ public final class PromiseTests { assert promise2.getCompletion() != null && promise2.getCompletion().getException() instanceof IllegalStateException; } + @Test + public void testStream() { + var res = promises.combine(Stream.of(1, 2, 3).map(promises::resolve)).await(); + assert res.size() == 3; + } + + @Test + public void combineMappedTest() { + var res = promises.combineMapped(List.of(1, 2, 3), + n -> promises.start().thenSupplyDelayedAsync(() -> n * 2, 50, TimeUnit.MILLISECONDS) + ).await(); + + assert res.size() == 3; + assert res.get(1) == 2; + assert res.get(2) == 4; + assert res.get(3) == 6; + } + } 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 633328f..288e983 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 @@ -6,7 +6,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 org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -15,7 +14,6 @@ import java.util.Iterator; 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; From 0eb91906218e82d2c44962c551c0e8be56c1f5db Mon Sep 17 00:00:00 2001 From: tommyskeff Date: Thu, 9 Jan 2025 17:28:17 +0000 Subject: [PATCH 11/16] fix Promises formatting --- .../java/dev/tommyjs/futur/lazy/Promises.java | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) 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 288e983..85f4d28 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 @@ -90,7 +90,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); } @@ -118,7 +118,7 @@ public final class Promises { * @return the combined promise */ public static @NotNull Promise> combineMapped(@NotNull Iterator>> promises, - int expectedSize, boolean link) { + int expectedSize, boolean link) { return factory.combineMapped(promises, expectedSize, link); } @@ -131,7 +131,7 @@ public final class Promises { * @return the combined promise */ public static @NotNull Promise> combineMapped(@NotNull Collection>> promises, - boolean link) { + boolean link) { return factory.combineMapped(promises, link); } @@ -146,7 +146,7 @@ public final class Promises { * @return the combined promise */ public static @NotNull Promise> combineMapped(@NotNull Map> promises, - boolean link) { + boolean link) { return factory.combineMapped(promises, link); } @@ -185,7 +185,7 @@ public final class Promises { * @return the combined promise */ public static @NotNull Promise> combineMapped(@NotNull Stream>> promises, - boolean link) { + boolean link) { return factory.combineMapped(promises, link); } @@ -213,8 +213,8 @@ public final class Promises { * @return the combined promise */ public static @NotNull Promise> combineMapped(@NotNull Iterable keys, - @NotNull Function> mapper, - boolean link) { + @NotNull Function> mapper, + boolean link) { return factory.combineMapped(keys, mapper, link); } @@ -228,7 +228,7 @@ public final class Promises { * @return the combined promise */ public static @NotNull Promise> combineMapped(@NotNull Iterable keys, - @NotNull Function> mapper) { + @NotNull Function> mapper) { return factory.combineMapped(keys, mapper); } @@ -254,7 +254,7 @@ public final class Promises { * @return the combined promise */ public static @NotNull Promise> combine(@NotNull Iterator> promises, int expectedSize, - boolean link) { + boolean link) { return factory.combine(promises, expectedSize, link); } @@ -321,7 +321,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); } @@ -336,7 +336,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); } @@ -362,7 +362,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); } @@ -388,7 +388,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); } From ae15089b3d405851c6da3a33d352e27ae5593a37 Mon Sep 17 00:00:00 2001 From: tommyskeff Date: Thu, 9 Jan 2025 18:41:05 +0000 Subject: [PATCH 12/16] more optimized promise implementation for completed promises --- .../futur/promise/AbstractPromise.java | 365 +++++------------- .../futur/promise/AbstractPromiseFactory.java | 25 -- .../futur/promise/AsyncPromiseListener.java | 1 - .../tommyjs/futur/promise/BasePromise.java | 194 ++++++++++ .../futur/promise/CompletedPromise.java | 68 ++++ .../tommyjs/futur/promise/PromiseFactory.java | 4 +- .../futur/promise/PromiseFactoryImpl.java | 39 +- .../java/dev/tommyjs/futur/PromiseTests.java | 16 +- 8 files changed, 423 insertions(+), 289 deletions(-) create mode 100644 futur-api/src/main/java/dev/tommyjs/futur/promise/BasePromise.java create mode 100644 futur-api/src/main/java/dev/tommyjs/futur/promise/CompletedPromise.java 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 c66ba1d..b6a1bc1 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 @@ -9,47 +9,29 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; -import java.lang.invoke.MethodHandles; -import java.lang.invoke.VarHandle; -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; import java.util.function.Consumer; -@SuppressWarnings({"FieldMayBeFinal", "unchecked"}) -public abstract class AbstractPromise implements CompletablePromise { - - private static final VarHandle COMPLETION_HANDLE; - private static final VarHandle LISTENERS_HANDLE; - - static { - try { - MethodHandles.Lookup lookup = MethodHandles.lookup(); - COMPLETION_HANDLE = lookup.findVarHandle(AbstractPromise.class, "completion", PromiseCompletion.class); - LISTENERS_HANDLE = lookup.findVarHandle(AbstractPromise.class, "listeners", Collection.class); - } catch (ReflectiveOperationException e) { - throw new ExceptionInInitializerError(e); - } - } - - private final Sync sync; - - private volatile Collection> listeners; - private volatile PromiseCompletion completion; - - public AbstractPromise() { - this.sync = new Sync(); - this.listeners = Collections.EMPTY_LIST; - this.completion = null; - } +public abstract class AbstractPromise implements Promise { public abstract @NotNull AbstractPromiseFactory getFactory(); - private void runCompleter(@NotNull CompletablePromise promise, @NotNull ExceptionalRunnable completer) { + protected abstract @NotNull Promise addAnyListener(@NotNull PromiseListener listener); + + protected @NotNull Logger getLogger() { + return getFactory().getLogger(); + } + + protected void callListener(@NotNull PromiseListener listener, @NotNull PromiseCompletion cmp) { + if (listener instanceof AsyncPromiseListener) { + callListenerAsync(listener, cmp); + } else { + callListenerNow(listener, cmp); + } + } + + protected void runCompleter(@NotNull CompletablePromise promise, @NotNull ExceptionalRunnable completer) { try { completer.run(); } catch (Error e) { @@ -60,11 +42,8 @@ public abstract class AbstractPromise implements CompletablePromise @NotNull Runnable createCompleter( - T result, - @NotNull CompletablePromise promise, - @NotNull ExceptionalFunction completer - ) { + protected @NotNull Runnable createCompleter(T result, @NotNull CompletablePromise promise, + @NotNull ExceptionalFunction completer) { return () -> { if (!promise.isCompleted()) { runCompleter(promise, () -> promise.complete(completer.apply(result))); @@ -72,43 +51,37 @@ public abstract class AbstractPromise implements CompletablePromise @NotNull CompletablePromise createLinked() { + CompletablePromise promise = getFactory().unresolved(); + PromiseUtil.propagateCancel(promise, this); + return promise; } - @Override - public T get() throws InterruptedException, ExecutionException { - sync.acquireSharedInterruptibly(1); - return joinCompletion(); - } - - @Override - public T get(long time, @NotNull TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { - boolean success = sync.tryAcquireSharedNanos(1, unit.toNanos(time)); - if (!success) { - throw new TimeoutException("Promise stopped waiting after " + time + " " + unit); - } - - return joinCompletion(); - } - - @Override - public T await() { + protected void callListenerAsync(PromiseListener listener, PromiseCompletion res) { try { - sync.acquireSharedInterruptibly(1); - } catch (InterruptedException e) { - throw new RuntimeException(e); + getFactory().getAsyncExecutor().run(() -> callListenerNow(listener, res)); + } catch (RejectedExecutionException ignored) { + } catch (Exception e) { + getLogger().warn("Exception caught while running promise listener", e); } - - PromiseCompletion completion = Objects.requireNonNull(getCompletion()); - if (completion.isSuccess()) return completion.getResult(); - throw new CompletionException(completion.getException()); } - private T joinCompletion() throws ExecutionException { - PromiseCompletion completion = Objects.requireNonNull(getCompletion()); - if (completion.isSuccess()) return completion.getResult(); - throw new ExecutionException(completion.getException()); + protected void callListenerNow(PromiseListener listener, PromiseCompletion res) { + try { + listener.handle(res); + } catch (Error e) { + getLogger().error("Error caught in promise listener", e); + throw e; + } catch (Throwable e) { + getLogger().error("Exception caught in promise listener", e); + } + } + + protected void callListenerAsyncLastResort(PromiseListener listener, PromiseCompletion completion) { + try { + getFactory().getAsyncExecutor().run(() -> callListenerNow(listener, completion)); + } catch (Throwable ignored) { + } } @Override @@ -141,33 +114,68 @@ public abstract class AbstractPromise implements CompletablePromise @NotNull Promise thenApply(@NotNull ExceptionalFunction task) { - CompletablePromise promise = getFactory().unresolved(); - addDirectListener( - res -> createCompleter(res, promise, task).run(), - promise::completeExceptionally - ); + PromiseCompletion completion = getCompletion(); + if (completion == null) { + CompletablePromise promise = createLinked(); + addDirectListener( + res -> createCompleter(res, promise, task).run(), + promise::completeExceptionally + ); - PromiseUtil.propagateCancel(promise, this); - return promise; + return promise; + } else if (completion.isSuccess()) { + try { + V result = task.apply(completion.getResult()); + return getFactory().resolve(result); + } catch (Exception e) { + return getFactory().error(e); + } + } else { + Throwable ex = completion.getException(); + assert ex != null; + return getFactory().error(ex); + } } @Override public @NotNull Promise thenCompose(@NotNull ExceptionalFunction> task) { - CompletablePromise promise = getFactory().unresolved(); - thenApply(task).addDirectListener( - nestedPromise -> { - if (nestedPromise == null) { - promise.complete(null); - } else { - PromiseUtil.propagateCompletion(nestedPromise, promise); - PromiseUtil.propagateCancel(promise, nestedPromise); - } - }, - promise::completeExceptionally - ); + PromiseCompletion completion = getCompletion(); + if (completion == null) { + CompletablePromise promise = createLinked(); + thenApply(task).addDirectListener( + result -> { + if (result == null) { + promise.complete(null); + } else { + PromiseUtil.propagateCompletion(result, promise); + PromiseUtil.propagateCancel(promise, result); + } + }, + promise::completeExceptionally + ); - PromiseUtil.propagateCancel(promise, this); - return promise; + return promise; + } else if (completion.isSuccess()) { + try { + Promise result = task.apply(completion.getResult()); + if (result == null) { + return getFactory().resolve(null); + } else if (result.isCompleted()) { + return result; + } else { + CompletablePromise promise = createLinked(); + PromiseUtil.propagateCompletion(result, promise); + PromiseUtil.propagateCancel(promise, result); + return promise; + } + } catch (Exception e) { + return getFactory().error(e); + } + } else { + Throwable ex = completion.getException(); + assert ex != null; + return getFactory().error(ex); + } } @Override @@ -214,7 +222,7 @@ public abstract class AbstractPromise implements CompletablePromise @NotNull Promise thenApplySync(@NotNull ExceptionalFunction task) { - CompletablePromise promise = getFactory().unresolved(); + CompletablePromise promise = createLinked(); addDirectListener( res -> runCompleter(promise, () -> { Runnable runnable = createCompleter(res, promise, task); @@ -224,13 +232,12 @@ public abstract class AbstractPromise implements CompletablePromise @NotNull Promise thenApplyDelayedSync(@NotNull ExceptionalFunction task, long delay, @NotNull TimeUnit unit) { - CompletablePromise promise = getFactory().unresolved(); + CompletablePromise promise = createLinked(); addDirectListener( res -> runCompleter(promise, () -> { Runnable runnable = createCompleter(res, promise, task); @@ -240,13 +247,12 @@ public abstract class AbstractPromise implements CompletablePromise @NotNull Promise thenComposeSync(@NotNull ExceptionalFunction> task) { - CompletablePromise promise = getFactory().unresolved(); + CompletablePromise promise = createLinked(); thenApplySync(task).addDirectListener( nestedPromise -> { if (nestedPromise == null) { @@ -259,7 +265,6 @@ public abstract class AbstractPromise implements CompletablePromise implements CompletablePromise @NotNull Promise thenApplyAsync(@NotNull ExceptionalFunction task) { - CompletablePromise promise = getFactory().unresolved(); + CompletablePromise promise = createLinked(); addDirectListener( (res) -> runCompleter(promise, () -> { Runnable runnable = createCompleter(res, promise, task); @@ -317,13 +322,12 @@ public abstract class AbstractPromise implements CompletablePromise @NotNull Promise thenApplyDelayedAsync(@NotNull ExceptionalFunction task, long delay, @NotNull TimeUnit unit) { - CompletablePromise promise = getFactory().unresolved(); + CompletablePromise promise = createLinked(); addDirectListener( res -> runCompleter(promise, () -> { Runnable runnable = createCompleter(res, promise, task); @@ -333,13 +337,12 @@ public abstract class AbstractPromise implements CompletablePromise @NotNull Promise thenComposeAsync(@NotNull ExceptionalFunction> task) { - CompletablePromise promise = getFactory().unresolved(); + CompletablePromise promise = createLinked(); thenApplyAsync(task).addDirectListener( nestedPromise -> { if (nestedPromise == null) { @@ -352,7 +355,6 @@ public abstract class AbstractPromise implements CompletablePromise implements CompletablePromise addAnyListener(PromiseListener listener) { - Collection> prev = listeners, next = null; - for (boolean haveNext = false; ; ) { - if (!haveNext) { - next = prev == Collections.EMPTY_LIST ? new ConcurrentLinkedQueue<>() : prev; - if (next != null) next.add(listener); - } - - if (LISTENERS_HANDLE.weakCompareAndSet(this, prev, next)) - break; - - haveNext = (prev == (prev = listeners)); - } - - if (next == null) { - if (listener instanceof AsyncPromiseListener) { - callListenerAsync(listener, Objects.requireNonNull(getCompletion())); - } else { - callListenerNow(listener, Objects.requireNonNull(getCompletion())); - } - } - - return this; - } - - private void callListenerAsync(PromiseListener listener, PromiseCompletion res) { - try { - getFactory().getAsyncExecutor().run(() -> callListenerNow(listener, res)); - } catch (RejectedExecutionException ignored) { - } catch (Exception e) { - getLogger().warn("Exception caught while running promise listener", e); - } - } - - private void callListenerNow(PromiseListener listener, PromiseCompletion res) { - try { - listener.handle(res); - } catch (Error e) { - getLogger().error("Error caught in promise listener", e); - throw e; - } catch (Throwable e) { - getLogger().error("Exception caught in promise listener", e); - } - } - @Override public @NotNull Promise onSuccess(@NotNull Consumer listener) { return addAsyncListener(listener, null); @@ -489,92 +446,11 @@ public abstract class AbstractPromise implements CompletablePromise 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); + CompletablePromise promise = createLinked(); + addDirectListener(promise::complete, e -> runCompleter(promise, () -> promise.complete(function.apply(e)))); return promise; } - @Override - public @NotNull Promise timeout(long time, @NotNull TimeUnit unit) { - Exception e = new CancellationException("Promise timed out after " + time + " " + unit.toString().toLowerCase()); - return completeExceptionallyDelayed(e, time, unit); - } - - @Override - public @NotNull Promise maxWaitTime(long time, @NotNull TimeUnit unit) { - Exception e = new TimeoutException("Promise stopped waiting after " + time + " " + unit.toString().toLowerCase()); - return completeExceptionallyDelayed(e, time, unit); - } - - private Promise completeExceptionallyDelayed(Throwable e, long delay, TimeUnit unit) { - runCompleter(this, () -> { - FA future = getFactory().getAsyncExecutor().run(() -> completeExceptionally(e), delay, unit); - addDirectListener(_ -> getFactory().getAsyncExecutor().cancel(future)); - }); - return this; - } - - private void handleCompletion(@NotNull PromiseCompletion cmp) { - if (!COMPLETION_HANDLE.compareAndSet(this, null, cmp)) return; - sync.releaseShared(1); - - - Iterator> iter = ((Iterable>) LISTENERS_HANDLE.getAndSet(this, null)).iterator(); - try { - while (iter.hasNext()) { - PromiseListener listener = iter.next(); - if (listener instanceof AsyncPromiseListener) { - callListenerAsync(listener, cmp); - } else { - callListenerNow(listener, cmp); - } - } - } finally { - iter.forEachRemaining(v -> callListenerAsyncLastResort(v, cmp)); - } - } - - private void callListenerAsyncLastResort(PromiseListener listener, PromiseCompletion completion) { - try { - getFactory().getAsyncExecutor().run(() -> callListenerNow(listener, completion)); - } catch (Throwable ignored) { - } - } - - @Override - public void cancel(@NotNull CancellationException e) { - completeExceptionally(e); - } - - @Override - public void complete(@Nullable T result) { - handleCompletion(new PromiseCompletion<>(result)); - } - - @Override - public void completeExceptionally(@NotNull Throwable result) { - handleCompletion(new PromiseCompletion<>(result)); - } - - @Override - public boolean isCompleted() { - return completion != null; - } - - @Override - public @Nullable PromiseCompletion getCompletion() { - return completion; - } - @Override public @NotNull CompletableFuture toFuture() { CompletableFuture future = new CompletableFuture<>(); @@ -591,31 +467,4 @@ public abstract class AbstractPromise implements CompletablePromise implements PromiseFactory { @@ -23,20 +22,6 @@ public abstract class AbstractPromiseFactory implements PromiseFactory { public abstract @NotNull PromiseExecutor getAsyncExecutor(); - @Override - public @NotNull Promise resolve(T value) { - CompletablePromise promise = unresolved(); - promise.complete(value); - return promise; - } - - @Override - public @NotNull Promise error(@NotNull Throwable error) { - CompletablePromise promise = unresolved(); - promise.completeExceptionally(error); - return promise; - } - @Override public @NotNull Promise wrap(@NotNull CompletableFuture future) { return wrap(future, future); @@ -112,14 +97,4 @@ public abstract class AbstractPromiseFactory implements PromiseFactory { return promise; } - @Override - public @NotNull Promise race(@NotNull Iterable> promises, boolean cancelLosers) { - return race(promises.iterator(), cancelLosers); - } - - @Override - public @NotNull Promise race(@NotNull Stream> promises, boolean cancelLosers) { - return race(promises.iterator(), cancelLosers); - } - } diff --git a/futur-api/src/main/java/dev/tommyjs/futur/promise/AsyncPromiseListener.java b/futur-api/src/main/java/dev/tommyjs/futur/promise/AsyncPromiseListener.java index c53f86d..799b6be 100644 --- a/futur-api/src/main/java/dev/tommyjs/futur/promise/AsyncPromiseListener.java +++ b/futur-api/src/main/java/dev/tommyjs/futur/promise/AsyncPromiseListener.java @@ -5,5 +5,4 @@ package dev.tommyjs.futur.promise; * executed asynchronously by the {@link PromiseFactory} that created the completed promise. */ public interface AsyncPromiseListener extends PromiseListener { - } diff --git a/futur-api/src/main/java/dev/tommyjs/futur/promise/BasePromise.java b/futur-api/src/main/java/dev/tommyjs/futur/promise/BasePromise.java new file mode 100644 index 0000000..f2a1a67 --- /dev/null +++ b/futur-api/src/main/java/dev/tommyjs/futur/promise/BasePromise.java @@ -0,0 +1,194 @@ +package dev.tommyjs.futur.promise; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.lang.invoke.MethodHandles; +import java.lang.invoke.VarHandle; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.Objects; +import java.util.concurrent.*; +import java.util.concurrent.locks.AbstractQueuedSynchronizer; + +@SuppressWarnings({"FieldMayBeFinal"}) +public abstract class BasePromise extends AbstractPromise implements CompletablePromise { + + private static final VarHandle COMPLETION_HANDLE; + private static final VarHandle LISTENERS_HANDLE; + + static { + try { + MethodHandles.Lookup lookup = MethodHandles.lookup(); + COMPLETION_HANDLE = lookup.findVarHandle(BasePromise.class, "completion", PromiseCompletion.class); + LISTENERS_HANDLE = lookup.findVarHandle(BasePromise.class, "listeners", Collection.class); + } catch (ReflectiveOperationException e) { + throw new ExceptionInInitializerError(e); + } + } + + private final Sync sync; + + private volatile PromiseCompletion completion; + + @SuppressWarnings("FieldMayBeFinal") + private volatile Collection> listeners; + + @SuppressWarnings("unchecked") + public BasePromise() { + this.sync = new Sync(); + this.completion = null; + this.listeners = Collections.EMPTY_LIST; + } + + protected T joinCompletion() throws ExecutionException { + PromiseCompletion completion = Objects.requireNonNull(getCompletion()); + if (completion.isSuccess()) return completion.getResult(); + throw new ExecutionException(completion.getException()); + } + + protected void handleCompletion(@NotNull PromiseCompletion cmp) { + if (!COMPLETION_HANDLE.compareAndSet(this, null, cmp)) return; + sync.releaseShared(1); + callListeners(cmp); + } + + protected Promise completeExceptionallyDelayed(Throwable e, long delay, TimeUnit unit) { + runCompleter(this, () -> { + FA future = getFactory().getAsyncExecutor().run(() -> completeExceptionally(e), delay, unit); + addDirectListener(_ -> getFactory().getAsyncExecutor().cancel(future)); + }); + + return this; + } + + @SuppressWarnings("unchecked") + protected void callListeners(@NotNull PromiseCompletion cmp) { + Iterator> iter = ((Iterable>) LISTENERS_HANDLE.getAndSet(this, null)).iterator(); + try { + while (iter.hasNext()) { + callListener(iter.next(), cmp); + } + } finally { + iter.forEachRemaining(v -> callListenerAsyncLastResort(v, cmp)); + } + } + + @Override + protected @NotNull Promise addAnyListener(@NotNull PromiseListener listener) { + Collection> prev = listeners, next = null; + for (boolean haveNext = false; ; ) { + if (!haveNext) { + next = prev == Collections.EMPTY_LIST ? new ConcurrentLinkedQueue<>() : prev; + if (next != null) next.add(listener); + } + + if (LISTENERS_HANDLE.weakCompareAndSet(this, prev, next)) + break; + + haveNext = (prev == (prev = listeners)); + } + + if (next == null) { + callListener(listener, Objects.requireNonNull(getCompletion())); + } + + return this; + } + + @Override + public T get() throws InterruptedException, ExecutionException { + sync.acquireSharedInterruptibly(1); + return joinCompletion(); + } + + @Override + public T get(long time, @NotNull TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { + boolean success = sync.tryAcquireSharedNanos(1, unit.toNanos(time)); + if (!success) { + throw new TimeoutException("Promise stopped waiting after " + time + " " + unit); + } + + return joinCompletion(); + } + + @Override + public T await() { + try { + sync.acquireSharedInterruptibly(1); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + + PromiseCompletion completion = Objects.requireNonNull(getCompletion()); + if (completion.isSuccess()) return completion.getResult(); + throw new CompletionException(completion.getException()); + } + + @Override + public @NotNull Promise timeout(long time, @NotNull TimeUnit unit) { + Exception e = new CancellationException("Promise timed out after " + time + " " + unit.toString().toLowerCase()); + return completeExceptionallyDelayed(e, time, unit); + } + + @Override + public @NotNull Promise maxWaitTime(long time, @NotNull TimeUnit unit) { + Exception e = new TimeoutException("Promise stopped waiting after " + time + " " + unit.toString().toLowerCase()); + return completeExceptionallyDelayed(e, time, unit); + } + + @Override + public void cancel(@NotNull CancellationException e) { + completeExceptionally(e); + } + + @Override + public void complete(@Nullable T result) { + handleCompletion(new PromiseCompletion<>(result)); + } + + @Override + public void completeExceptionally(@NotNull Throwable result) { + handleCompletion(new PromiseCompletion<>(result)); + } + + @Override + public boolean isCompleted() { + return completion != null; + } + + @Override + public @Nullable PromiseCompletion getCompletion() { + return completion; + } + + private static final class Sync extends AbstractQueuedSynchronizer { + + private Sync() { + setState(1); + } + + @Override + protected int tryAcquireShared(int acquires) { + return getState() == 0 ? 1 : -1; + } + + @Override + protected boolean tryReleaseShared(int releases) { + int c1, c2; + do { + c1 = getState(); + if (c1 == 0) { + return false; + } + + c2 = c1 - 1; + } while (!compareAndSetState(c1, c2)); + + return c2 == 0; + } + + } + +} diff --git a/futur-api/src/main/java/dev/tommyjs/futur/promise/CompletedPromise.java b/futur-api/src/main/java/dev/tommyjs/futur/promise/CompletedPromise.java new file mode 100644 index 0000000..18aaeee --- /dev/null +++ b/futur-api/src/main/java/dev/tommyjs/futur/promise/CompletedPromise.java @@ -0,0 +1,68 @@ +package dev.tommyjs.futur.promise; + +import org.jetbrains.annotations.NotNull; + +import java.util.concurrent.CancellationException; +import java.util.concurrent.TimeUnit; + +public abstract class CompletedPromise extends AbstractPromise { + + private static final PromiseCompletion EMPTY = new PromiseCompletion<>(); + + private final @NotNull PromiseCompletion completion; + + public CompletedPromise(@NotNull PromiseCompletion completion) { + this.completion = completion; + } + + @SuppressWarnings("unchecked") + public CompletedPromise() { + this((PromiseCompletion) EMPTY); + } + + @Override + protected @NotNull Promise addAnyListener(@NotNull PromiseListener listener) { + callListener(listener, completion); + return this; + } + + @Override + public @NotNull Promise timeout(long time, @NotNull TimeUnit unit) { + return this; + } + + @Override + public @NotNull Promise maxWaitTime(long time, @NotNull TimeUnit unit) { + return this; + } + + @Override + public void cancel(@NotNull CancellationException exception) { + } + + @Override + public T get() { + return null; + } + + @Override + public T get(long timeout, @NotNull TimeUnit unit) { + return null; + } + + @Override + public T await() { + return null; + } + + @Override + public @NotNull PromiseCompletion getCompletion() { + return completion; + } + + @Override + public boolean isCompleted() { + return true; + } + +} 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 f44c919..901721b 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 @@ -70,9 +70,7 @@ public interface PromiseFactory { * @return the new promise * @apiNote This method is often useful for starting promise chains. */ - default @NotNull Promise start() { - return resolve(null); - } + @NotNull Promise start(); /** * Creates a new promise, completed exceptionally with the given error. diff --git a/futur-api/src/main/java/dev/tommyjs/futur/promise/PromiseFactoryImpl.java b/futur-api/src/main/java/dev/tommyjs/futur/promise/PromiseFactoryImpl.java index f5a2153..7604aeb 100644 --- a/futur-api/src/main/java/dev/tommyjs/futur/promise/PromiseFactoryImpl.java +++ b/futur-api/src/main/java/dev/tommyjs/futur/promise/PromiseFactoryImpl.java @@ -2,6 +2,7 @@ package dev.tommyjs.futur.promise; import dev.tommyjs.futur.executor.PromiseExecutor; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; public class PromiseFactoryImpl extends AbstractPromiseFactory { @@ -25,6 +26,21 @@ public class PromiseFactoryImpl extends AbstractPromiseFactory { return new PromiseImpl<>(); } + @Override + public @NotNull Promise resolve(T value) { + return new CompletedPromiseImpl<>(value); + } + + @Override + public @NotNull Promise start() { + return new CompletedPromiseImpl<>(); + } + + @Override + public @NotNull Promise error(@NotNull Throwable error) { + return new CompletedPromiseImpl<>(error); + } + @Override public @NotNull Logger getLogger() { return logger; @@ -40,7 +56,28 @@ public class PromiseFactoryImpl extends AbstractPromiseFactory { return asyncExecutor; } - public class PromiseImpl extends AbstractPromise { + private class PromiseImpl extends BasePromise { + + @Override + public @NotNull AbstractPromiseFactory getFactory() { + return PromiseFactoryImpl.this; + } + + } + + private class CompletedPromiseImpl extends CompletedPromise { + + public CompletedPromiseImpl(@Nullable T result) { + super(new PromiseCompletion<>(result)); + } + + public CompletedPromiseImpl(@NotNull Throwable exception) { + super(new PromiseCompletion<>(exception)); + } + + public CompletedPromiseImpl() { + super(); + } @Override public @NotNull AbstractPromiseFactory getFactory() { 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 9885398..8b65017 100644 --- a/futur-api/src/test/java/dev/tommyjs/futur/PromiseTests.java +++ b/futur-api/src/test/java/dev/tommyjs/futur/PromiseTests.java @@ -1,6 +1,7 @@ package dev.tommyjs.futur; import dev.tommyjs.futur.promise.CompletablePromise; +import dev.tommyjs.futur.promise.CompletedPromise; import dev.tommyjs.futur.promise.Promise; import dev.tommyjs.futur.promise.PromiseFactory; import org.junit.jupiter.api.Test; @@ -17,7 +18,7 @@ import java.util.stream.Stream; public final class PromiseTests { private final Logger logger = LoggerFactory.getLogger(PromiseTests.class); - private final ScheduledExecutorService executor = Executors.newScheduledThreadPool(5); + private final ScheduledExecutorService executor = Executors.newScheduledThreadPool(6); private final PromiseFactory promises = PromiseFactory.of(logger, executor); @Test @@ -229,4 +230,17 @@ public final class PromiseTests { assert res.get(3) == 6; } + @Test + public void testImmediate1() { + var promise = promises.start().thenSupply(() -> 10); + assert promise.isCompleted() && promise instanceof CompletedPromise; + } + + @Test + public void testImmediate2() { + var resolved = promises.resolve(10); + var promise = promises.start().thenCompose(_ -> resolved); + assert promise.isCompleted() && promise instanceof CompletedPromise; + } + } From e4e61c4b6c2759efe8e934b54894f28f00386f8e Mon Sep 17 00:00:00 2001 From: tommyskeff Date: Thu, 9 Jan 2025 20:21:41 +0000 Subject: [PATCH 13/16] a few more post-complete optimizations --- .../futur/promise/AbstractPromise.java | 30 ++++++++++++++----- .../futur/promise/CompletedPromise.java | 5 ++++ 2 files changed, 28 insertions(+), 7 deletions(-) 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 b6a1bc1..aa64c85 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 @@ -86,9 +86,14 @@ public abstract class AbstractPromise implements Promise { @Override public @NotNull Promise fork() { - CompletablePromise fork = getFactory().unresolved(); - PromiseUtil.propagateCompletion(this, fork); - return fork; + PromiseCompletion completion = getCompletion(); + if (completion == null) { + CompletablePromise fork = getFactory().unresolved(); + PromiseUtil.propagateCompletion(this, fork); + return fork; + } else { + return this; + } } @Override @@ -446,9 +451,20 @@ public abstract class AbstractPromise implements Promise { @Override public @NotNull Promise orDefault(@NotNull ExceptionalFunction function) { - CompletablePromise promise = createLinked(); - addDirectListener(promise::complete, e -> runCompleter(promise, () -> promise.complete(function.apply(e)))); - return promise; + PromiseCompletion completion = getCompletion(); + if (completion == null) { + CompletablePromise promise = createLinked(); + addDirectListener(promise::complete, e -> runCompleter(promise, () -> promise.complete(function.apply(e)))); + return promise; + } else if (completion.isSuccess()) { + return getFactory().resolve(completion.getResult()); + } else { + try { + return getFactory().resolve(function.apply(completion.getException())); + } catch (Exception e) { + return getFactory().error(e); + } + } } @Override @@ -457,7 +473,7 @@ public abstract class AbstractPromise implements Promise { addDirectListener(future::complete, future::completeExceptionally); future.whenComplete((_, e) -> { if (e instanceof CancellationException) { - this.cancel(); + cancel(); } }); diff --git a/futur-api/src/main/java/dev/tommyjs/futur/promise/CompletedPromise.java b/futur-api/src/main/java/dev/tommyjs/futur/promise/CompletedPromise.java index 18aaeee..ba91627 100644 --- a/futur-api/src/main/java/dev/tommyjs/futur/promise/CompletedPromise.java +++ b/futur-api/src/main/java/dev/tommyjs/futur/promise/CompletedPromise.java @@ -55,6 +55,11 @@ public abstract class CompletedPromise extends AbstractPromise fork() { + return this; + } + @Override public @NotNull PromiseCompletion getCompletion() { return completion; From 4d01a8a418eb83126ebb24076a72ccde2a979ae0 Mon Sep 17 00:00:00 2001 From: WhatCats Date: Fri, 10 Jan 2025 21:15:52 +0100 Subject: [PATCH 14/16] cleanup combine methods --- .../futur/joiner/CompletionJoiner.java | 4 +- .../futur/joiner/MappedResultJoiner.java | 4 +- .../tommyjs/futur/joiner/PromiseJoiner.java | 52 +- .../tommyjs/futur/joiner/ResultJoiner.java | 4 +- .../dev/tommyjs/futur/joiner/VoidJoiner.java | 4 +- .../futur/promise/AbstractPromiseFactory.java | 58 +- .../tommyjs/futur/promise/PromiseFactory.java | 569 ++++++++---------- .../java/dev/tommyjs/futur/PromiseTests.java | 12 + .../java/dev/tommyjs/futur/lazy/Promises.java | 549 ++++++++--------- 9 files changed, 578 insertions(+), 678 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 8284805..82f9539 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 @@ -16,11 +16,11 @@ public class CompletionJoiner extends PromiseJoiner, Void, Void, List public CompletionJoiner( @NotNull PromiseFactory factory, @NotNull Iterator> promises, - int expectedSize, boolean link + int expectedSize ) { super(factory); results = new ConcurrentResultArray<>(expectedSize); - join(promises, link); + join(promises); } @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 4fdcd65..ee64b0e 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 @@ -15,11 +15,11 @@ public class MappedResultJoiner extends PromiseJoiner>> promises, - int expectedSize, boolean link + int expectedSize ) { super(factory); this.results = new ConcurrentResultArray<>(expectedSize); - join(promises, link); + join(promises); } @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 222bc27..56b465f 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 @@ -10,49 +10,51 @@ import org.jetbrains.annotations.NotNull; import java.util.Iterator; import java.util.concurrent.atomic.AtomicInteger; -public abstract class PromiseJoiner { +public abstract class PromiseJoiner { - private final CompletablePromise joined; + private final CompletablePromise joined; protected PromiseJoiner(@NotNull PromiseFactory factory) { this.joined = factory.unresolved(); } - protected abstract K getChildKey(V value); + protected abstract Key getChildKey(T value); - protected abstract @NotNull Promise getChildPromise(V value); + protected abstract @NotNull Promise getChildPromise(T value); - protected abstract void onChildComplete(int index, K key, @NotNull PromiseCompletion completion); + protected abstract void onChildComplete(int index, Key key, @NotNull PromiseCompletion completion); - protected abstract R getResult(); + protected abstract Result getResult(); - protected void join(@NotNull Iterator promises, boolean link) { + protected void join(@NotNull Iterator promises) { AtomicInteger count = new AtomicInteger(); int i = 0; do { - V value = promises.next(); - Promise p = getChildPromise(value); + if (joined.isCompleted()) { + promises.forEachRemaining(v -> getChildPromise(v).cancel()); + return; + } - if (link) { + T value = promises.next(); + Promise p = getChildPromise(value); + if (!p.isCompleted()) { PromiseUtil.cancelOnComplete(joined, p); } - if (!joined.isCompleted()) { - count.incrementAndGet(); - K key = getChildKey(value); - int index = i++; + count.incrementAndGet(); + Key key = getChildKey(value); + int index = i++; - p.addAsyncListener(res -> { - onChildComplete(index, key, res); - if (res.isError()) { - assert res.getException() != null; - joined.completeExceptionally(res.getException()); - } else if (count.decrementAndGet() == -1) { - joined.complete(getResult()); - } - }); - } + p.addAsyncListener(res -> { + onChildComplete(index, key, res); + if (res.isError()) { + assert res.getException() != null; + joined.completeExceptionally(res.getException()); + } else if (count.decrementAndGet() == -1) { + joined.complete(getResult()); + } + }); } while (promises.hasNext()); if (count.decrementAndGet() == -1) { @@ -60,7 +62,7 @@ public abstract class PromiseJoiner { } } - public @NotNull Promise joined() { + public @NotNull Promise joined() { return joined; } 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 9cdfe6d..b4e1a2d 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 @@ -16,11 +16,11 @@ public class ResultJoiner extends PromiseJoiner, Void, T, List> public ResultJoiner( @NotNull PromiseFactory factory, @NotNull Iterator> promises, - int expectedSize, boolean link + int expectedSize ) { super(factory); this.results = new ConcurrentResultArray<>(expectedSize); - join(promises, link); + join(promises); } @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 673c2c7..56a58d5 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 @@ -9,9 +9,9 @@ import java.util.Iterator; public class VoidJoiner extends PromiseJoiner, Void, Void, Void> { - public VoidJoiner(@NotNull PromiseFactory factory, @NotNull Iterator> promises, boolean link) { + public VoidJoiner(@NotNull PromiseFactory factory, @NotNull Iterator> promises) { super(factory); - join(promises, link); + join(promises); } @Override diff --git a/futur-api/src/main/java/dev/tommyjs/futur/promise/AbstractPromiseFactory.java b/futur-api/src/main/java/dev/tommyjs/futur/promise/AbstractPromiseFactory.java index da542f0..7ce84d1 100644 --- a/futur-api/src/main/java/dev/tommyjs/futur/promise/AbstractPromiseFactory.java +++ b/futur-api/src/main/java/dev/tommyjs/futur/promise/AbstractPromiseFactory.java @@ -7,10 +7,10 @@ import dev.tommyjs.futur.joiner.ResultJoiner; import dev.tommyjs.futur.joiner.VoidJoiner; 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.CompletionStage; import java.util.concurrent.Future; @@ -23,11 +23,7 @@ public abstract class AbstractPromiseFactory implements PromiseFactory { public abstract @NotNull PromiseExecutor getAsyncExecutor(); @Override - public @NotNull Promise wrap(@NotNull CompletableFuture future) { - return wrap(future, future); - } - - private @NotNull Promise wrap(@NotNull CompletionStage completion, Future future) { + public @NotNull Promise wrap(@NotNull CompletionStage completion, @Nullable Future future) { CompletablePromise promise = unresolved(); completion.whenComplete((v, e) -> { if (e != null) { @@ -37,62 +33,72 @@ public abstract class AbstractPromiseFactory implements PromiseFactory { } }); - promise.onCancel(_ -> future.cancel(true)); + if (future != null) { + promise.onCancel(_ -> future.cancel(true)); + } + return promise; } @Override public @NotNull Promise> combine( - @NotNull Promise p1, - @NotNull Promise p2, - boolean link + @NotNull Promise p1, @NotNull Promise p2 ) { - return all(link, p1, p2).thenApply(_ -> new AbstractMap.SimpleImmutableEntry<>( + return all(p1, p2).thenApply(_ -> new AbstractMap.SimpleImmutableEntry<>( Objects.requireNonNull(p1.getCompletion()).getResult(), Objects.requireNonNull(p2.getCompletion()).getResult() )); } @Override - public @NotNull Promise> combineMapped(@NotNull Iterator>> promises, int expectedSize, boolean link) { + public @NotNull Promise> combineMapped( + @NotNull Iterator>> promises, + int expectedSize + ) { if (!promises.hasNext()) return resolve(Collections.emptyMap()); - return new MappedResultJoiner<>(this, promises, expectedSize, link).joined(); + return new MappedResultJoiner<>(this, promises, expectedSize).joined(); } @Override public @NotNull Promise> combine( @NotNull Iterator> promises, - int expectedSize, boolean link + int expectedSize ) { if (!promises.hasNext()) return resolve(Collections.emptyList()); - return new ResultJoiner<>(this, promises, expectedSize, link).joined(); + return new ResultJoiner<>(this, promises, expectedSize).joined(); } @Override public @NotNull Promise>> allSettled( @NotNull Iterator> promises, - int expectedSize, - boolean link + int expectedSize ) { if (!promises.hasNext()) return resolve(Collections.emptyList()); - return new CompletionJoiner(this, promises, expectedSize, link).joined(); + return new CompletionJoiner(this, promises, expectedSize).joined(); } @Override - public @NotNull Promise all(@NotNull Iterator> promises, boolean link) { + public @NotNull Promise all(@NotNull Iterator> promises) { if (!promises.hasNext()) return resolve(null); - return new VoidJoiner(this, promises, link).joined(); + return new VoidJoiner(this, promises).joined(); } @Override - public @NotNull Promise race(@NotNull Iterator> promises, boolean cancelLosers) { + public @NotNull Promise race( + @NotNull Iterator> promises, + boolean ignoreErrors + ) { CompletablePromise promise = unresolved(); - promises.forEachRemaining(p -> { - if (cancelLosers) PromiseUtil.cancelOnComplete(promise, p); - if (!promise.isCompleted()) { - PromiseUtil.propagateCompletion(p, promise); + while (promises.hasNext()) { + if (promise.isCompleted()) { + promises.forEachRemaining(Promise::cancel); + break; } - }); + + Promise p = promises.next(); + PromiseUtil.cancelOnComplete(promise, p); + p.addDirectListener(promise::complete, ignoreErrors ? null : promise::completeExceptionally); + } return promise; } 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 901721b..0aac0c9 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,15 +3,19 @@ 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.CompletionStage; +import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; import java.util.function.Function; import java.util.stream.Stream; import java.util.stream.StreamSupport; +@SuppressWarnings("unchecked") public interface PromiseFactory { /** @@ -81,480 +85,415 @@ public interface PromiseFactory { @NotNull Promise error(@NotNull Throwable error); /** - * Creates a new promise backed by the given future. The promise will be completed upon completion - * of the future. + * Creates a new promise backed by the given completion and future. + * The promise will be completed upon completion of the {@link CompletionStage} + * and the {@link Future} will be cancelled upon cancellation of the promise. + * + * @param completion the completion stage to wrap + * @param future the future to wrap + * @return the new promise + */ + @NotNull Promise wrap(@NotNull CompletionStage completion, @Nullable Future future); + + /** + * Creates a new promise backed by the given future. + * The promise will be completed upon completion of the {@link CompletableFuture} + * and the {@link CompletableFuture} will be cancelled upon cancellation of the promise. * * @param future the future to wrap * @return the new promise */ - @NotNull Promise wrap(@NotNull CompletableFuture future); + default @NotNull Promise wrap(@NotNull CompletableFuture future) { + return wrap(future, future); + }; /** - * Combines two promises into a single promise that completes when both promises complete. If - * {@code link} is {@code true} and either input promise completes exceptionally (including - * cancellation), the other promise will be cancelled and the output promise will complete - * exceptionally. + * Combines two promises into a single promise that resolves when both promises are completed. + * If either input promise completes exceptionally, the other promise will be cancelled + * and the output promise will complete exceptionally. * * @param p1 the first promise * @param p2 the second promise - * @param link whether to cancel the other promise on error * @return the combined promise */ - @NotNull Promise> combine(@NotNull Promise p1, @NotNull Promise p2, - boolean link); + @NotNull Promise> combine(@NotNull Promise p1, @NotNull Promise p2); + /** - * Combines two promises into a single promise that completes when both promises complete. If either - * input promise completes exceptionally, the other promise will be cancelled and the output promise - * will complete exceptionally. - * - * @param p1 the first promise - * @param p2 the second promise - * @return the combined promise - */ - default @NotNull Promise> combine(@NotNull Promise p1, @NotNull Promise p2) { - return combine(p1, p2, 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. + * Combines key-value pairs of promises into a single promise that completes + * when all promises are completed, with the results mapped by their keys. + * If any promise completes exceptionally, the other promises will be cancelled + * and the combined promise will complete exceptionally. * * @param promises the input promises - * @param link whether to cancel all promises on any exceptional completions + * @param expectedSize the expected size of the iterator (used for optimization) * @return the combined promise */ @NotNull Promise> combineMapped(@NotNull Iterator>> promises, - int expectedSize, boolean link); + int expectedSize); /** - * 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. + * Combines key-value pairs of promises into a single promise that completes + * when all promises are completed, with the results mapped by their keys. + * If any promise completes exceptionally, the other promises will be cancelled + * and the combined promise will complete exceptionally. * * @param promises the input promises * @return the combined promise */ - default @NotNull Promise> combineMapped(@NotNull Collection>> promises, - boolean link) { - return combineMapped(promises.iterator(), promises.size(), link); + default @NotNull Promise> combineMapped(@NotNull Spliterator>> promises) { + return combineMapped(Spliterators.iterator(promises), PromiseUtil.estimateSize(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 - */ - default @NotNull Promise> combineMapped(@NotNull Map> promises, - boolean link) { - return combineMapped(promises.entrySet().iterator(), promises.size(), 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 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) { - Spliterator>> spliterator = promises.spliterator(); - return combineMapped(Spliterators.iterator(spliterator), PromiseUtil.estimateSize(spliterator), 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. + * Combines key-value pairs of promises into a single promise that completes + * when all promises are completed, with the results mapped by their keys. + * If any promise completes exceptionally, the other promises will be cancelled + * and the combined promise will complete exceptionally. * * @param promises the input promises * @return the combined promise */ default @NotNull Promise> combineMapped(@NotNull Stream>> promises) { - return combineMapped(promises, true); + return combineMapped(promises.spliterator()); } /** - * 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. + * Combines key-value pairs of promises into a single promise that completes + * when all promises are completed, with the results mapped by their keys. + * If any promise completes exceptionally, the other promises will be cancelled + * and the combined 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 + * @param promises the input promises * @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); + default @NotNull Promise> combineMapped(@NotNull Iterable>> promises) { + return combineMapped(promises.spliterator()); } /** - * 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. + * Combines key-value pairs of promises into a single promise that completes + * when all promises are completed, with the results mapped by their keys. + * If any promise completes exceptionally, the other promises will be cancelled + * and the combined promise will complete exceptionally. * - * @param keys the input keys - * @param mapper the function to map keys to value promises + * @param promises the input promises + * @return the combined promise + */ + default @NotNull Promise> combineMapped(@NotNull Map.Entry>... promises) { + return combineMapped(Arrays.spliterator(promises)); + } + + /** + * Combines key-value pairs of promises into a single promise that completes + * when all promises are completed, with the results mapped by their keys. + * If any promise completes exceptionally, the other promises will be cancelled + * and the combined promise will complete exceptionally. + * + * @param promises the input promises + * @return the combined promise + */ + default @NotNull Promise> combineMapped(@NotNull Map> promises) { + return combineMapped(promises.entrySet().iterator(), promises.size()); + } + + /** + * Combines key-value pairs of promises into a single promise that completes + * when all promises are completed, with the results mapped by their keys. + * If any promise completes exceptionally, the other promises will be cancelled + * and the combined promise will complete exceptionally. + * + * @param keys the keys to map to promises + * @param mapper the function to map keys to promises + * @return the combined promise + */ + default @NotNull Promise> combineMapped(@NotNull Stream keys, + @NotNull Function> mapper) { + return combineMapped(keys.map(k -> new AbstractMap.SimpleImmutableEntry<>(k, mapper.apply(k)))); + } + + /** + * Combines key-value pairs of promises into a single promise that completes + * when all promises are completed, with the results mapped by their keys. + * If any promise completes exceptionally, the other promises will be cancelled + * and the combined promise will complete exceptionally. + * + * @param keys the keys to map to promises + * @param mapper the function to map keys to 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); + return combineMapped(StreamSupport.stream(keys.spliterator(), false), mapper); } + /** + * @deprecated Use combineMapped instead. + */ @Deprecated default @NotNull Promise> combine(@NotNull Map> promises) { return combineMapped(promises); } /** - * Combines an iterator 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. If an exception - * 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. + * Combines multiple promises into a single promise that completes when all promises + * are completed, with a list of results in the original order. + * If any promise completes exceptionally, all other promises will be cancelled + * and the combined promise will complete exceptionally. * * @param promises the input promises - * @param link whether to cancel all promises on any exceptional completions + * @param expectedSize the expected size of the iterator (used for optimization) * @return the combined promise */ - @NotNull Promise> combine(@NotNull Iterator> promises, int expectedSize, - boolean link); + @NotNull Promise> combine(@NotNull Iterator> promises, int expectedSize); /** - * 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 - * other promises will be cancelled and the output promise will complete exceptionally. + * Combines multiple promises into a single promise that completes when all promises + * are completed, with a list of results in the original order. + * If any promise completes exceptionally, all other promises will be cancelled + * and the combined 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 Collection> promises, boolean link) { - return combine(promises.iterator(), promises.size(), link); + default @NotNull Promise> combine(@NotNull Spliterator> promises) { + return combine(Spliterators.iterator(promises), PromiseUtil.estimateSize(promises)); } /** - * 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 output promise will complete exceptionally. - * - * @param promises the input promises - * @return the combined promise - */ - default @NotNull Promise> combine(@NotNull Collection> promises) { - return combine(promises, 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. If an exception - * 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 link whether to cancel all promises on any exceptional completions - * @return the combined promise - */ - default @NotNull Promise> combine(@NotNull Stream> promises, boolean link) { - Spliterator> spliterator = promises.spliterator(); - return combine(Spliterators.iterator(spliterator), PromiseUtil.estimateSize(spliterator), link); - } - - /** - * Combines a stream of promises into a single promise that completes with a list of results when all - * promises complete. The output promise will always complete successfully regardless of whether input - * promises fail. + * Combines multiple promises into a single promise that completes when all promises + * are completed, with a list of results in the original order. + * If any promise completes exceptionally, all other promises will be cancelled + * and the combined promise will complete exceptionally. * * @param promises the input promises * @return the combined promise */ default @NotNull Promise> combine(@NotNull Stream> promises) { - return combine(promises, true); + return combine(promises.spliterator()); } /** - * Combines an iterator 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. The output promise will always complete successfully regardless - * of whether input promises fail. + * Combines multiple promises into a single promise that completes when all promises + * are completed, with a list of results in the original order. + * If any promise completes exceptionally, all other promises will be cancelled + * and the combined promise will complete exceptionally. * - * @param promises the input promises - * @param expectedSize the expected size of the list (used for optimization) - * @param link whether to cancel all promises on any exceptional completions + * @param promises the input promises + * @return the combined promise + */ + default @NotNull Promise> combine(@NotNull Iterable> promises) { + return combine(promises.spliterator()); + } + + /** + * Combines multiple promises into a single promise that completes when all promises + * are completed, with a list of results in the original order. + * If any promise completes exceptionally, all other promises will be cancelled + * and the combined promise will complete exceptionally. + * + * @param promises the input promises + * @return the combined promise + */ + default @NotNull Promise> combine(@NotNull Promise... promises) { + return combine(Arrays.spliterator(promises)); + } + + /** + * Combines multiple promises into a single promise that completes when all promises + * are completed, with a list of completions in the original order. + * + * @param promises the input promises + * @param expectedSize the expected size of the iterator (used for optimization) * @return the combined promise */ @NotNull Promise>> allSettled(@NotNull Iterator> promises, - int expectedSize, boolean link); + int expectedSize); /** - * 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 - * other promises will be cancelled. The output promise will always complete successfully regardless - * of whether input promises fail. - * - * @param promises the input promises - * @param link whether to cancel all promises on any exceptional completions - * @return the combined promise - */ - default @NotNull Promise>> allSettled(@NotNull Collection> promises, - boolean link) { - return allSettled(promises.iterator(), promises.size(), 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, all other promises will be cancelled. + * Combines multiple promises into a single promise that completes when all promises + * are completed, with a list of completions in the original order. * * @param promises the input promises * @return the combined promise */ - default @NotNull Promise>> allSettled(@NotNull Collection> promises) { - return allSettled(promises.iterator(), promises.size(), true); + default @NotNull Promise>> allSettled(@NotNull Spliterator> promises) { + return allSettled(Spliterators.iterator(promises), PromiseUtil.estimateSize(promises)); } /** - * 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. The output promise will always complete successfully regardless - * of whether input promises fail. - * - * @param promises the input promises - * @param link whether to cancel all promises on any exceptional completions - * @return the combined promise - */ - default @NotNull Promise>> allSettled(@NotNull Stream> promises, - boolean link) { - Spliterator> spliterator = promises.spliterator(); - return allSettled(Spliterators.iterator(spliterator), PromiseUtil.estimateSize(spliterator), 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, all other promises will be cancelled. + * Combines multiple promises into a single promise that completes when all promises + * are completed, with a list of completions in the original order. * * @param promises the input promises * @return the combined promise */ default @NotNull Promise>> allSettled(@NotNull Stream> promises) { - return allSettled(promises, true); + return allSettled(promises.spliterator()); } /** - * Combines an array 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. The output promise will always complete successfully regardless - * of whether input promises fail. + * Combines multiple promises into a single promise that completes when all promises + * are completed, with a list of completions in the original order. * - * @param link whether to cancel all promises on any exceptional completions * @param promises the input promises * @return the combined promise */ - default @NotNull Promise>> allSettled(boolean link, - @NotNull Promise... promises) { - return allSettled(Arrays.asList(promises).iterator(), promises.length, link); + default @NotNull Promise>> allSettled(@NotNull Iterable> promises) { + return allSettled(promises.spliterator()); } /** - * Combines an array of promises into a single promise that completes with a list of results when all - * promises complete. If any promise completes exceptionally, all other promises will be cancelled. + * Combines multiple promises into a single promise that completes when all promises + * are completed, with a list of completions in the original order. * * @param promises the input promises * @return the combined promise */ default @NotNull Promise>> allSettled(@NotNull Promise... promises) { - return allSettled(Arrays.asList(promises).iterator(), promises.length, true); + return allSettled(Arrays.spliterator(promises)); } /** - * Combines an iterator of promises into a single promise that completes 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 - */ - @NotNull Promise all(@NotNull Iterator> promises, boolean link); - - /** - * Combines an iterable of promises into a single promise that completes 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 all(@NotNull Iterable> promises, boolean link) { - return all(promises.iterator(), link); - } - - /** - * Combines an iterable of promises into a single promise that completes when all promises complete. - * If any promise completes exceptionally, all other promises will be cancelled and the output - * promise will complete exceptionally. + * Combines multiple promises into a single promise that completes when all promises complete. + * If any promise completes exceptionally, all other promises will be cancelled + * and the output promise will complete exceptionally. * * @param promises the input promises * @return the combined promise */ - default @NotNull Promise all(@NotNull Iterable> promises) { - return all(promises.iterator(), true); - } + @NotNull Promise all(@NotNull Iterator> promises); /** - * Combines a stream of promises into a single promise that completes 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 all(@NotNull Stream> promises, boolean link) { - return all(promises.iterator(), link); - } - - /** - * Combines a stream of promises into a single promise that completes when all promises complete. - * If any promise completes exceptionally, all other promises will be cancelled and the output - * promise willcomplete exceptionally. + * Combines multiple promises into a single promise that completes when all promises complete. + * If any promise completes exceptionally, all other promises will be cancelled + * and the output promise will complete exceptionally. * * @param promises the input promises * @return the combined promise */ default @NotNull Promise all(@NotNull Stream> promises) { - return all(promises.iterator(), true); + return all(promises.iterator()); } /** - * Combines an array of promises into a single promise that completes when all promises complete. - * If {@code link} is {@code true} and any promise completes exceptionally, all other promises will - * be cancelled + * Combines multiple promises into a single promise that completes when all promises complete. + * If any promise completes exceptionally, all other promises will be cancelled * and the output promise will complete exceptionally. * - * @param link whether to cancel all promises on any exceptional completions * @param promises the input promises * @return the combined promise */ - default @NotNull Promise all(boolean link, @NotNull Promise... promises) { - return all(Arrays.asList(promises).iterator(), link); + default @NotNull Promise all(@NotNull Iterable> promises) { + return all(promises.iterator()); } /** - * Combines an array of promises into a single promise that completes when all promises complete. - * If any promise completes exceptionally, all other promises will be cancelled and the output - * promise will complete exceptionally. + * Combines multiple promises into a single promise that completes when all promises complete. + * If any promise completes exceptionally, all other promises will be cancelled + * and the output promise will complete exceptionally. * * @param promises the input promises * @return the combined promise */ default @NotNull Promise all(@NotNull Promise... promises) { - return all(Arrays.asList(promises).iterator(), true); + return all(Arrays.asList(promises).iterator()); } /** - * Combines an iterator of promises into a single promise that completes when the first promise - * completes (successfully or exceptionally). If {@code cancelLosers} is {@code true}, all other - * promises will be cancelled when the first promise - * completes. - * - * @param promises the input promises - * @param cancelLosers whether to cancel the other promises when the first completes - * @return the combined promise - */ - @NotNull Promise race(@NotNull Iterator> promises, boolean cancelLosers); - - /** - * Combines an iterable of promises into a single promise that completes when the first promise - * completes (successfully or exceptionally). All other promises will be cancelled when the first - * promise completes. + * Combines multiple promises into a single promise that completes when any promise is completed. + * If {@code ignoreErrors} is {@code false} and the first promise completed exceptionally, the + * combined promise will also complete exceptionally. Otherwise, the combined promise will wait for a + * successful completion or complete with {@code null} if all promises complete exceptionally. + * Additionally, if {@code cancelLosers} is {@code true}, the other promises will be cancelled + * once the combined promise is completed. * * @param promises the input promises + * @param ignoreErrors whether to ignore promises that complete exceptionally * @return the combined promise */ - default @NotNull Promise race(@NotNull Iterable> promises, boolean cancelLosers) { - return race(promises.iterator(), cancelLosers); - } + @NotNull Promise race(@NotNull Iterator> promises, boolean ignoreErrors); + /** - * Combines an iterable of promises into a single promise that completes when the first promise - * completes (successfully or exceptionally). All other promises will be cancelled when the first - * promise completes. + * Combines multiple promises into a single promise that completes when any promise is completed. + * If {@code ignoreErrors} is {@code false} and the first promise completed exceptionally, the + * combined promise will also complete exceptionally. Otherwise, the combined promise will wait for a + * successful completion or complete with {@code null} if all promises complete exceptionally. + * Additionally, The other promises will be cancelled once the combined promise is completed. * * @param promises the input promises - */ - default @NotNull Promise race(@NotNull Iterable> promises) { - return race(promises.iterator(), true); - } - - /** - * Combines a stream of promises into a single promise that completes when the first promise - * completes (successfully or exceptionally). If {@code cancelLosers} is {@code true}, all other - * promises will be cancelled when the first promise completes. - * - * @param promises the input promises - * @param cancelLosers whether to cancel the other promises when the first completes + * @param ignoreErrors whether to ignore promises that complete exceptionally * @return the combined promise */ - default @NotNull Promise race(@NotNull Stream> promises, boolean cancelLosers) { - return race(promises.iterator(), cancelLosers); + default @NotNull Promise race(@NotNull Stream> promises, boolean ignoreErrors) { + return race(promises.iterator(), ignoreErrors); } /** - * Combines a stream of promises into a single promise that completes when the first promise - * completes (successfully or exceptionally). All other promises will be cancelled when the first - * promise completes. + * Combines multiple promises into a single promise that completes when any promise is completed. + * If the first promise completed exceptionally, the combined promise will also complete exceptionally. + * Additionally, the other promises will be cancelled once the combined promise is completed. * * @param promises the input promises * @return the combined promise */ default @NotNull Promise race(@NotNull Stream> promises) { - return race(promises.iterator(), true); + return race(promises, false); + } + + /** + * Combines multiple promises into a single promise that completes when any promise is completed. + * If {@code ignoreErrors} is {@code false} and the first promise completed exceptionally, the + * combined promise will also complete exceptionally. Otherwise, the combined promise will wait for a + * successful completion or complete with {@code null} if all promises complete exceptionally. + * Additionally, The other promises will be cancelled once the combined promise is completed. + * + * @param promises the input promises + * @param ignoreErrors whether to ignore promises that complete exceptionally + * @return the combined promise + */ + default @NotNull Promise race(@NotNull Iterable> promises, boolean ignoreErrors) { + return race(promises.iterator(), ignoreErrors); + } + + /** + * Combines multiple promises into a single promise that completes when any promise is completed. + * If the first promise completed exceptionally, the combined promise will also complete exceptionally. + * Additionally, the other promises will be cancelled once the combined promise is completed. + * + * @param promises the input promises + * @return the combined promise + */ + default @NotNull Promise race(@NotNull Iterable> promises) { + return race(promises, false); + } + + /** + * Combines multiple promises into a single promise that completes when any promise is completed. + * If {@code ignoreErrors} is {@code false} and the first promise completed exceptionally, the + * combined promise will also complete exceptionally. Otherwise, the combined promise will wait for a + * successful completion or complete with {@code null} if all promises complete exceptionally. + * Additionally, The other promises will be cancelled once the combined promise is completed. + * + * @param promises the input promises + * @param ignoreErrors whether to ignore promises that complete exceptionally + * @return the combined promise + */ + default @NotNull Promise race(boolean ignoreErrors, @NotNull Promise... promises) { + return race(Arrays.asList(promises), ignoreErrors); + } + + /** + * Combines multiple promises into a single promise that completes when any promise is completed. + * If the first promise completed exceptionally, the combined promise will also complete exceptionally. + * Additionally, the other promises will be cancelled once the combined promise is completed. + * + * @param promises the input promises + * @return the combined promise + */ + default @NotNull Promise race(@NotNull Promise... promises) { + return race(false, promises); } } 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 8b65017..5f3a8ca 100644 --- a/futur-api/src/test/java/dev/tommyjs/futur/PromiseTests.java +++ b/futur-api/src/test/java/dev/tommyjs/futur/PromiseTests.java @@ -56,6 +56,18 @@ public final class PromiseTests { assert !finished.get(); } + @Test + public void testFork() throws InterruptedException { + var finished = new AtomicBoolean(); + promises.start() + .thenRunDelayedAsync(() -> finished.set(true), 50, TimeUnit.MILLISECONDS) + .fork() + .cancel(); + + Thread.sleep(100L); + assert finished.get(); + } + @Test public void testToFuture() throws InterruptedException { assert promises.resolve(true).toFuture().getNow(false); 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 85f4d28..e24fc4a 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 @@ -6,17 +6,21 @@ 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 org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Spliterator; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.Future; import java.util.function.Function; import java.util.stream.Stream; +@SuppressWarnings("unchecked") public final class Promises { private static final Logger LOGGER = LoggerFactory.getLogger(Promises.class); @@ -68,8 +72,22 @@ public final class Promises { } /** - * Creates a new promise backed by the given future. The promise will be completed upon completion - * of the future. + * Creates a new promise backed by the given completion and future. + * The promise will be completed upon completion of the {@link CompletionStage} + * and the {@link Future} will be cancelled upon cancellation of the promise. + * + * @param completion the completion stage to wrap + * @param future the future to wrap + * @return the new promise + */ + public static @NotNull Promise wrap(@NotNull CompletionStage completion, @Nullable Future future) { + return factory.wrap(completion, future); + } + + /** + * Creates a new promise backed by the given future. + * The promise will be completed upon completion of the {@link CompletableFuture} + * and the {@link CompletableFuture} will be cancelled upon cancellation of the promise. * * @param future the future to wrap * @return the new promise @@ -79,25 +97,9 @@ public final class Promises { } /** - * Combines two promises into a single promise that completes when both promises complete. If - * {@code link} is {@code true} and either input promise completes exceptionally (including - * cancellation), the other promise will be cancelled and the output promise will complete - * exceptionally. - * - * @param p1 the first promise - * @param p2 the second promise - * @param link whether to cancel the other promise on error - * @return the combined promise - */ - public static @NotNull Promise> combine(@NotNull Promise p1, @NotNull Promise p2, - boolean link) { - return factory.combine(p1, p2, link); - } - - /** - * Combines two promises into a single promise that completes when both promises complete. If either - * input promise completes exceptionally, the other promise will be cancelled and the output promise - * will complete exceptionally. + * Combines two promises into a single promise that resolves when both promises are completed. + * If either input promise completes exceptionally, the other promise will be cancelled + * and the output promise will complete exceptionally. * * @param p1 the first promise * @param p2 the second promise @@ -108,91 +110,38 @@ 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. + * Combines key-value pairs of promises into a single promise that completes + * when all promises are completed, with the results mapped by their keys. + * If any promise completes exceptionally, the other promises will be cancelled + * and the combined promise will complete exceptionally. * - * @param promises the input promises - * @param link whether to cancel all promises on any exceptional completions + * @param promises the input promises + * @param expectedSize the expected size of the iterator (used for optimization) * @return the combined promise */ public static @NotNull Promise> combineMapped(@NotNull Iterator>> promises, - int expectedSize, boolean link) { - return factory.combineMapped(promises, expectedSize, link); + int expectedSize) { + return factory.combineMapped(promises, expectedSize); } /** - * 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. + * Combines key-value pairs of promises into a single promise that completes + * when all promises are completed, with the results mapped by their keys. + * If any promise completes exceptionally, the other promises will be cancelled + * and the combined promise will complete exceptionally. * * @param promises the input promises * @return the combined promise */ - public static @NotNull Promise> combineMapped(@NotNull Collection>> 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 {@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 Map> 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 Map> promises) { + public static @NotNull Promise> combineMapped(@NotNull Spliterator>> 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. + * Combines key-value pairs of promises into a single promise that completes + * when all promises are completed, with the results mapped by their keys. + * If any promise completes exceptionally, the other promises will be cancelled + * and the combined promise will complete exceptionally. * * @param promises the input promises * @return the combined promise @@ -202,29 +151,67 @@ 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. + * Combines key-value pairs of promises into a single promise that completes + * when all promises are completed, with the results mapped by their keys. + * If any promise completes exceptionally, the other promises will be cancelled + * and the combined 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 + * @param promises the input promises * @return the combined promise */ - public static @NotNull Promise> combineMapped(@NotNull Iterable keys, - @NotNull Function> mapper, - boolean link) { - return factory.combineMapped(keys, mapper, link); + public static @NotNull Promise> combineMapped(@NotNull Iterable>> 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. + * Combines key-value pairs of promises into a single promise that completes + * when all promises are completed, with the results mapped by their keys. + * If any promise completes exceptionally, the other promises will be cancelled + * and the combined promise will complete exceptionally. * - * @param keys the input keys - * @param mapper the function to map keys to value promises + * @param promises the input promises + * @return the combined promise + */ + public static @NotNull Promise> combineMapped(@NotNull Map.Entry>... promises) { + return factory.combineMapped(promises); + } + + /** + * Combines key-value pairs of promises into a single promise that completes + * when all promises are completed, with the results mapped by their keys. + * If any promise completes exceptionally, the other promises will be cancelled + * and the combined promise will complete exceptionally. + * + * @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 promises into a single promise that completes + * when all promises are completed, with the results mapped by their keys. + * If any promise completes exceptionally, the other promises will be cancelled + * and the combined promise will complete exceptionally. + * + * @param keys the keys to map to promises + * @param mapper the function to map keys to promises + * @return the combined promise + */ + public static @NotNull Promise> combineMapped(@NotNull Stream keys, + @NotNull Function> mapper) { + return factory.combineMapped(keys, mapper); + } + + /** + * Combines key-value pairs of promises into a single promise that completes + * when all promises are completed, with the results mapped by their keys. + * If any promise completes exceptionally, the other promises will be cancelled + * and the combined promise will complete exceptionally. + * + * @param keys the keys to map to promises + * @param mapper the function to map keys to promises * @return the combined promise */ public static @NotNull Promise> combineMapped(@NotNull Iterable keys, @@ -232,75 +219,46 @@ public final class Promises { return factory.combineMapped(keys, mapper); } - @Deprecated - public static @NotNull Promise> combine(@NotNull Map> promises, boolean link) { - return factory.combine(promises, link); - } - + /** + * @deprecated Use combineMapped instead. + */ @Deprecated public static @NotNull Promise> combine(@NotNull Map> promises) { return factory.combine(promises); } /** - * Combines an iterator 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. If an exception - * 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. + * Combines multiple promises into a single promise that completes when all promises + * are completed, with a list of results in the original order. + * If any promise completes exceptionally, all other promises will be cancelled + * and the combined promise will complete exceptionally. * - * @param promises the input promises - * @param link whether to cancel all promises on any exceptional completions + * @param promises the input promises + * @param expectedSize the expected size of the iterator (used for optimization) * @return the combined promise */ - public static @NotNull Promise> combine(@NotNull Iterator> promises, int expectedSize, - boolean link) { - return factory.combine(promises, expectedSize, link); + public static @NotNull Promise> combine(@NotNull Iterator> promises, int expectedSize) { + return factory.combine(promises, expectedSize); } /** - * 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 - * 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> combine(@NotNull Collection> promises, boolean link) { - return factory.combine(promises, 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 output promise will complete exceptionally. + * Combines multiple promises into a single promise that completes when all promises + * are completed, with a list of results in the original order. + * If any promise completes exceptionally, all other promises will be cancelled + * and the combined promise will complete exceptionally. * * @param promises the input promises * @return the combined promise */ - public static @NotNull Promise> combine(@NotNull Collection> promises) { + public static @NotNull Promise> combine(@NotNull Spliterator> promises) { return factory.combine(promises); } /** - * 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. If an exception - * 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 link whether to cancel all promises on any exceptional completions - * @return the combined promise - */ - public static @NotNull Promise> combine(@NotNull Stream> promises, boolean link) { - return factory.combine(promises, link); - } - - /** - * Combines a stream of promises into a single promise that completes with a list of results when all - * promises complete. The output promise will always complete successfully regardless of whether input - * promises fail. + * Combines multiple promises into a single promise that completes when all promises + * are completed, with a list of results in the original order. + * If any promise completes exceptionally, all other promises will be cancelled + * and the combined promise will complete exceptionally. * * @param promises the input promises * @return the combined promise @@ -310,65 +268,58 @@ public final class Promises { } /** - * Combines an iterator 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. The output promise will always complete successfully regardless - * of whether input promises fail. + * Combines multiple promises into a single promise that completes when all promises + * are completed, with a list of results in the original order. + * If any promise completes exceptionally, all other promises will be cancelled + * and the combined promise will complete exceptionally. + * + * @param promises the input promises + * @return the combined promise + */ + public static @NotNull Promise> combine(@NotNull Iterable> promises) { + return factory.combine(promises); + } + + /** + * Combines multiple promises into a single promise that completes when all promises + * are completed, with a list of results in the original order. + * If any promise completes exceptionally, all other promises will be cancelled + * and the combined promise will complete exceptionally. + * + * @param promises the input promises + * @return the combined promise + */ + public static @NotNull Promise> combine(@NotNull Promise... promises) { + return factory.combine(promises); + } + + /** + * Combines multiple promises into a single promise that completes when all promises + * are completed, with a list of completions in the original order. * * @param promises the input promises - * @param expectedSize the expected size of the list (used for optimization) - * @param link whether to cancel all promises on any exceptional completions + * @param expectedSize the expected size of the iterator (used for optimization) * @return the combined promise */ public static @NotNull Promise>> allSettled(@NotNull Iterator> promises, - int expectedSize, boolean link) { - return factory.allSettled(promises, expectedSize, link); + int expectedSize) { + return factory.allSettled(promises, expectedSize); } /** - * 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 - * other promises will be cancelled. The output promise will always complete successfully regardless - * of whether input promises fail. - * - * @param promises the input promises - * @param link whether to cancel all promises on any exceptional completions - * @return the combined promise - */ - public static @NotNull Promise>> allSettled(@NotNull Collection> promises, - boolean link) { - return factory.allSettled(promises, 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, all other promises will be cancelled. + * Combines multiple promises into a single promise that completes when all promises + * are completed, with a list of completions in the original order. * * @param promises the input promises * @return the combined promise */ - public static @NotNull Promise>> allSettled(@NotNull Collection> promises) { + public static @NotNull Promise>> allSettled(@NotNull Spliterator> promises) { return factory.allSettled(promises); } /** - * 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. The output promise will always complete successfully regardless - * of whether input promises fail. - * - * @param promises the input promises - * @param link whether to cancel all promises on any exceptional completions - * @return the combined promise - */ - public static @NotNull Promise>> allSettled(@NotNull Stream> promises, - boolean link) { - return factory.allSettled(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, all other promises will be cancelled. + * Combines multiple promises into a single promise that completes when all promises + * are completed, with a list of completions in the original order. * * @param promises the input promises * @return the combined promise @@ -378,23 +329,19 @@ public final class Promises { } /** - * Combines an array 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. The output promise will always complete successfully regardless - * of whether input promises fail. + * Combines multiple promises into a single promise that completes when all promises + * are completed, with a list of completions in the original order. * - * @param link whether to cancel all promises on any exceptional completions * @param promises the input promises * @return the combined promise */ - public static @NotNull Promise>> allSettled(boolean link, - @NotNull Promise... promises) { - return factory.allSettled(link, promises); + public static @NotNull Promise>> allSettled(@NotNull Iterable> promises) { + return factory.allSettled(promises); } /** - * Combines an array of promises into a single promise that completes with a list of results when all - * promises complete. If any promise completes exceptionally, all other promises will be cancelled. + * Combines multiple promises into a single promise that completes when all promises + * are completed, with a list of completions in the original order. * * @param promises the input promises * @return the combined promise @@ -404,60 +351,21 @@ public final class Promises { } /** - * Combines an iterator of promises into a single promise that completes 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 - */ - public static @NotNull Promise all(@NotNull Iterator> promises, boolean link) { - return factory.all(promises, link); - } - - /** - * Combines an iterable of promises into a single promise that completes 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 - */ - public static @NotNull Promise all(@NotNull Iterable> promises, boolean link) { - return factory.all(promises, link); - } - - /** - * Combines an iterable of promises into a single promise that completes when all promises complete. - * If any promise completes exceptionally, all other promises will be cancelled and the output - * promise will complete exceptionally. + * Combines multiple promises into a single promise that completes when all promises complete. + * If any promise completes exceptionally, all other promises will be cancelled + * and the output promise will complete exceptionally. * * @param promises the input promises * @return the combined promise */ - public static @NotNull Promise all(@NotNull Iterable> promises) { + public static @NotNull Promise all(@NotNull Iterator> promises) { return factory.all(promises); } /** - * Combines a stream of promises into a single promise that completes 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 - */ - public static @NotNull Promise all(@NotNull Stream> promises, boolean link) { - return factory.all(promises, link); - } - - /** - * Combines a stream of promises into a single promise that completes when all promises complete. - * If any promise completes exceptionally, all other promises will be cancelled and the output - * promise willcomplete exceptionally. + * Combines multiple promises into a single promise that completes when all promises complete. + * If any promise completes exceptionally, all other promises will be cancelled + * and the output promise will complete exceptionally. * * @param promises the input promises * @return the combined promise @@ -467,23 +375,21 @@ public final class Promises { } /** - * Combines an array of promises into a single promise that completes when all promises complete. - * If {@code link} is {@code true} and any promise completes exceptionally, all other promises will - * be cancelled + * Combines multiple promises into a single promise that completes when all promises complete. + * If any promise completes exceptionally, all other promises will be cancelled * and the output promise will complete exceptionally. * - * @param link whether to cancel all promises on any exceptional completions * @param promises the input promises * @return the combined promise */ - public static @NotNull Promise all(boolean link, @NotNull Promise... promises) { - return factory.all(link, promises); + public static @NotNull Promise all(@NotNull Iterable> promises) { + return factory.all(promises); } /** - * Combines an array of promises into a single promise that completes when all promises complete. - * If any promise completes exceptionally, all other promises will be cancelled and the output - * promise will complete exceptionally. + * Combines multiple promises into a single promise that completes when all promises complete. + * If any promise completes exceptionally, all other promises will be cancelled + * and the output promise will complete exceptionally. * * @param promises the input promises * @return the combined promise @@ -493,59 +399,40 @@ public final class Promises { } /** - * Combines an iterator of promises into a single promise that completes when the first promise - * completes (successfully or exceptionally). If {@code cancelLosers} is {@code true}, all other - * promises will be cancelled when the first promise - * completes. + * Combines multiple promises into a single promise that completes when any promise is completed. + * If {@code ignoreErrors} is {@code false} and the first promise completed exceptionally, the + * combined promise will also complete exceptionally. Otherwise, the combined promise will wait for a + * successful completion or complete with {@code null} if all promises complete exceptionally. + * Additionally, if {@code cancelLosers} is {@code true}, the other promises will be cancelled + * once the combined promise is completed. * * @param promises the input promises - * @param cancelLosers whether to cancel the other promises when the first completes + * @param ignoreErrors whether to ignore promises that complete exceptionally * @return the combined promise */ - public static @NotNull Promise race(@NotNull Iterator> promises, boolean cancelLosers) { - return factory.race(promises, cancelLosers); + public static @NotNull Promise race(@NotNull Iterator> promises, boolean ignoreErrors) { + return factory.race(promises, ignoreErrors); } /** - * Combines an iterable of promises into a single promise that completes when the first promise - * completes (successfully or exceptionally). All other promises will be cancelled when the first - * promise completes. - * - * @param promises the input promises - * @return the combined promise - */ - public static @NotNull Promise race(@NotNull Iterable> promises, boolean cancelLosers) { - return factory.race(promises, cancelLosers); - } - - /** - * Combines an iterable of promises into a single promise that completes when the first promise - * completes (successfully or exceptionally). All other promises will be cancelled when the first - * promise completes. - * - * @param promises the input promises - */ - public static @NotNull Promise race(@NotNull Iterable> promises) { - return factory.race(promises); - } - - /** - * Combines a stream of promises into a single promise that completes when the first promise - * completes (successfully or exceptionally). If {@code cancelLosers} is {@code true}, all other - * promises will be cancelled when the first promise completes. + * Combines multiple promises into a single promise that completes when any promise is completed. + * If {@code ignoreErrors} is {@code false} and the first promise completed exceptionally, the + * combined promise will also complete exceptionally. Otherwise, the combined promise will wait for a + * successful completion or complete with {@code null} if all promises complete exceptionally. + * Additionally, The other promises will be cancelled once the combined promise is completed. * * @param promises the input promises - * @param cancelLosers whether to cancel the other promises when the first completes + * @param ignoreErrors whether to ignore promises that complete exceptionally * @return the combined promise */ - public static @NotNull Promise race(@NotNull Stream> promises, boolean cancelLosers) { - return factory.race(promises, cancelLosers); + public static @NotNull Promise race(@NotNull Stream> promises, boolean ignoreErrors) { + return factory.race(promises, ignoreErrors); } /** - * Combines a stream of promises into a single promise that completes when the first promise - * completes (successfully or exceptionally). All other promises will be cancelled when the first - * promise completes. + * Combines multiple promises into a single promise that completes when any promise is completed. + * If the first promise completed exceptionally, the combined promise will also complete exceptionally. + * Additionally, the other promises will be cancelled once the combined promise is completed. * * @param promises the input promises * @return the combined promise @@ -554,4 +441,58 @@ public final class Promises { return factory.race(promises); } + /** + * Combines multiple promises into a single promise that completes when any promise is completed. + * If {@code ignoreErrors} is {@code false} and the first promise completed exceptionally, the + * combined promise will also complete exceptionally. Otherwise, the combined promise will wait for a + * successful completion or complete with {@code null} if all promises complete exceptionally. + * Additionally, The other promises will be cancelled once the combined promise is completed. + * + * @param promises the input promises + * @param ignoreErrors whether to ignore promises that complete exceptionally + * @return the combined promise + */ + public static @NotNull Promise race(@NotNull Iterable> promises, boolean ignoreErrors) { + return factory.race(promises, ignoreErrors); + } + + /** + * Combines multiple promises into a single promise that completes when any promise is completed. + * If the first promise completed exceptionally, the combined promise will also complete exceptionally. + * Additionally, the other promises will be cancelled once the combined promise is completed. + * + * @param promises the input promises + * @return the combined promise + */ + public static @NotNull Promise race(@NotNull Iterable> promises) { + return factory.race(promises); + } + + /** + * Combines multiple promises into a single promise that completes when any promise is completed. + * If {@code ignoreErrors} is {@code false} and the first promise completed exceptionally, the + * combined promise will also complete exceptionally. Otherwise, the combined promise will wait for a + * successful completion or complete with {@code null} if all promises complete exceptionally. + * Additionally, The other promises will be cancelled once the combined promise is completed. + * + * @param promises the input promises + * @param ignoreErrors whether to ignore promises that complete exceptionally + * @return the combined promise + */ + public static @NotNull Promise race(boolean ignoreErrors, @NotNull Promise... promises) { + return factory.race(ignoreErrors, promises); + } + + /** + * Combines multiple promises into a single promise that completes when any promise is completed. + * If the first promise completed exceptionally, the combined promise will also complete exceptionally. + * Additionally, the other promises will be cancelled once the combined promise is completed. + * + * @param promises the input promises + * @return the combined promise + */ + public static @NotNull Promise race(@NotNull Promise... promises) { + return factory.race(promises); + } + } \ No newline at end of file From 5447e06455deb6741bd3a86ba8d03d6ee9900778 Mon Sep 17 00:00:00 2001 From: WhatCats Date: Fri, 10 Jan 2025 22:14:59 +0100 Subject: [PATCH 15/16] cleanup AbstractPromise (still messy) --- .../futur/promise/AbstractPromise.java | 220 ++++++++++-------- .../tommyjs/futur/promise/BasePromise.java | 37 ++- .../futur/promise/CompletedPromise.java | 19 +- 3 files changed, 149 insertions(+), 127 deletions(-) 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 aa64c85..149df3c 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 @@ -12,6 +12,8 @@ import org.slf4j.Logger; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; public abstract class AbstractPromise implements Promise { @@ -31,17 +33,40 @@ public abstract class AbstractPromise implements Promise { } } - protected void runCompleter(@NotNull CompletablePromise promise, @NotNull ExceptionalRunnable completer) { + protected V supplySafe(@NotNull ExceptionalSupplier supplier, @NotNull Function handler) { try { - completer.run(); - } catch (Error e) { - promise.completeExceptionally(e); - throw e; + return supplier.get(); + } catch (Error error) { + // Rethrow error so the Thread can shut down + throw error; } catch (Throwable e) { - promise.completeExceptionally(e); + return handler.apply(e); } } + protected void runSafe(@NotNull ExceptionalRunnable runnable, @NotNull Consumer handler) { + try { + runnable.run(); + } catch (Error error) { + handler.accept(error); + // Rethrow error so the Thread can shut down + throw error; + } catch (Throwable e) { + handler.accept(e); + } + } + + protected void runCompleter(@NotNull CompletablePromise promise, @NotNull ExceptionalRunnable completer) { + runSafe(completer, promise::completeExceptionally); + } + + protected V useCompletion(Supplier unresolved, Function completed, Function failed) { + PromiseCompletion completion = getCompletion(); + if (completion == null) return unresolved.get(); + else if (completion.isSuccess()) return completed.apply(completion.getResult()); + else return failed.apply(completion.getException()); + } + protected @NotNull Runnable createCompleter(T result, @NotNull CompletablePromise promise, @NotNull ExceptionalFunction completer) { return () -> { @@ -67,14 +92,7 @@ public abstract class AbstractPromise implements Promise { } protected void callListenerNow(PromiseListener listener, PromiseCompletion res) { - try { - listener.handle(res); - } catch (Error e) { - getLogger().error("Error caught in promise listener", e); - throw e; - } catch (Throwable e) { - getLogger().error("Exception caught in promise listener", e); - } + runSafe(() -> listener.handle(res), e -> getLogger().error("Exception caught in promise listener", e)); } protected void callListenerAsyncLastResort(PromiseListener listener, PromiseCompletion completion) { @@ -84,16 +102,27 @@ public abstract class AbstractPromise implements Promise { } } + protected T joinCompletionChecked() throws ExecutionException { + PromiseCompletion completion = getCompletion(); + assert completion != null; + if (completion.isSuccess()) return completion.getResult(); + throw new ExecutionException(completion.getException()); + } + + protected T joinCompletionUnchecked() { + PromiseCompletion completion = getCompletion(); + assert completion != null; + if (completion.isSuccess()) return completion.getResult(); + throw new CompletionException(completion.getException()); + } + @Override public @NotNull Promise fork() { - PromiseCompletion completion = getCompletion(); - if (completion == null) { - CompletablePromise fork = getFactory().unresolved(); - PromiseUtil.propagateCompletion(this, fork); - return fork; - } else { - return this; - } + if (isCompleted()) return this; + + CompletablePromise fork = getFactory().unresolved(); + PromiseUtil.propagateCompletion(this, fork); + return fork; } @Override @@ -119,68 +148,61 @@ public abstract class AbstractPromise implements Promise { @Override public @NotNull Promise thenApply(@NotNull ExceptionalFunction task) { - PromiseCompletion completion = getCompletion(); - if (completion == null) { - CompletablePromise promise = createLinked(); - addDirectListener( - res -> createCompleter(res, promise, task).run(), - promise::completeExceptionally - ); + return useCompletion( + () -> { + CompletablePromise promise = createLinked(); + addDirectListener( + res -> createCompleter(res, promise, task).run(), + promise::completeExceptionally + ); - return promise; - } else if (completion.isSuccess()) { - try { - V result = task.apply(completion.getResult()); - return getFactory().resolve(result); - } catch (Exception e) { - return getFactory().error(e); - } - } else { - Throwable ex = completion.getException(); - assert ex != null; - return getFactory().error(ex); - } + return promise; + }, + result -> supplySafe( + () -> getFactory().resolve(task.apply(result)), + getFactory()::error + ), + getFactory()::error + ); } @Override public @NotNull Promise thenCompose(@NotNull ExceptionalFunction> task) { - PromiseCompletion completion = getCompletion(); - if (completion == null) { - CompletablePromise promise = createLinked(); - thenApply(task).addDirectListener( - result -> { - if (result == null) { - promise.complete(null); + return useCompletion( + () -> { + CompletablePromise promise = createLinked(); + thenApply(task).addDirectListener( + result -> { + if (result == null) { + promise.complete(null); + } else { + PromiseUtil.propagateCompletion(result, promise); + PromiseUtil.propagateCancel(promise, result); + } + }, + promise::completeExceptionally + ); + + return promise; + }, + result -> supplySafe( + () -> { + Promise nested = task.apply(result); + if (nested == null) { + return getFactory().resolve(null); + } else if (nested.isCompleted()) { + return nested; } else { - PromiseUtil.propagateCompletion(result, promise); - PromiseUtil.propagateCancel(promise, result); + CompletablePromise promise = createLinked(); + PromiseUtil.propagateCompletion(nested, promise); + PromiseUtil.propagateCancel(promise, nested); + return promise; } }, - promise::completeExceptionally - ); - - return promise; - } else if (completion.isSuccess()) { - try { - Promise result = task.apply(completion.getResult()); - if (result == null) { - return getFactory().resolve(null); - } else if (result.isCompleted()) { - return result; - } else { - CompletablePromise promise = createLinked(); - PromiseUtil.propagateCompletion(result, promise); - PromiseUtil.propagateCancel(promise, result); - return promise; - } - } catch (Exception e) { - return getFactory().error(e); - } - } else { - Throwable ex = completion.getException(); - assert ex != null; - return getFactory().error(ex); - } + getFactory()::error + ), + getFactory()::error + ); } @Override @@ -451,36 +473,38 @@ public abstract class AbstractPromise implements Promise { @Override public @NotNull Promise orDefault(@NotNull ExceptionalFunction function) { - PromiseCompletion completion = getCompletion(); - if (completion == null) { - CompletablePromise promise = createLinked(); - addDirectListener(promise::complete, e -> runCompleter(promise, () -> promise.complete(function.apply(e)))); - return promise; - } else if (completion.isSuccess()) { - return getFactory().resolve(completion.getResult()); - } else { - try { - return getFactory().resolve(function.apply(completion.getException())); - } catch (Exception e) { - return getFactory().error(e); - } - } + return useCompletion( + () -> { + CompletablePromise promise = createLinked(); + addDirectListener(promise::complete, e -> runCompleter(promise, () -> promise.complete(function.apply(e)))); + return promise; + }, + getFactory()::resolve, + getFactory()::error + ); } @Override public @NotNull CompletableFuture toFuture() { - CompletableFuture future = new CompletableFuture<>(); - addDirectListener(future::complete, future::completeExceptionally); - future.whenComplete((_, e) -> { - if (e instanceof CancellationException) { - cancel(); - } - }); + return useCompletion( + () -> { + CompletableFuture future = new CompletableFuture<>(); + addDirectListener(future::complete, future::completeExceptionally); + future.whenComplete((_, e) -> { + if (e instanceof CancellationException) { + cancel(); + } + }); - return future; + return future; + }, + CompletableFuture::completedFuture, + CompletableFuture::failedFuture + ); } private static class DeferredExecutionException extends ExecutionException { + } } diff --git a/futur-api/src/main/java/dev/tommyjs/futur/promise/BasePromise.java b/futur-api/src/main/java/dev/tommyjs/futur/promise/BasePromise.java index f2a1a67..45c8c48 100644 --- a/futur-api/src/main/java/dev/tommyjs/futur/promise/BasePromise.java +++ b/futur-api/src/main/java/dev/tommyjs/futur/promise/BasePromise.java @@ -42,12 +42,6 @@ public abstract class BasePromise extends AbstractPromise this.listeners = Collections.EMPTY_LIST; } - protected T joinCompletion() throws ExecutionException { - PromiseCompletion completion = Objects.requireNonNull(getCompletion()); - if (completion.isSuccess()) return completion.getResult(); - throw new ExecutionException(completion.getException()); - } - protected void handleCompletion(@NotNull PromiseCompletion cmp) { if (!COMPLETION_HANDLE.compareAndSet(this, null, cmp)) return; sync.releaseShared(1); @@ -99,31 +93,36 @@ public abstract class BasePromise extends AbstractPromise @Override public T get() throws InterruptedException, ExecutionException { - sync.acquireSharedInterruptibly(1); - return joinCompletion(); + if (!isCompleted()) { + sync.acquireSharedInterruptibly(1); + } + + return joinCompletionChecked(); } @Override public T get(long time, @NotNull TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { - boolean success = sync.tryAcquireSharedNanos(1, unit.toNanos(time)); - if (!success) { - throw new TimeoutException("Promise stopped waiting after " + time + " " + unit); + if (!isCompleted()) { + boolean success = sync.tryAcquireSharedNanos(1, unit.toNanos(time)); + if (!success) { + throw new TimeoutException("Promise stopped waiting after " + time + " " + unit); + } } - return joinCompletion(); + return joinCompletionChecked(); } @Override public T await() { - try { - sync.acquireSharedInterruptibly(1); - } catch (InterruptedException e) { - throw new RuntimeException(e); + if (!isCompleted()) { + try { + sync.acquireSharedInterruptibly(1); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } } - PromiseCompletion completion = Objects.requireNonNull(getCompletion()); - if (completion.isSuccess()) return completion.getResult(); - throw new CompletionException(completion.getException()); + return joinCompletionUnchecked(); } @Override diff --git a/futur-api/src/main/java/dev/tommyjs/futur/promise/CompletedPromise.java b/futur-api/src/main/java/dev/tommyjs/futur/promise/CompletedPromise.java index ba91627..d13fa2c 100644 --- a/futur-api/src/main/java/dev/tommyjs/futur/promise/CompletedPromise.java +++ b/futur-api/src/main/java/dev/tommyjs/futur/promise/CompletedPromise.java @@ -3,6 +3,7 @@ package dev.tommyjs.futur.promise; import org.jetbrains.annotations.NotNull; import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; public abstract class CompletedPromise extends AbstractPromise { @@ -28,36 +29,34 @@ public abstract class CompletedPromise extends AbstractPromise timeout(long time, @NotNull TimeUnit unit) { + // Promise is already completed so can't time out return this; } @Override public @NotNull Promise maxWaitTime(long time, @NotNull TimeUnit unit) { + // Promise is already completed so can't time out return this; } @Override public void cancel(@NotNull CancellationException exception) { + // Promise is already completed so can't be cancelled } @Override - public T get() { - return null; + public T get() throws ExecutionException { + return joinCompletionChecked(); } @Override - public T get(long timeout, @NotNull TimeUnit unit) { - return null; + public T get(long timeout, @NotNull TimeUnit unit) throws ExecutionException { + return joinCompletionChecked(); } @Override public T await() { - return null; - } - - @Override - public @NotNull Promise fork() { - return this; + return joinCompletionUnchecked(); } @Override From 5b2843acd3f02a133d02153de6dd1ac08f6b4119 Mon Sep 17 00:00:00 2001 From: tommyskeff Date: Fri, 10 Jan 2025 21:39:57 +0000 Subject: [PATCH 16/16] add a few extra doc headers --- .../main/java/dev/tommyjs/futur/executor/PromiseExecutor.java | 3 +++ .../main/java/dev/tommyjs/futur/promise/PromiseFactory.java | 3 +++ 2 files changed, 6 insertions(+) diff --git a/futur-api/src/main/java/dev/tommyjs/futur/executor/PromiseExecutor.java b/futur-api/src/main/java/dev/tommyjs/futur/executor/PromiseExecutor.java index a66d07f..1143ec2 100644 --- a/futur-api/src/main/java/dev/tommyjs/futur/executor/PromiseExecutor.java +++ b/futur-api/src/main/java/dev/tommyjs/futur/executor/PromiseExecutor.java @@ -6,6 +6,9 @@ import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; +/** + * An executor that can run tasks and schedule tasks to run in the future. + */ public interface PromiseExecutor { /** 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 0aac0c9..a651855 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 @@ -15,6 +15,9 @@ import java.util.function.Function; import java.util.stream.Stream; import java.util.stream.StreamSupport; +/** + * A factory for creating and combining promises. + */ @SuppressWarnings("unchecked") public interface PromiseFactory {