1 /*
2 * Copyright 2006-2016 The JGUIraffe Team.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License")
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 package net.sf.jguiraffe.gui.cmd;
17
18 import java.util.concurrent.ExecutorService;
19 import java.util.concurrent.LinkedBlockingQueue;
20 import java.util.concurrent.ThreadPoolExecutor;
21 import java.util.concurrent.TimeUnit;
22 import java.util.concurrent.atomic.AtomicInteger;
23
24 import javax.swing.event.EventListenerList;
25
26 import net.sf.jguiraffe.gui.builder.utils.GUISynchronizer;
27
28 import org.apache.commons.logging.Log;
29 import org.apache.commons.logging.LogFactory;
30
31 /**
32 * <p>
33 * A command queue implementation for GUI applications.
34 * </p>
35 * <p>
36 * This class maintains a queue, in which <code>Command</code> objects can be
37 * inserted by an application. At least one worker thread monitors the queue and
38 * executes the commands.
39 * </p>
40 * <p>
41 * In an application usually the <code>execute()</code> method is of most
42 * importance. Here new <code>Command</code> objects are passed. Then the
43 * application need not bother about execution of these commands in a background
44 * thread. The other methods will be used by worker threads to obtain commands
45 * and send notifications about executed commands.
46 * </p>
47 *
48 * @author Oliver Heger
49 * @version $Id: CommandQueueImpl.java 205 2012-01-29 18:29:57Z oheger $
50 */
51 public class CommandQueueImpl implements CommandQueue
52 {
53 /** The logger. */
54 private final Log log = LogFactory.getLog(CommandQueueImpl.class);
55
56 /** Stores the GUI synchronizer. */
57 private volatile GUISynchronizer sync;
58
59 /** Stores the executor service. */
60 private final ExecutorService executorService;
61
62 /** A list with the registered event listeners. */
63 private final EventListenerList listeners;
64
65 /** A counter for the commands that have been scheduled, but are not yet complete.*/
66 private final AtomicInteger pendingCommands;
67
68 /**
69 * Creates a new instance of <code>CommandQueue</code> and initializes it
70 * with the <code>GUISynchronizer</code>. A default
71 * <code>ExecutorService</code> will be created.
72 *
73 * @param sync the GUI synchronizer object (must not be <b>null</b>)
74 * @throws IllegalArgumentException if a required parameter is missing
75 */
76 public CommandQueueImpl(GUISynchronizer sync)
77 {
78 this(sync, createDefaultExecutorService());
79 }
80
81 /**
82 * Creates a new instance of <code>CommandQueueImpl</code> and initializes
83 * it with the <code>GUISynchronizer</code> and the
84 * <code>ExecutorService</code> to be used.
85 *
86 * @param sync the <code>GUISynchronizer</code> (must not be <b>null</b>)
87 * @param execSrvc the <code>ExecutorService</code> (must not be <b>null</b>)
88 * @throws IllegalArgumentException if a required parameter is missing
89 */
90 public CommandQueueImpl(GUISynchronizer sync, ExecutorService execSrvc)
91 {
92 if (execSrvc == null)
93 {
94 throw new IllegalArgumentException(
95 "Executor service must not be null!");
96 }
97
98 setGUISynchronizer(sync);
99 executorService = execSrvc;
100 listeners = new EventListenerList();
101 pendingCommands = new AtomicInteger();
102 }
103
104 /**
105 * Returns the <code>GUISynchronizer</code>.
106 *
107 * @return the GUI synchronizer
108 */
109 public GUISynchronizer getGUISynchronizer()
110 {
111 return sync;
112 }
113
114 /**
115 * Sets the <code>GUISynchronizer</code>.
116 *
117 * @param sync the GUI synchronizer (must not be <b>null</b>)
118 * @throws IllegalArgumentException if the synchronizer is <b>null</b>
119 */
120 public void setGUISynchronizer(GUISynchronizer sync)
121 {
122 if (sync == null)
123 {
124 throw new IllegalArgumentException(
125 "GUISynchronizer must not be null!");
126 }
127 this.sync = sync;
128 }
129
130 /**
131 * Returns the <code>ExecutorService</code> used by this command queue.
132 *
133 * @return the executor service
134 */
135 public ExecutorService getExecutorService()
136 {
137 return executorService;
138 }
139
140 /**
141 * Adds an event listener to this queue.
142 *
143 * @param l the listener to add (must not be <b>null</b>)
144 * @throws IllegalArgumentException if the listener is undefined
145 */
146 public void addQueueListener(CommandQueueListener l)
147 {
148 if (l == null)
149 {
150 throw new IllegalArgumentException(
151 "Event listener must not be null!");
152 }
153
154 listeners.add(CommandQueueListener.class, l);
155 }
156
157 /**
158 * Removes the specified event listener from this queue.
159 *
160 * @param l the listener to remove
161 */
162 public void removeQueueListener(CommandQueueListener l)
163 {
164 listeners.remove(CommandQueueListener.class, l);
165 }
166
167 /**
168 * Executes the specified command object. This implementation calls
169 * <code>createTask()</code> to create a task object for actually
170 * executing the command. This task is then passed to the
171 * <code>ExecutorService</code>, so it will be processed by a background
172 * thread.
173 *
174 * @param cmd the command to be executed (must not be <b>null</b>)
175 * @throws IllegalArgumentException if the command is <b>null</b>
176 * @throws IllegalStateException if <code>shutdown()</code> has already
177 * been called
178 */
179 public void execute(Command cmd)
180 {
181 if (cmd == null)
182 {
183 throw new IllegalArgumentException("Command must not be null!");
184 }
185 if (isShutdown())
186 {
187 throw new IllegalStateException(
188 "Cannot execute commands after shutdown!");
189 }
190
191 checkForBusyEvent();
192 fireQueueEvent(cmd, CommandQueueEvent.Type.COMMAND_ADDED);
193
194 if (cmd instanceof ScheduleAware)
195 {
196 ((ScheduleAware) cmd).commandScheduled(this);
197 }
198 getExecutorService().execute(createTask(cmd));
199 }
200
201 /**
202 * This method is called by a worker thread if a command has been executed.
203 *
204 * @param cmd the command that has been processed
205 */
206 public void processingFinished(Command cmd)
207 {
208 fireQueueEvent(cmd, CommandQueueEvent.Type.COMMAND_EXECUTED);
209 checkForIdleEvent();
210 }
211
212 /**
213 * Tests whether there are commands that have not been executed. This
214 * implementation uses a counter that keeps track of the scheduled
215 * commands (i.e. the number of commands passed to the
216 * {@code execute()} method) that have not yet been completed.
217 *
218 * @return a flag whether there are pending commands
219 */
220 public boolean isPending()
221 {
222 return pendingCommands.get() > 0;
223 }
224
225 /**
226 * Tests whether this command queue was shutdown. This information can be
227 * obtained from the underlying executor service.
228 *
229 * @return a flag whether <code>shutdown()</code> was called
230 */
231 public boolean isShutdown()
232 {
233 return getExecutorService().isShutdown();
234 }
235
236 /**
237 * Shuts down this command queue. For this implementation, a shutdown means
238 * that the underlying executor has to be shut down. Depending on the
239 * boolean parameter this method will block until the executor service's
240 * shutdown is complete.
241 *
242 * @param immediate a flag whether an immediate shutdown should be performed
243 */
244 public void shutdown(boolean immediate)
245 {
246 if (immediate)
247 {
248 getExecutorService().shutdownNow();
249 }
250
251 else
252 {
253 getExecutorService().shutdown();
254 try
255 {
256 getExecutorService().awaitTermination(Long.MAX_VALUE,
257 TimeUnit.SECONDS);
258 }
259 catch (InterruptedException iex)
260 {
261 log.warn("Waiting for shutdown was interrupted.", iex);
262 }
263 }
264 }
265
266 /**
267 * Notifies all registered listeners about a change in the state of this
268 * queue.
269 *
270 * @param cmd the affected command
271 * @param eventType the type of the event to fire
272 */
273 protected void fireQueueEvent(Command cmd, CommandQueueEvent.Type eventType)
274 {
275 CommandQueueEvent event = null;
276
277 Object[] lst = listeners.getListenerList();
278 for (int i = lst.length - 2; i >= 0; i -= 2)
279 {
280 if (lst[i] == CommandQueueListener.class)
281 {
282 if (event == null)
283 {
284 // lazily create event
285 event = new CommandQueueEvent(this, cmd, eventType);
286 }
287 ((CommandQueueListener) lst[i + 1]).commandQueueChanged(event);
288 }
289 }
290 }
291
292 /**
293 * Creates a task object for executing the passed in command. This task will
294 * then be passed to the <code>ExecutorService</code>.
295 *
296 * @param cmd the command to be executed
297 * @return the task for executing this command
298 */
299 protected Runnable createTask(Command cmd)
300 {
301 return new CommandExecutorTask(cmd);
302 }
303
304 /**
305 * Fires a busy event if necessary. This method is called when a new command
306 * is added. It updates the command counter and notifies the queue
307 * listeners registered if the queue changes from the idle into the busy
308 * state.
309 */
310 private void checkForBusyEvent()
311 {
312 if (pendingCommands.getAndIncrement() == 0)
313 {
314 fireQueueEvent(null, CommandQueueEvent.Type.QUEUE_BUSY);
315 }
316 }
317
318 /**
319 * Fires an idle event if necessary. This method is called when the
320 * execution of a command is complete. It updates the command counter and
321 * notifies the queue listeners registered if the queue changes from the
322 * busy into the idle state.
323 */
324 private void checkForIdleEvent()
325 {
326 if (pendingCommands.decrementAndGet() == 0)
327 {
328 fireQueueEvent(null, CommandQueueEvent.Type.QUEUE_IDLE);
329 }
330 }
331
332 /**
333 * Creates a default <code>ExecutorService</code>. This method is called
334 * by the constructor that does not accept an executor service. It creates a
335 * thread pool executor service using a single thread with an unbounded
336 * queue. This means that an arbitrary number of tasks can be scheduled,
337 * which will be executed by a single worker thread only (so no
338 * synchronization between the commands has to be performed).
339 *
340 * @return the default <code>ExecutorService</code>
341 */
342 static ExecutorService createDefaultExecutorService()
343 {
344 return new ThreadPoolExecutor(1, 1, Long.MAX_VALUE, TimeUnit.SECONDS,
345 new LinkedBlockingQueue<Runnable>());
346 }
347
348 /**
349 * An internally used task class for executing a command. Instances of this
350 * class are created for each scheduled command object and then passed to
351 * the executor service. In the run() method the life-cycle methods of the
352 * associated command object are called in the correct order.
353 */
354 private class CommandExecutorTask implements Runnable
355 {
356 /** The command to be executed. */
357 private final Command cmd;
358
359 /**
360 * Creates a new instance of <code>CommandExecutorTask</code> and sets
361 * the command to be executed.
362 *
363 * @param c the command
364 */
365 public CommandExecutorTask(Command c)
366 {
367 cmd = c;
368 }
369
370 /**
371 * The main method of this task. Executes the associated command.
372 */
373 public void run()
374 {
375 log.debug("Executing command.");
376 fireQueueEvent(cmd, CommandQueueEvent.Type.COMMAND_EXECUTING);
377
378 try
379 {
380 cmd.execute();
381 }
382 catch (Throwable t)
383 {
384 cmd.onException(t);
385 }
386 finally
387 {
388 cmd.onFinally();
389 handleGUIUpdate();
390 processingFinished(cmd);
391 }
392 }
393
394 /**
395 * Cares for GUI updates after a command has been successfully executed.
396 */
397 protected void handleGUIUpdate()
398 {
399 Runnable r = cmd.getGUIUpdater();
400 if (r != null)
401 {
402 getGUISynchronizer().syncInvoke(r);
403 }
404 }
405 }
406 }