org.osgi.util.promise.PromiseImpl Maven / Gradle / Ivy
Show all versions of biz.aQute.bndlib Show documentation
/*
* Copyright (c) OSGi Alliance (2014). All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.osgi.util.promise;
import java.lang.reflect.InvocationTargetException;
import java.util.NoSuchElementException;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CountDownLatch;
import org.osgi.util.function.Function;
import org.osgi.util.function.Predicate;
/**
* Promise implementation.
*
*
* This class is not used directly by clients. Clients should use
* {@link Deferred} to create a resolvable {@link Promise}.
*
* @param The result type associated with the Promise.
*
* @ThreadSafe
* @author $Id: ca2be5e80ac61cca849ae7373d42bde4fade7466 $
*/
final class PromiseImpl implements Promise {
/**
* A ConcurrentLinkedQueue to hold the callbacks for this Promise, so no
* additional synchronization is required to write to or read from the
* queue.
*/
private final ConcurrentLinkedQueue callbacks;
/**
* A CountDownLatch to manage the resolved state of this Promise.
*
*
* This object is used as the synchronizing object to provide a critical
* section in {@link #resolve(Object, Throwable)} so that only a single
* thread can write the resolved state variables and open the latch.
*
*
* The resolved state variables, {@link #value} and {@link #fail}, must only
* be written when the latch is closed (getCount() != 0) and must only be
* read when the latch is open (getCount() == 0). The latch state must
* always be checked before writing or reading since the resolved state
* variables' memory consistency is guarded by the latch.
*/
private final CountDownLatch resolved;
/**
* The value of this Promise if successfully resolved.
*
* @GuardedBy("resolved")
* @see #resolved
*/
private T value;
/**
* The failure of this Promise if resolved with a failure or {@code null} if
* successfully resolved.
*
* @GuardedBy("resolved")
* @see #resolved
*/
private Throwable fail;
/**
* Initialize this Promise.
*/
PromiseImpl() {
callbacks = new ConcurrentLinkedQueue();
resolved = new CountDownLatch(1);
}
/**
* Initialize and resolve this Promise.
*
* @param v The value of this resolved Promise.
* @param f The failure of this resolved Promise.
*/
PromiseImpl(T v, Throwable f) {
value = v;
fail = f;
callbacks = new ConcurrentLinkedQueue();
resolved = new CountDownLatch(0);
}
/**
* Resolve this Promise.
*
* @param v The value of this Promise.
* @param f The failure of this Promise.
*/
void resolve(T v, Throwable f) {
// critical section: only one resolver at a time
synchronized (resolved) {
if (resolved.getCount() == 0) {
throw new IllegalStateException("Already resolved");
}
/*
* The resolved state variables must be set before opening the
* latch. This safely publishes them to be read by other threads
* that must verify the latch is open before reading.
*/
value = v;
fail = f;
resolved.countDown();
}
notifyCallbacks(); // call any registered callbacks
}
/**
* Call any registered callbacks if this Promise is resolved.
*/
private void notifyCallbacks() {
if (resolved.getCount() != 0) {
return; // return if not resolved
}
/*
* Note: multiple threads can be in this method removing callbacks from
* the queue and calling them, so the order in which callbacks are
* called cannot be specified.
*/
for (Runnable callback = callbacks.poll(); callback != null; callback = callbacks.poll()) {
try {
callback.run();
} catch (Throwable t) {
Logger.logCallbackException(t);
}
}
}
/**
* {@inheritDoc}
*/
public boolean isDone() {
return resolved.getCount() == 0;
}
/**
* {@inheritDoc}
*/
public T getValue() throws InvocationTargetException, InterruptedException {
resolved.await();
if (fail == null) {
return value;
}
throw new InvocationTargetException(fail);
}
/**
* {@inheritDoc}
*/
public Throwable getFailure() throws InterruptedException {
resolved.await();
return fail;
}
/**
* {@inheritDoc}
*/
public Promise onResolve(Runnable callback) {
callbacks.offer(callback);
notifyCallbacks(); // call any registered callbacks
return this;
}
/**
* {@inheritDoc}
*/
public Promise then(Success super T, ? extends R> success, Failure failure) {
PromiseImpl chained = new PromiseImpl();
onResolve(new Then(chained, success, failure));
return chained;
}
/**
* {@inheritDoc}
*/
public Promise then(Success super T, ? extends R> success) {
return then(success, null);
}
/**
* A callback used to chain promises for the {@link #then(Success, Failure)}
* method.
*
* @Immutable
*/
private final class Then implements Runnable {
private final PromiseImpl chained;
private final Success success;
private final Failure failure;
@SuppressWarnings("unchecked")
Then(PromiseImpl chained, Success super T, ? extends R> success, Failure failure) {
this.chained = chained;
this.success = (Success) success;
this.failure = failure;
}
public void run() {
Throwable f;
final boolean interrupted = Thread.interrupted();
try {
f = getFailure();
} catch (Throwable e) {
f = e; // propagate new exception
} finally {
if (interrupted) { // restore interrupt status
Thread.currentThread().interrupt();
}
}
if (f != null) {
if (failure != null) {
try {
failure.fail(PromiseImpl.this);
} catch (Throwable e) {
f = e; // propagate new exception
}
}
// fail chained
chained.resolve(null, f);
return;
}
Promise extends R> returned = null;
if (success != null) {
try {
returned = success.call(PromiseImpl.this);
} catch (Throwable e) {
chained.resolve(null, e);
return;
}
}
if (returned == null) {
// resolve chained with null value
chained.resolve(null, null);
} else {
// resolve chained when returned promise is resolved
returned.onResolve(new Chain(chained, returned));
}
}
}
/**
* A callback used to resolve the chained Promise when the Promise promise
* is resolved.
*
* @Immutable
*/
private final static class Chain implements Runnable {
private final PromiseImpl chained;
private final Promise extends R> promise;
private final Throwable failure;
Chain(PromiseImpl chained, Promise extends R> promise) {
this.chained = chained;
this.promise = promise;
this.failure = null;
}
Chain(PromiseImpl chained, Promise extends R> promise, Throwable failure) {
this.chained = chained;
this.promise = promise;
this.failure = failure;
}
public void run() {
R value = null;
Throwable f;
final boolean interrupted = Thread.interrupted();
try {
f = promise.getFailure();
if (f == null) {
value = promise.getValue();
} else if (failure != null) {
f = failure;
}
} catch (Throwable e) {
f = e; // propagate new exception
} finally {
if (interrupted) { // restore interrupt status
Thread.currentThread().interrupt();
}
}
chained.resolve(value, f);
}
}
/**
* Resolve this Promise with the specified Promise.
*
*
* If the specified Promise is successfully resolved, this Promise is
* resolved with the value of the specified Promise. If the specified
* Promise is resolved with a failure, this Promise is resolved with the
* failure of the specified Promise.
*
* @param with A Promise whose value or failure must be used to resolve this
* Promise. Must not be {@code null}.
* @return A Promise that is resolved only when this Promise is resolved by
* the specified Promise. The returned Promise must be successfully
* resolved with the value {@code null}, if this Promise was
* resolved by the specified Promise. The returned Promise must be
* resolved with a failure of {@link IllegalStateException}, if this
* Promise was already resolved when the specified Promise was
* resolved.
*/
Promise resolveWith(Promise extends T> with) {
PromiseImpl chained = new PromiseImpl();
ResolveWith resolveWith = new ResolveWith(chained);
with.then(resolveWith, resolveWith);
return chained;
}
/**
* A callback used to resolve this Promise with another Promise for the
* {@link PromiseImpl#resolveWith(Promise)} method.
*
* @Immutable
*/
private final class ResolveWith implements Success, Failure {
private final PromiseImpl chained;
ResolveWith(PromiseImpl chained) {
this.chained = chained;
}
public Promise call(Promise with) throws Exception {
try {
resolve(with.getValue(), null);
} catch (Throwable e) {
chained.resolve(null, e);
return null;
}
chained.resolve(null, null);
return null;
}
public void fail(Promise> with) throws Exception {
try {
resolve(null, with.getFailure());
} catch (Throwable e) {
chained.resolve(null, e);
return;
}
chained.resolve(null, null);
}
}
/**
* {@inheritDoc}
*/
public Promise filter(Predicate super T> predicate) {
return then(new Filter(predicate));
}
/**
* A callback used by the {@link PromiseImpl#filter(Predicate)} method.
*
* @Immutable
*/
private static final class Filter implements Success {
private final Predicate super T> predicate;
Filter(Predicate super T> predicate) {
this.predicate = requireNonNull(predicate);
}
public Promise call(Promise resolved) throws Exception {
if (predicate.test(resolved.getValue())) {
return resolved;
}
throw new NoSuchElementException();
}
}
/**
* {@inheritDoc}
*/
public Promise map(Function super T, ? extends R> mapper) {
return then(new Map(mapper));
}
/**
* A callback used by the {@link PromiseImpl#map(Function)} method.
*
* @Immutable
*/
private static final class Map implements Success {
private final Function super T, ? extends R> mapper;
Map(Function super T, ? extends R> mapper) {
this.mapper = requireNonNull(mapper);
}
public Promise call(Promise resolved) throws Exception {
return new PromiseImpl(mapper.apply(resolved.getValue()), null);
}
}
/**
* {@inheritDoc}
*/
public Promise flatMap(Function super T, Promise extends R>> mapper) {
return then(new FlatMap(mapper));
}
/**
* A callback used by the {@link PromiseImpl#flatMap(Function)} method.
*
* @Immutable
*/
private static final class FlatMap implements Success {
private final Function super T, Promise extends R>> mapper;
FlatMap(Function super T, Promise extends R>> mapper) {
this.mapper = requireNonNull(mapper);
}
@SuppressWarnings("unchecked")
public Promise call(Promise resolved) throws Exception {
return (Promise) mapper.apply(resolved.getValue());
}
}
/**
* {@inheritDoc}
*/
public Promise recover(Function, ? extends T> recovery) {
PromiseImpl chained = new PromiseImpl();
Recover recover = new Recover(chained, recovery);
then(recover, recover);
return chained;
}
/**
* A callback used by the {@link PromiseImpl#recover(Function)} method.
*
* @Immutable
*/
private static final class Recover implements Success, Failure {
private final PromiseImpl chained;
private final Function, ? extends T> recovery;
Recover(PromiseImpl chained, Function, ? extends T> recovery) {
this.chained = chained;
this.recovery = requireNonNull(recovery);
}
public Promise call(Promise resolved) throws Exception {
T value;
try {
value = resolved.getValue();
} catch (Throwable e) {
chained.resolve(null, e);
return null;
}
chained.resolve(value, null);
return null;
}
public void fail(Promise> resolved) throws Exception {
T recovered;
Throwable failure;
try {
recovered = recovery.apply(resolved);
failure = resolved.getFailure();
} catch (Throwable e) {
chained.resolve(null, e);
return;
}
if (recovered == null) {
chained.resolve(null, failure);
} else {
chained.resolve(recovered, null);
}
}
}
/**
* {@inheritDoc}
*/
public Promise recoverWith(Function, Promise extends T>> recovery) {
PromiseImpl chained = new PromiseImpl();
RecoverWith recoverWith = new RecoverWith(chained, recovery);
then(recoverWith, recoverWith);
return chained;
}
/**
* A callback used by the {@link PromiseImpl#recoverWith(Function)} method.
*
* @Immutable
*/
private static final class RecoverWith implements Success, Failure {
private final PromiseImpl chained;
private final Function, Promise extends T>> recovery;
RecoverWith(PromiseImpl chained, Function, Promise extends T>> recovery) {
this.chained = chained;
this.recovery = requireNonNull(recovery);
}
public Promise call(Promise resolved) throws Exception {
T value;
try {
value = resolved.getValue();
} catch (Throwable e) {
chained.resolve(null, e);
return null;
}
chained.resolve(value, null);
return null;
}
public void fail(Promise> resolved) throws Exception {
Promise extends T> recovered;
Throwable failure;
try {
recovered = recovery.apply(resolved);
failure = resolved.getFailure();
} catch (Throwable e) {
chained.resolve(null, e);
return;
}
if (recovered == null) {
chained.resolve(null, failure);
} else {
recovered.onResolve(new Chain(chained, recovered));
}
}
}
/**
* {@inheritDoc}
*/
public Promise fallbackTo(Promise extends T> fallback) {
PromiseImpl chained = new PromiseImpl();
FallbackTo fallbackTo = new FallbackTo(chained, fallback);
then(fallbackTo, fallbackTo);
return chained;
}
/**
* A callback used by the {@link PromiseImpl#fallbackTo(Promise)} method.
*
* @Immutable
*/
private static final class FallbackTo implements Success, Failure {
private final PromiseImpl chained;
private final Promise extends T> fallback;
FallbackTo(PromiseImpl chained, Promise extends T> fallback) {
this.chained = chained;
this.fallback = requireNonNull(fallback);
}
public Promise call(Promise resolved) throws Exception {
T value;
try {
value = resolved.getValue();
} catch (Throwable e) {
chained.resolve(null, e);
return null;
}
chained.resolve(value, null);
return null;
}
public void fail(Promise> resolved) throws Exception {
Throwable failure;
try {
failure = resolved.getFailure();
} catch (Throwable e) {
chained.resolve(null, e);
return;
}
fallback.onResolve(new Chain(chained, fallback, failure));
}
}
static V requireNonNull(V value) {
if (value != null) {
return value;
}
throw new NullPointerException();
}
/**
* Use the lazy initialization holder class idiom to delay creating a Logger
* until we actually need it.
*/
private static final class Logger {
private final static java.util.logging.Logger LOGGER;
static {
LOGGER = java.util.logging.Logger.getLogger(PromiseImpl.class.getName());
}
static void logCallbackException(Throwable t) {
LOGGER.log(java.util.logging.Level.WARNING, "Exception from Promise callback", t);
}
}
}