View Javadoc

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 }