001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: package java.util;
019:
020: import org.apache.harmony.luni.util.Msg;
021:
022: /**
023: * Timers are used to schedule jobs for execution in a background process. A
024: * single thread is used for the scheduling and this thread has the option of
025: * being a daemon thread. By calling <code>cancel</code> you can terminate a
026: * timer and it's associated thread. All tasks which are scheduled to run after
027: * this point are cancelled. Tasks are executed sequentially but are subject to
028: * the delays from other tasks run methods. If a specific task takes an
029: * excessive amount of time to run it may impact the time at which subsequent
030: * tasks may run.
031: * <p>
032: *
033: * The Timer task does not offer any guarantees about the real-time nature of
034: * scheduling tasks as it's underlying implementation relies on the
035: * <code>Object.wait(long)</code> method.
036: * <p>
037: *
038: * Multiple threads can share a single Timer without the need for their own
039: * synchronization.
040: *
041: * @see TimerTask
042: * @see java.lang.Object#wait(long)
043: */
044: public class Timer {
045:
046: private static final class TimerImpl extends Thread {
047:
048: private static final class TimerNode {
049: TimerNode parent, left, right;
050:
051: TimerTask task;
052:
053: public TimerNode(TimerTask value) {
054: this .task = value;
055: }
056:
057: public void deleteIfCancelled(TimerTree tasks) {
058: /*
059: * All changes in the tree structure during deleting this node
060: * affect only the structure of the subtree having this node as
061: * its root
062: */
063: if (left != null) {
064: left.deleteIfCancelled(tasks);
065: }
066: if (right != null) {
067: right.deleteIfCancelled(tasks);
068: }
069: if (task.cancelled) {
070: tasks.delete(this );
071: tasks.deletedCancelledNumber++;
072: }
073: }
074: }
075:
076: private static final class TimerTree {
077:
078: int deletedCancelledNumber;
079:
080: TimerNode root;
081:
082: boolean isEmpty() {
083: return root == null;
084: }
085:
086: void insert(TimerNode z) {
087: TimerNode y = null, x = root;
088: while (x != null) {
089: y = x;
090: if (z.task.getWhen() < x.task.getWhen()) {
091: x = x.left;
092: } else {
093: x = x.right;
094: }
095: }
096: z.parent = y;
097: if (y == null) {
098: root = z;
099: } else if (z.task.getWhen() < y.task.getWhen()) {
100: y.left = z;
101: } else {
102: y.right = z;
103: }
104: }
105:
106: void delete(TimerNode z) {
107: TimerNode y = null, x = null;
108: if (z.left == null || z.right == null) {
109: y = z;
110: } else {
111: y = successor(z);
112: }
113: if (y.left != null) {
114: x = y.left;
115: } else {
116: x = y.right;
117: }
118: if (x != null) {
119: x.parent = y.parent;
120: }
121: if (y.parent == null) {
122: root = x;
123: } else if (y == y.parent.left) {
124: y.parent.left = x;
125: } else {
126: y.parent.right = x;
127: }
128: if (y != z) {
129: z.task = y.task;
130: }
131: }
132:
133: private TimerNode successor(TimerNode x) {
134: if (x.right != null) {
135: return minimum(x.right);
136: }
137: TimerNode y = x.parent;
138: while (y != null && x == y.right) {
139: x = y;
140: y = y.parent;
141: }
142: return y;
143: }
144:
145: private TimerNode minimum(TimerNode x) {
146: while (x.left != null) {
147: x = x.left;
148: }
149: return x;
150: }
151:
152: TimerNode minimum() {
153: return minimum(root);
154: }
155: }
156:
157: /**
158: * True if the method cancel() of the Timer was called or the !!!stop()
159: * method was invoked
160: */
161: private boolean cancelled;
162:
163: /**
164: * True if the Timer has become garbage
165: */
166: private boolean finished;
167:
168: /**
169: * Vector consists of scheduled events, sorted according to
170: * <code>when</code> field of TaskScheduled object.
171: */
172: private TimerTree tasks = new TimerTree();
173:
174: /**
175: * Starts a new timer.
176: *
177: * @param isDaemon
178: */
179: TimerImpl(boolean isDaemon) {
180: this .setDaemon(isDaemon);
181: this .start();
182: }
183:
184: TimerImpl(String name, boolean isDaemon) {
185: this .setName(name);
186: this .setDaemon(isDaemon);
187: this .start();
188: }
189:
190: /**
191: * This method will be launched on separate thread for each Timer
192: * object.
193: */
194: @Override
195: public void run() {
196: while (true) {
197: TimerTask task;
198: synchronized (this ) {
199: // need to check cancelled inside the synchronized block
200: if (cancelled) {
201: return;
202: }
203: if (tasks.isEmpty()) {
204: if (finished) {
205: return;
206: }
207: // no tasks scheduled -- sleep until any task appear
208: try {
209: this .wait();
210: } catch (InterruptedException e) {
211: }
212: continue;
213: }
214:
215: long currentTime = System.currentTimeMillis();
216:
217: TimerNode taskNode = tasks.minimum();
218: task = taskNode.task;
219: long timeToSleep;
220:
221: synchronized (task.lock) {
222: if (task.cancelled) {
223: tasks.delete(taskNode);
224: continue;
225: }
226:
227: // check the time to sleep for the first task scheduled
228: timeToSleep = task.when - currentTime;
229: }
230:
231: if (timeToSleep > 0) {
232: // sleep!
233: try {
234: this .wait(timeToSleep);
235: } catch (InterruptedException e) {
236: // Ignored
237: }
238: continue;
239: }
240:
241: // no sleep is necessary before launching the task
242:
243: synchronized (task.lock) {
244: if (task.cancelled) {
245: tasks.delete(taskNode);
246: continue;
247: }
248:
249: // set time to schedule
250: task.setScheduledTime(task.when);
251:
252: // remove task from queue
253: tasks.delete(taskNode);
254:
255: // set when the next task should be launched
256: if (task.period >= 0) {
257: // this is a repeating task,
258: if (task.fixedRate) {
259: // task is scheduled at fixed rate
260: task.when = task.when + task.period;
261: } else {
262: // task is scheduled at fixed delay
263: task.when = System.currentTimeMillis()
264: + task.period;
265: }
266:
267: // insert this task into queue
268: insertTask(task);
269: } else {
270: task.when = 0;
271: }
272: }
273: }
274:
275: // run the task
276: try {
277: task.run();
278: } catch (Exception e) {
279: // Ignored
280: }
281: }
282: }
283:
284: private void insertTask(TimerTask newTask) {
285: // callers are synchronized
286: tasks.insert(new TimerNode(newTask));
287: this .notify();
288: }
289:
290: /**
291: * Cancels timer.
292: */
293: public synchronized void cancel() {
294: cancelled = true;
295: tasks = new TimerTree();
296: this .notify();
297: }
298:
299: public int purge() {
300: if (tasks.isEmpty()) {
301: return 0;
302: }
303: // callers are synchronized
304: tasks.deletedCancelledNumber = 0;
305: tasks.root.deleteIfCancelled(tasks);
306: return tasks.deletedCancelledNumber;
307: }
308:
309: }
310:
311: /* This object will be used in synchronization purposes */
312: private TimerImpl impl;
313:
314: // Used to finalize thread
315: @SuppressWarnings("unused")
316: private Object finalizer = new Object() { // $NON-LOCK-1$
317: @Override
318: protected void finalize() {
319: synchronized (impl) {
320: impl.finished = true;
321: impl.notify();
322: }
323: }
324: };
325:
326: /**
327: * Creates a new Timer which may be specified to be run as a Daemon Thread.
328: *
329: * @param isDaemon
330: * true if Timers thread should be a daemon thread.
331: */
332: public Timer(boolean isDaemon) {
333: impl = new TimerImpl(isDaemon);
334: }
335:
336: /**
337: * Creates a new non-daemon Timer.
338: */
339: public Timer() {
340: impl = new TimerImpl(false);
341: }
342:
343: public Timer(String name, boolean isDaemon) {
344: impl = new TimerImpl(name, isDaemon);
345: }
346:
347: public Timer(String name) {
348: impl = new TimerImpl(name, false);
349: }
350:
351: /**
352: * Cancels the Timer and removed any scheduled tasks. If there is a
353: * currently running task it is not effected. No more tasks may be scheduled
354: * on this Timer. Subsequent calls do nothing.
355: */
356: public void cancel() {
357: impl.cancel();
358: }
359:
360: public int purge() {
361: synchronized (impl) {
362: return impl.purge();
363: }
364: }
365:
366: /**
367: * Schedule a task for single execution. If when is less than the current
368: * time, it will be scheduled to executed as soon as possible.
369: *
370: * @param task
371: * The task to schedule
372: * @param when
373: * Time of execution
374: *
375: * @exception IllegalArgumentException
376: * if when.getTime() < 0
377: * @exception IllegalStateException
378: * if the timer has been cancelled, the task has been
379: * scheduled or cancelled.
380: */
381: public void schedule(TimerTask task, Date when) {
382: if (when.getTime() < 0) {
383: throw new IllegalArgumentException();
384: }
385: long delay = when.getTime() - System.currentTimeMillis();
386: scheduleImpl(task, delay < 0 ? 0 : delay, -1, false);
387: }
388:
389: /**
390: * Schedule a task for single execution after a specific delay.
391: *
392: * @param task
393: * The task to schedule
394: * @param delay
395: * Amount of time before execution
396: *
397: * @exception IllegalArgumentException
398: * if delay < 0
399: * @exception IllegalStateException
400: * if the timer has been cancelled, the task has been
401: * scheduled or cancelled.
402: */
403: public void schedule(TimerTask task, long delay) {
404: if (delay < 0) {
405: throw new IllegalArgumentException();
406: }
407: scheduleImpl(task, delay, -1, false);
408: }
409:
410: /**
411: * Schedule a task for repeated fix-delay execution after a specific delay.
412: *
413: * @param task
414: * The task to schedule
415: * @param delay
416: * Amount of time before first execution
417: * @param period
418: * Amount of time between subsequent executions
419: *
420: * @exception IllegalArgumentException
421: * if delay < 0 or period < 0
422: * @exception IllegalStateException
423: * if the timer has been cancelled, the task has been
424: * scheduled or cancelled.
425: */
426: public void schedule(TimerTask task, long delay, long period) {
427: if (delay < 0 || period <= 0) {
428: throw new IllegalArgumentException();
429: }
430: scheduleImpl(task, delay, period, false);
431: }
432:
433: /**
434: * Schedule a task for repeated fix-delay execution after a specific time
435: * has been reached.
436: *
437: * @param task
438: * The task to schedule
439: * @param when
440: * Time of first execution
441: * @param period
442: * Amount of time between subsequent executions
443: *
444: * @exception IllegalArgumentException
445: * if when.getTime() < 0 or period < 0
446: * @exception IllegalStateException
447: * if the timer has been cancelled, the task has been
448: * scheduled or cancelled.
449: */
450: public void schedule(TimerTask task, Date when, long period) {
451: if (period <= 0 || when.getTime() < 0) {
452: throw new IllegalArgumentException();
453: }
454: long delay = when.getTime() - System.currentTimeMillis();
455: scheduleImpl(task, delay < 0 ? 0 : delay, period, false);
456: }
457:
458: /**
459: * Schedule a task for repeated fixed-rate execution after a specific delay
460: * has been happened. The difference of fixed-rate is that it may bunch up
461: * subsequent task runs to try to get the task repeating at it's desired
462: * time.
463: *
464: * @param task
465: * The task to schedule
466: * @param delay
467: * Amount of time before first execution
468: * @param period
469: * Amount of time between subsequent executions
470: *
471: * @exception IllegalArgumentException
472: * if delay < 0 or period < 0
473: * @exception IllegalStateException
474: * if the timer has been cancelled, the task has been
475: * scheduled or cancelled.
476: */
477: public void scheduleAtFixedRate(TimerTask task, long delay,
478: long period) {
479: if (delay < 0 || period <= 0) {
480: throw new IllegalArgumentException();
481: }
482: scheduleImpl(task, delay, period, true);
483: }
484:
485: /**
486: * Schedule a task for repeated fixed-rate execution after a specific time
487: * has been reached. The difference of fixed-rate is that it may bunch up
488: * subsequent task runs to try to get the task repeating at it's desired
489: * time.
490: *
491: * @param task
492: * The task to schedule
493: * @param when
494: * Time of first execution
495: * @param period
496: * Amount of time between subsequent executions
497: *
498: * @exception IllegalArgumentException
499: * if when.getTime() < 0 or period < 0
500: * @exception IllegalStateException
501: * if the timer has been cancelled, the task has been
502: * scheduled or cancelled.
503: */
504: public void scheduleAtFixedRate(TimerTask task, Date when,
505: long period) {
506: if (period <= 0 || when.getTime() < 0) {
507: throw new IllegalArgumentException();
508: }
509: long delay = when.getTime() - System.currentTimeMillis();
510: scheduleImpl(task, delay < 0 ? 0 : delay, period, true);
511: }
512:
513: /**
514: * Schedule a task.
515: *
516: * @param task
517: * @param delay
518: * @param period
519: * @param fixed
520: */
521: private void scheduleImpl(TimerTask task, long delay, long period,
522: boolean fixed) {
523: synchronized (impl) {
524: if (impl.cancelled) {
525: throw new IllegalStateException(Msg.getString("K00f3")); //$NON-NLS-1$
526: }
527:
528: long when = delay + System.currentTimeMillis();
529:
530: if (when < 0) {
531: throw new IllegalArgumentException(Msg
532: .getString("K00f5")); //$NON-NLS-1$
533: }
534:
535: synchronized (task.lock) {
536: if (task.isScheduled()) {
537: throw new IllegalStateException(Msg
538: .getString("K00f6")); //$NON-NLS-1$
539: }
540:
541: if (task.cancelled) {
542: throw new IllegalStateException(Msg
543: .getString("K00f7")); //$NON-NLS-1$
544: }
545:
546: task.when = when;
547: task.period = period;
548: task.fixedRate = fixed;
549: }
550:
551: // insert the newTask into queue
552: impl.insertTask(task);
553: }
554: }
555: }
|