| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package java.util; |
| |
| import org.apache.harmony.luni.internal.nls.Messages; |
| |
| /** |
| * {@code Timer}s are used to schedule jobs for execution in a background process. A |
| * single thread is used for the scheduling and this thread has the option of |
| * being a daemon thread. By calling {@code cancel} you can terminate a |
| * {@code Timer} and its associated thread. All tasks which are scheduled to run after |
| * this point are cancelled. Tasks are executed sequentially but are subject to |
| * the delays from other tasks run methods. If a specific task takes an |
| * excessive amount of time to run it may impact the time at which subsequent |
| * tasks may run. |
| * <p> |
| * |
| * The {@code TimerTask} does not offer any guarantees about the real-time nature of |
| * scheduling tasks as its underlying implementation relies on the |
| * {@code Object.wait(long)} method. |
| * <p> |
| * Multiple threads can share a single {@code Timer} without the need for their own |
| * synchronization. |
| * <p> |
| * A {@code Timer} can be set to schedule tasks either at a fixed rate or |
| * with a fixed period. Fixed-period execution is the default. |
| * <p> |
| * The difference between fixed-rate and fixed-period execution |
| * is the following: With fixed-rate execution, the start time of each |
| * successive run of the task is scheduled in absolute terms without regard for when the previous |
| * task run actually took place. This can result in a series of bunched-up runs |
| * (one launched immediately after another) if busy resources or other |
| * system delays prevent the {@code Timer} from firing for an extended time. |
| * With fixed-period execution, each successive run of the |
| * task is scheduled relative to the start time of the previous run of the |
| * task, so two runs of the task are never fired closer together in time than |
| * the specified {@code period}. |
| * |
| * @see TimerTask |
| * @see java.lang.Object#wait(long) |
| */ |
| public class Timer { |
| |
| private static final class TimerImpl extends Thread { |
| |
| private static final class TimerHeap { |
| private int DEFAULT_HEAP_SIZE = 256; |
| |
| private TimerTask[] timers = new TimerTask[DEFAULT_HEAP_SIZE]; |
| |
| private int size = 0; |
| |
| private int deletedCancelledNumber = 0; |
| |
| public TimerTask minimum() { |
| return timers[0]; |
| } |
| |
| public boolean isEmpty() { |
| return size == 0; |
| } |
| |
| public void insert(TimerTask task) { |
| if (timers.length == size) { |
| TimerTask[] appendedTimers = new TimerTask[size * 2]; |
| System.arraycopy(timers, 0, appendedTimers, 0, size); |
| timers = appendedTimers; |
| } |
| timers[size++] = task; |
| upHeap(); |
| } |
| |
| public void delete(int pos) { |
| // posible to delete any position of the heap |
| if (pos >= 0 && pos < size) { |
| timers[pos] = timers[--size]; |
| timers[size] = null; |
| downHeap(pos); |
| } |
| } |
| |
| private void upHeap() { |
| int current = size - 1; |
| int parent = (current - 1) / 2; |
| |
| while (timers[current].when < timers[parent].when) { |
| // swap the two |
| TimerTask tmp = timers[current]; |
| timers[current] = timers[parent]; |
| timers[parent] = tmp; |
| |
| // update pos and current |
| current = parent; |
| parent = (current - 1) / 2; |
| } |
| } |
| |
| private void downHeap(int pos) { |
| int current = pos; |
| int child = 2 * current + 1; |
| |
| while (child < size && size > 0) { |
| // compare the children if they exist |
| if (child + 1 < size |
| && timers[child + 1].when < timers[child].when) { |
| child++; |
| } |
| |
| // compare selected child with parent |
| if (timers[current].when < timers[child].when) { |
| break; |
| } |
| |
| // swap the two |
| TimerTask tmp = timers[current]; |
| timers[current] = timers[child]; |
| timers[child] = tmp; |
| |
| // update pos and current |
| current = child; |
| child = 2 * current + 1; |
| } |
| } |
| |
| public void reset() { |
| timers = new TimerTask[DEFAULT_HEAP_SIZE]; |
| size = 0; |
| } |
| |
| public void adjustMinimum() { |
| downHeap(0); |
| } |
| |
| public void deleteIfCancelled() { |
| for (int i = 0; i < size; i++) { |
| if (timers[i].cancelled) { |
| deletedCancelledNumber++; |
| delete(i); |
| // re-try this point |
| i--; |
| } |
| } |
| } |
| |
| private int getTask(TimerTask task) { |
| for (int i = 0; i < timers.length; i++) { |
| if (timers[i] == task) { |
| return i; |
| } |
| } |
| return -1; |
| } |
| |
| } |
| |
| /** |
| * True if the method cancel() of the Timer was called or the !!!stop() |
| * method was invoked |
| */ |
| private boolean cancelled; |
| |
| /** |
| * True if the Timer has become garbage |
| */ |
| private boolean finished; |
| |
| /** |
| * Vector consists of scheduled events, sorted according to |
| * {@code when} field of TaskScheduled object. |
| */ |
| private TimerHeap tasks = new TimerHeap(); |
| |
| /** |
| * Starts a new timer. |
| * |
| * @param name thread's name |
| * @param isDaemon daemon thread or not |
| */ |
| TimerImpl(String name, boolean isDaemon) { |
| this.setName(name); |
| this.setDaemon(isDaemon); |
| this.start(); |
| } |
| |
| /** |
| * This method will be launched on separate thread for each Timer |
| * object. |
| */ |
| @Override |
| public void run() { |
| while (true) { |
| TimerTask task; |
| synchronized (this) { |
| // need to check cancelled inside the synchronized block |
| if (cancelled) { |
| return; |
| } |
| if (tasks.isEmpty()) { |
| if (finished) { |
| return; |
| } |
| // no tasks scheduled -- sleep until any task appear |
| try { |
| this.wait(); |
| } catch (InterruptedException ignored) { |
| } |
| continue; |
| } |
| |
| long currentTime = System.currentTimeMillis(); |
| |
| task = tasks.minimum(); |
| long timeToSleep; |
| |
| synchronized (task.lock) { |
| if (task.cancelled) { |
| tasks.delete(0); |
| continue; |
| } |
| |
| // check the time to sleep for the first task scheduled |
| timeToSleep = task.when - currentTime; |
| } |
| |
| if (timeToSleep > 0) { |
| // sleep! |
| try { |
| this.wait(timeToSleep); |
| } catch (InterruptedException ignored) { |
| } |
| continue; |
| } |
| |
| // no sleep is necessary before launching the task |
| |
| synchronized (task.lock) { |
| int pos = 0; |
| if (tasks.minimum().when != task.when) { |
| pos = tasks.getTask(task); |
| } |
| if (task.cancelled) { |
| tasks.delete(tasks.getTask(task)); |
| continue; |
| } |
| |
| // set time to schedule |
| task.setScheduledTime(task.when); |
| |
| // remove task from queue |
| tasks.delete(pos); |
| |
| // set when the next task should be launched |
| if (task.period >= 0) { |
| // this is a repeating task, |
| if (task.fixedRate) { |
| // task is scheduled at fixed rate |
| task.when = task.when + task.period; |
| } else { |
| // task is scheduled at fixed delay |
| task.when = System.currentTimeMillis() |
| + task.period; |
| } |
| |
| // insert this task into queue |
| insertTask(task); |
| } else { |
| task.when = 0; |
| } |
| } |
| } |
| |
| boolean taskCompletedNormally = false; |
| try { |
| task.run(); |
| taskCompletedNormally = true; |
| } finally { |
| if (!taskCompletedNormally) { |
| synchronized (this) { |
| cancelled = true; |
| } |
| } |
| } |
| } |
| } |
| |
| private void insertTask(TimerTask newTask) { |
| // callers are synchronized |
| tasks.insert(newTask); |
| this.notify(); |
| } |
| |
| /** |
| * Cancels timer. |
| */ |
| public synchronized void cancel() { |
| cancelled = true; |
| tasks.reset(); |
| this.notify(); |
| } |
| |
| public int purge() { |
| if (tasks.isEmpty()) { |
| return 0; |
| } |
| // callers are synchronized |
| tasks.deletedCancelledNumber = 0; |
| tasks.deleteIfCancelled(); |
| return tasks.deletedCancelledNumber; |
| } |
| |
| } |
| |
| private static final class FinalizerHelper { |
| private final TimerImpl impl; |
| |
| FinalizerHelper(TimerImpl impl) { |
| super(); |
| this.impl = impl; |
| } |
| |
| @Override |
| protected void finalize() { |
| synchronized (impl) { |
| impl.finished = true; |
| impl.notify(); |
| } |
| } |
| } |
| |
| private static long timerId; |
| |
| private synchronized static long nextId() { |
| return timerId++; |
| } |
| |
| /* This object will be used in synchronization purposes */ |
| private final TimerImpl impl; |
| |
| // Used to finalize thread |
| @SuppressWarnings("unused") |
| private final FinalizerHelper finalizer; |
| |
| /** |
| * Creates a new named {@code Timer} which may be specified to be run as a |
| * daemon thread. |
| * |
| * @param name the name of the {@code Timer}. |
| * @param isDaemon true if {@code Timer}'s thread should be a daemon thread. |
| * @throws NullPointerException is {@code name} is {@code null} |
| */ |
| public Timer(String name, boolean isDaemon) { |
| super(); |
| if (name == null){ |
| throw new NullPointerException("name is null"); |
| } |
| this.impl = new TimerImpl(name, isDaemon); |
| this.finalizer = new FinalizerHelper(impl); |
| } |
| |
| /** |
| * Creates a new named {@code Timer} which does not run as a daemon thread. |
| * |
| * @param name the name of the Timer. |
| * @throws NullPointerException is {@code name} is {@code null} |
| */ |
| public Timer(String name) { |
| this(name, false); |
| } |
| |
| /** |
| * Creates a new {@code Timer} which may be specified to be run as a daemon thread. |
| * |
| * @param isDaemon {@code true} if the {@code Timer}'s thread should be a daemon thread. |
| */ |
| public Timer(boolean isDaemon) { |
| this("Timer-" + Timer.nextId(), isDaemon); |
| } |
| |
| /** |
| * Creates a new non-daemon {@code Timer}. |
| */ |
| public Timer() { |
| this(false); |
| } |
| |
| /** |
| * Cancels the {@code Timer} and removes any scheduled tasks. If there is a |
| * currently running task it is not affected. No more tasks may be scheduled |
| * on this {@code Timer}. Subsequent calls do nothing. |
| */ |
| public void cancel() { |
| impl.cancel(); |
| } |
| |
| /** |
| * Removes all canceled tasks from the task queue. If there are no |
| * other references on the tasks, then after this call they are free |
| * to be garbage collected. |
| * |
| * @return the number of canceled tasks that were removed from the task |
| * queue. |
| */ |
| public int purge() { |
| synchronized (impl) { |
| return impl.purge(); |
| } |
| } |
| |
| /** |
| * Schedule a task for single execution. If {@code when} is less than the |
| * current time, it will be scheduled to be executed as soon as possible. |
| * |
| * @param task |
| * the task to schedule. |
| * @param when |
| * time of execution. |
| * @throws IllegalArgumentException |
| * if {@code when.getTime() < 0}. |
| * @throws IllegalStateException |
| * if the {@code Timer} has been canceled, or if the task has been |
| * scheduled or canceled. |
| */ |
| public void schedule(TimerTask task, Date when) { |
| if (when.getTime() < 0) { |
| throw new IllegalArgumentException(); |
| } |
| long delay = when.getTime() - System.currentTimeMillis(); |
| scheduleImpl(task, delay < 0 ? 0 : delay, -1, false); |
| } |
| |
| /** |
| * Schedule a task for single execution after a specified delay. |
| * |
| * @param task |
| * the task to schedule. |
| * @param delay |
| * amount of time in milliseconds before execution. |
| * @throws IllegalArgumentException |
| * if {@code delay < 0}. |
| * @throws IllegalStateException |
| * if the {@code Timer} has been canceled, or if the task has been |
| * scheduled or canceled. |
| */ |
| public void schedule(TimerTask task, long delay) { |
| if (delay < 0) { |
| throw new IllegalArgumentException(); |
| } |
| scheduleImpl(task, delay, -1, false); |
| } |
| |
| /** |
| * Schedule a task for repeated fixed-delay execution after a specific delay. |
| * |
| * @param task |
| * the task to schedule. |
| * @param delay |
| * amount of time in milliseconds before first execution. |
| * @param period |
| * amount of time in milliseconds between subsequent executions. |
| * @throws IllegalArgumentException |
| * if {@code delay < 0} or {@code period < 0}. |
| * @throws IllegalStateException |
| * if the {@code Timer} has been canceled, or if the task has been |
| * scheduled or canceled. |
| */ |
| public void schedule(TimerTask task, long delay, long period) { |
| if (delay < 0 || period <= 0) { |
| throw new IllegalArgumentException(); |
| } |
| scheduleImpl(task, delay, period, false); |
| } |
| |
| /** |
| * Schedule a task for repeated fixed-delay execution after a specific time |
| * has been reached. |
| * |
| * @param task |
| * the task to schedule. |
| * @param when |
| * time of first execution. |
| * @param period |
| * amount of time in milliseconds between subsequent executions. |
| * @throws IllegalArgumentException |
| * if {@code when.getTime() < 0} or {@code period < 0}. |
| * @throws IllegalStateException |
| * if the {@code Timer} has been canceled, or if the task has been |
| * scheduled or canceled. |
| */ |
| public void schedule(TimerTask task, Date when, long period) { |
| if (period <= 0 || when.getTime() < 0) { |
| throw new IllegalArgumentException(); |
| } |
| long delay = when.getTime() - System.currentTimeMillis(); |
| scheduleImpl(task, delay < 0 ? 0 : delay, period, false); |
| } |
| |
| /** |
| * Schedule a task for repeated fixed-rate execution after a specific delay |
| * has passed. |
| * |
| * @param task |
| * the task to schedule. |
| * @param delay |
| * amount of time in milliseconds before first execution. |
| * @param period |
| * amount of time in milliseconds between subsequent executions. |
| * @throws IllegalArgumentException |
| * if {@code delay < 0} or {@code period < 0}. |
| * @throws IllegalStateException |
| * if the {@code Timer} has been canceled, or if the task has been |
| * scheduled or canceled. |
| */ |
| public void scheduleAtFixedRate(TimerTask task, long delay, long period) { |
| if (delay < 0 || period <= 0) { |
| throw new IllegalArgumentException(); |
| } |
| scheduleImpl(task, delay, period, true); |
| } |
| |
| /** |
| * Schedule a task for repeated fixed-rate execution after a specific time |
| * has been reached. |
| * |
| * @param task |
| * the task to schedule. |
| * @param when |
| * time of first execution. |
| * @param period |
| * amount of time in milliseconds between subsequent executions. |
| * @throws IllegalArgumentException |
| * if {@code when.getTime() < 0} or {@code period < 0}. |
| * @throws IllegalStateException |
| * if the {@code Timer} has been canceled, or if the task has been |
| * scheduled or canceled. |
| */ |
| public void scheduleAtFixedRate(TimerTask task, Date when, long period) { |
| if (period <= 0 || when.getTime() < 0) { |
| throw new IllegalArgumentException(); |
| } |
| long delay = when.getTime() - System.currentTimeMillis(); |
| scheduleImpl(task, delay, period, true); |
| } |
| |
| /* |
| * Schedule a task. |
| */ |
| private void scheduleImpl(TimerTask task, long delay, long period, |
| boolean fixed) { |
| synchronized (impl) { |
| if (impl.cancelled) { |
| throw new IllegalStateException(Messages.getString("luni.41")); //$NON-NLS-1$ |
| } |
| |
| long when = delay + System.currentTimeMillis(); |
| |
| if (when < 0) { |
| throw new IllegalArgumentException(Messages.getString("luni.42")); //$NON-NLS-1$ |
| } |
| |
| synchronized (task.lock) { |
| if (task.isScheduled()) { |
| throw new IllegalStateException(Messages.getString("luni.43")); //$NON-NLS-1$ |
| } |
| |
| if (task.cancelled) { |
| throw new IllegalStateException(Messages.getString("luni.44")); //$NON-NLS-1$ |
| } |
| |
| task.when = when; |
| task.period = period; |
| task.fixedRate = fixed; |
| } |
| |
| // insert the newTask into queue |
| impl.insertTask(task); |
| } |
| } |
| } |