001    /*
002     * Copyright 2006 Stephen J. McConnell.
003     *
004     * Licensed  under the  Apache License,  Version 2.0  (the "License");
005     * you may not use  this file  except in  compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     *   http://www.apache.org/licenses/LICENSE-2.0
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed  under the  License is distributed on an "AS IS" BASIS,
012     * WITHOUT  WARRANTIES OR CONDITIONS  OF ANY KIND, either  express  or
013     * implied.
014     *
015     * See the License for the specific language governing permissions and
016     * limitations under the License.
017     */
018    
019    package net.dpml.util;
020    
021    import java.util.EventObject;
022    import java.util.List;
023    import java.util.LinkedList;
024    
025    import net.dpml.transit.monitor.LoggingAdapter;
026    
027    /**
028     * A abstract base class that established an event queue and handles event dispatch 
029     * operations for listeners declared in classes extending this base class.
030     *
031     * @author <a href="http://www.dpml.net">Digital Product Meta Library</a>
032     * @version 1.0.3
033     */
034    public class EventQueue
035    {
036        // ------------------------------------------------------------------------
037        // state
038        // ------------------------------------------------------------------------
039        
040        private final EventDispatchThread m_thread;
041        
042        private final Logger m_logger;
043        
044        private final List m_queue;
045        
046        // ------------------------------------------------------------------------
047        // constructor
048        // ------------------------------------------------------------------------
049    
050       /**
051        * Creation of a new event queue.
052        * @param category the name used to construct a logging channel
053        * @deprecated Use new EventQueue( logger, "Thread name" ) instead.
054        */
055        public EventQueue( String category ) 
056        {
057            this( category, "Event Dispatch Thread" );
058        }
059    
060       /**
061        * Creation of a new model.
062        * @param logger the assigned logging channel
063        * @exception NullPointerException if the supplied logging channel is null
064        * @deprecated Use new EventQueue( logger, "Thread name" ) instead.
065        */
066        public EventQueue( Logger logger ) 
067          throws NullPointerException
068        {
069            this( logger, "Event Dispatch Thread" );
070        }
071        
072       /**
073        * Creation of a new event queue.
074        * @param category the name used to construct a logging channel
075        * @param name the name to assign to the thread
076        */
077        public EventQueue( String category, String name ) 
078        {
079            this( getLoggerForCategory( category ), name );
080        }
081    
082       /**
083        * Creation of a new model.
084        * @param logger the assigned logging channel
085        * @param name the name to assign to the thread
086        * @exception NullPointerException if the supplied logging channel or 
087        *   thread name is null
088        */
089        public EventQueue( Logger logger, String name ) 
090          throws NullPointerException
091        {
092            if( null == logger )
093            {
094                throw new NullPointerException( "logger" );
095            }
096            if( null == name )
097            {
098                throw new NullPointerException( "name" );
099            }
100            m_logger = logger;
101            m_queue = new LinkedList();
102            m_thread = new EventDispatchThread();
103            m_thread.setName( name );
104            m_thread.setDaemon( true );
105            m_thread.start();
106        }
107    
108        // ------------------------------------------------------------------------
109        // EventQueue
110        // ------------------------------------------------------------------------
111    
112       /**
113        * Terminate the dispatch thread.
114        */
115        public synchronized void terminateDispatchThread()
116        {
117            if( null != m_thread )
118            {
119                m_thread.dispose();
120            }
121        }
122    
123       /**
124        * Return the assigned logging channel.
125        * @return the logging channel
126        */
127        private Logger getLogger()
128        {
129            return m_logger;
130        }
131        
132        /**
133         * A single background thread ("the event notification thread") monitors
134         * the event queue and delivers events that are placed on the queue.
135         */
136        private class EventDispatchThread extends Thread 
137        {
138            private boolean m_continue = true;
139    
140            void dispose()
141            {
142                synchronized( m_queue )
143                {
144                    m_continue = false;
145                    m_queue.notify();
146                }
147            }
148            
149            public void run() 
150            {
151                while( m_continue ) 
152                {
153                    // Wait on m_queue till an event is present
154                    EventObject event = null;
155                    synchronized( m_queue ) 
156                    {
157                        try 
158                        {
159                            while( m_continue && m_queue.isEmpty() )
160                            { 
161                                m_queue.wait();
162                            }
163                            if ( !m_continue )
164                            {
165                                break;
166                            }
167                            Object object = m_queue.remove( 0 );
168                            try
169                            {
170                                event = (EventObject) object;
171                            }
172                            catch( ClassCastException cce )
173                            {
174                                final String error = 
175                                  "Unexpected class cast exception while processing an event." 
176                                  + "\nEvent: " + object;
177                                throw new IllegalStateException( error );
178                            }
179                        }
180                        catch( InterruptedException e )
181                        {
182                            return;
183                        }
184                    }
185    
186                    Object source = event.getSource();
187                    if( source instanceof EventHandler )
188                    {
189                        EventHandler handler = (EventHandler) source;
190                        try
191                        {
192                            handler.processEvent( event );
193                        }
194                        catch( Throwable e )
195                        {
196                            final String error = 
197                              "Unexpected error while processing event."
198                              + "\nEvent: " + event
199                              + "\nSource: " + this;
200                            getLogger().error( error, e );
201                        }
202                    }
203                    else
204                    {
205                        final String error = 
206                          "Event source is not an instance of " 
207                          + EventHandler.class.getName();
208                        getLogger().error( error );
209                    }
210                }
211            }
212        }
213    
214        /**
215         * Enqueue an event for delivery to registered
216         * listeners unless there are no registered
217         * listeners.
218         *
219         * @param event the event object to add to the queue
220         */
221        public void enqueueEvent( EventObject event )
222        {
223            enqueueEvent( event, false );
224        }
225    
226        /**
227         * Enqueue an event for delivery to registered
228         * listeners unless there are no registered
229         * listeners.
230         *
231         * @param event the event object to add to the queue
232         * @param waitForCompletion if TRUE the implementation will apply
233         *   the event to the event source event handler and return on 
234         *   copmpletion of evetn delivery
235         */
236        public void enqueueEvent( EventObject event, boolean waitForCompletion )
237        {
238            if( !waitForCompletion )
239            {    
240                synchronized( m_queue ) 
241                {
242                    m_queue.add( event );
243                    m_queue.notify();
244                }
245            }
246            else
247            {
248                Object source = event.getSource();
249                if( source instanceof EventHandler )
250                {
251                    EventHandler handler = (EventHandler) source;
252                    try
253                    {
254                        handler.processEvent( event );
255                    }
256                    catch( Throwable e )
257                    {
258                        final String error = 
259                          "Unexpected error while processing event."
260                          + "\nEvent: " + event
261                          + "\nSource: " + source;
262                        getLogger().error( error );
263                    }
264                }
265                else
266                {
267                    final String error = 
268                      "Event source is not an instance of " 
269                      + EventHandler.class.getName()
270                      + "\nSource: " + source.getClass().getName();
271                    throw new IllegalStateException( error );
272                }
273            }
274        }
275    
276       /**
277        * Return a logging channel for the supplied name.
278        * @param name the name to use in construction of the logging channel
279        * @return the logging channel
280        */
281        static Logger getLoggerForCategory( String name )
282        {
283            if( null == name )
284            {
285                return new LoggingAdapter( "" );
286            }
287            else
288            {
289                return new LoggingAdapter( name );
290            }
291        }
292    }