| /* Timer.java -- Timer that runs TimerTasks at a later time. | |
| Copyright (C) 2000, 2001, 2005 Free Software Foundation, Inc. | |
| This file is part of GNU Classpath. | |
| GNU Classpath is free software; you can redistribute it and/or modify | |
| it under the terms of the GNU General Public License as published by | |
| the Free Software Foundation; either version 2, or (at your option) | |
| any later version. | |
| GNU Classpath is distributed in the hope that it will be useful, but | |
| WITHOUT ANY WARRANTY; without even the implied warranty of | |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
| General Public License for more details. | |
| You should have received a copy of the GNU General Public License | |
| along with GNU Classpath; see the file COPYING. If not, write to the | |
| Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | |
| 02110-1301 USA. | |
| Linking this library statically or dynamically with other modules is | |
| making a combined work based on this library. Thus, the terms and | |
| conditions of the GNU General Public License cover the whole | |
| combination. | |
| As a special exception, the copyright holders of this library give you | |
| permission to link this library with independent modules to produce an | |
| executable, regardless of the license terms of these independent | |
| modules, and to copy and distribute the resulting executable under | |
| terms of your choice, provided that you also meet, for each linked | |
| independent module, the terms and conditions of the license of that | |
| module. An independent module is a module which is not derived from | |
| or based on this library. If you modify this library, you may extend | |
| this exception to your version of the library, but you are not | |
| obligated to do so. If you do not wish to do so, delete this | |
| exception statement from your version. */ | |
| package java.util; | |
| /** | |
| * Timer that can run TimerTasks at a later time. | |
| * TimerTasks can be scheduled for one time execution at some time in the | |
| * future. They can be scheduled to be rescheduled at a time period after the | |
| * task was last executed. Or they can be scheduled to be executed repeatedly | |
| * at a fixed rate. | |
| * <p> | |
| * The normal scheduling will result in a more or less even delay in time | |
| * between successive executions, but the executions could drift in time if | |
| * the task (or other tasks) takes a long time to execute. Fixed delay | |
| * scheduling guarantees more or less that the task will be executed at a | |
| * specific time, but if there is ever a delay in execution then the period | |
| * between successive executions will be shorter. The first method of | |
| * repeated scheduling is preferred for repeated tasks in response to user | |
| * interaction, the second method of repeated scheduling is preferred for tasks | |
| * that act like alarms. | |
| * <p> | |
| * The Timer keeps a binary heap as a task priority queue which means that | |
| * scheduling and serving of a task in a queue of n tasks costs O(log n). | |
| * | |
| * @see TimerTask | |
| * @since 1.3 | |
| * @author Mark Wielaard (mark@klomp.org) | |
| */ | |
| public class Timer | |
| { | |
| /** | |
| * Priority Task Queue. | |
| * TimerTasks are kept in a binary heap. | |
| * The scheduler calls sleep() on the queue when it has nothing to do or | |
| * has to wait. A sleeping scheduler can be notified by calling interrupt() | |
| * which is automatically called by the enqueue(), cancel() and | |
| * timerFinalized() methods. | |
| */ | |
| private static final class TaskQueue | |
| { | |
| /** Default size of this queue */ | |
| private static final int DEFAULT_SIZE = 32; | |
| /** Whether to return null when there is nothing in the queue */ | |
| private boolean nullOnEmpty; | |
| /** | |
| * The heap containing all the scheduled TimerTasks | |
| * sorted by the TimerTask.scheduled field. | |
| * Null when the stop() method has been called. | |
| */ | |
| private TimerTask heap[]; | |
| /** | |
| * The actual number of elements in the heap | |
| * Can be less then heap.length. | |
| * Note that heap[0] is used as a sentinel. | |
| */ | |
| private int elements; | |
| /** | |
| * Creates a TaskQueue of default size without any elements in it. | |
| */ | |
| public TaskQueue() | |
| { | |
| heap = new TimerTask[DEFAULT_SIZE]; | |
| elements = 0; | |
| nullOnEmpty = false; | |
| } | |
| /** | |
| * Adds a TimerTask at the end of the heap. | |
| * Grows the heap if necessary by doubling the heap in size. | |
| */ | |
| private void add(TimerTask task) | |
| { | |
| elements++; | |
| if (elements == heap.length) | |
| { | |
| TimerTask new_heap[] = new TimerTask[heap.length * 2]; | |
| System.arraycopy(heap, 0, new_heap, 0, heap.length); | |
| heap = new_heap; | |
| } | |
| heap[elements] = task; | |
| } | |
| /** | |
| * Removes the last element from the heap. | |
| * Shrinks the heap in half if | |
| * elements+DEFAULT_SIZE/2 <= heap.length/4. | |
| */ | |
| private void remove() | |
| { | |
| // clear the entry first | |
| heap[elements] = null; | |
| elements--; | |
| if (elements + DEFAULT_SIZE / 2 <= (heap.length / 4)) | |
| { | |
| TimerTask new_heap[] = new TimerTask[heap.length / 2]; | |
| System.arraycopy(heap, 0, new_heap, 0, elements + 1); | |
| heap = new_heap; | |
| } | |
| } | |
| /** | |
| * Adds a task to the queue and puts it at the correct place | |
| * in the heap. | |
| */ | |
| public synchronized void enqueue(TimerTask task) | |
| { | |
| // Check if it is legal to add another element | |
| if (heap == null) | |
| { | |
| throw new IllegalStateException | |
| ("cannot enqueue when stop() has been called on queue"); | |
| } | |
| heap[0] = task; // sentinel | |
| add(task); // put the new task at the end | |
| // Now push the task up in the heap until it has reached its place | |
| int child = elements; | |
| int parent = child / 2; | |
| while (heap[parent].scheduled > task.scheduled) | |
| { | |
| heap[child] = heap[parent]; | |
| child = parent; | |
| parent = child / 2; | |
| } | |
| // This is the correct place for the new task | |
| heap[child] = task; | |
| heap[0] = null; // clear sentinel | |
| // Maybe sched() is waiting for a new element | |
| this.notify(); | |
| } | |
| /** | |
| * Returns the top element of the queue. | |
| * Can return null when no task is in the queue. | |
| */ | |
| private TimerTask top() | |
| { | |
| if (elements == 0) | |
| { | |
| return null; | |
| } | |
| else | |
| { | |
| return heap[1]; | |
| } | |
| } | |
| /** | |
| * Returns the top task in the Queue. | |
| * Removes the element from the heap and reorders the heap first. | |
| * Can return null when there is nothing in the queue. | |
| */ | |
| public synchronized TimerTask serve() | |
| { | |
| // The task to return | |
| TimerTask task = null; | |
| while (task == null) | |
| { | |
| // Get the next task | |
| task = top(); | |
| // return null when asked to stop | |
| // or if asked to return null when the queue is empty | |
| if ((heap == null) || (task == null && nullOnEmpty)) | |
| { | |
| return null; | |
| } | |
| // Do we have a task? | |
| if (task != null) | |
| { | |
| // The time to wait until the task should be served | |
| long time = task.scheduled - System.currentTimeMillis(); | |
| if (time > 0) | |
| { | |
| // This task should not yet be served | |
| // So wait until this task is ready | |
| // or something else happens to the queue | |
| task = null; // set to null to make sure we call top() | |
| try | |
| { | |
| this.wait(time); | |
| } | |
| catch (InterruptedException _) | |
| { | |
| } | |
| } | |
| } | |
| else | |
| { | |
| // wait until a task is added | |
| // or something else happens to the queue | |
| try | |
| { | |
| this.wait(); | |
| } | |
| catch (InterruptedException _) | |
| { | |
| } | |
| } | |
| } | |
| // reconstruct the heap | |
| TimerTask lastTask = heap[elements]; | |
| remove(); | |
| // drop lastTask at the beginning and move it down the heap | |
| int parent = 1; | |
| int child = 2; | |
| heap[1] = lastTask; | |
| while (child <= elements) | |
| { | |
| if (child < elements) | |
| { | |
| if (heap[child].scheduled > heap[child + 1].scheduled) | |
| { | |
| child++; | |
| } | |
| } | |
| if (lastTask.scheduled <= heap[child].scheduled) | |
| break; // found the correct place (the parent) - done | |
| heap[parent] = heap[child]; | |
| parent = child; | |
| child = parent * 2; | |
| } | |
| // this is the correct new place for the lastTask | |
| heap[parent] = lastTask; | |
| // return the task | |
| return task; | |
| } | |
| /** | |
| * When nullOnEmpty is true the serve() method will return null when | |
| * there are no tasks in the queue, otherwise it will wait until | |
| * a new element is added to the queue. It is used to indicate to | |
| * the scheduler that no new tasks will ever be added to the queue. | |
| */ | |
| public synchronized void setNullOnEmpty(boolean nullOnEmpty) | |
| { | |
| this.nullOnEmpty = nullOnEmpty; | |
| this.notify(); | |
| } | |
| /** | |
| * When this method is called the current and all future calls to | |
| * serve() will return null. It is used to indicate to the Scheduler | |
| * that it should stop executing since no more tasks will come. | |
| */ | |
| public synchronized void stop() | |
| { | |
| this.heap = null; | |
| this.elements = 0; | |
| this.notify(); | |
| } | |
| /** | |
| * Remove all canceled tasks from the queue. | |
| */ | |
| public synchronized int purge() | |
| { | |
| int removed = 0; | |
| // Null out any elements that are canceled. Skip element 0 as | |
| // it is the sentinel. | |
| for (int i = elements; i > 0; --i) | |
| { | |
| if (heap[i].scheduled < 0) | |
| { | |
| ++removed; | |
| // Remove an element by pushing the appropriate child | |
| // into place, and then iterating to the bottom of the | |
| // tree. | |
| int index = i; | |
| while (heap[index] != null) | |
| { | |
| int child = 2 * index; | |
| if (child >= heap.length) | |
| { | |
| // Off end; we're done. | |
| heap[index] = null; | |
| break; | |
| } | |
| if (child + 1 >= heap.length || heap[child + 1] == null) | |
| { | |
| // Nothing -- we're done. | |
| } | |
| else if (heap[child] == null | |
| || (heap[child].scheduled | |
| > heap[child + 1].scheduled)) | |
| ++child; | |
| heap[index] = heap[child]; | |
| index = child; | |
| } | |
| } | |
| } | |
| // Make a new heap if we shrank enough. | |
| int newLen = heap.length; | |
| while (elements - removed + DEFAULT_SIZE / 2 <= newLen / 4) | |
| newLen /= 2; | |
| if (newLen != heap.length) | |
| { | |
| TimerTask[] newHeap = new TimerTask[newLen]; | |
| System.arraycopy(heap, 0, newHeap, 0, elements + 1); | |
| heap = newHeap; | |
| } | |
| return removed; | |
| } | |
| } // TaskQueue | |
| /** | |
| * The scheduler that executes all the tasks on a particular TaskQueue, | |
| * reschedules any repeating tasks and that waits when no task has to be | |
| * executed immediately. Stops running when canceled or when the parent | |
| * Timer has been finalized and no more tasks have to be executed. | |
| */ | |
| private static final class Scheduler implements Runnable | |
| { | |
| // The priority queue containing all the TimerTasks. | |
| private TaskQueue queue; | |
| /** | |
| * Creates a new Scheduler that will schedule the tasks on the | |
| * given TaskQueue. | |
| */ | |
| public Scheduler(TaskQueue queue) | |
| { | |
| this.queue = queue; | |
| } | |
| public void run() | |
| { | |
| TimerTask task; | |
| while ((task = queue.serve()) != null) | |
| { | |
| // If this task has not been canceled | |
| if (task.scheduled >= 0) | |
| { | |
| // Mark execution time | |
| task.lastExecutionTime = task.scheduled; | |
| // Repeatable task? | |
| if (task.period < 0) | |
| { | |
| // Last time this task is executed | |
| task.scheduled = -1; | |
| } | |
| // Run the task | |
| try | |
| { | |
| task.run(); | |
| } | |
| catch (ThreadDeath death) | |
| { | |
| // If an exception escapes, the Timer becomes invalid. | |
| queue.stop(); | |
| throw death; | |
| } | |
| catch (Throwable t) | |
| { | |
| // If an exception escapes, the Timer becomes invalid. | |
| queue.stop(); | |
| } | |
| } | |
| // Calculate next time and possibly re-enqueue. | |
| if (task.scheduled >= 0) | |
| { | |
| if (task.fixed) | |
| { | |
| task.scheduled += task.period; | |
| } | |
| else | |
| { | |
| task.scheduled = task.period + System.currentTimeMillis(); | |
| } | |
| try | |
| { | |
| queue.enqueue(task); | |
| } | |
| catch (IllegalStateException ise) | |
| { | |
| // Ignore. Apparently the Timer queue has been stopped. | |
| } | |
| } | |
| } | |
| } | |
| } // Scheduler | |
| // Number of Timers created. | |
| // Used for creating nice Thread names. | |
| private static int nr; | |
| // The queue that all the tasks are put in. | |
| // Given to the scheduler | |
| private TaskQueue queue; | |
| // The Scheduler that does all the real work | |
| private Scheduler scheduler; | |
| // Used to run the scheduler. | |
| // Also used to checked if the Thread is still running by calling | |
| // thread.isAlive(). Sometimes a Thread is suddenly killed by the system | |
| // (if it belonged to an Applet). | |
| private Thread thread; | |
| // When cancelled we don't accept any more TimerTasks. | |
| private boolean canceled; | |
| /** | |
| * Creates a new Timer with a non daemon Thread as Scheduler, with normal | |
| * priority and a default name. | |
| */ | |
| public Timer() | |
| { | |
| this(false); | |
| } | |
| /** | |
| * Creates a new Timer with a daemon Thread as scheduler if daemon is true, | |
| * with normal priority and a default name. | |
| */ | |
| public Timer(boolean daemon) | |
| { | |
| this(daemon, Thread.NORM_PRIORITY); | |
| } | |
| /** | |
| * Create a new Timer whose Thread has the indicated name. It will have | |
| * normal priority and will not be a daemon thread. | |
| * @param name the name of the Thread | |
| * @since 1.5 | |
| */ | |
| public Timer(String name) | |
| { | |
| this(false, Thread.NORM_PRIORITY, name); | |
| } | |
| /** | |
| * Create a new Timer whose Thread has the indicated name. It will have | |
| * normal priority. The boolean argument controls whether or not it | |
| * will be a daemon thread. | |
| * @param name the name of the Thread | |
| * @param daemon true if the Thread should be a daemon thread | |
| * @since 1.5 | |
| */ | |
| public Timer(String name, boolean daemon) | |
| { | |
| this(daemon, Thread.NORM_PRIORITY, name); | |
| } | |
| /** | |
| * Creates a new Timer with a daemon Thread as scheduler if daemon is true, | |
| * with the priority given and a default name. | |
| */ | |
| private Timer(boolean daemon, int priority) | |
| { | |
| this(daemon, priority, "Timer-" + (++nr)); | |
| } | |
| /** | |
| * Creates a new Timer with a daemon Thread as scheduler if daemon is true, | |
| * with the priority and name given.E | |
| */ | |
| private Timer(boolean daemon, int priority, String name) | |
| { | |
| canceled = false; | |
| queue = new TaskQueue(); | |
| scheduler = new Scheduler(queue); | |
| thread = new Thread(scheduler, name); | |
| thread.setDaemon(daemon); | |
| thread.setPriority(priority); | |
| thread.start(); | |
| } | |
| /** | |
| * Cancels the execution of the scheduler. If a task is executing it will | |
| * normally finish execution, but no other tasks will be executed and no | |
| * more tasks can be scheduled. | |
| */ | |
| public void cancel() | |
| { | |
| canceled = true; | |
| queue.stop(); | |
| } | |
| /** | |
| * Schedules the task at Time time, repeating every period | |
| * milliseconds if period is positive and at a fixed rate if fixed is true. | |
| * | |
| * @exception IllegalArgumentException if time is negative | |
| * @exception IllegalStateException if the task was already scheduled or | |
| * canceled or this Timer is canceled or the scheduler thread has died | |
| */ | |
| private void schedule(TimerTask task, long time, long period, boolean fixed) | |
| { | |
| if (time < 0) | |
| throw new IllegalArgumentException("negative time"); | |
| if (task.scheduled == 0 && task.lastExecutionTime == -1) | |
| { | |
| task.scheduled = time; | |
| task.period = period; | |
| task.fixed = fixed; | |
| } | |
| else | |
| { | |
| throw new IllegalStateException | |
| ("task was already scheduled or canceled"); | |
| } | |
| if (!this.canceled && this.thread != null) | |
| { | |
| queue.enqueue(task); | |
| } | |
| else | |
| { | |
| throw new IllegalStateException | |
| ("timer was canceled or scheduler thread has died"); | |
| } | |
| } | |
| private static void positiveDelay(long delay) | |
| { | |
| if (delay < 0) | |
| { | |
| throw new IllegalArgumentException("delay is negative"); | |
| } | |
| } | |
| private static void positivePeriod(long period) | |
| { | |
| if (period < 0) | |
| { | |
| throw new IllegalArgumentException("period is negative"); | |
| } | |
| } | |
| /** | |
| * Schedules the task at the specified data for one time execution. | |
| * | |
| * @exception IllegalArgumentException if date.getTime() is negative | |
| * @exception IllegalStateException if the task was already scheduled or | |
| * canceled or this Timer is canceled or the scheduler thread has died | |
| */ | |
| public void schedule(TimerTask task, Date date) | |
| { | |
| long time = date.getTime(); | |
| schedule(task, time, -1, false); | |
| } | |
| /** | |
| * Schedules the task at the specified date and reschedules the task every | |
| * period milliseconds after the last execution of the task finishes until | |
| * this timer or the task is canceled. | |
| * | |
| * @exception IllegalArgumentException if period or date.getTime() is | |
| * negative | |
| * @exception IllegalStateException if the task was already scheduled or | |
| * canceled or this Timer is canceled or the scheduler thread has died | |
| */ | |
| public void schedule(TimerTask task, Date date, long period) | |
| { | |
| positivePeriod(period); | |
| long time = date.getTime(); | |
| schedule(task, time, period, false); | |
| } | |
| /** | |
| * Schedules the task after the specified delay milliseconds for one time | |
| * execution. | |
| * | |
| * @exception IllegalArgumentException if delay or | |
| * System.currentTimeMillis + delay is negative | |
| * @exception IllegalStateException if the task was already scheduled or | |
| * canceled or this Timer is canceled or the scheduler thread has died | |
| */ | |
| public void schedule(TimerTask task, long delay) | |
| { | |
| positiveDelay(delay); | |
| long time = System.currentTimeMillis() + delay; | |
| schedule(task, time, -1, false); | |
| } | |
| /** | |
| * Schedules the task after the delay milliseconds and reschedules the | |
| * task every period milliseconds after the last execution of the task | |
| * finishes until this timer or the task is canceled. | |
| * | |
| * @exception IllegalArgumentException if delay or period is negative | |
| * @exception IllegalStateException if the task was already scheduled or | |
| * canceled or this Timer is canceled or the scheduler thread has died | |
| */ | |
| public void schedule(TimerTask task, long delay, long period) | |
| { | |
| positiveDelay(delay); | |
| positivePeriod(period); | |
| long time = System.currentTimeMillis() + delay; | |
| schedule(task, time, period, false); | |
| } | |
| /** | |
| * Schedules the task at the specified date and reschedules the task at a | |
| * fixed rate every period milliseconds until this timer or the task is | |
| * canceled. | |
| * | |
| * @exception IllegalArgumentException if period or date.getTime() is | |
| * negative | |
| * @exception IllegalStateException if the task was already scheduled or | |
| * canceled or this Timer is canceled or the scheduler thread has died | |
| */ | |
| public void scheduleAtFixedRate(TimerTask task, Date date, long period) | |
| { | |
| positivePeriod(period); | |
| long time = date.getTime(); | |
| schedule(task, time, period, true); | |
| } | |
| /** | |
| * Schedules the task after the delay milliseconds and reschedules the task | |
| * at a fixed rate every period milliseconds until this timer or the task | |
| * is canceled. | |
| * | |
| * @exception IllegalArgumentException if delay or | |
| * System.currentTimeMillis + delay is negative | |
| * @exception IllegalStateException if the task was already scheduled or | |
| * canceled or this Timer is canceled or the scheduler thread has died | |
| */ | |
| public void scheduleAtFixedRate(TimerTask task, long delay, long period) | |
| { | |
| positiveDelay(delay); | |
| positivePeriod(period); | |
| long time = System.currentTimeMillis() + delay; | |
| schedule(task, time, period, true); | |
| } | |
| /** | |
| * Tells the scheduler that the Timer task died | |
| * so there will be no more new tasks scheduled. | |
| */ | |
| protected void finalize() throws Throwable | |
| { | |
| queue.setNullOnEmpty(true); | |
| } | |
| /** | |
| * Removes all cancelled tasks from the queue. | |
| * @return the number of tasks removed | |
| * @since 1.5 | |
| */ | |
| public int purge() | |
| { | |
| return queue.purge(); | |
| } | |
| } |