/************************************************* * * = PACKAGE * ACE.Reactor * * = FILENAME * TimerQueue.java * *@author Prashant Jain * *************************************************/ package ACE.Reactor; import java.util.*; import ACE.ASX.*; class TimerNode { public TimerNode (EventHandler handler, Object arg, TimeValue timerValue, TimeValue interval, TimerNode next, int timerId) { this.handler_ = handler; this.arg_ = arg; this.timerValue_ = timerValue; this.interval_ = interval; this.next_ = next; this.timerId_ = timerId; } public EventHandler handler_; // Handler to invoke on when a timeout occurs. public Object arg_; // Argument to pass to . public TimeValue timerValue_; // Time until the timer expires. public TimeValue interval_; // If this is a periodic timer this holds the time until the next // timeout. public TimerNode next_; // Pointer to next timer. public int timerId_; // Id of this timer (used to cancel timers before they expire). } class WaitObject extends TimedWait { public boolean condition () { return this.condition_; } public void condition (boolean c) { this.condition_ = c; } private boolean condition_ = false; } /** *
*

SYNOPSIS

*
* Class that provides an interface to timers. * *
* *

DESCRIPTION

*
* This is a simple implementation that keeps a linked list of * absolute timers. It allows multiple timers to be scheduled * and returns a timer id for each timer scheduled. In addition, * it allows periodic timers to be scheduled. *
*/ public class TimerQueue implements Runnable { /** * Default Constructor */ public TimerQueue () { new Thread (this).start (); } /** * This is the work horse of TimerQueue. It forms the event loop and * takes care of all scheduling. */ public void run () { TimeValue timeout = null; TimeValue earliest = null; for (;;) { synchronized (this.obj_) { earliest = this.earliestTime (); if (earliest != null) timeout = TimeValue.minus (earliest, TimeValue.getTimeOfDay ()); else timeout = new TimeValue (); try { // Extract the earliest time from the queue and do a timed wait this.obj_.timedWait (timeout); // We have been notified. Check to see if we need to // restart the wait with a different timeout if (this.reset_) { this.reset_ = false; this.obj_.condition (false); timeout = TimeValue.minus (this.earliestTime (), TimeValue.getTimeOfDay ()); } } catch (TimeoutException e) { // Timeout occurred. Call handleTimeout on appropriate // Event Handlers this.dispatchHandlers (); } catch (InterruptedException e) { } } } } /** * Check if the queue is empty. *@return true if queue is empty, else false. */ boolean isEmpty () { return this.head_ == null; } /** * Get the node of the earliest node in the TimerQueue. *@return the time of the earlier node in the TimerQueue. */ TimeValue earliestTime () { synchronized (this.obj_) { if (!this.isEmpty ()) return this.head_.timerValue_; else return null; } } /** * Schedule an that will expire after amount * of time. If it expires then is passed in as the value to * the 's callback method. This method * returns a timer id that uniquely identifies the timer and can be * used to cancel the timer before it expires. *@param handler Event Handler that is to be scheduled with the timer *@param obj Object that is passed back to the Event Handler when * timeout occurs (Asynchronous Completion Token) *@param delta amount of time for which to schedule the timer *@return id of the timer scheduled */ public int scheduleTimer (EventHandler handler, Object obj, TimeValue delta) { return this.scheduleTimer (handler, obj, delta, TimeValue.zero); } /** * Schedule an that will expire after amount * of time. If it expires then is passed in as the value to * the 's callback method. If * is != to then it is used to * reschedule the automatically. This method * returns a timer id that uniquely identifies the timer and can be * used to cancel the timer before it expires. *@param handler Event Handler that is to be scheduled with the timer *@param arg Object that is passed back to the Event Handler when * timeout occurs (Asynchronous Completion Token) *@param timeout amount of time for which to schedule the timer *@param interval amount of time to use to reschedule the timer *@return id of the timer scheduled */ public int scheduleTimer (EventHandler handler, Object arg, TimeValue timeout, TimeValue interval) { // Increment the sequence number (it will wrap around). this.timerId_++; TimeValue futureTime = TimeValue.plus (timeout, TimeValue.getTimeOfDay ()); TimerNode node = new TimerNode (handler, arg, futureTime, interval, null, this.timerId_); synchronized (this.obj_) { if (this.isEmpty () || futureTime.lessThan (this.earliestTime ())) { // Insert the node into (the beginning of) the queue to be // scheduled. this.reschedule (node); // Notify the waiting thread so that it can reschedule // using the earliest timeout this.obj_.notify (); } else // Place in the middle of the list somewhere. { this.reschedule (node); } } return this.timerId_; } /** * Cancel the single timer associated with . *@param timerId id of the timer that needs to be cancelled. *@return Object that was passed in when timer was scheduled * (Asynchronous Completion Token). */ public Object cancelTimer (int timerId) { TimerNode prev = null; TimerNode curr = null; // Try to locate the TimerNode that matches the timerId. for (curr = this.head_; curr != null && curr.timerId_ != timerId; curr = curr.next_) prev = curr; if (curr != null) { if (prev == null) this.head_ = curr.next_; else prev.next_ = curr.next_; return curr.arg_; } return null; } /** * Cancel all timers associated with . *@param handler Event Handler whose associated timers need to be cancelled. */ public void cancelTimer (EventHandler handler) { TimerNode prev = null; TimerNode curr = this.head_; while (curr != null) { if (curr.handler_ == handler) { if (prev == null) { this.head_ = curr.next_; curr = this.head_; } else { prev.next_ = curr.next_; curr = prev.next_; } } else { prev = curr; curr = curr.next_; } } } // Call handleTimeout() on all handlers whose timers have expired. private void dispatchHandlers () { TimeValue currentTime = TimeValue.getTimeOfDay (); for (;;) { if (this.isEmpty () || this.earliestTime ().greaterThan (currentTime)) break; // There aren't any more timers eligible to expire. TimerNode expired = this.head_; EventHandler handler = expired.handler_; Object arg = expired.arg_; int result; this.head_ = this.head_.next_; // Check whether this is an interval timer. if (expired.interval_.greaterThan (TimeValue.zero)) { // Make sure that we skip past values that have already // "expired". do expired.timerValue_.plusEquals (expired.interval_); while (expired.timerValue_.lessThanEqual (currentTime)); // Since this is an interval timer, we need to reschedule // it. this.reschedule (expired); } // Perform the callback. result = handler.handleTimeout (currentTime, arg); if (result == -1) this.cancelTimer (handler); } } // Reschedule a TimerNode by inserting it at the appropriate // position in the queue. private void reschedule (TimerNode expired) { if (this.isEmpty () || expired.timerValue_.lessThan (this.earliestTime ())) { expired.next_ = this.head_; this.head_ = expired; // Set the condition to true so that the waiting thread can be // notified and it can reschedule. this.obj_.condition (true); this.reset_ = true; } else { TimerNode prev = this.head_; TimerNode after = this.head_.next_; // Locate the proper position in the queue. while (after != null && expired.timerValue_.greaterThan (after.timerValue_)) { prev = after; after = after.next_; } expired.next_ = after; prev.next_ = expired; } } private WaitObject obj_ = new WaitObject (); // Synchronization object (as well as object to use to do wait on) private TimerNode head_; // Pointer to linked list of TimerHandles. private int timerId_; // Keeps track of the timer id that uniquely identifies each timer. // This id can be used to cancel a timer via the // method. private boolean reset_; // Flag indicating whether to start the wait again }