All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.osgi.util.promise.PromiseImpl Maven / Gradle / Ivy

There is a newer version: 7.0.0
Show newest version
/*
 * 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 success, Failure failure) { PromiseImpl chained = new PromiseImpl(); onResolve(new Then(chained, success, failure)); return chained; } /** * {@inheritDoc} */ public Promise then(Success 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 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 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 promise; private final Throwable failure; Chain(PromiseImpl chained, Promise promise) { this.chained = chained; this.promise = promise; this.failure = null; } Chain(PromiseImpl chained, Promise 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 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 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 predicate; Filter(Predicate 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 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 mapper; Map(Function 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> 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> mapper; FlatMap(Function> 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> 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> recovery; RecoverWith(PromiseImpl chained, Function, Promise> 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 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 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 fallback; FallbackTo(PromiseImpl chained, Promise 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); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy