com.github.tonivade.purefun.concurrent.Promise Maven / Gradle / Ivy
/*
* Copyright (c) 2018-2020, Antonio Gabriel Muñoz Conejo
* Distributed under the terms of the MIT License
*/
package com.github.tonivade.purefun.concurrent;
import com.github.tonivade.purefun.Consumer1;
import com.github.tonivade.purefun.type.Option;
import com.github.tonivade.purefun.type.Try;
import java.time.Duration;
import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicReference;
import static java.util.Objects.isNull;
import static java.util.Objects.requireNonNull;
public interface Promise {
boolean tryComplete(Try value);
default Promise complete(Try value) {
if (tryComplete(value)) {
return this;
} else throw new IllegalStateException("promise already completed");
}
default Promise succeeded(T value) {
return complete(Try.success(value));
}
default Promise failed(Throwable error) {
return complete(Try.failure(error));
}
Promise onComplete(Consumer1> consumer);
default Promise onSuccess(Consumer1 consumer) {
return onComplete(value -> value.onSuccess(consumer));
}
default Promise onFailure(Consumer1 consumer) {
return onComplete(value -> value.onFailure(consumer));
}
Try get();
Try get(Duration timeout);
boolean isCompleted();
static Promise make() {
return make(Future.DEFAULT_EXECUTOR);
}
static Promise make(Executor executor) {
return new PromiseImpl<>(executor);
}
static Promise from(CompletableFuture future) {
return from(Future.DEFAULT_EXECUTOR, future);
}
static Promise from(Executor executor, CompletableFuture future) {
Promise promise = make(executor);
future.whenCompleteAsync((unit, error) -> {
if (unit != null) {
promise.tryComplete(Try.success(unit));
} else {
promise.tryComplete(Try.failure(error));
}
}, executor);
return promise;
}
}
final class PromiseImpl implements Promise {
private final State state = new State();
private final Queue>> consumers = new LinkedList<>();
private final AtomicReference> reference = new AtomicReference<>();
private final Executor executor;
PromiseImpl(Executor executor) {
this.executor = requireNonNull(executor);
}
@Override
public boolean tryComplete(Try value) {
if (isEmpty()) {
synchronized (state) {
if (isEmpty()) {
state.completed = true;
setValue(value);
state.notifyAll();
return true;
}
}
}
return false;
}
@Override
public Try get() {
if (isEmpty()) {
synchronized (state) {
if (isEmpty()) {
try {
state.wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return Try.failure(e);
}
}
}
}
return requireNonNull(reference.get());
}
@Override
public Try get(Duration timeout) {
if (isEmpty()) {
synchronized (state) {
if (isEmpty()) {
try {
state.wait(timeout.toMillis());
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return Try.failure(e);
}
}
}
}
return Option.of(reference::get).getOrElse(Try.failure(new TimeoutException()));
}
@Override
public boolean isCompleted() {
synchronized (state) {
return state.completed;
}
}
@Override
public Promise onComplete(Consumer1> consumer) {
current(consumer).ifPresent(consumer);
return this;
}
private Option> current(Consumer1> consumer) {
Try current = reference.get();
if (isNull(current)) {
synchronized (state) {
current = reference.get();
if (isNull(current)) {
consumers.add(consumer);
}
}
}
return Option.of(current);
}
private void setValue(Try value) {
reference.set(value);
consumers.forEach(consumer -> submit(value, consumer));
}
private void submit(Try value, Consumer1> consumer) {
executor.execute(() -> consumer.accept(value));
}
private boolean isEmpty() {
return isNull(reference.get());
}
private static final class State {
private boolean completed = false;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy