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 * | |------------>| |------------>| |
56 * -->| STOPPED | | STARTED | | TRIGGERED |
57 * | |<------------| |<------------| |
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>
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>
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> <= 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> <= 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> <= 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> <= 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 }