/*
* Mojito Distributed Hash Table (Mojito DHT)
* Copyright (C) 2006-2007 LimeWire LLC
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
package org.limewire.concurrent;
import java.util.concurrent.CancellationException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* The OnewayExchanger is an one-way synchronization point
* for Threads. One or more Threads can wait for the arrival
* of a value by calling the get() method which will block
* and suspend the Threads until an another Thread sets a
* return value or an Exception which will be thrown by the
* get() method.
* <p>
* The main differences between OnewayExchanger and
* java.util.concurrent.Exchanger are:
*
* <ol>
* <li> Multiple Threads can wait for a result from a single Thread.
* <li> It's a one-way exchange.
* <li> The setter Thread may set an exception causing this exception
* to be thrown on the getter side.
* <li> The OnewayExchanger is cancellable.
* <li> The OnewayExchanger can be configured for a single shot. That
* means once a return value or an exception has been set they cannot
* be changed anymore.
* </ol>
*/
public class OnewayExchanger<V, E extends Throwable> {
/** A helper Object to represent 'this' at construction time */
private static final Object THIS = new Object();
/** The lock Object */
private final Object lock;
/** Flag for whether or not this is an one-shot exchanger */
private final boolean oneShot;
/** Flag for whether or not we're done */
private boolean done = false;
/** Flag for whether or not the exchanger was cancelled */
private boolean cancelled = false;
/** The value we're going to return */
private V value;
/** The Exception we're going to throw */
private E exception;
/**
* Creates an {@link OnewayExchanger} with the default configuration.
*/
public OnewayExchanger() {
this(THIS, false);
}
/**
* Creates an {@link OnewayExchanger} with the given lock.
*/
public OnewayExchanger(Object lock) {
this(lock, false);
}
/**
* Creates an {@link OnewayExchanger} that is either re-usable
* or not.
*/
public OnewayExchanger(boolean oneShot) {
this(THIS, oneShot);
}
/**
* Creates an {@link OnewayExchanger} with the given lock
* and whether or not it can be re-used.
*/
public OnewayExchanger(Object lock, boolean oneShot) {
this.lock = lock != THIS ? lock : this;
this.oneShot = oneShot;
}
/**
* Returns the lock Object.
*/
public Object getLock() {
return lock;
}
/**
* Waits for another Thread for a value or an Exception
* unless they're already set in which case this method
* will return immediately.
*/
public V get() throws InterruptedException, E {
try {
return get(0L, TimeUnit.MILLISECONDS);
} catch (TimeoutException cannotHappen) {
throw new Error(cannotHappen);
}
}
/**
* Waits for another Thread for the given time for a value
* or an Exception unless they're already set in which case
* this method will return immediately.
*/
public V get(long timeout, TimeUnit unit)
throws InterruptedException, TimeoutException, E {
synchronized (lock) {
if (!done) {
if (timeout == 0L) {
lock.wait();
} else {
unit.timedWait(lock, timeout);
}
// Not done? Must be a timeout!
if (!done) {
throw new TimeoutException();
}
}
if (cancelled) {
throw new CancellationException();
}
// Prioritize Exceptions!
if (exception != null) {
throw exception;
}
return value;
}
}
/**
* Tries to get the value without blocking.
*/
public V tryGet() throws InterruptedException, E {
synchronized (lock) {
if (done) {
return get();
} else {
return null;
}
}
}
/**
* Tries to cancel the OnewayExchanger and returns true
* on success.
*/
public boolean cancel() {
synchronized (lock) {
if (done) {
return cancelled;
}
done = true;
cancelled = true;
lock.notifyAll();
return true;
}
}
/**
* Returns true if the OnewayExchanger is cancelled.
*/
public boolean isCancelled() {
synchronized (lock) {
return cancelled;
}
}
/**
* Returns true if the get() method will return immediately
* by throwing an Exception or returning a value.
*/
public boolean isDone() {
synchronized (lock) {
return done;
}
}
/**
* Returns true if calling the get() method will
* throw an Exception.
*/
public boolean throwsException() {
synchronized (lock) {
return cancelled || exception != null;
}
}
/**
* Returns true if this is an one-shot OnewayExchanger.
*/
public boolean isOneShot() {
return oneShot;
}
/**
* Sets the value that will be returned by the get() method.
*/
public boolean setValue(V value) {
synchronized (lock) {
if (cancelled) {
return false;
}
if (done && oneShot) {
return false;
}
done = true;
this.value = value;
lock.notifyAll();
return true;
}
}
/**
* Sets the Exception that will be thrown by the get() method.
*/
public boolean setException(E exception) {
synchronized (lock) {
if (exception == null) {
throw new NullPointerException();
}
if (cancelled) {
return false;
}
if (done && oneShot) {
return false;
}
done = true;
this.exception = exception;
lock.notifyAll();
return true;
}
}
/**
* Resets the OnewayExchanger so that it can be
* reused unless it's configured for a single shot.
*/
public boolean reset() {
synchronized (lock) {
if (!oneShot && done) {
done = false;
cancelled = false;
value = null;
exception = null;
return true;
}
return false;
}
}
}