/****************************************************************************
* Copyright (C) 2013 ecsec GmbH.
* All rights reserved.
* Contact: ecsec GmbH (info@ecsec.de)
*
* This file is part of the Open eCard App.
*
* GNU General Public License Usage
* This file may be used under the terms of the GNU General Public
* License version 3.0 as published by the Free Software Foundation
* and appearing in the file LICENSE.GPL included in the packaging of
* this file. Please review the following information to ensure the
* GNU General Public License version 3.0 requirements will be met:
* http://www.gnu.org/copyleft/gpl.html.
*
* Other Usage
* Alternatively, this file may be used in accordance with the terms
* and conditions contained in a signed written agreement between
* you and ecsec GmbH.
*
***************************************************************************/
package org.openecard.common.util;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import javax.annotation.Nonnegative;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* Implementation of a promise inspired by clojure's promise.
* The implementation blocks until the value is set or a timeout occured.
*
* @see <tt><a href="http://clojuredocs.org/clojure_core/clojure.core/promise#source">clojure.core/promise</a></tt>
* @author Tobias Wich <tobias.wich@ecsec.de>
*/
public class Promise <T> {
private final CountDownLatch gate;
private T value;
/**
* Creates an undelivered promise.
*/
public Promise() {
gate = new CountDownLatch(1);
}
/**
* Checks if the promise is already delivered.
*
* @return {@code true} if the promise is delivered, {@code false} otherwise.
*/
public synchronized boolean isDelivered() {
try {
boolean isSet = gate.await(0, TimeUnit.MILLISECONDS);
return isSet;
} catch (InterruptedException ex) {
// very unlikely if not impossible, but if it happens the right thing to do is probably kill the thread
throw new RuntimeException("Promise interrupted while waiting. Shutting down.");
}
}
/**
* Delivers the given value to the promise.
* The result of calling this function is that the promise is delivered and either contains the given value or a
* value which has been set previously. In the latter case an IllegalStateException is thrown. If that happens it is
* an indicator that the implementation using the promise is flawed.
*
* @param value Value to be delivered.
* @return The value that has been delivered.
* @throws IllegalStateException Thrown in case the promise is already delivered when this function gets called.
*/
public synchronized @Nullable T deliver(@Nullable final T value) throws IllegalStateException {
if (! isDelivered()) {
this.value = value;
this.gate.countDown();
return this.value;
} else {
throw new IllegalStateException("Failed to deliver promise, as it is already delivered.");
}
}
/**
* Dereferences the promise, aka tries to get its result.
* The function waits as long as the promise is not delivered yet.
*
* @return The value that has been delivered to the promise.
* @throws InterruptedException Thrown in case the current thread has been interrupted while waiting.
*/
public @Nullable T deref() throws InterruptedException {
try {
// wait an infinite time, most certainly longer than the machine running this program exists :p
return deref(Long.MAX_VALUE, TimeUnit.DAYS);
} catch (TimeoutException ex) {
// ignore the exception, as trhe timout is quasi forever
return null;
}
}
/**
* Dereferences the promise, aka tries to get its result.
* The function waits as long as defined by the wait parameters. A timeout value of 0 indicates not to wait at all.
*
* @param timeout Value from 0 to {@link Long#MAX_VALUE} indicating the number of units to wait.
* @param unit The unit qualifying the timeout value.
* @return The value that has been delivered to the promise.
* @throws InterruptedException Thrown in case the current thread has been interrupted while waiting.
* @throws TimeoutException Thrown in case a timeout occured.
*/
public @Nullable T deref(@Nonnegative long timeout, @Nonnull TimeUnit unit) throws InterruptedException,
TimeoutException {
boolean delivered = gate.await(timeout, unit);
synchronized (this) {
if (delivered) {
return this.value;
} else {
throw new TimeoutException("Wait for promised value timed out.");
}
}
}
/**
* Gets the value of the promise but does not wait for its delivery.
* This method is different than calling {@link #deref(long, java.util.concurrent.TimeUnit)} with a timeout value of
* 0. It does not throw a TimeoutException, but returns null when no value is delivered yet. This may be
* unambiguous with a delivered null value.
*
* @return The delivered value or {@code null} when no value has been deliverd yet.
*/
public @Nullable T derefNonblocking() {
if (isDelivered()) {
return value;
} else {
return null;
}
}
}