* java/util/Timer.java: New version from Classpath.

From-SVN: r39573
This commit is contained in:
Tom Tromey 2001-02-09 23:28:55 +00:00 committed by Tom Tromey
parent 0e206b71aa
commit 932670198f
2 changed files with 503 additions and 436 deletions

View file

@ -1,3 +1,7 @@
2001-02-09 Tom Tromey <tromey@redhat.com>
* java/util/Timer.java: New version from Classpath.
2001-02-09 Bryce McKinlay <bryce@albatross.co.nz>
* java/lang/Double.java (doubleToRawLongBits): Now native.

View file

@ -1,5 +1,5 @@
/* Timer.java -- Timer that runs TimerTasks at a later time.
Copyright (C) 2000 Free Software Foundation, Inc.
Copyright (C) 2000, 2001 Free Software Foundation, Inc.
This file is part of GNU Classpath.
@ -7,7 +7,7 @@ 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
@ -50,476 +50,539 @@ package java.util;
* @since 1.3
* @author Mark Wielaard (mark@klomp.org)
*/
public class Timer {
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 final int DEFAULT_SIZE = 32;
/** Wheter to return null when there is nothing in the queue */
private boolean nullOnEmpty;
/**
* 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.
* The heap containing all the scheduled TimerTasks
* sorted by the TimerTask.scheduled field.
* Null when the stop() method has been called.
*/
private static final class TaskQueue {
/** Default size of this queue */
private final int DEFAULT_SIZE = 32;
/** Wheter 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);
}
}
/**
* 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.notify();
}
} // TaskQueue
private TimerTask heap[];
/**
* 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 immediatly. Stops running when canceled or when the parent
* Timer has been finalized and no more tasks have to be executed.
* The actual number of elements in the heap
* Can be less then heap.length.
* Note that heap[0] is used as a sentinel.
*/
private static final class Scheduler implements Runnable {
private int elements;
// The priority queue containing all the TimerTasks.
private TaskQueue queue;
/**
* Creates a TaskQueue of default size without any elements in it.
*/
public TaskQueue()
{
heap = new TimerTask[DEFAULT_SIZE];
elements = 0;
nullOnEmpty = false;
}
/**
* Creates a new Scheduler that will schedule the tasks on the
* given TaskQueue.
*/
public Scheduler(TaskQueue queue) {
this.queue = queue;
}
/**
* 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;
}
public void run() {
TimerTask task;
while((task = queue.serve()) != null) {
// If this task has not been canceled
if (task.scheduled >= 0) {
/**
* 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;
}
}
// Mark execution time
task.lastExecutionTime = task.scheduled;
/**
* 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");
}
// Repeatable task?
if (task.period < 0) {
// Last time this task is executed
task.scheduled = -1;
}
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();
}
// Run the task
try {
task.run();
} catch (Throwable t) {/* ignore all errors */}
}
/**
* 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];
}
}
// 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();
}
queue.enqueue(task);
}
}
}
} // Scheduler
/**
* 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;
// Number of Timers created.
// Used for creating nice Thread names.
private static int nr = 0;
while (task == null)
{
// Get the next task
task = top();
// The queue that all the tasks are put in.
// Given to the scheduler
// 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.notify();
}
} // 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 immediatly. 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;
// 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 deamon Thread as Scheduler, with normal
* priority and a default name.
* Creates a new Scheduler that will schedule the tasks on the
* given TaskQueue.
*/
public Timer() {
this(false);
public Scheduler(TaskQueue queue)
{
this.queue = queue;
}
/**
* Creates a new Timer with a deamon Thread as scheduler if deamon is true,
* with normal priority and a default name.
*/
public Timer(boolean daemon) {
this(daemon, Thread.NORM_PRIORITY);
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 (Throwable t)
{
/* ignore all errors */
}
}
// 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();
}
queue.enqueue(task);
}
}
}
} // Scheduler
/**
* Creates a new Timer with a deamon Thread as scheduler if deamon is true,
* with the priority given and a default name.
*/
private Timer(boolean daemon, int priority) {
this(daemon, priority, "Timer-" + (++nr));
}
// Number of Timers created.
// Used for creating nice Thread names.
private static int nr = 0;
/**
* Creates a new Timer with a deamon Thread as scheduler if deamon 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();
}
// The queue that all the tasks are put in.
// Given to the scheduler
private TaskQueue queue;
/**
* 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();
}
// The Scheduler that does all the real work
private Scheduler scheduler;
/**
* 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) {
// 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;
if (time < 0)
throw new IllegalArgumentException("negative time");
// When cancelled we don't accept any more TimerTasks.
private boolean canceled;
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");
}
/**
* Creates a new Timer with a non deamon Thread as Scheduler, with normal
* priority and a default name.
*/
public Timer()
{
this(false);
}
if (!this.canceled && this.thread != null) {
queue.enqueue(task);
} else {
throw new IllegalStateException
("timer was canceled or scheduler thread has died");
}
}
/**
* Creates a new Timer with a deamon Thread as scheduler if deamon is true,
* with normal priority and a default name.
*/
public Timer(boolean daemon)
{
this(daemon, Thread.NORM_PRIORITY);
}
private static void positiveDelay(long delay) {
if (delay < 0) {
throw new IllegalArgumentException("delay is negative");
}
}
/**
* Creates a new Timer with a deamon Thread as scheduler if deamon is true,
* with the priority given and a default name.
*/
private Timer(boolean daemon, int priority)
{
this(daemon, priority, "Timer-" + (++nr));
}
private static void positivePeriod(long period) {
if (period < 0) {
throw new IllegalArgumentException("period is negative");
}
}
/**
* Creates a new Timer with a deamon Thread as scheduler if deamon 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();
}
/**
* 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);
}
/**
* 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 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 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");
/**
* 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);
}
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");
}
/**
* 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);
}
if (!this.canceled && this.thread != null)
{
queue.enqueue(task);
}
else
{
throw new IllegalStateException
("timer was canceled or scheduler thread has died");
}
}
/**
* 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);
}
private static void positiveDelay(long delay)
{
if (delay < 0)
{
throw new IllegalArgumentException("delay is negative");
}
}
/**
* 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);
}
private static void positivePeriod(long period)
{
if (period < 0)
{
throw new IllegalArgumentException("period is negative");
}
}
/**
* Tells the scheduler that the Timer task died
* so there will be no more new tasks scheduled.
*/
protected void finalize() {
queue.setNullOnEmpty(true);
}
/**
* 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()
{
queue.setNullOnEmpty(true);
}
}