spullara.util.concurrent.Promise Maven / Gradle / Ivy
The newest version!
package spullara.util.concurrent;
import spullara.util.functions.Block;
import spullara.util.functions.Blocks;
import spullara.util.functions.Mapper;
import spullara.util.functions.Pair;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
/**
* You can use a Promise like an asychronous callback or you can block
* on it like you would a Future.
*
* Loosely based on: http://twitter.github.com/scala_school/finagle.html
*/
public class Promise implements SettableFuture {
public Promise() {
}
/**
* Create a Promise already satisfied with a successful value.
*
* @param t The value of the Promise.
*/
public Promise(T t) {
set(t);
}
public Promise(Throwable t) {
setException(t);
}
/**
* This semaphore guards whether or not a Promise has already been set.
*/
private final Semaphore set = new Semaphore(1);
/**
* This latch is counted down when the Promise can be read.
*/
private final CountDownLatch read = new CountDownLatch(1);
/**
* Cancelled
*/
private final AtomicBoolean cancelled = new AtomicBoolean(false);
/**
* The value of a successful Promise.
*/
private T value;
/**
* The throwable of a failed Promise.
*/
private Throwable throwable;
/**
* Successful
*/
private volatile boolean successful = false;
/**
* Satisfy the Promise with a successful value. This executes all the Blocks associated
* with "success" in the order they were added to the Promise. We synchronize against
* another thread adding more success Blocks.
*/
public void set(T value) {
if (set.tryAcquire()) {
successful = true;
this.value = value;
read.countDown();
Block localSuccess = null;
synchronized (this) {
if (success != null) {
localSuccess = success;
success = null;
}
}
if (localSuccess != null) {
localSuccess.apply(value);
}
}
}
/**
* Satisfy the Promise with a failure throwable. This executes all the Blocks associated
* with "failure" in the order they were added to the Promise. We synchronize against
* another thread adding more failure Blocks.
*/
public void setException(Throwable throwable) {
if (set.tryAcquire()) {
if (throwable == null) {
throw new NullPointerException("Throwable cannot be null");
}
this.throwable = throwable;
read.countDown();
Block localFailed = null;
synchronized (this) {
if (failed != null) {
localFailed = failed;
failed = null;
}
}
if (localFailed != null) {
localFailed.apply(throwable);
}
}
}
private Set linked;
private Block raise;
private Block failed;
/**
* Block that is executed when this Promise succeeds.
*/
private Block success;
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
if (cancelled.compareAndSet(false, true)) {
CancellationException cancel = new CancellationException();
raise(cancel);
setException(cancel);
return true;
}
return false;
}
@Override
public boolean isCancelled() {
return cancelled.get();
}
@Override
public boolean isDone() {
return set.availablePermits() == 0;
}
@Override
public void done() {
}
/**
* Wait until the Promise is satisfied. If it was successful, return the
* value. If it fails, throw an ExecutionException with the failure throwable
* as the cause. It can be interrupted.
*/
public T get() throws InterruptedException, ExecutionException {
read.await();
if (throwable == null) {
return value;
} else {
throw new ExecutionException(throwable);
}
}
/**
* Wait until the Promise is satisfied or timeout. If it was successful, return
* the value. If it fails, throw an ExecutionException with the failure throwable
* as the cause. Otherwise, throw a TimeoutException. It can be interrupted.
*/
public T get(long timeout, TimeUnit timeUnit) throws InterruptedException, ExecutionException, TimeoutException {
if (read.await(timeout, timeUnit)) {
if (throwable == null) {
return value;
} else {
throw new ExecutionException(throwable);
}
} else {
throw new TimeoutException();
}
}
/**
* This adds an additional Block that is executed when success occurs if the Promise
* is not yet satisfied. If it was satisifed successfully we immediately call the Block
* with the resulting value.
*/
private void addSuccess(Block block) {
synchronized (this) {
if (read.getCount() == 0) {
if (successful) {
block.apply(value);
}
} else if (success == null) {
success = block;
} else {
success = Blocks.chain(success, block);
}
}
}
/**
* This adds an additional Block that is executed when failure occurs if the Promise
* is not yet satisfied. If it has already failed we immediately call the Block with
* the resulting throwable.
*/
private void addFailure(Block block) {
synchronized (this) {
if (read.getCount() == 0) {
if (!successful) {
block.apply(throwable);
}
} else if (failed == null) {
failed = block;
} else {
failed = Blocks.chain(failed, block);
}
}
}
/**
* Return a Promise that holds the resulting Mapper type. When the underlying Promise completes
* successfully, map the value using the mapper and satisfied the returned Promise. If the
* underlying Promise fails, the return promise fails.
*/
public Promise map(final Mapper mapper) {
final Promise promise = new Promise();
promise.link(this);
addSuccess(new Block() {
public void apply(T value) {
promise.set(mapper.map(value));
}
});
addFailure(new Block() {
public void apply(Throwable throwable) {
promise.setException(throwable);
}
});
return promise;
}
private void link(Promise promise) {
synchronized (this) {
if (linked == null) {
linked = new HashSet();
}
linked.add(promise);
}
}
public Promise flatMap(final Mapper> mapper) {
final Promise promise = new Promise();
promise.link(this);
addSuccess(new Block() {
public void apply(T value) {
Promise mapped = mapper.map(value);
mapped.addSuccess(new Block() {
public void apply(V v) {
promise.set(v);
}
});
mapped.addFailure(new Block() {
public void apply(Throwable throwable) {
promise.setException(throwable);
}
});
}
});
addFailure(new Block() {
public void apply(Throwable throwable) {
promise.setException(throwable);
}
});
return promise;
}
public Promise> join(Promise promiseB) {
final Promise> promise = new Promise>();
promise.link(this);
promise.link(promiseB);
final AtomicReference ref = new AtomicReference();
addSuccess(new Block() {
@Override
public void apply(T value) {
if (!ref.weakCompareAndSet(null, value)) {
promise.set(new Pair(value, (B) ref.get()));
}
}
});
promiseB.addSuccess(new Block() {
@Override
public void apply(B value) {
if (!ref.weakCompareAndSet(null, value)) {
promise.set(new Pair((T) ref.get(), value));
}
}
});
addFailure(new Block() {
@Override
public void apply(Throwable throwable) {
promise.setException(throwable);
}
});
promiseB.addFailure(new Block() {
@Override
public void apply(Throwable throwable) {
promise.setException(throwable);
}
});
return promise;
}
public Promise select(Promise promiseB) {
final Promise promise = new Promise();
promise.link(this);
promise.link(promiseB);
final AtomicBoolean done = new AtomicBoolean();
final AtomicBoolean failed = new AtomicBoolean();
Block success = new Block() {
@Override
public void apply(T t) {
if (done.compareAndSet(false, true)) {
promise.set(t);
}
}
};
Block fail = new Block() {
@Override
public void apply(Throwable t) {
if (!failed.compareAndSet(false, true)) {
promise.setException(t);
}
}
};
addSuccess(success);
promiseB.addSuccess(success);
addFailure(fail);
promiseB.addFailure(fail);
return promise;
}
public void foreach(Block block) {
addSuccess(block);
}
/**
* If and only if the Promise succeeds, execute this Block. If this method is called
* after the Promise has already succeeded the Block is executed immediately.
*/
public Promise onSuccess(Block block) {
addSuccess(block);
return this;
}
/**
* If and only if the Promise fails, execute this Block. If this method is called
* after the Promise has already failed the Block is executed immediately.
*/
public Promise onFailure(Block block) {
addFailure(block);
return this;
}
public Promise onRaise(Block block) {
synchronized (this) {
if (raise == null) {
raise = block;
} else {
raise = Blocks.chain(raise, block);
}
}
return this;
}
public Promise ensure(final Runnable runnable) {
addSuccess(new Block() {
public void apply(T t) {
runnable.run();
}
});
addFailure(new Block() {
public void apply(Throwable throwable) {
runnable.run();
}
});
return this;
}
public Promise rescue(final Mapper mapper) {
final Promise promise = new Promise();
promise.link(this);
addSuccess(new Block() {
public void apply(T t) {
promise.set(t);
}
});
addFailure(new Block() {
public void apply(Throwable throwable) {
promise.set(mapper.map(throwable));
}
});
return promise;
}
public void raise(Throwable e) {
synchronized (this) {
if (set.availablePermits() == 1) {
if (raise != null) {
raise.apply(e);
}
}
if (linked != null) {
for (Promise promise : linked) {
promise.raise(e);
}
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy