From: Niki Roo Date: Thu, 9 Apr 2020 16:10:33 +0000 (+0200) Subject: DelayWorker: now delays on work done, not on work scheduled X-Git-Url: https://git.nikiroo.be/?a=commitdiff_plain;h=e7d4218c344d292a7450a4c18bbcab1e567d8d9a;p=nikiroo-utils.git DelayWorker: now delays on work done, not on work scheduled --- diff --git a/src/be/nikiroo/fanfix_swing/gui/utils/DelayWorker.java b/src/be/nikiroo/fanfix_swing/gui/utils/DelayWorker.java index 19a9b5c..6d630c5 100644 --- a/src/be/nikiroo/fanfix_swing/gui/utils/DelayWorker.java +++ b/src/be/nikiroo/fanfix_swing/gui/utils/DelayWorker.java @@ -1,26 +1,65 @@ package be.nikiroo.fanfix_swing.gui.utils; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.ArrayList; import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; import java.util.Map; +import java.util.TreeSet; import javax.swing.SwingWorker; +/** + * This class helps you delay some graphical actions and execute the most recent + * ones when under contention. + *

+ * How does it work? + *

+ * + * @author niki + * + */ @SuppressWarnings("rawtypes") public class DelayWorker { private Map lazyEnCours; private Object lazyEnCoursLock; + private TreeSet wip; + private Object waiter; private boolean cont; private boolean paused; private Thread loop; + /** + * Create a new {@link DelayWorker} with the given delay (in milliseconds) + * before each drain of the queue. + * + * @param delayMs + * the delay in milliseconds (can be 0, cannot be negative) + */ public DelayWorker(final int delayMs) { + if (delayMs < 0) { + throw new IllegalArgumentException( + "A waiting delay cannot be negative"); + } + lazyEnCours = new HashMap(); lazyEnCoursLock = new Object(); + wip = new TreeSet(); waiter = new Object(); cont = true; paused = false; @@ -34,13 +73,36 @@ public class DelayWorker { } catch (InterruptedException e) { } - List workers; + Map workers = new HashMap(); synchronized (lazyEnCoursLock) { - workers = new LinkedList( - lazyEnCours.values()); - lazyEnCours.clear(); + for (String key : new ArrayList( + lazyEnCours.keySet())) { + if (!wip.contains(key)) { + workers.put(key, lazyEnCours.remove(key)); + } + } } - for (SwingWorker worker : workers) { + + for (final String key : workers.keySet()) { + SwingWorker worker = workers.get(key); + + synchronized (lazyEnCoursLock) { + wip.add(key); + } + + worker.addPropertyChangeListener( + new PropertyChangeListener() { + @Override + public void propertyChange( + PropertyChangeEvent evt) { + synchronized (lazyEnCoursLock) { + wip.remove(key); + } + wakeup(); + } + }); + + // Start it, at last worker.execute(); } @@ -56,23 +118,47 @@ public class DelayWorker { } } }); + loop.setDaemon(true); loop.setName("Loop for DelayWorker"); } - // twice = not legal + /** + * Start the internal loop that will drain the processing queue. MUST + * NOT be started twice (but see {@link DelayWorker#pause()} and + * {@link DelayWorker#resume()} instead). + */ public void start() { loop.start(); } + /** + * Pause the system until {@link DelayWorker#resume()} is called -- note + * that it will still continue on the processes currently scheduled to run, + * but will pause after that. + *

+ * Can be called even if already paused, will just do nothing in that + * context. + */ public void pause() { paused = true; } + /** + * Check if the {@link DelayWorker} is currently paused. + * + * @return TRUE if it is + */ public boolean isPaused() { return paused; } + /** + * Resume the system after a pause. + *

+ * Can be called even if already running, will just do nothing in that + * context. + */ public void resume() { synchronized (waiter) { paused = false; @@ -80,6 +166,13 @@ public class DelayWorker { } } + /** + * Stop the system. + *

+ * Note: this is final, you MUST NOT call {@link DelayWorker#start()} + * a second time (but see {@link DelayWorker#pause()} and + * {@link DelayWorker#resume()} instead). + */ public void stop() { synchronized (waiter) { cont = false; @@ -87,13 +180,28 @@ public class DelayWorker { } } + /** + * Clear all the processes that were put on the queue but not yet scheduled + * to be executed -- note that it will still continue on the processes + * currently scheduled to run. + */ public void clear() { synchronized (lazyEnCoursLock) { lazyEnCours.clear(); + wip.clear(); } } - public void delay(final String id, final SwingWorker worker) { + /** + * Put a new process in the delay queue. + * + * @param id + * the ID of this process (if you want to skip workers when they + * are superseded by a new one, you need to use the same ID key) + * @param worker + * the process to delay + */ + public void delay(final String id, final SwingWorker worker) { synchronized (lazyEnCoursLock) { lazyEnCours.put(id, worker); } @@ -101,6 +209,9 @@ public class DelayWorker { wakeup(); } + /** + * Wake up the loop thread. + */ private void wakeup() { synchronized (waiter) { waiter.notifyAll();