package com.idega.util.timer; import java.util.Date; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.SortedSet; import java.util.TreeSet; /** * This class implements an timer manager similar to Unix <code>cron</code> * and <code>at</code> daemons. It is intended to fire events * when timers' date and time match the current ones. Timers are * added dynamically and can be one-shot or repetitive * (i.e. rescheduled when matched). Time unit is seconds. Timers * scheduled less than one second to the current time are rejected (a * <code>PastDateException</code> is thrown).<p> * * The timer scheduler has been designed to * manage a large quantity of timers (it uses a priority queue to * optimize timer dates selection) and to reduce the use of the CPU * time (the TimerManager's thread is started only when there are * timers to be managed and it sleeps until the next timer * date). <p> * * Note : because of clocks' skews some timer dates may be erroneous, * particularly if the next timer date is scheduled for a remote time * (e.g. more than a few days). In order to avoid that problem, * well-connected machines can use the <a * href="ftp://ftp.inria.fr/rfc/rfc13xx/rfc1305.Z">Network Time * Protocol</a> (NTP) to synchronize their clock.<p> * * Example of use: * <pre> * // Creates a new TimerManager * TimerManager mgr = new TimerManager(); * * // Date timer (non repetitive) * mgr.addTimer(new Date(System.currentTimeMillis() + 300000), * new TimerListener() { * public void handleTimer(TimerEntry entry) { * System.out.println("5 minutes later"); * } * }); * * Calendar cal = Calendar.getInstance(); * cal.add(Calendar.WEEK_OF_YEAR, 1); * mgr.addTimer(cal.getTime(), new TimerListener() { * public void handleTimer(TimerEntry entry) { * System.out.println("One week later"); * } * }); * * // Timer with a delay (in minute) relative to the current time. * mgr.addTimer(1, true, new TimerListener() { * public void handleTimer(TimerEntry entry) { * System.out.println("1 more minute ! (" + new Date() + ")"); * } * }); * * // Cron-like timer (minute, hour, day of month, month, day of week, year) * // Repetitive when the year is not specified. * * mgr.addTimer(-1, -1, -1, -1, -1, -1, new TimerListener() { * public void handleTimer(TimerEntry entry) { * System.out.println("Every minute (" + new Date() + ")"); * } * }); * * mgr.addTimer(5, -1, -1, -1, -1, -1, new TimerListener() { * public void handleTimer(TimerEntry entry) { * System.out.println("Every hour at 5' (" + new Date() + ")"); * } * }); * * mgr.addTimer(00, 12, -1, -1, -1, -1, new TimerListener() { * public void handleTimer(TimerEntry entry) { * System.out.println("Lunch time (" + new Date() + ")"); * } * }); * * mgr.addTimer(07, 14, 1, Calendar.JANUARY, -1, -1, new TimerListener() { * public void handleTimer(TimerEntry entry) { * System.out.println("Happy birthday Lucas !"); * } * }); * * mgr.addTimer(30, 9, 1, -1, -1, -1, new TimerListener() { * public void handleTimer(TimerEntry entry) { * System.out.println("On the first of every month at 9:30"); * } * }); * * mgr.addTimer(00, 18, -1, -1, Calendar.FRIDAY, -1, new TimerListener() { * public void handleTimer(TimerEntry entry) { * System.out.println("On every Friday at 18:00"); * } * }); * * mgr.addTimer(00, 13, 1, Calendar.AUGUST, -1, 2001, new TimerListener() { * public void handleTimer(TimerEntry entry) { * System.out.println("2 years that this class was programmed !"); * } * }); * </pre> * * @author Olivier Dedieu, David Sims, Jim Lerner * @version 1.4, 01/02/2002 * @modified Eirikur Hrafnsson eiki@idega.is */ public class TimerManager { protected TimerThread waiter; protected SortedSet /* of TimerEntry */ queue; // was a PriorityQueue private boolean debug = false; private void debug(String s) { if (this.debug) { System.out.println("[" + Thread.currentThread().getName() + "] TimerManager: " + s); } } /** * Creates a new TimerManager. The waiter thread will be started * only when the first timer listener will be added. * * @param isDaemon true if the waiter thread should run as a daemon. * @param threadName the name of the waiter thread */ public TimerManager(boolean isDaemon, String threadName) { this.queue = new TreeSet(); // PriorityQueue(false); this.waiter = new TimerThread(this, isDaemon, threadName); } /** * Creates a new TimerManager. The waiter thread will be started * only when the first timer listener will be added. The waiter * thread will <i>not</i> run as a daemon. */ public TimerManager() { this(false, "TimerManager"); } /** * Adds an timer for a specified date. * * @param date the timer date to be added. * @param listener the timer listener. * @return the TimerEntry. * @exception PastDateException if the timer date is in the past * or less than 1 second closed to the current date). */ public synchronized TimerEntry addTimer(Date date, TimerListener listener) throws PastDateException { TimerEntry entry = new TimerEntry(date, listener); addTimer(entry); return entry; } /** * Adds an timer for a specified date. * * @param date the timer date to be added. * @param listener the timer listener. * @param name timer name * @return the TimerEntry. * @exception PastDateException if the timer date is in the past * or less than 1 second closed to the current date). */ public synchronized TimerEntry addTimer(Date date, TimerListener listener, String name) throws PastDateException { TimerEntry entry = new TimerEntry(date, listener); entry.timerName = name; addTimer(entry); return entry; } /** * Adds an timer for a specified delay. * * @param delay the timer delay in minute (relative to now). * @param isRepetitive <code>true</code> if the timer must be * reactivated, <code>false</code> otherwise. * @param listener the timer listener. * @return the TimerEntry. * @exception PastDateException if the timer date is in the past * (or less than 1 second closed to the current date). */ public synchronized TimerEntry addTimer(int delay, boolean isRepetitive, TimerListener listener) throws PastDateException { TimerEntry entry = new TimerEntry(delay, isRepetitive, listener); addTimer(entry); return entry; } /** * Adds an timer for a specified delay. * * @param delay the timer delay in minute (relative to now). * @param isRepetitive <code>true</code> if the timer must be * reactivated, <code>false</code> otherwise. * @param listener the timer listener. * @param name timer name * @return the TimerEntry. * @exception PastDateException if the timer date is in the past * (or less than 1 second closed to the current date). */ public synchronized TimerEntry addTimer(int delay, boolean isRepetitive, TimerListener listener, String name) throws PastDateException { TimerEntry entry = new TimerEntry(delay, isRepetitive, listener); entry.timerName = name; addTimer(entry); return entry; } /** * Adds an timer for a specified date. * * @param minute minute of the timer. Allowed values 0-59. * @param hour hour of the timer. Allowed values 0-23. * @param dayOfMonth day of month of the timer (-1 if every * day). This attribute is exclusive with * <code>dayOfWeek</code>. Allowed values 1-7 (1 = Sunday, 2 = * Monday, ...). <code>java.util.Calendar</code> constants can be used. * @param month month of the timer (-1 if every month). Allowed values * 0-11 (0 = January, 1 = February, ...). <code>java.util.Calendar</code> * constants can be used. * @param dayOfWeek day of week of the timer (-1 if every * day). This attribute is exclusive with * <code>dayOfMonth</code>. Allowed values 1-31. * @param year year of the timer. When this field is not set * (i.e. -1) the timer is repetitive (i.e. it is rescheduled when * reached). * @param listener the timer listener. * @return the TimerEntry. * @exception PastDateException if the timer date is in the past * (or less than 1 second closed to the current date). */ public synchronized TimerEntry addTimer(int minute, int hour, int dayOfMonth, int month, int dayOfWeek, int year, TimerListener listener) throws PastDateException { TimerEntry entry = new TimerEntry(minute, hour, dayOfMonth, month, dayOfWeek, year, listener); addTimer(entry); return entry; } /** * Adds an timer for a specified date. * * @param minute minute of the timer. Allowed values 0-59. * @param hour hour of the timer. Allowed values 0-23. * @param dayOfMonth day of month of the timer (-1 if every * day). This attribute is exclusive with * <code>dayOfWeek</code>. Allowed values 1-7 (1 = Sunday, 2 = * Monday, ...). <code>java.util.Calendar</code> constants can be used. * @param month month of the timer (-1 if every month). Allowed values * 0-11 (0 = January, 1 = February, ...). <code>java.util.Calendar</code> * constants can be used. * @param dayOfWeek day of week of the timer (-1 if every * day). This attribute is exclusive with * <code>dayOfMonth</code>. Allowed values 1-31. * @param year year of the timer. When this field is not set * (i.e. -1) the timer is repetitive (i.e. it is rescheduled when * reached). * @param listener the timer listener. * @param name timer name. * @return the TimerEntry. * @exception PastDateException if the timer date is in the past * (or less than 1 second closed to the current date). */ public synchronized TimerEntry addTimer(int minute, int hour, int dayOfMonth, int month, int dayOfWeek, int year, TimerListener listener,String name) throws PastDateException { TimerEntry entry = new TimerEntry(minute, hour, dayOfMonth, month, dayOfWeek, year, listener,name); addTimer(entry); return entry; } /** * Adds an timer for a specified TimerEntry * * @param entry the TimerEntry. * @exception PastDateException if the timer date is in the past * (or less than one second too closed to the current date). */ public synchronized void addTimer(TimerEntry entry) throws PastDateException { debug("Add a new timer entry : " + entry); if (this.queue.add(entry)) { debug("This new timer is the top one, update the waiter thread"); this.waiter.update(((TimerEntry)this.queue.first()).timerTime); // waiter.update(((TimerEntry)queue.getTop()).timerTime); } } /** * Removes the specified TimerEntry. * * @param entry the TimerEntry that needs to be removed. * @return <code>true</code> if there was an timer for this date, * <code>false</code> otherwise. */ public synchronized boolean removeTimer(TimerEntry entry) { if (!this.queue.contains(entry)) { return false; } // if // remove the item from the queue if (this.queue.remove(entry)) { // do not update the waiter if there are no more items in the queue if (this.queue.size() > 0) { this.waiter.update(( (TimerEntry) this.queue.first()).timerTime); } // if // waiter.update(((TimerEntry)queue.getTop()).timerTime); } // if return true; } // removeTimer() /** * Removes all the timers. No more timers, even newly added ones, will * be fired. */ public synchronized void removeAllTimers() { if(this.waiter!=null){ this.waiter.stop(); this.waiter = null; } this.queue.clear(); } /** Tests whether the supplied TimerEntry is in the manager. @param TimerEntry @return boolean whether TimerEntry is contained within the manager */ public synchronized boolean containsTimer(TimerEntry timerEntry) { return this.queue.contains(timerEntry); } // containsTimer() /** * Returns a copy of all timers in the manager. */ public synchronized List /* TimerEntry */ getAllTimers() { final LinkedList result = new LinkedList(); Iterator iterator = this.queue.iterator(); while (iterator.hasNext()) { result.add(iterator.next()); } // while return result; } // getAllTimers() /** * This is method is called when an timer date is reached. It * is only be called by the the TimerThread or by itself (if * the next timer is in less than 1 second.) */ protected synchronized void notifyListeners() { debug("I receive an timer notification"); // if the queue is empty, there's nothing to do if (this.queue.isEmpty()) { return; } // if // Removes this timer and notifies the listener IF they previous run is finished ( use entry.isDone() ) TimerEntry entry = (TimerEntry) this.queue.first(); this.queue.remove(entry); if(entry.canRun()){ try { entry.listener.handleTimer(entry); } catch(Exception e) { e.printStackTrace(); } } // Reactivates the timer if it is repetitive if (entry.isRepetitive) { entry.updateTimerTime(); this.queue.add(entry); } // Notifies the TimerThread thread for the next timer if (this.queue.isEmpty()) { debug("There is no more timer to manage"); } else { long timerTime = ((TimerEntry)this.queue.first()).timerTime; if (timerTime - System.currentTimeMillis() < 1000) { debug("The next timer is very close, I notify the listeners now"); notifyListeners(); } else { debug("I update the waiter for " + this.queue.first()); this.waiter.restart(timerTime); } } // else } // notifyListeners() /** * Stops the waiter thread before ending. */ public void finalize() { if (this.waiter != null) { this.waiter.stop(); } } }