package com.eleybourn.bookcatalogue.utils; import java.io.Serializable; import java.util.Comparator; import java.util.PriorityQueue; import com.eleybourn.bookcatalogue.utils.SimpleTaskQueue.SimpleTask; import com.eleybourn.bookcatalogue.utils.SimpleTaskQueue.SimpleTaskContext; /** * Class to execute Runnable objects in a separate thread after a predetermined delay. * * @author pjw */ public class Terminator { /** * Dummy method to make sure static initialization is done. Need to be * called from main thread (usually at app startup). */ public static void init() { } /** Task queue to get book lists in background */ private static final SimpleTaskQueue mTaskQueue = new SimpleTaskQueue("Terminator", 1); /** Flag indicating the main thread process is still running and waiting for * a timer to elapse. */ private static boolean mIsRunning = false; /** Object used in synchronization */ private static final Object mWaitObject = new Object(); /** Details of the runnable to run */ private static class Event { public final long time; public final Runnable runnable; public Event(Runnable r, long time) { runnable = r; this.time = time; } } /** Comparator to ensure Event objects are returned in the correct order */ private static class EventComparator implements Comparator<Event>, Serializable { private static final long serialVersionUID = 1L; @Override public int compare(Event lhs, Event rhs) { if (lhs.time < rhs.time) { return -1; } else if (lhs.time > rhs.time) { return 1; } else { return 0; } } } /** Queue of Event objects currently awaiting execution */ private static final PriorityQueue<Event> mEvents = new PriorityQueue<Event>(10, new EventComparator()); /** * Enqueue the passed runnable to be run after the specified delay. * * @param r Runnable to execute * @param delay Delay before execution */ public static void enqueue(Runnable r, long delay) { // Compute actual time long time = System.currentTimeMillis() + delay; // Create Event and add to queue. Event e = new Event(r, time); synchronized(mTaskQueue) { mEvents.add(e); // Make sure task is actually running if (!mIsRunning) { mTaskQueue.enqueue(new TerminatorTask()); mIsRunning = true; } else { // Wake up task in case this object has a shorter timer synchronized(mWaitObject){ mWaitObject.notify(); } } } } /** * Background task to process the queue and schedule appropriate delays * * @author pjw */ private static class TerminatorTask implements SimpleTask { @Override public void run(SimpleTaskContext taskContext) throws Exception { System.out.println("Terminator starting"); do { Event e; long delay; // Check when next task due synchronized(mTaskQueue) { // Lok for a task; if exception or none found, abort. try { e = mEvents.peek(); } catch(Exception ex) { e = null; } if (e == null) { mIsRunning = false; return; } // Check how long until it should run delay = e.time - System.currentTimeMillis(); // If it's due now, then remove it from the queue. if (delay <= 0) mEvents.remove(e); } if (delay > 0) { // If we have nothing to run, wait for first synchronized(mWaitObject) { try { mWaitObject.wait(delay); } catch(Exception ex) { Logger.logError(ex); } } } else { // Run the available event // TODO: if 'run' blocks, then our Terminator stops terminating! Should probably be another thread. But...not for now. try { e.runnable.run(); } catch(Exception ex) { Logger.logError(ex); } } } while (true); } @Override public void onFinish(Exception e) { System.out.println("Terminator terminating. I'll be back."); } } }