package jadex.commons;
import jadex.commons.concurrent.IResultListener;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
/**
* Future that includes mechanisms for callback notification.
* This allows a caller to decide if
* a) a blocking call to get() should be used
* b) a callback shall be invoked
*/
public class Future implements IFuture
{
//-------- constants --------
/** A caller is queued for suspension. */
protected final String CALLER_QUEUED = "queued";
/** A caller is resumed. */
protected final String CALLER_RESUMED = "resumed";
/** A caller is suspended. */
protected final String CALLER_SUSPENDED = "suspended";
//-------- attributes --------
/** The result. */
protected Object result;
/** The exception (if any). */
protected Exception exception;
// protected Exception resultex;
/** Flag indicating if result is available. */
protected boolean resultavailable;
/** The blocked callers. */
protected Map callers;
/** The listeners. */
protected List listeners;
//-------- constructors --------
/**
* Create a new future.
*/
public Future()
{
}
/**
* Create a future that is already done.
* @param result The result, if any.
*/
public Future(Object result)
{
this();
setResult(result);
}
//-------- methods --------
/**
* Test if done, i.e. result is available.
* @return True, if done.
*/
public synchronized boolean isDone()
{
return resultavailable;
}
/**
* Get the result - blocking call.
* @return The future result.
*/
public Object get(ISuspendable caller)
{
return get(caller, -1);
}
// todo: this are always realtime timeouts, what about simulation clocks!
/**
* Get the result - blocking call.
* @param timeout The timeout in millis.
* @return The future result.
*/
public Object get(ISuspendable caller, long timeout)
{
boolean suspend = false;
synchronized(this)
{
if(!resultavailable)
{
if(caller==null)
throw new RuntimeException("No suspendable element.");
// caller = new ThreadSuspendable(this);
// System.out.println(this+" suspend: "+caller);
if(callers==null)
callers = Collections.synchronizedMap(new HashMap());
callers.put(caller, CALLER_QUEUED);
suspend = true;
}
}
if(suspend)
{
Object mon = caller.getMonitor()!=null? caller.getMonitor(): caller;
synchronized(mon)
{
Object state = callers.get(caller);
if(CALLER_QUEUED.equals(state))
{
callers.put(caller, CALLER_SUSPENDED);
// System.out.println(this+" caller suspending: "+caller+" "+mon);
caller.suspend(timeout);
if(exception!=null)
{
// Nest exception to have both calling and manually set exception stack trace.
// exception = new RuntimeException("Exception when evaluating future", exception);
exception = new RuntimeException(exception.getMessage(), exception);
}
// System.out.println(this+" caller awoke: "+caller+" "+mon);
}
// else already resumed.
}
}
// if(result==null)
// System.out.println(this+" here: "+caller);
if(exception!=null)
{
throw exception instanceof RuntimeException ?(RuntimeException)exception
:new RuntimeException(exception);
}
else
{
return result;
}
}
/**
* Set the exception.
* Listener notifications occur on calling thread of this method.
* @param exception The exception.
*/
public void setException(Exception exception)
{
synchronized(this)
{
// Ignore exception when already continued?!
if(resultavailable)
{
if(this.exception!=null)
this.exception.printStackTrace();
// if(resultex!=null)
// {
// System.err.println("Result: "+result);
// resultex.printStackTrace();
// }
throw new RuntimeException(this.exception);
}
// System.out.println(this+" setResult: "+result);
this.exception = exception;
resultavailable = true;
}
resume();
}
/**
* Set the exception.
* Listener notifications occur on calling thread of this method.
* @param exception The exception.
*/
public void setExceptionIfUndone(Exception exception)
{
synchronized(this)
{
// If done just return.
if(resultavailable)
return;
// System.out.println(this+" setResult: "+result);
this.exception = exception;
resultavailable = true;
}
resume();
}
/**
* Set the result.
* Listener notifications occur on calling thread of this method.
* @param result The result.
*/
public void setResult(Object result)
{
synchronized(this)
{
if(resultavailable)
throw new RuntimeException();
// System.out.println(this+" setResult: "+result);
this.result = result;
resultavailable = true;
// this.resultex = new Exception();
}
resume();
}
/**
* Set the result.
* Listener notifications occur on calling thread of this method.
* @param result The result.
*/
public void setResultIfUndone(Object result)
{
synchronized(this)
{
if(resultavailable)
return;
// System.out.println(this+" setResult: "+result);
this.result = result;
resultavailable = true;
}
resume();
}
/**
* Resume after result or exception has been set.
*/
protected void resume()
{
synchronized(this)
{
if(callers!=null)
{
for(Iterator it=callers.keySet().iterator(); it.hasNext(); )
{
ISuspendable caller = (ISuspendable)it.next();
Object mon = caller.getMonitor()!=null? caller.getMonitor(): caller;
// System.out.println(this+" resume: "+caller+" "+mon);
synchronized(mon)
{
Object state = callers.get(caller);
if(CALLER_SUSPENDED.equals(state))
{
// Only reactivate thread when previously suspended.
caller.resume();
}
callers.put(caller, CALLER_RESUMED);
}
}
}
}
if(listeners!=null)
{
for(int i=0; i<listeners.size(); i++)
{
notifyListener((IResultListener)listeners.get(i));
}
}
}
/**
* Add a result listener.
* @param listsner The listener.
*/
public void addResultListener(IResultListener listener)
{
if(listener==null)
throw new RuntimeException();
boolean notify = false;
synchronized(this)
{
if(resultavailable)
{
notify = true;
}
else
{
if(listeners==null)
listeners = new ArrayList();
listeners.add(listener);
}
}
if(notify)
notifyListener(listener);
}
/**
* Notify a result listener.
* @param listener The listener.
*/
protected void notifyListener(IResultListener listener)
{
// todo: source?
try
{
if(exception!=null)
{
listener.exceptionOccurred(this, exception);
}
else
{
listener.resultAvailable(this, result);
}
}
catch(Exception e)
{
e.printStackTrace();
}
}
/**
* Main for testing.
*/
public static void main(String[] args) throws Exception
{
final Future f = new Future();
f.addResultListener(new IResultListener()
{
public void resultAvailable(Object source, Object result)
{
System.out.println(Thread.currentThread().getName()+": listener: "+result);
}
public void exceptionOccurred(Object source, Exception exception)
{
}
});
Thread t = new Thread(new Runnable()
{
public void run()
{
try
{
System.out.println(Thread.currentThread().getName()+": waiting for 1 sec");
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName()+": setting result");
f.setResult("my result");
}
catch(Exception e)
{
e.printStackTrace();
}
}
});
t.start();
System.out.println(Thread.currentThread().getName()+": waiting for result");
Object result = f.get(new ThreadSuspendable(new Object()));
System.out.println(Thread.currentThread().getName()+": result is: "+result);
}
}