View Javadoc
1   //******************************************************************************
2   //
3   // File:    Timer.java
4   // Package: edu.rit.util
5   // Unit:    Class edu.rit.util.Timer
6   //
7   // This Java source file is copyright (C) 2002-2004 by Alan Kaminsky. All rights
8   // reserved. For further information, contact the author, Alan Kaminsky, at
9   // ark@cs.rit.edu.
10  //
11  // This Java source file is part of the Parallel Java Library ("PJ"). PJ is free
12  // software; you can redistribute it and/or modify it under the terms of the GNU
13  // General Public License as published by the Free Software Foundation; either
14  // version 3 of the License, or (at your option) any later version.
15  //
16  // PJ is distributed in the hope that it will be useful, but WITHOUT ANY
17  // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
18  // A PARTICULAR PURPOSE. See the GNU General Public License for more details.
19  //
20  // Linking this library statically or dynamically with other modules is making a
21  // combined work based on this library. Thus, the terms and conditions of the GNU
22  // General Public License cover the whole combination.
23  //
24  // As a special exception, the copyright holders of this library give you
25  // permission to link this library with independent modules to produce an
26  // executable, regardless of the license terms of these independent modules, and
27  // to copy and distribute the resulting executable under terms of your choice,
28  // provided that you also meet, for each linked independent module, the terms
29  // and conditions of the license of that module. An independent module is a module
30  // which is not derived from or based on this library. If you modify this library,
31  // you may extend this exception to your version of the library, but you are not
32  // obligated to do so. If you do not wish to do so, delete this exception
33  // statement from your version.
34  //
35  // A copy of the GNU General Public License is provided in the file gpl.txt. You
36  // may also obtain a copy of the GNU General Public License on the World Wide
37  // Web at http://www.gnu.org/licenses/gpl.html.
38  //
39  //******************************************************************************
40  package edu.rit.util;
41  
42  import java.util.Date;
43  
44  /**
45   * Class Timer controls the execution of a {@linkplain TimerTask}'s timed
46   * actions.
47   * <P>
48   * A timer is in one of three states as shown by this state diagram:
49   *
50   * <PRE>
51   *       +---+                   +---+
52   *       |   | stop              |   | start
53   *       |   v                   |   v
54   *    +---------+    start    +---------+   timeout   +-----------+
55   *    |         |------------&gt;|         |------------&gt;|           |
56   * --&gt;| STOPPED |             | STARTED |             | TRIGGERED |
57   *    |         |&lt;------------|         |&lt;------------|           |
58   *    +---------+     stop    +---------+    start    +-----------+
59   *       ^   ^                     ^                     |     |
60   *       |   |                     |            action() |     | stop
61   *       |   |                     |           performed |     |
62   *       |   |                     |                     |     |
63   *       |   |                     | periodic timeout    |     |
64   *       |   |                     +---------------------+     |
65   *       |   |                       one-shot timeout    |     |
66   *       |   +-------------------------------------------+     |
67   *       |                                                     |
68   *       +-----------------------------------------------------+
69   * </PRE>
70   * <P>
71   * When a timer is created, it is associated with a {@linkplain TimerTask}. The
72   * timer is initially <B>stopped.</B> The timer can then be <B>started</B> in
73   * various ways. The timer will then <B>time out</B> at some instant, depending
74   * on how it was started. When the timer times out, the timer becomes
75   * <B>triggered,</B> and the timer's timer task's <code>action()</code> method is
76   * called. The timer task can stop the timer, or it can start the timer again
77   * for a different timeout, or it can leave the timer alone. If the timer task
78   * does not start or stop the timer, the timer goes into a state determined by
79   * the way the timer was originally started. If the timer was started with a
80   * <B>periodic timeout,</B> the timer automatically starts itself to time out
81   * again after a certain interval. If the timer was started with a <B>one-shot
82   * timeout,</B> the timer automatically stops itself.
83   * <P>
84   * By calling the appropriate method, a timer may be started to do timeouts in
85   * any of the following ways:
86   * <UL>
87   * <LI>
88   * <B>One-shot timeout.</B> The timer task's action is performed just once at a
89   * certain point in time, specified either as an absolute time or as an interval
90   * from now.
91   * <BR>&nbsp;
92   * <LI>
93   * <B>Periodic fixed-rate timeout.</B> The timer task's action is performed
94   * repeatedly at a given interval. The first action happens at a certain point
95   * in time, specified either as an absolute time or as an interval from now.
96   * Thereafter, each subsequent action starts at the given repetition interval
97   * after the <I>scheduled start</I> of the previous action. Thus, the interval
98   * between the scheduled starts of successive actions is always the same,
99   * regardless of how long each action takes to complete, and the interval
100  * between successive actions may vary, depending on how long each action takes
101  * to complete.
102  * <BR>&nbsp;
103  * <LI>
104  * <B>Periodic fixed-interval timeout.</B> The timer task's action is performed
105  * repeatedly at a given interval. The first action happens at a certain point
106  * in time, specified either as an absolute time or as an interval from now.
107  * Thereafter, each subsequent action starts at the given repetition interval
108  * after the <I>end</I> of the previous action. Thus, the interval between the
109  * starts of successive actions may vary, depending on how long each action
110  * takes to complete, and the interval between successive actions is always the
111  * same, regardless of how long each action takes to complete.
112  * </UL>
113  * <P>
114  * A Timer object is not constructed directly. Rather, a timer object is created
115  * by calling a {@linkplain TimerThread}'s <code>createTimer()</code> method, giving
116  * a timer task to associate with the timer. A single timer thread can control
117  * any number of timers. The timer thread is responsible for doing all the
118  * timers' timeouts and causing the timers to call the timer tasks'
119  * <code>action()</code> methods at the proper times. Since a timer thread does not
120  * provide real-time guarantees, the time at which a timer task's action
121  * actually starts may be later than when the timeout occurred.
122  * <P>
123  * Classes Timer, {@linkplain TimerTask}, and {@linkplain TimerThread} provide
124  * capabilities similar to classes java.util.Timer and java.util.TimerTask.
125  * Unlike the latter, they also provide the ability to stop and restart a timer
126  * and the ability to deal with race conditions in multithreaded programs.
127  *
128  * @author Alan Kaminsky
129  * @version 15-Dec-2002
130  */
131 public class Timer {
132 
133 // Hidden data members.
134     // Timer thread which created this timer.
135     private TimerThread myTimerThread;
136 
137     // Associated timer task.
138     private TimerTask myTimerTask;
139 
140     // State of this timer.
141     private int myState = STOPPED;
142     private static final int STOPPED = 0;
143     private static final int STARTED = 1;
144     private static final int TRIGGERED = 2;
145 
146     // Kind of timeout.
147     private int myKind;
148     private static final int ONE_SHOT_TIMEOUT = 0;
149     private static final int FIXED_RATE_TIMEOUT = 1;
150     private static final int FIXED_INTERVAL_TIMEOUT = 2;
151 
152     // Time at which this timer is next scheduled to time out (milliseconds
153     // since midnight 01-Jan-1970 UTC).
154     private long myTimeout;
155 
156     // Interval for periodic timeouts (milliseconds).
157     private long myInterval;
158 
159 // Hidden constructors.
160     /**
161      * Construct a new timer.
162      *
163      * @param theTimerThread Timer thread.
164      * @param theTimerTask Timer task.
165      *
166      * @exception NullPointerException (unchecked exception) Thrown if
167      * <code>theTimerTask</code> is null.
168      */
169     Timer(TimerThread theTimerThread,
170             TimerTask theTimerTask) {
171         if (theTimerTask == null) {
172             throw new NullPointerException();
173         }
174         myTimerThread = theTimerThread;
175         myTimerTask = theTimerTask;
176     }
177 
178 // Exported operations.
179     /**
180      * Start this timer with a one-shot timeout at the given absolute time. If
181      * the time denotes a point in the past, the action is performed
182      * immediately.
183      *
184      * @param theTime Absolute time at which to perform the action.
185      */
186     public synchronized void start(Date theTime) {
187         myState = STARTED;
188         myKind = ONE_SHOT_TIMEOUT;
189         myTimeout = theTime.getTime();
190         myTimerThread.schedule(myTimeout, this);
191     }
192 
193     /**
194      * Start this timer with a one-shot timeout at the given interval from now.
195      * If the interval is less than or equal to zero, the action is performed
196      * immediately.
197      *
198      * @param theInterval Timeout interval before performing the action
199      * (milliseconds).
200      */
201     public synchronized void start(long theInterval) {
202         myState = STARTED;
203         myKind = ONE_SHOT_TIMEOUT;
204         myTimeout = System.currentTimeMillis() + theInterval;
205         myTimerThread.schedule(myTimeout, this);
206     }
207 
208     /**
209      * Start this timer with a periodic fixed-rate timeout starting at the given
210      * absolute time. If the start time denotes a point in the past, the first
211      * action is performed immediately. Each subsequent action is started at the
212      * given repetition interval after the scheduled start of the previous
213      * action.
214      *
215      * @param theFirstTime Absolute time at which to perform the first action.
216      * @param theRepetitionInterval Interval between successive actions
217      * (milliseconds).
218      * @exception IllegalArgumentException (unchecked exception) Thrown if
219      * <code>theRepetitionInterval</code> &lt;= 0.
220      */
221     public synchronized void start(Date theFirstTime,
222             long theRepetitionInterval) {
223         if (theRepetitionInterval <= 0) {
224             throw new IllegalArgumentException();
225         }
226         myState = STARTED;
227         myKind = FIXED_RATE_TIMEOUT;
228         myTimeout = theFirstTime.getTime();
229         myInterval = theRepetitionInterval;
230         myTimerThread.schedule(myTimeout, this);
231     }
232 
233     /**
234      * Start this timer with a periodic fixed-rate timeout starting at the given
235      * interval from now. If the interval is less than or equal to zero, the
236      * first action is performed immediately. Each subsequent action is started
237      * at the given repetition interval after the scheduled start of the
238      * previous action.
239      *
240      * @param theFirstInterval Timeout interval before performing the first
241      * action (milliseconds).
242      * @param theRepetitionInterval Interval between successive actions
243      * (milliseconds).
244      * @exception IllegalArgumentException (unchecked exception) Thrown if
245      * <code>theRepetitionInterval</code> &lt;= 0.
246      */
247     public synchronized void start(long theFirstInterval,
248             long theRepetitionInterval) {
249         if (theRepetitionInterval <= 0) {
250             throw new IllegalArgumentException();
251         }
252         myState = STARTED;
253         myKind = FIXED_RATE_TIMEOUT;
254         myTimeout = System.currentTimeMillis() + theFirstInterval;
255         myInterval = theRepetitionInterval;
256         myTimerThread.schedule(myTimeout, this);
257     }
258 
259     /**
260      * Start this timer with a periodic fixed-interval timeout starting at the
261      * given absolute time. If the start time denotes a point in the past, the
262      * first action is performed immediately. Each subsequent action is started
263      * at the given repetition interval after the end of the previous action.
264      *
265      * @param theFirstTime Absolute time at which to perform the first action.
266      * @param theRepetitionInterval Interval between successive actions
267      * (milliseconds).
268      * @exception IllegalArgumentException (unchecked exception) Thrown if
269      * <code>theRepetitionInterval</code> &lt;= 0.
270      */
271     public synchronized void startFixedIntervalTimeout(Date theFirstTime,
272             long theRepetitionInterval) {
273         if (theRepetitionInterval <= 0) {
274             throw new IllegalArgumentException();
275         }
276         myState = STARTED;
277         myKind = FIXED_INTERVAL_TIMEOUT;
278         myTimeout = theFirstTime.getTime();
279         myInterval = theRepetitionInterval;
280         myTimerThread.schedule(myTimeout, this);
281     }
282 
283     /**
284      * Start this timer with a periodic fixed-interval timeout starting at the
285      * given interval from now. If the interval is less than or equal to zero,
286      * the first action is performed immediately. Each subsequent action is
287      * started at the given repetition interval after the end of the previous
288      * action.
289      *
290      * @param theFirstInterval Timeout interval before performing the first
291      * action (milliseconds).
292      * @param theRepetitionInterval Interval between successive actions
293      * (milliseconds).
294      * @exception IllegalArgumentException (unchecked exception) Thrown if
295      * <code>theRepetitionInterval</code> &lt;= 0.
296      */
297     public synchronized void startFixedIntervalTimeout(long theFirstInterval,
298             long theRepetitionInterval) {
299         if (theRepetitionInterval <= 0) {
300             throw new IllegalArgumentException();
301         }
302         myState = STARTED;
303         myKind = FIXED_INTERVAL_TIMEOUT;
304         myTimeout = System.currentTimeMillis() + theFirstInterval;
305         myInterval = theRepetitionInterval;
306         myTimerThread.schedule(myTimeout, this);
307     }
308 
309     /**
310      * Stop this timer.
311      */
312     public synchronized void stop() {
313         myState = STOPPED;
314     }
315 
316     /**
317      * Determine whether this timer is stopped.
318      *
319      * @return True if this timer is in the stopped state, false otherwise.
320      */
321     public synchronized boolean isStopped() {
322         return myState == STOPPED;
323     }
324 
325     /**
326      * Determine whether this timer is started.
327      *
328      * @return True if this timer is in the started state, false otherwise.
329      */
330     public synchronized boolean isStarted() {
331         return myState == STARTED;
332     }
333 
334     /**
335      * Determine whether this timer is triggered.
336      *
337      * @return True if this timer is in the triggered state, false otherwise.
338      */
339     public synchronized boolean isTriggered() {
340         return myState == TRIGGERED;
341     }
342 
343     /**
344      * Determine the time when this timer is or was scheduled to time out.
345      * <P>
346      * If the <code>getTimeout()</code> method is called when this timer is in the
347      * stopped state, then <code>Long.MAX_VALUE</code> is returned.
348      * <P>
349      * If the <code>getTimeout()</code> method is called when this timer is in the
350      * started state, then the <code>getTimeout()</code> method returns the time
351      * when the next timeout will occur.
352      * <P>
353      * If the <code>getTimeout()</code> method is called when this timer is in the
354      * triggered state, such as by this timer's timer task's <code>action()</code>
355      * method, then the <code>getTimeout()</code> method returns the time when the
356      * present timeout was <I>scheduled</I> to occur. Since a timer thread
357      * provides no real-time guarantees, the time when the timeout
358      * <I>actually</I> occurred may be some time later. If desired, the caller
359      * can compare the scheduled timeout to the actual time and decide whether
360      * it's too late to perform the action.
361      *
362      * @return Time of scheduled timeout (milliseconds since midnight
363      * 01-Jan-1970 UTC).
364      */
365     public synchronized long getTimeout() {
366         return myState == STOPPED ? Long.MAX_VALUE : myTimeout;
367     }
368 
369     /**
370      * Returns this timer's timer task.
371      *
372      * @return a {@link edu.rit.util.TimerTask} object.
373      */
374     public TimerTask getTimerTask() {
375         return myTimerTask;
376     }
377 
378 // Hidden operations.
379     /**
380      * Trigger this timer.
381      *
382      * @param theTriggerTime Time at which the trigger occurred (milliseconds
383      * since midnight 01-Jan-1970 UTC).
384      */
385     void trigger(long theTriggerTime) {
386         synchronized (this) {
387             // Make sure we're started and it really is time to trigger.
388             if (myState != STARTED || myTimeout > theTriggerTime) {
389                 return;
390             }
391 
392             // Switch to the triggered state.
393             myState = TRIGGERED;
394         }
395 
396         // Call the timer task's action() method. Do it outside the synchronized
397         // block, otherwise a deadlock may happen if another thread tries to
398         // start or stop the timer.
399         myTimerTask.action(this);
400 
401         synchronized (this) {
402             // Decide whether to do an automatic restart.
403             if (myState != TRIGGERED) {
404                 // Someone already stopped or restarted us. Do nothing.
405             } else if (myKind == FIXED_RATE_TIMEOUT) {
406                 myState = STARTED;
407                 myTimeout += myInterval;
408                 myTimerThread.schedule(myTimeout, this);
409             } else if (myKind == FIXED_INTERVAL_TIMEOUT) {
410                 myState = STARTED;
411                 myTimeout = System.currentTimeMillis() + myInterval;
412                 myTimerThread.schedule(myTimeout, this);
413             } else // ONE_SHOT_TIMEOUT
414             {
415                 myState = STOPPED;
416             }
417         }
418     }
419 
420 }