1/*
2 *  Licensed to the Apache Software Foundation (ASF) under one or more
3 *  contributor license agreements.  See the NOTICE file distributed with
4 *  this work for additional information regarding copyright ownership.
5 *  The ASF licenses this file to You under the Apache License, Version 2.0
6 *  (the "License"); you may not use this file except in compliance with
7 *  the License.  You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 *  Unless required by applicable law or agreed to in writing, software
12 *  distributed under the License is distributed on an "AS IS" BASIS,
13 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *  See the License for the specific language governing permissions and
15 *  limitations under the License.
16 */
17
18package java.util;
19
20/**
21 * Timers schedule one-shot or recurring {@link TimerTask tasks} for execution.
22 * Prefer {@link java.util.concurrent.ScheduledThreadPoolExecutor
23 * ScheduledThreadPoolExecutor} for new code.
24 *
25 * <p>Each timer has one thread on which tasks are executed sequentially. When
26 * this thread is busy running a task, runnable tasks may be subject to delays.
27 *
28 * <p>One-shot tasks are scheduled to run at an absolute time or after a relative
29 * delay.
30 *
31 * <p>Recurring tasks are scheduled with either a fixed period or a fixed rate:
32 * <ul>
33 *   <li>With the default <strong>fixed-period execution</strong>, each
34 *       successive run of a task is scheduled relative to the start time of
35 *       the previous run, so two runs are never fired closer together in time
36 *       than the specified {@code period}.
37 *   <li>With <strong>fixed-rate execution</strong>, the start time of each
38 *       successive run of a task is scheduled without regard for when the
39 *       previous run took place. This may result in a series of bunched-up runs
40 *       (one launched immediately after another) if delays prevent the timer
41 *       from starting tasks on time.
42 * </ul>
43 *
44 * <p>When a timer is no longer needed, users should call {@link #cancel}, which
45 * releases the timer's thread and other resources. Timers not explicitly
46 * cancelled may hold resources indefinitely.
47 *
48 * <p>This class does not offer guarantees about the real-time nature of task
49 * scheduling. Multiple threads can share a single timer without
50 * synchronization.
51 */
52public class Timer {
53
54    private static final class TimerImpl extends Thread {
55
56        private static final class TimerHeap {
57            private int DEFAULT_HEAP_SIZE = 256;
58
59            private TimerTask[] timers = new TimerTask[DEFAULT_HEAP_SIZE];
60
61            private int size = 0;
62
63            private int deletedCancelledNumber = 0;
64
65            public TimerTask minimum() {
66                return timers[0];
67            }
68
69            public boolean isEmpty() {
70                return size == 0;
71            }
72
73            public void insert(TimerTask task) {
74                if (timers.length == size) {
75                    TimerTask[] appendedTimers = new TimerTask[size * 2];
76                    System.arraycopy(timers, 0, appendedTimers, 0, size);
77                    timers = appendedTimers;
78                }
79                timers[size++] = task;
80                upHeap();
81            }
82
83            public void delete(int pos) {
84                // posible to delete any position of the heap
85                if (pos >= 0 && pos < size) {
86                    timers[pos] = timers[--size];
87                    timers[size] = null;
88                    downHeap(pos);
89                }
90            }
91
92            private void upHeap() {
93                int current = size - 1;
94                int parent = (current - 1) / 2;
95
96                while (timers[current].when < timers[parent].when) {
97                    // swap the two
98                    TimerTask tmp = timers[current];
99                    timers[current] = timers[parent];
100                    timers[parent] = tmp;
101
102                    // update pos and current
103                    current = parent;
104                    parent = (current - 1) / 2;
105                }
106            }
107
108            private void downHeap(int pos) {
109                int current = pos;
110                int child = 2 * current + 1;
111
112                while (child < size && size > 0) {
113                    // compare the children if they exist
114                    if (child + 1 < size
115                            && timers[child + 1].when < timers[child].when) {
116                        child++;
117                    }
118
119                    // compare selected child with parent
120                    if (timers[current].when < timers[child].when) {
121                        break;
122                    }
123
124                    // swap the two
125                    TimerTask tmp = timers[current];
126                    timers[current] = timers[child];
127                    timers[child] = tmp;
128
129                    // update pos and current
130                    current = child;
131                    child = 2 * current + 1;
132                }
133            }
134
135            public void reset() {
136                timers = new TimerTask[DEFAULT_HEAP_SIZE];
137                size = 0;
138            }
139
140            public void adjustMinimum() {
141                downHeap(0);
142            }
143
144            public void deleteIfCancelled() {
145                for (int i = 0; i < size; i++) {
146                    if (timers[i].cancelled) {
147                        deletedCancelledNumber++;
148                        delete(i);
149                        // re-try this point
150                        i--;
151                    }
152                }
153            }
154
155            private int getTask(TimerTask task) {
156                for (int i = 0; i < timers.length; i++) {
157                    if (timers[i] == task) {
158                        return i;
159                    }
160                }
161                return -1;
162            }
163
164        }
165
166        /**
167         * True if the method cancel() of the Timer was called or the !!!stop()
168         * method was invoked
169         */
170        private boolean cancelled;
171
172        /**
173         * True if the Timer has become garbage
174         */
175        private boolean finished;
176
177        /**
178         * Contains scheduled events, sorted according to
179         * {@code when} field of TaskScheduled object.
180         */
181        private TimerHeap tasks = new TimerHeap();
182
183        /**
184         * Starts a new timer.
185         *
186         * @param name thread's name
187         * @param isDaemon daemon thread or not
188         */
189        TimerImpl(String name, boolean isDaemon) {
190            this.setName(name);
191            this.setDaemon(isDaemon);
192            this.start();
193        }
194
195        /**
196         * This method will be launched on separate thread for each Timer
197         * object.
198         */
199        @Override
200        public void run() {
201            while (true) {
202                TimerTask task;
203                synchronized (this) {
204                    // need to check cancelled inside the synchronized block
205                    if (cancelled) {
206                        return;
207                    }
208                    if (tasks.isEmpty()) {
209                        if (finished) {
210                            return;
211                        }
212                        // no tasks scheduled -- sleep until any task appear
213                        try {
214                            this.wait();
215                        } catch (InterruptedException ignored) {
216                        }
217                        continue;
218                    }
219
220                    long currentTime = System.currentTimeMillis();
221
222                    task = tasks.minimum();
223                    long timeToSleep;
224
225                    synchronized (task.lock) {
226                        if (task.cancelled) {
227                            tasks.delete(0);
228                            continue;
229                        }
230
231                        // check the time to sleep for the first task scheduled
232                        timeToSleep = task.when - currentTime;
233                    }
234
235                    if (timeToSleep > 0) {
236                        // sleep!
237                        try {
238                            this.wait(timeToSleep);
239                        } catch (InterruptedException ignored) {
240                        }
241                        continue;
242                    }
243
244                    // no sleep is necessary before launching the task
245
246                    synchronized (task.lock) {
247                        int pos = 0;
248                        if (tasks.minimum().when != task.when) {
249                            pos = tasks.getTask(task);
250                        }
251                        if (task.cancelled) {
252                            tasks.delete(tasks.getTask(task));
253                            continue;
254                        }
255
256                        // set time to schedule
257                        task.setScheduledTime(task.when);
258
259                        // remove task from queue
260                        tasks.delete(pos);
261
262                        // set when the next task should be launched
263                        if (task.period >= 0) {
264                            // this is a repeating task,
265                            if (task.fixedRate) {
266                                // task is scheduled at fixed rate
267                                task.when = task.when + task.period;
268                            } else {
269                                // task is scheduled at fixed delay
270                                task.when = System.currentTimeMillis()
271                                        + task.period;
272                            }
273
274                            // insert this task into queue
275                            insertTask(task);
276                        } else {
277                            task.when = 0;
278                        }
279                    }
280                }
281
282                boolean taskCompletedNormally = false;
283                try {
284                    task.run();
285                    taskCompletedNormally = true;
286                } finally {
287                    if (!taskCompletedNormally) {
288                        synchronized (this) {
289                            cancelled = true;
290                        }
291                    }
292                }
293            }
294        }
295
296        private void insertTask(TimerTask newTask) {
297            // callers are synchronized
298            tasks.insert(newTask);
299            this.notify();
300        }
301
302        /**
303         * Cancels timer.
304         */
305        public synchronized void cancel() {
306            cancelled = true;
307            tasks.reset();
308            this.notify();
309        }
310
311        public int purge() {
312            if (tasks.isEmpty()) {
313                return 0;
314            }
315            // callers are synchronized
316            tasks.deletedCancelledNumber = 0;
317            tasks.deleteIfCancelled();
318            return tasks.deletedCancelledNumber;
319        }
320
321    }
322
323    private static final class FinalizerHelper {
324        private final TimerImpl impl;
325
326        FinalizerHelper(TimerImpl impl) {
327            this.impl = impl;
328        }
329
330        @Override protected void finalize() throws Throwable {
331            try {
332                synchronized (impl) {
333                    impl.finished = true;
334                    impl.notify();
335                }
336            } finally {
337                super.finalize();
338            }
339        }
340    }
341
342    private static long timerId;
343
344    private synchronized static long nextId() {
345        return timerId++;
346    }
347
348    /* This object will be used in synchronization purposes */
349    private final TimerImpl impl;
350
351    // Used to finalize thread
352    @SuppressWarnings("unused")
353    private final FinalizerHelper finalizer;
354
355    /**
356     * Creates a new named {@code Timer} which may be specified to be run as a
357     * daemon thread.
358     *
359     * @throws NullPointerException if {@code name == null}
360     */
361    public Timer(String name, boolean isDaemon) {
362        if (name == null) {
363            throw new NullPointerException("name == null");
364        }
365        this.impl = new TimerImpl(name, isDaemon);
366        this.finalizer = new FinalizerHelper(impl);
367    }
368
369    /**
370     * Creates a new named {@code Timer} which does not run as a daemon thread.
371     *
372     * @throws NullPointerException if {@code name == null}
373     */
374    public Timer(String name) {
375        this(name, false);
376    }
377
378    /**
379     * Creates a new {@code Timer} which may be specified to be run as a daemon thread.
380     *
381     * @param isDaemon {@code true} if the {@code Timer}'s thread should be a daemon thread.
382     */
383    public Timer(boolean isDaemon) {
384        this("Timer-" + Timer.nextId(), isDaemon);
385    }
386
387    /**
388     * Creates a new non-daemon {@code Timer}.
389     */
390    public Timer() {
391        this(false);
392    }
393
394    /**
395     * Cancels the {@code Timer} and all scheduled tasks. If there is a
396     * currently running task it is not affected. No more tasks may be scheduled
397     * on this {@code Timer}. Subsequent calls do nothing.
398     */
399    public void cancel() {
400        impl.cancel();
401    }
402
403    /**
404     * Removes all canceled tasks from the task queue. If there are no
405     * other references on the tasks, then after this call they are free
406     * to be garbage collected.
407     *
408     * @return the number of canceled tasks that were removed from the task
409     *         queue.
410     */
411    public int purge() {
412        synchronized (impl) {
413            return impl.purge();
414        }
415    }
416
417    /**
418     * Schedule a task for single execution. If {@code when} is less than the
419     * current time, it will be scheduled to be executed as soon as possible.
420     *
421     * @param task
422     *            the task to schedule.
423     * @param when
424     *            time of execution.
425     * @throws IllegalArgumentException
426     *                if {@code when.getTime() < 0}.
427     * @throws IllegalStateException
428     *                if the {@code Timer} has been canceled, or if the task has been
429     *                scheduled or canceled.
430     */
431    public void schedule(TimerTask task, Date when) {
432        if (when.getTime() < 0) {
433            throw new IllegalArgumentException("when < 0: " + when.getTime());
434        }
435        long delay = when.getTime() - System.currentTimeMillis();
436        scheduleImpl(task, delay < 0 ? 0 : delay, -1, false);
437    }
438
439    /**
440     * Schedule a task for single execution after a specified delay.
441     *
442     * @param task
443     *            the task to schedule.
444     * @param delay
445     *            amount of time in milliseconds before execution.
446     * @throws IllegalArgumentException
447     *                if {@code delay < 0}.
448     * @throws IllegalStateException
449     *                if the {@code Timer} has been canceled, or if the task has been
450     *                scheduled or canceled.
451     */
452    public void schedule(TimerTask task, long delay) {
453        if (delay < 0) {
454            throw new IllegalArgumentException("delay < 0: " + delay);
455        }
456        scheduleImpl(task, delay, -1, false);
457    }
458
459    /**
460     * Schedule a task for repeated fixed-delay execution after a specific delay.
461     *
462     * @param task
463     *            the task to schedule.
464     * @param delay
465     *            amount of time in milliseconds before first execution.
466     * @param period
467     *            amount of time in milliseconds between subsequent executions.
468     * @throws IllegalArgumentException
469     *                if {@code delay < 0} or {@code period <= 0}.
470     * @throws IllegalStateException
471     *                if the {@code Timer} has been canceled, or if the task has been
472     *                scheduled or canceled.
473     */
474    public void schedule(TimerTask task, long delay, long period) {
475        if (delay < 0 || period <= 0) {
476            throw new IllegalArgumentException();
477        }
478        scheduleImpl(task, delay, period, false);
479    }
480
481    /**
482     * Schedule a task for repeated fixed-delay execution after a specific time
483     * has been reached.
484     *
485     * @param task
486     *            the task to schedule.
487     * @param when
488     *            time of first execution.
489     * @param period
490     *            amount of time in milliseconds between subsequent executions.
491     * @throws IllegalArgumentException
492     *                if {@code when.getTime() < 0} or {@code period <= 0}.
493     * @throws IllegalStateException
494     *                if the {@code Timer} has been canceled, or if the task has been
495     *                scheduled or canceled.
496     */
497    public void schedule(TimerTask task, Date when, long period) {
498        if (period <= 0 || when.getTime() < 0) {
499            throw new IllegalArgumentException();
500        }
501        long delay = when.getTime() - System.currentTimeMillis();
502        scheduleImpl(task, delay < 0 ? 0 : delay, period, false);
503    }
504
505    /**
506     * Schedule a task for repeated fixed-rate execution after a specific delay
507     * has passed.
508     *
509     * @param task
510     *            the task to schedule.
511     * @param delay
512     *            amount of time in milliseconds before first execution.
513     * @param period
514     *            amount of time in milliseconds between subsequent executions.
515     * @throws IllegalArgumentException
516     *                if {@code delay < 0} or {@code period <= 0}.
517     * @throws IllegalStateException
518     *                if the {@code Timer} has been canceled, or if the task has been
519     *                scheduled or canceled.
520     */
521    public void scheduleAtFixedRate(TimerTask task, long delay, long period) {
522        if (delay < 0 || period <= 0) {
523            throw new IllegalArgumentException();
524        }
525        scheduleImpl(task, delay, period, true);
526    }
527
528    /**
529     * Schedule a task for repeated fixed-rate execution after a specific time
530     * has been reached.
531     *
532     * @param task
533     *            the task to schedule.
534     * @param when
535     *            time of first execution.
536     * @param period
537     *            amount of time in milliseconds between subsequent executions.
538     * @throws IllegalArgumentException
539     *                if {@code when.getTime() < 0} or {@code period <= 0}.
540     * @throws IllegalStateException
541     *                if the {@code Timer} has been canceled, or if the task has been
542     *                scheduled or canceled.
543     */
544    public void scheduleAtFixedRate(TimerTask task, Date when, long period) {
545        if (period <= 0 || when.getTime() < 0) {
546            throw new IllegalArgumentException();
547        }
548        long delay = when.getTime() - System.currentTimeMillis();
549        scheduleImpl(task, delay, period, true);
550    }
551
552    /*
553     * Schedule a task.
554     */
555    private void scheduleImpl(TimerTask task, long delay, long period, boolean fixed) {
556        synchronized (impl) {
557            if (impl.cancelled) {
558                throw new IllegalStateException("Timer was canceled");
559            }
560
561            long when = delay + System.currentTimeMillis();
562
563            if (when < 0) {
564                throw new IllegalArgumentException("Illegal delay to start the TimerTask: " + when);
565            }
566
567            synchronized (task.lock) {
568                if (task.isScheduled()) {
569                    throw new IllegalStateException("TimerTask is scheduled already");
570                }
571
572                if (task.cancelled) {
573                    throw new IllegalStateException("TimerTask is canceled");
574                }
575
576                task.when = when;
577                task.period = period;
578                task.fixedRate = fixed;
579            }
580
581            // insert the newTask into queue
582            impl.insertTask(task);
583        }
584    }
585}
586