// Task - a periodic action // // Copyright (C) 1996 by Jef Poskanzer . All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions // are met: // 1. Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // 2. Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS // OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY // OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF // SUCH DAMAGE. // // Visit the ACME Labs Java page for up-to-date versions of this and other // fine Java utilities: http://www.acme.com/java/ package Acme; import java.util.*; /// A periodic action. //

// A Task represents a bit of code that you want to run repeatedly at // equally-timed intervals. Doing the timing yourself via sleeps is // either inaccurate, since it depends on how long the task takes to // run, or else complicated. Well, this class does the timing accurately // but hides the complexity from you. //

// Tasks use the Runnable interface, just like Threads. This means that // just like with Threads, there are two different ways you can create // a Task: //

//

// Fetch the software.
// Fetch the entire Acme package. public class Task implements Runnable { /*private*/ protected Runnable runnable; /*private*/ protected long msPeriod; /*private*/ protected TaskRunner runner; /*private*/ protected long nextRun; /// Constructor for subclasses that override run(). public Task( long msPeriod ) { this( null, msPeriod ); } /// Constructor with a Runnable specified. public Task( Runnable runnable, long msPeriod ) { this.runnable = runnable; this.msPeriod = msPeriod; runner = new TaskRunner( this ); TaskTimer.add( this ); } /// The body of the Task. // You must either override this method by subclassing class Task, // or you must create the Task with a Runnable target. public void run() { if ( runnable == null ) throw new RuntimeException(); // didn't override runnable.run(); } /// Stops a Task from running in the future. public synchronized void deschedule() { TaskTimer.deschedule( this ); } } // TaskRunner - thread for running a task // // There's one of these for each Task. However, we can't put these methods // into the Task class itself because the run() method has to be a loop here. class TaskRunner extends Thread { private Task task; /*private*/ protected boolean done; public TaskRunner( Task task ) { this.task = task; done = false; this.start(); } public synchronized void run() { while ( true ) { try { wait(); } catch ( InterruptedException e ) { } if ( done ) break; task.run(); } } public synchronized void notifier() { notify(); } } // TaskTimer - trigger TaskRunners at the right moment // // Only one of these gets created, and it handles the timing of all the // Tasks. class TaskTimer extends Thread { private static boolean done = true; private static Vector tasks = null; private static TaskTimer timer = null; public static synchronized void add( Task task ) { task.nextRun = System.currentTimeMillis() + task.msPeriod; if ( tasks == null ) tasks = new Vector(); tasks.addElement( task ); sortTasks( tasks ); if ( timer == null ) { done = false; timer = new TaskTimer(); } } public static synchronized void deschedule( Task task ) { task.runner.done = true; task.runner.notifier(); tasks.removeElement( task ); if ( tasks.size() == 0 ) { done = true; tasks = null; timer.notifier(); timer = null; } } public TaskTimer() { this.setPriority( Thread.MAX_PRIORITY ); this.start(); } public synchronized void run() { while ( true ) { if ( done ) break; // The tasks are kept sorted by nextRun, so the next runnable // task is the first one in the list. Task nextTask = (Task) tasks.elementAt( 0 ); long nextRun = nextTask.nextRun; // Sleep until it's time to run it. try { wait( nextRun - System.currentTimeMillis() ); } catch ( InterruptedException e ) { } if ( done ) break; // Run all tasks that should be run. long now = System.currentTimeMillis(); for ( int i = 0; i < tasks.size(); ++i ) { Task task = (Task) tasks.elementAt( i ); if ( task.nextRun > now ) break; task.runner.notifier(); task.nextRun += task.msPeriod; } // And re-sort, since the nextRuns have changed. sortTasks( tasks ); } } /// Stupid bubble sort. Maybe someday Java will have a Sortable interface. private static void sortTasks( Vector tasks ) { for ( int i = 0; i < tasks.size() - i; ++i ) { Task taski = (Task) tasks.elementAt( i ); for ( int j = i + 1; j < tasks.size(); ++j ) { Task taskj = (Task) tasks.elementAt( j ); if ( taskj.nextRun < taski.nextRun ) { tasks.setElementAt( taskj, i ); tasks.setElementAt( taski, j ); taski = taskj; } } } } public synchronized void notifier() { notify(); } }