/* * Copyright (C) 2011 Christopher Probst * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * * Neither the name of the 'FoxNet RMI' nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.indyforge.foxnet.rmi.util; import java.util.ArrayList; import java.util.List; import java.util.logging.Logger; /** * This class represents a future which can be basically any asynchronous * operation. You can add/remove {@link FutureCallback}s to react on the given * event. This class is thread-safe and optimized for concurrent usage. * * @author Christopher Probst * @see FutureCallback */ public class Future { // The logger protected final Logger logger = Logger.getLogger(getClass().getName()); // The lock object private final Object lock = new Object(); // Used to store callbacks private List<FutureCallback> lazyCallbacks; // Used to store the cause private Throwable cause; // Used to store the attachment private Object attachment; // Used to store the state private volatile boolean completed; /** * Used to modify the attachment when completed. * * @param attachment * The attachment you can modify. * @return the attachment which should be saved. */ protected Object modifyAttachment(Object attachment) { return attachment; } /** * Used to modify the cause when completed. * * @param cause * The cause you can modify. * @return the cause which should be saved. */ protected Throwable modifyCause(Throwable cause) { return cause; } /** * Notifies the given callback. * * @param callback * The callback you want to notify. */ private void notifyCallback(FutureCallback callback) { try { callback.completed(this); } catch (Exception e) { logger.warning("Failed to notify callback. Reason: " + e.getMessage()); } } /** * Adds a callback to this future. If the future is already completed it * will be called immediately, otherwise it will be queued until completion. * * @param callback * The callback you want to add. * @return true if the callback is added to the future, otherwise false. * */ public boolean add(FutureCallback callback) { if (callback == null) { throw new NullPointerException("callback"); } // The notify now flag boolean notifyNow = false; // Synchronize with lock synchronized (lock) { // Not completed yet ? if (!completed) { // Do lazy callback creation if (lazyCallbacks == null) { lazyCallbacks = new ArrayList<FutureCallback>(); } // Add to list lazyCallbacks.add(callback); } else { notifyNow = true; } } // Notify the callback now ? if (notifyNow) { notifyCallback(callback); } return !notifyNow; } /** * Removes a callback from this future. If the future is already completed * nothing happens. * * @param callback * The callback you want to remove. * @return true if the callback is removed, otherwise false. */ public boolean remove(FutureCallback callback) { if (callback == null) { throw new NullPointerException("callback"); } synchronized (lock) { if (completed || lazyCallbacks == null || lazyCallbacks.isEmpty()) { return false; } return lazyCallbacks.remove(callback); } } /** * Synchronize the future with the calling thread. Basically this method * blocks until the future completes. If this thread gets interrupted before * the future is completed this method will fail this future. So after * calling this method this future is definitely completed. * * @return true if the future was successful, otherwise false. */ public boolean synchronize() { return synchronize(-1); } /** * Synchronize the future with the calling thread. Basically this method * blocks until the future completes OR the given timeout expires. If this * thread gets interrupted before the future is completed or the timeout * expires this method will fail this future. So after calling this method * this future is definitely completed. * * @param timeoutMillies * The timeout you want to wait in milliseconds. * @return true if the future was successful, otherwise false. */ public boolean synchronize(long timeoutMillies) { synchronized (lock) { if (!completed) { try { // If the timeout makes sense use it! if (timeoutMillies > 0) { // Wait using the given timeout lock.wait(timeoutMillies); /* * If the future is still not completed yet the timeout * must have reached the limit. We fail this future in * this case. */ if (!completed) { fail(new IllegalStateException("Future has timed " + "out during synchronization")); } } else { // DefaultBomb wait lock.wait(); } } catch (InterruptedException e) { // Fail this future (Thread got interrupted...) fail(e); } } // True if success (cause == null), otherwise false! return isSuccessful(); } } /** * @return true if this future is completed and has a cause. */ public boolean isFailed() { if (!completed) { throw new IllegalStateException("Future is not completed yet"); } return cause != null; } /** * @return true if this future is completed and has not a cause. */ public boolean isSuccessful() { if (!completed) { throw new IllegalStateException("Future is not completed yet"); } return !isFailed(); } /** * @return true if this future is completed. */ public boolean isCompleted() { return completed; } /** * @return the cause of this future. */ public Throwable cause() { // Volatile refresh if (!completed) { throw new IllegalStateException("Future is not completed yet"); } return cause; } /** * @return the attachment of this future. */ public Object attachment() { // Volatile refresh if (!completed) { throw new IllegalStateException("Future is not completed yet"); } return attachment; } /** * Fails this future if it is not completed yet. * * @param cause * The cause of this future. * @return true if the future is completed now, otherwise false. */ public boolean fail(Throwable cause) { if (cause == null) { throw new NullPointerException("cause"); } return complete(null, cause); } /** * Succeeds this future if it is not completed yet. * * @param attachment * The attachment you want to set. * @return true if the future is completed now, otherwise false. */ public boolean succeed(Object attachment) { return complete(attachment, null); } /** * Completes this future if it is not completed yet. * * @param attachment * The attachment you want to set. * @param cause * The cause of this future. * @return true if the future is completed now, otherwise false. */ public boolean complete(Object attachment, Throwable cause) { List<FutureCallback> tmpCallbacks; synchronized (lock) { if (completed) { return false; } // Save attachment this.attachment = modifyAttachment(attachment); // Save cause this.cause = modifyCause(cause); // Set completed to true completed = true; // Signal all! lock.notifyAll(); // Save old callbacks tmpCallbacks = lazyCallbacks; // GC lazyCallbacks = null; } // Notify the queued callbacks if (tmpCallbacks != null) { for (FutureCallback callback : tmpCallbacks) { notifyCallback(callback); } } return true; } }