Commit | Line | Data |
---|---|---|
a917f100 NR |
1 | package be.nikiroo.utils.ui; |
2 | ||
3 | import java.beans.PropertyChangeEvent; | |
4 | import java.beans.PropertyChangeListener; | |
5 | import java.util.ArrayList; | |
6 | import java.util.HashMap; | |
7 | import java.util.Map; | |
8 | import java.util.TreeSet; | |
9 | ||
10 | import javax.swing.SwingWorker; | |
11 | ||
12 | /** | |
13 | * This class helps you delay some graphical actions and execute the most recent | |
14 | * ones when under contention. | |
15 | * <p> | |
16 | * How does it work? | |
17 | * <ul> | |
18 | * <li>it takes an ID and an associated {@link SwingWorker} and will call | |
19 | * {@link SwingWorker#execute()} after a small delay (see | |
20 | * {@link DelayWorker#DelayWorker(int)})</li> | |
21 | * <li>if a second call to {@link DelayWorker#delay(String, SwingWorker)} comes | |
22 | * with the same ID before the first one is done, it will be put on a waiting | |
23 | * queue</li> | |
24 | * <li>if a third call still with the same ID comes, its associated worker will | |
25 | * <b>replace</b> the one in the queue (only one worker per ID in the queue, | |
26 | * always the latest one)</li> | |
27 | * <li>when the first worker is done, it will check the waiting queue and | |
28 | * execute that latest worker if any</li> | |
29 | * </ul> | |
30 | * | |
31 | * @author niki | |
32 | * | |
33 | */ | |
34 | @SuppressWarnings("rawtypes") | |
35 | public class DelayWorker { | |
36 | private Map<String, SwingWorker> lazyEnCours; | |
37 | private Object lazyEnCoursLock; | |
38 | ||
39 | private TreeSet<String> wip; | |
40 | ||
41 | private Object waiter; | |
42 | ||
43 | private boolean cont; | |
44 | private boolean paused; | |
45 | private Thread loop; | |
46 | ||
47 | /** | |
48 | * Create a new {@link DelayWorker} with the given delay (in milliseconds) | |
49 | * before each drain of the queue. | |
50 | * | |
51 | * @param delayMs | |
52 | * the delay in milliseconds (can be 0, cannot be negative) | |
53 | */ | |
54 | public DelayWorker(final int delayMs) { | |
55 | if (delayMs < 0) { | |
56 | throw new IllegalArgumentException( | |
57 | "A waiting delay cannot be negative"); | |
58 | } | |
59 | ||
60 | lazyEnCours = new HashMap<String, SwingWorker>(); | |
61 | lazyEnCoursLock = new Object(); | |
62 | wip = new TreeSet<String>(); | |
63 | waiter = new Object(); | |
64 | cont = true; | |
65 | paused = false; | |
66 | ||
67 | loop = new Thread(new Runnable() { | |
68 | @Override | |
69 | public void run() { | |
70 | while (cont) { | |
71 | try { | |
72 | Thread.sleep(delayMs); | |
73 | } catch (InterruptedException e) { | |
74 | } | |
75 | ||
76 | Map<String, SwingWorker> workers = new HashMap<String, SwingWorker>(); | |
77 | synchronized (lazyEnCoursLock) { | |
78 | for (String key : new ArrayList<String>( | |
79 | lazyEnCours.keySet())) { | |
80 | if (!wip.contains(key)) { | |
81 | workers.put(key, lazyEnCours.remove(key)); | |
82 | } | |
83 | } | |
84 | } | |
85 | ||
86 | for (final String key : workers.keySet()) { | |
87 | SwingWorker worker = workers.get(key); | |
88 | ||
89 | synchronized (lazyEnCoursLock) { | |
90 | wip.add(key); | |
91 | } | |
92 | ||
93 | worker.addPropertyChangeListener( | |
94 | new PropertyChangeListener() { | |
95 | @Override | |
96 | public void propertyChange( | |
97 | PropertyChangeEvent evt) { | |
98 | synchronized (lazyEnCoursLock) { | |
99 | wip.remove(key); | |
100 | } | |
101 | wakeup(); | |
102 | } | |
103 | }); | |
104 | ||
105 | // Start it, at last | |
106 | worker.execute(); | |
107 | } | |
108 | ||
109 | synchronized (waiter) { | |
110 | do { | |
111 | try { | |
112 | if (cont) | |
113 | waiter.wait(); | |
114 | } catch (InterruptedException e) { | |
115 | } | |
116 | } while (cont && paused); | |
117 | } | |
118 | } | |
119 | } | |
120 | }); | |
121 | ||
122 | loop.setDaemon(true); | |
123 | loop.setName("Loop for DelayWorker"); | |
124 | } | |
125 | ||
126 | /** | |
127 | * Start the internal loop that will drain the processing queue. <b>MUST | |
128 | * NOT</b> be started twice (but see {@link DelayWorker#pause()} and | |
129 | * {@link DelayWorker#resume()} instead). | |
130 | */ | |
131 | public void start() { | |
132 | loop.start(); | |
133 | } | |
134 | ||
135 | /** | |
136 | * Pause the system until {@link DelayWorker#resume()} is called -- note | |
137 | * that it will still continue on the processes currently scheduled to run, | |
138 | * but will pause after that. | |
139 | * <p> | |
140 | * Can be called even if already paused, will just do nothing in that | |
141 | * context. | |
142 | */ | |
143 | public void pause() { | |
144 | paused = true; | |
145 | } | |
146 | ||
147 | /** | |
148 | * Check if the {@link DelayWorker} is currently paused. | |
149 | * | |
150 | * @return TRUE if it is | |
151 | */ | |
152 | public boolean isPaused() { | |
153 | return paused; | |
154 | } | |
155 | ||
156 | /** | |
157 | * Resume the system after a pause. | |
158 | * <p> | |
159 | * Can be called even if already running, will just do nothing in that | |
160 | * context. | |
161 | */ | |
162 | public void resume() { | |
163 | synchronized (waiter) { | |
164 | paused = false; | |
165 | wakeup(); | |
166 | } | |
167 | } | |
168 | ||
169 | /** | |
170 | * Stop the system. | |
171 | * <p> | |
172 | * Note: this is final, you <b>MUST NOT</b> call {@link DelayWorker#start()} | |
173 | * a second time (but see {@link DelayWorker#pause()} and | |
174 | * {@link DelayWorker#resume()} instead). | |
175 | */ | |
176 | public void stop() { | |
177 | synchronized (waiter) { | |
178 | cont = false; | |
179 | wakeup(); | |
180 | } | |
181 | } | |
182 | ||
183 | /** | |
184 | * Clear all the processes that were put on the queue but not yet scheduled | |
185 | * to be executed -- note that it will still continue on the processes | |
186 | * currently scheduled to run. | |
187 | */ | |
188 | public void clear() { | |
189 | synchronized (lazyEnCoursLock) { | |
190 | lazyEnCours.clear(); | |
191 | wip.clear(); | |
192 | } | |
193 | } | |
194 | ||
195 | /** | |
196 | * Put a new process in the delay queue. | |
197 | * | |
198 | * @param id | |
199 | * the ID of this process (if you want to skip workers when they | |
200 | * are superseded by a new one, you need to use the same ID key) | |
201 | * @param worker | |
202 | * the process to delay | |
203 | */ | |
8a4297ea | 204 | public void delay(String id, SwingWorker worker) { |
a917f100 NR |
205 | synchronized (lazyEnCoursLock) { |
206 | lazyEnCours.put(id, worker); | |
207 | } | |
208 | ||
209 | wakeup(); | |
210 | } | |
211 | ||
212 | /** | |
213 | * Wake up the loop thread. | |
214 | */ | |
215 | private void wakeup() { | |
216 | synchronized (waiter) { | |
217 | waiter.notifyAll(); | |
218 | } | |
219 | } | |
220 | } |