package org.limewire.collection; import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import org.limewire.util.Clock; /** * A utility to schedule, reschedule and cancel the execution of a task. * * <pre> * Calendar cal = new GregorianCalendar(); * System.out.println("1) " + cal.get(Calendar.SECOND)); * * Periodic p = new Periodic(new Runnable() { * public void run() { * Calendar cal = new GregorianCalendar(); * System.out.println("3) " + cal.get(Calendar.SECOND)); * }}, new SimpleTimer(false)); * * p.rescheduleIfLater(5000); * System.out.println("2) " + cal.get(Calendar.SECOND)); * * Time run-dependant Output: * 1) 23 * 2) 23 * 3) 28 * </pre> */ public class Periodic { private static final long NANO_ORIGIN = System.nanoTime(); private final ScheduledExecutorService scheduler; private final Delegate d; private long nextExecuteTime; private Future future; private final Clock clock; private long now() { if(clock == null) { return System.nanoTime() - NANO_ORIGIN; } else { return clock.nanoTime() - NANO_ORIGIN; } } /** * Creates a periodic task. * * @param r the <tt>Runnable</tt> to execute * @param scheduler the <tt>SchedulingThreadPool</tt> to schedule execution * on. */ public Periodic(Runnable r, ScheduledExecutorService scheduler) { this(r, scheduler, null); } /** * Creates a periodic task, with the given clock. * * @param r the <tt>Runnable</tt> to execute * @param scheduler the <tt>SchedulingThreadPool</tt> to schedule execution * on. */ public Periodic(Runnable r, ScheduledExecutorService scheduler, Clock clock) { this.d = new Delegate(r); this.scheduler = scheduler; this.clock = clock; } /** * Changes the execution time of this Periodic task if it is later than the * current execution time. * <p> * Note: some implementations of <tt>ScheduledExecutorService</tt> use * nanoseconds as their time unit, so do not schedule anything for more than * 292 years in the future. More practically, this means you should not use * Long.MAX_VALUE as parameter. * <p> * * @param newDelay the new delay from now when this should be executed * @return true if the execution time changed */ public synchronized boolean rescheduleIfLater(long newDelay) { newDelay = TimeUnit.MILLISECONDS.toNanos(newDelay); long now = now(); if (Long.MAX_VALUE - now < newDelay) newDelay = Long.MAX_VALUE - now; long newTime = now + newDelay; if (future == null) { nextExecuteTime = newTime; if (newDelay > 0) future = scheduler.schedule(d, newDelay, TimeUnit.NANOSECONDS); else scheduler.execute(d); return true; } else if (newTime > nextExecuteTime) { nextExecuteTime = newTime; return true; } return false; } /** * Changes the execution time of this Periodic task if it is sooner than the * current execution time. * * @param newDelay the new delay from now when this should be executed * @return true if the execution time changed */ public synchronized boolean rescheduleIfSooner(long newDelay) { // if not scheduled at all, just schedule if (future == null) return rescheduleIfLater(newDelay); newDelay = TimeUnit.MILLISECONDS.toNanos(newDelay); long now = now(); if (Long.MAX_VALUE - now < newDelay) newDelay = Long.MAX_VALUE - now; long newTime = now + newDelay; if (newTime < nextExecuteTime) { future.cancel(false); nextExecuteTime = newTime; if (newDelay > 0) future = scheduler.schedule(d, newDelay, TimeUnit.NANOSECONDS); else { future = null; scheduler.execute(d); } return true; } return false; } /** * Cancels any scheduled execution of the task. */ public synchronized void unschedule() { if (future != null) { future.cancel(false); future = null; nextExecuteTime = -1; } } private class Delegate implements Runnable { private final Runnable r; Delegate(Runnable r) { this.r = r; } public void run() { synchronized (Periodic.this) { future = null; long now = now(); if (now < nextExecuteTime) { future = scheduler.schedule(this, Math.max(0, nextExecuteTime - now), TimeUnit.NANOSECONDS); return; } } r.run(); } } protected Runnable getRunnable() { return d.r; } }