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 }