KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > jtheory > jdring > AlarmManager


1 /*
2  * com/jtheory/jdring/AlarmManager.java
3  * Copyright (C) 1999 - 2004 jtheory creations, Olivier Dedieu et al.
4  *
5  * This library is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU Library General Public License as published
7  * by the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU Library General Public License for more details.
14  *
15  * You should have received a copy of the GNU Library General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18  *
19  * History:
20  *
21  * 04/02/2004 Rob Whelan:
22  * - AlarmEntry, AlarmManager:
23  * - complete rewrite of updateAlarmTime() to support new features
24  * - added support for lists of values for each field (like cron)
25  * - changed handling of dayOfMonth and dayOfWeek to match logic
26  * in Vixie cron (if there are values for both, the closest is
27  * taken).
28  * - now AlarmEntry takes a name parameter - now two alarms can
29  * have the same schedule without confusing compareTo() and equals().
30  * - changed AlarmEntry compareTo() to also use the last updated time
31  * of an alarm to give priority if the alarmTimes match
32  * - changed AlarmEntry equals() method to only return true if
33  * name AND alarm date AND schedule details match. Otherwise it's too
34  * easy to have alarms overlap -- and one will secretly never be run.
35  * - added method setRingInNewThread() to AlarmEntry. By default
36  * all AlarmListeners are notified in a single Thread, so a
37  * long-running handleAlarm() method will cause other alarms to pile
38  * up, waiting. Calling this method causes the given AlarmEntry's
39  * listener to be notified in a new Thread.
40  * - Fixed odd behavior caused by misuse of SortedSet.add()
41  * - Comments and some variable name changes for clarity
42  * 06/15/2000 Olivier Dedieu:
43  * - support deamon and named thread
44  * - clean the sources to support only JDK1.2 and above
45  *
46  * 05/21/2000 Olivier Dedieu:
47  * - AlarmEntry:
48  * - fixed a bug in comparable implementation (equals method)
49  * - implements Serializable
50  *
51  * 09/28/1999 Olivier Dedieu: fixed a bug in AlarmEntry when used with
52  * JDK1.1.8
53  *
54  * 09/27/1999 David Sims <david@simscomputing.com>:
55  * fixed a couple more bugs in AlarmManager.java and AlarmWaiter.java.
56  * (see changes)
57  *
58  * 09/13/1999 David Sims <david@simscomputing.com>: rewrites the
59  * class to use a TreeSet instead of the
60  * fr.dyade.util.PriorityQueue.
61  *
62  * 08/01/1999 Olivier Dedieu: creates the class
63  * */

64
65 package com.jtheory.jdring;
66
67 import java.util.Calendar JavaDoc;
68 import java.util.Date JavaDoc;
69
70 import java.util.ArrayList JavaDoc;
71 import java.util.Collections JavaDoc;
72 import java.util.Iterator JavaDoc;
73 import java.util.LinkedList JavaDoc;
74 import java.util.List JavaDoc;
75 import java.util.SortedSet JavaDoc;
76 import java.util.TreeSet JavaDoc;
77
78 /**
79  * This class implements an alarm manager similar to Unix <code>cron</code>
80  * and <code>at</code> daemons. It is intended to fire events
81  * when alarms' date and time match the current ones. Alarms are
82  * added dynamically and can be one-shot or repetitive
83  * (i.e. rescheduled when matched). Time unit is seconds. Alarms
84  * scheduled less than one second to the current time are rejected (a
85  * <code>PastDateException</code> is thrown).<p>
86  *
87  * The alarm scheduler has been designed to
88  * manage a large quantity of alarms (it uses a priority queue to
89  * optimize alarm dates selection) and to reduce the use of the CPU
90  * time (the AlarmManager's thread is started only when there are
91  * alarms to be managed and it sleeps until the next alarm
92  * date).<p>
93  *
94  * Note : because of clocks' skews some alarm dates may be erroneous,
95  * particularly if the next alarm date is scheduled for a remote time
96  * (e.g. more than a few days). In order to avoid that problem,
97  * well-connected machines can use the <a
98  * HREF="ftp://ftp.inria.fr/rfc/rfc13xx/rfc1305.Z">Network Time
99  * Protocol</a> (NTP) to synchronize their clock.<p>
100  *
101  * Example of use:
102  * <pre>
103  * // Creates a new AlarmManager
104  * AlarmManager mgr = new AlarmManager();
105  *
106  * // Date alarm (non repetitive)
107  * mgr.addAlarm("fixed5min",new Date(System.currentTimeMillis() + 300000),
108  * new AlarmListener() {
109  * public void handleAlarm(AlarmEntry entry) {
110  * System.out.println("5 minutes later");
111  * }
112  * });
113  *
114  * Calendar cal = Calendar.getInstance();
115  * cal.add(Calendar.WEEK_OF_YEAR, 1);
116  * mgr.addAlarm("week_one", cal.getTime(), new AlarmListener() {
117  * public void handleAlarm(AlarmEntry entry) {
118  * System.out.println("One week later");
119  * }
120  * });
121  *
122  * // Alarm with a delay (in minute) relative to the current time.
123  * mgr.addAlarm(1, true, new AlarmListener() {
124  * public void handleAlarm(AlarmEntry entry) {
125  * System.out.println("1 more minute ! (" + new Date() + ")");
126  * }
127  * });
128  *
129  * // Cron-like alarm (minute, hour, day of month, month, day of week, year)
130  * // Repetitive when the year is not specified.
131  *
132  * mgr.addAlarm(-1, -1, -1, -1, -1, -1, new AlarmListener() {
133  * public void handleAlarm(AlarmEntry entry) {
134  * System.out.println("Every minute (" + new Date() + ")");
135  * }
136  * });
137  *
138  * mgr.addAlarm(5, -1, -1, -1, -1, -1, new AlarmListener() {
139  * public void handleAlarm(AlarmEntry entry) {
140  * System.out.println("Every hour at 5' (" + new Date() + ")");
141  * }
142  * });
143  *
144  * mgr.addAlarm(00, 12, -1, -1, -1, -1, new AlarmListener() {
145  * public void handleAlarm(AlarmEntry entry) {
146  * System.out.println("Lunch time (" + new Date() + ")");
147  * }
148  * });
149  *
150  * mgr.addAlarm(07, 14, 1, Calendar.JANUARY, -1, -1, new AlarmListener() {
151  * public void handleAlarm(AlarmEntry entry) {
152  * System.out.println("Happy birthday Lucas !");
153  * }
154  * });
155  *
156  * mgr.addAlarm(30, 9, 1, -1, -1, -1, new AlarmListener() {
157  * public void handleAlarm(AlarmEntry entry) {
158  * System.out.println("On the first of every month at 9:30");
159  * }
160  * });
161  *
162  * mgr.addAlarm(00, 18, -1, -1, Calendar.FRIDAY, -1, new AlarmListener() {
163  * public void handleAlarm(AlarmEntry entry) {
164  * System.out.println("On every Friday at 18:00");
165  * }
166  * });
167  *
168  * mgr.addAlarm(00, 13, 1, Calendar.AUGUST, -1, 2001, new AlarmListener() {
169  * public void handleAlarm(AlarmEntry entry) {
170  * System.out.println("2 years that this class was programmed !");
171  * }
172  * });
173  * </pre>
174  *
175  * @author Olivier Dedieu, David Sims, Jim Lerner, Rob Whelan
176  * @version 1.3, 15/06/2000
177  */

178
179 public class AlarmManager {
180     
181     protected AlarmWaiter waiter;
182     protected SortedSet JavaDoc /* of AlarmEntry */ queue;
183     private boolean debug = false;
184     
185     private void debug(String JavaDoc s) {
186         if (debug)
187             System.out.println("[" + Thread.currentThread().getName() + "] AlarmManager: " + s);
188     }
189     
190     /**
191      * Creates a new AlarmManager. The waiter thread will be started
192      * only when the first alarm listener will be added.
193      *
194      * @param isDaemon true if the waiter thread should run as a daemon.
195      * @param threadName the name of the waiter thread
196      */

197     public AlarmManager(boolean isDaemon, String JavaDoc threadName) {
198         queue = new TreeSet JavaDoc();
199         waiter = new AlarmWaiter(this, isDaemon, threadName);
200     }
201     
202     /**
203      * Creates a new AlarmManager. The waiter thread will be started
204      * only when the first alarm listener will be added. The waiter
205      * thread will <i>not</i> run as a daemon.
206      */

207     public AlarmManager() {
208         this(false, "AlarmManager");
209     }
210     
211     /**
212      * Adds an alarm for a specified date.
213      *
214      * @param date the alarm date to be added.
215      * @param listener the alarm listener.
216      * @return the AlarmEntry.
217      * @exception PastDateException if the alarm date is in the past
218      * or less than 1 second closed to the current date).
219      */

220     public synchronized AlarmEntry addAlarm(String JavaDoc _name, Date JavaDoc _date,
221             AlarmListener _listener) throws PastDateException {
222         AlarmEntry entry = new AlarmEntry(_name, _date, _listener);
223         addAlarm(entry);
224         return entry;
225     }
226     /** @deprecated for backwards compatibility, w/o name param: */
227     public AlarmEntry addAlarm(Date JavaDoc _date,
228             AlarmListener _listener) throws PastDateException {
229         return addAlarm(null, _date, _listener);
230     }
231     
232     /**
233      * Adds an alarm for a specified delay.
234      *
235      * @param delay the alarm delay in minute (relative to now).
236      * @param isRepeating <code>true</code> if the alarm must be
237      * reactivated, <code>false</code> otherwise.
238      * @param listener the alarm listener.
239      * @return the AlarmEntry.
240      * @exception PastDateException if the alarm date is in the past
241      * (or less than 1 second closed to the current date).
242      */

243     public synchronized AlarmEntry addAlarm(String JavaDoc _name, int _delay, boolean _isRepeating,
244             AlarmListener _listener) throws PastDateException {
245         AlarmEntry entry = new AlarmEntry(_name, _delay, _isRepeating, _listener);
246         addAlarm(entry);
247         return entry;
248     }
249     /** @deprecated for backwards compatibility, w/o name param: */
250     public AlarmEntry addAlarm(int _delay, boolean _isRepeating,
251             AlarmListener _listener) throws PastDateException {
252         return addAlarm(null, _delay, _isRepeating, _listener);
253     }
254     
255     /**
256      * Adds an alarm for a specified date.
257      *
258      * @param minute minute of the alarm. Allowed values 0-59, or -1 for all.
259      * @param hour hour of the alarm. Allowed values 0-23, or -1 for all.
260      * @param dayOfMonth day of month of the alarm. Allowed values 1-7
261      * (1 = Sunday, 2 = Monday, ...), or -1 for all.
262      * <code>java.util.Calendar</code> constants can be used.
263      * @param month month of the alarm. Allowed values 0-11 (0 = January,
264      * 1 = February, ...), or -1 for all. <code>java.util.Calendar</code>
265      * constants can be used.
266      * @param dayOfWeek day of week of the alarm. Allowed values 1-31,
267      * or -1 for all.
268      * @param year year of the alarm. When this field is not set
269      * (i.e. -1) the alarm is repetitive (i.e. it is rescheduled when
270      * reached).
271      * @param listener the alarm listener.
272      * @return the AlarmEntry.
273      * @exception PastDateException if the alarm date is in the past
274      * (or less than 1 second away from the current date).
275      */

276     public synchronized AlarmEntry addAlarm(String JavaDoc _name, int _minute, int _hour,
277             int _dayOfMonth, int _month,
278             int _dayOfWeek,
279             int _year,
280             AlarmListener _listener)
281     throws PastDateException {
282         
283         AlarmEntry entry = new AlarmEntry(_name, _minute, _hour,
284                 _dayOfMonth, _month,
285                 _dayOfWeek,
286                 _year,
287                 _listener);
288         addAlarm(entry);
289         return entry;
290     }
291     /** @deprecated for backwards compatibility, w/o name param: */
292     public AlarmEntry addAlarm(int _minute, int _hour,
293             int _dayOfMonth, int _month, int _dayOfWeek, int _year,
294             AlarmListener _listener)
295     throws PastDateException {
296         return addAlarm(_minute, _hour, _dayOfMonth, _month, _dayOfWeek, _year,_listener);
297     }
298     
299     /**
300      * Adds an alarm for a specified date or matching dates (for unrestricted
301      * fields).
302      *
303      * @param minutes minutes of the alarm. Allowed values 0-59, or -1 for all.
304      * @param hours hours of the alarm. Allowed values 0-23, or -1 for all.
305      * @param daysOfMonth days of month of the alarm. Allowed values 1-7
306      * (1 = Sunday, 2 = Monday, ...), or -1 for all.
307      * <code>java.util.Calendar</code> constants can be used.
308      * @param months months of the alarm. Allowed values 0-11 (0 = January,
309      * 1 = February, ...), or -1 for all. <code>java.util.Calendar</code>
310      * constants can be used.
311      * @param daysOfWeek days of week of the alarm. Allowed values 1-31,
312      * or -1 for all.
313      * @param year year of the alarm. When this field is not set
314      * (i.e. -1) the alarm is repetitive (i.e. it is rescheduled when
315      * reached).
316      * @param listener the alarm listener.
317      * @return the AlarmEntry.
318      * @exception PastDateException if the alarm date is in the past
319      * (or less than 1 second away from the current date).
320      */

321     public synchronized AlarmEntry addAlarm(String JavaDoc _name, int[] _minutes, int[] _hours,
322             int[] _daysOfMonth, int[] _months,
323             int[] _daysOfWeek,
324             int _year,
325             AlarmListener _listener)
326     throws PastDateException {
327         
328         AlarmEntry entry = new AlarmEntry(_name, _minutes, _hours,
329                 _daysOfMonth, _months,
330                 _daysOfWeek,
331                 _year,
332                 _listener);
333         addAlarm(entry);
334         return entry;
335     }
336     /** @deprecated for backwards compatibility, w/o name param: */
337     public AlarmEntry addAlarm(int[] _minutes, int[] _hours,
338             int[] _daysOfMonth, int[] _months, int[] _daysOfWeek, int _year,
339             AlarmListener _listener)
340     throws PastDateException {
341         return addAlarm( null, _minutes, _hours, _daysOfMonth, _months, _daysOfWeek,_year,_listener);
342     }
343     
344     /**
345      * Adds an alarm for a specified AlarmEntry
346      *
347      * @param entry the AlarmEntry.
348      * @exception PastDateException if the alarm date is in the past
349      * (or less than one second away from the current date).
350      */

351     public synchronized void addAlarm(AlarmEntry _entry) throws PastDateException {
352         debug("Add a new alarm entry : " + _entry);
353         
354         queue.add(_entry);
355         if (queue.first().equals(_entry)) {
356             debug("This new alarm is the top one, update the waiter thread");
357             waiter.update(_entry.alarmTime);
358         }
359     }
360     
361     
362     /**
363      * Removes the specified AlarmEntry.
364      *
365      * @param entry the AlarmEntry that needs to be removed.
366      * @return <code>true</code> if there was an alarm for this date,
367      * <code>false</code> otherwise.
368      */

369     public synchronized boolean removeAlarm(AlarmEntry _entry) {
370         
371         boolean found = false;
372         
373         if( ! queue.isEmpty() ) {
374             AlarmEntry was_first = (AlarmEntry)queue.first();
375             found = queue.remove(_entry);
376             
377             // update the queue if it's not now empty, and the first alarm has changed
378
if ( !queue.isEmpty() && _entry.equals(was_first) )
379             {
380                 waiter.update( ((AlarmEntry) queue.first()).alarmTime );
381             }
382         }
383         
384         return found;
385     } // removeAlarm()
386

387     /**
388      * Removes all the alarms. No more alarms, even newly added ones, will
389      * be fired.
390      */

391     public synchronized void removeAllAlarms() {
392         queue.clear();
393     }
394     
395     /**
396      * Removes all the alarms. No more alarms, even newly added ones, will
397      * be fired.
398      */

399     public synchronized void removeAllAlarmsAndStop() {
400         waiter.stop();
401         waiter = null;
402         queue.clear();
403     }
404     
405     public boolean isStopped() {
406         return (waiter == null);
407     }
408     
409     /**
410      Tests whether the supplied AlarmEntry is in the manager.
411      
412      @param AlarmEntry
413      @return boolean whether AlarmEntry is contained within the manager
414      */

415     public synchronized boolean containsAlarm(AlarmEntry _alarmEntry) {
416         return queue.contains(_alarmEntry);
417     }
418     
419     /**
420      * Returns a copy of all alarms in the manager.
421      */

422     public synchronized List JavaDoc getAllAlarms() {
423         List JavaDoc result = new ArrayList JavaDoc();
424         
425         Iterator JavaDoc iterator = queue.iterator();
426         while (iterator.hasNext()) {
427             result.add(iterator.next());
428         }
429         
430         return result;
431     }
432     
433     /**
434      * This is method is called when an alarm date is reached. It
435      * is only be called by the the AlarmWaiter or by itself (if
436      * the next alarm is less than 1 second away).
437      */

438     protected synchronized void ringNextAlarm() {
439         debug("ringing next alarm");
440         
441         // if the queue is empty, there's nothing to do
442
if (queue.isEmpty()) {
443             return;
444         }
445         
446         // Removes this alarm and notifies the listener
447
AlarmEntry entry = (AlarmEntry) queue.first();
448         queue.remove(entry);
449         
450         // NOTE: if the entry is still running when its next alarm time comes up,
451
// that execution of the entry will be skipped.
452
if( entry.isRingInNewThread() ) {
453             new Thread JavaDoc( new RunnableRinger(entry) ).start();
454         }
455         else {
456             // ring in same thread, sequentially.. can delay other alarms
457
try {
458                 entry.ringAlarm();
459             }
460             catch(Exception JavaDoc e) {
461                 e.printStackTrace();
462             }
463         }
464         
465         // Reactivates the alarm if it is repetitive
466
if (entry.isRepeating) {
467             entry.updateAlarmTime();
468             queue.add(entry);
469         }
470         
471         // Notifies the AlarmWaiter thread for the next alarm
472
if (queue.isEmpty()) {
473             debug("no more alarms to handle; queue is empty");
474         }
475         else {
476             long alarmTime = ((AlarmEntry)queue.first()).alarmTime;
477             if (alarmTime - System.currentTimeMillis() < 1000) {
478                 debug("next alarm is within 1 sec or already past - ring it without waiting");
479                 ringNextAlarm();
480             }
481             else {
482                 debug("updating the waiter for next alarm: " + queue.first());
483                 waiter.restart(alarmTime);
484             }
485         }
486     } // notifyListeners()
487

488     /**
489      * Stops the waiter thread before ending.
490      */

491     public void finalize() {
492         if (waiter != null)
493             waiter.stop();
494     }
495     
496     /**
497      * Used to ring an AlarmEntry in a new Thread.
498      * @see com.jtheory.jdring.AlarmEntry#setRingInNewThread()
499      */

500     private class RunnableRinger implements Runnable JavaDoc {
501         AlarmEntry entry = null;
502         
503         RunnableRinger(AlarmEntry _entry) {
504             entry = _entry;
505         }
506         
507         public void run() {
508             try {
509                 entry.ringAlarm();
510             }
511             catch(Exception JavaDoc e) {
512                 e.printStackTrace();
513             }
514         }
515     }
516 }
517
Popular Tags