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 ccebba7..a4b76b9 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ 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'