org.osgi.util.promise.DeferredPromiseImpl Maven / Gradle / Ivy
Show all versions of aem-sdk-api Show documentation
/*******************************************************************************
* Copyright (c) Contributors to the Eclipse Foundation
*
* 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.
*
* SPDX-License-Identifier: Apache-2.0
*******************************************************************************/
package org.osgi.util.promise;
import static java.util.Objects.requireNonNull;
import java.lang.reflect.InvocationTargetException;
import java.util.NoSuchElementException;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.BiConsumer;
import org.osgi.util.function.Consumer;
import org.osgi.util.function.Function;
import org.osgi.util.function.Predicate;
/**
* Deferred Promise implementation.
*
* This class is not used directly by clients. Clients should use
* {@link PromiseFactory#deferred()} to create a {@link Deferred} which can be
* used to obtain a Promise whose resolution can be deferred.
*
* @param The result type associated with the Promise.
* @since 1.1
* @ThreadSafe
* @author $Id: f660e300db822faf9798d14a8f355bc0705009e2 $
*/
final class DeferredPromiseImpl extends PromiseImpl {
/**
* 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 #tryResolve(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.
*
* @see resolved
*/
// @GuardedBy("resolved")
private T value;
/**
* The failure of this Promise if resolved with a failure or {@code null} if
* successfully resolved.
*
* @see resolved
*/
// @GuardedBy("resolved")
private Throwable fail;
/**
* Initialize this Promise.
*
* @param factory The factory to use for callbacks and scheduled operations.
*/
DeferredPromiseImpl(PromiseFactory factory) {
super(factory);
resolved = new CountDownLatch(1);
}
/**
* {@inheritDoc}
*/
@Override
public boolean isDone() {
return resolved.getCount() == 0;
}
/**
* Return a resolved PromiseImpl if this DeferredPromiseImpl is resolved.
*
* @return A ResolvedPromiseImpl holding the value of this
* DeferredPromiseImpl or a FailedPromiseImpl holding the failure of
* this DeferredPromiseImpl or this DeferredPromiseImpl if this
* DeferredPromiseImpl is not resolved.
*/
PromiseImpl orDone() {
// ensure latch open before reading state
if (!isDone()) {
return this;
}
if (fail == null) {
return resolved(value);
}
return failed(fail);
}
/**
* {@inheritDoc}
*/
@Override
public T getValue() throws InvocationTargetException, InterruptedException {
// ensure latch open before reading state
resolved.await();
if (fail == null) {
return value;
}
throw new InvocationTargetException(fail);
}
/**
* {@inheritDoc}
*/
@Override
public Throwable getFailure() throws InterruptedException {
// ensure latch open before reading state
resolved.await();
return fail;
}
/**
* {@inheritDoc}
*/
@Override
void result(Result< ? super T> consumer) {
// ensure latch open before reading state
if (!isDone()) {
consumer.accept(null, new AssertionError("promise not resolved"));
return;
}
consumer.accept(value, fail);
}
@Override
public String toString() {
// ensure latch open before reading state
if (!isDone()) {
return super.toString() + "[unresolved]";
}
if (fail == null) {
return super.toString() + "[resolved: " + value + "]";
}
return super.toString() + "[failed: " + fail + "]";
}
/**
* Try to resolve this Promise.
*
* If this Promise was already resolved, return false. Otherwise, resolve
* this Promise and return true.
*
* @param v The value of this Promise.
* @param f The failure of this Promise.
* @return false if this Promise was already resolved; true if this method
* resolved this Promise.
*/
boolean tryResolve(T v, Throwable f) {
// critical section: only one resolver at a time
synchronized (resolved) {
if (isDone()) {
return false;
}
/*
* 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.
*/
if (f == null) {
value = v;
} else {
fail = f;
}
resolved.countDown();
}
notifyCallbacks(); // call any registered callbacks
return true;
}
/**
* Resolve this Promise.
*
* If this Promise was already resolved, throw IllegalStateException.
* Otherwise, resolve this Promise.
*
* @param v The value of this Promise.
* @param f The failure of this Promise.
* @throws IllegalStateException If this Promise was already resolved.
*/
void resolve(T v, Throwable f) {
if (!tryResolve(v, f)) {
throw new IllegalStateException("Already resolved");
}
}
/**
* 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) {
DeferredPromiseImpl chained = deferred();
with.onResolve(new ResolveWith(with, chained));
return chained.orDone();
}
/**
* Resolve this Promise with the specified CompletionStage.
*
* If the specified CompletionStage is completed normally, this Promise is
* resolved with the value of the specified CompletionStage. If the
* specified CompletionStage is completed exceptionally, this Promise is
* resolved with the failure of the specified CompletionStage.
*
* @param with A CompletionStage whose result will 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 CompletionStage. The returned Promise must be
* successfully resolved with the value {@code null}, if this
* Promise was resolved by the specified CompletionStage. The
* returned Promise must be resolved with a failure of
* {@link IllegalStateException}, if this Promise was already
* resolved when the specified CompletionStage was completed.
* @since 1.2
*/
Promise resolveWith(CompletionStage< ? extends T> with) {
DeferredPromiseImpl chained = deferred();
with.whenComplete(new ResolveWith(chained));
return chained.orDone();
}
/**
* A callback used to resolve a Promise with another Promise for the
* {@link #resolveWith(Promise)} method or with another CompletionStage for
* the {@link #resolveWith(CompletionStage)} method.
*
* @Immutable
*/
private final class ResolveWith
implements Runnable, InlineCallback, Result,
BiConsumer {
private final Promise< ? extends T> with;
private final DeferredPromiseImpl promise;
/**
* For {@link #resolveWith(Promise)}
*/
ResolveWith(Promise< ? extends T> with,
DeferredPromiseImpl promise) {
this.with = requireNonNull(with);
this.promise = requireNonNull(promise);
}
/**
* For {@link #resolveWith(CompletionStage)}
*/
ResolveWith(DeferredPromiseImpl promise) {
this.with = null; // CompletionStage
this.promise = requireNonNull(promise);
}
@Override
public void run() {
result(with, this);
}
@Override
public void accept(T v, Throwable f) {
try {
resolve(v, f);
f = null; // resolve completed
} catch (Throwable e) {
f = e; // propagate new exception
}
promise.tryResolve(null, f);
}
}
/**
* A callback used to chain promises for the
* {@link PromiseImpl#then(Success, Failure)} method.
*
* @Immutable
*/
final class Then implements Runnable, Result
{
private final PromiseImpl
promise;
private final Success
success;
private final Failure failure;
@SuppressWarnings("unchecked")
Then(PromiseImpl
promise, Success< ? super P, ? extends T> success,
Failure failure) {
this.promise = requireNonNull(promise);
this.success = (Success
) success;
this.failure = failure;
}
@Override
public void run() {
promise.result(this);
}
@Override
public void accept(P v, Throwable f) {
if (f != null) {
if (failure != null) {
try {
failure.fail(promise);
} catch (Throwable e) {
f = e; // propagate new exception
}
}
} else if (success != null) {
Promise< ? extends T> returned = null;
try {
returned = success.call(promise);
} catch (Throwable e) {
f = e; // propagate new exception
}
if (returned != null) {
returned.onResolve(new Chain(returned));
return;
}
}
tryResolve(null, f);
}
}
/**
* A callback used to resolve the chained Promise when the Promise is
* resolved.
*
* @Immutable
*/
final class Chain implements Runnable, InlineCallback, Result {
private final Promise< ? extends T> promise;
Chain(Promise< ? extends T> promise) {
this.promise = requireNonNull(promise);
}
@Override
public void run() {
result(promise, this);
}
@Override
public void accept(T v, Throwable f) {
tryResolve(v, f);
}
}
/**
* A callback used to resolve the chained Promise when the PromiseImpl is
* resolved.
*
* @Immutable
*/
private final class ChainImpl
implements Runnable, InlineCallback, Result {
private final PromiseImpl promise;
ChainImpl(PromiseImpl promise) {
this.promise = requireNonNull(promise);
}
@Override
public void run() {
promise.result(this);
}
@Override
public void accept(T v, Throwable f) {
tryResolve(v, f);
}
}
/**
* A callback used by the {@link PromiseImpl#thenAccept(Consumer)} method.
*
* @Immutable
*/
final class ThenAccept implements Runnable, Result {
private final PromiseImpl promise;
private final Consumer< ? super T> consumer;
ThenAccept(PromiseImpl promise, Consumer< ? super T> consumer) {
this.promise = requireNonNull(promise);
this.consumer = requireNonNull(consumer);
}
@Override
public void run() {
promise.result(this);
}
@Override
public void accept(T v, Throwable f) {
if (f == null) {
try {
consumer.accept(v);
} catch (Throwable e) {
f = e;
}
}
tryResolve(v, f);
}
}
/**
* A callback used by the {@link PromiseImpl#filter(Predicate)} method.
*
* @Immutable
*/
final class Filter implements Runnable, Result {
private final PromiseImpl promise;
private final Predicate< ? super T> predicate;
Filter(PromiseImpl promise, Predicate< ? super T> predicate) {
this.promise = requireNonNull(promise);
this.predicate = requireNonNull(predicate);
}
@Override
public void run() {
promise.result(this);
}
@Override
public void accept(T v, Throwable f) {
if (f == null) {
try {
if (!predicate.test(v)) {
f = new NoSuchElementException();
}
} catch (Throwable e) { // propagate new exception
f = e;
}
}
tryResolve(v, f);
}
}
/**
* A callback used by the {@link PromiseImpl#map(Function)} method.
*
* @Immutable
*/
final class Map implements Runnable, Result
{
private final PromiseImpl
promise;
private final Function< ? super P, ? extends T> mapper;
Map(PromiseImpl
promise, Function< ? super P, ? extends T> mapper) {
this.promise = requireNonNull(promise);
this.mapper = requireNonNull(mapper);
}
@Override
public void run() {
promise.result(this);
}
@Override
public void accept(P v, Throwable f) {
T map = null;
if (f == null) {
try {
map = mapper.apply(v);
} catch (Throwable e) { // propagate new exception
f = e;
}
}
tryResolve(map, f);
}
}
/**
* A callback used by the {@link PromiseImpl#flatMap(Function)} method.
*
* @Immutable
*/
final class FlatMap
implements Runnable, Result
{
private final PromiseImpl
promise;
private final Function< ? super P,Promise< ? extends T>> mapper;
FlatMap(PromiseImpl
promise,
Function< ? super P,Promise< ? extends T>> mapper) {
this.promise = requireNonNull(promise);
this.mapper = requireNonNull(mapper);
}
@Override
public void run() {
promise.result(this);
}
@Override
public void accept(P v, Throwable f) {
if (f == null) {
Promise< ? extends T> flatmap = null;
try {
flatmap = mapper.apply(v);
} catch (Throwable e) { // propagate new exception
f = e;
}
if (flatmap != null) {
flatmap.onResolve(new Chain(flatmap));
return;
}
}
tryResolve(null, f);
}
}
/**
* A callback used by the {@link PromiseImpl#recover(Function)} method.
*
* @Immutable
*/
final class Recover implements Runnable, Result {
private final PromiseImpl promise;
private final Function, ? extends T> recovery;
Recover(PromiseImpl promise,
Function, ? extends T> recovery) {
this.promise = requireNonNull(promise);
this.recovery = requireNonNull(recovery);
}
@Override
public void run() {
promise.result(this);
}
@Override
public void accept(T v, Throwable f) {
if (f != null) {
try {
v = recovery.apply(promise);
if (v != null) {
f = null;
}
} catch (Throwable e) { // propagate new exception
f = e;
}
}
tryResolve(v, f);
}
}
/**
* A callback used by the {@link PromiseImpl#recoverWith(Function)} method.
*
* @Immutable
*/
final class RecoverWith implements Runnable, Result {
private final PromiseImpl promise;
private final Function,Promise< ? extends T>> recovery;
RecoverWith(PromiseImpl promise,
Function,Promise< ? extends T>> recovery) {
this.promise = requireNonNull(promise);
this.recovery = requireNonNull(recovery);
}
@Override
public void run() {
promise.result(this);
}
@Override
public void accept(T v, Throwable f) {
if (f != null) {
Promise< ? extends T> recovered = null;
try {
recovered = recovery.apply(promise);
} catch (Throwable e) { // propagate new exception
f = e;
}
if (recovered != null) {
recovered.onResolve(new Chain(recovered));
return;
}
}
tryResolve(v, f);
}
}
/**
* A callback used by the {@link PromiseImpl#fallbackTo(Promise)} method.
*
* @Immutable
*/
final class FallbackTo
implements Runnable, InlineCallback, Result {
private final PromiseImpl promise;
private final Promise< ? extends T> fallback;
FallbackTo(PromiseImpl promise, Promise< ? extends T> fallback) {
this.promise = requireNonNull(promise);
this.fallback = requireNonNull(fallback);
}
@Override
public void run() {
promise.result(this);
}
@Override
public void accept(T v, Throwable f) {
if (f != null) {
fallback.onResolve(new FallbackChain(fallback, f));
return;
}
tryResolve(v, null);
}
}
/**
* A callback used to resolve the chained Promise when the fallback Promise
* is resolved.
*
* @Immutable
*/
private final class FallbackChain
implements Runnable, InlineCallback, Result {
private final Promise< ? extends T> fallback;
private final Throwable failure;
FallbackChain(Promise< ? extends T> fallback, Throwable failure) {
this.fallback = requireNonNull(fallback);
this.failure = requireNonNull(failure);
}
@Override
public void run() {
result(fallback, this);
}
@Override
public void accept(T v, Throwable f) {
if (f != null) {
f = failure;
}
tryResolve(v, f);
}
}
/**
* A callback used by the {@link PromiseImpl#timeout(long)} method to
* schedule the timeout and to resolve the chained Promise and cancel the
* timeout.
*
* @Immutable
*/
final class Timeout implements Runnable, InlineCallback, Result {
private final PromiseImpl promise;
private final ScheduledFuture< ? > future;
Timeout(PromiseImpl promise, long millis) {
this.promise = requireNonNull(promise);
if (promise.isDone()) {
this.future = null;
} else {
FailedPromiseImpl timedout = failed(new TimeoutException());
Runnable operation = new ChainImpl(timedout);
this.future = schedule(operation, millis, TimeUnit.MILLISECONDS);
}
}
@Override
public void run() {
promise.result(this);
}
@Override
public void accept(T v, Throwable f) {
tryResolve(v, f);
if (future != null) {
future.cancel(false);
}
}
}
/**
* A callback used by the {@link PromiseImpl#delay(long)} method to delay
* chaining a promise.
*
* @Immutable
*/
final class Delay implements Runnable, InlineCallback {
private final Runnable operation;
private final long millis;
Delay(PromiseImpl promise, long millis) {
this.operation = new ChainImpl(promise);
this.millis = millis;
}
@Override
public void run() {
schedule(operation, millis, TimeUnit.MILLISECONDS);
}
}
/**
* A callback used by the {@link PromiseFactory#submit(Callable)} method.
*
* @Immutable
*/
final class Submit implements Runnable {
private final Callable< ? extends T> task;
Submit(Callable< ? extends T> task) {
this.task = requireNonNull(task);
}
@Override
public void run() {
T v;
Throwable f;
try {
v = task.call();
f = null;
} catch (Throwable e) {
f = e;
v = null;
}
tryResolve(v, f);
}
}
}