001    /*
002        Licensed to the Apache Software Foundation (ASF) under one
003        or more contributor license agreements.  See the NOTICE file
004        distributed with this work for additional information
005        regarding copyright ownership.  The ASF licenses this file
006        to you under the Apache License, Version 2.0 (the
007        "License"); you may not use this file except in compliance
008        with the License.  You may obtain a copy of the License at
009    
010           http://www.apache.org/licenses/LICENSE-2.0
011    
012        Unless required by applicable law or agreed to in writing,
013        software distributed under the License is distributed on an
014        "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015        KIND, either express or implied.  See the License for the
016        specific language governing permissions and limitations
017        under the License.  
018     */
019    
020    package org.apache.wiki.event;
021    
022    import java.lang.ref.WeakReference;
023    import java.util.*;
024    
025    import org.apache.log4j.Logger;
026    
027    /**
028     *  A singleton class that manages the addition and removal of WikiEvent
029     *  listeners to a event source, as well as the firing of events to those
030     *  listeners. An "event source" is the object delegating its event
031     *  handling to an inner delegating class supplied by this manager. The
032     *  class being serviced is considered a "client" of the delegate. The
033     *  WikiEventManager operates across any number of simultaneously-existing
034     *  WikiEngines since it manages all delegation on a per-object basis.
035     *  Anything that might fire a WikiEvent (or any of its subclasses) can be
036     *  a client.
037     *  </p>
038     *
039     *  <h3>Using a Delegate for Event Listener Management</h3>
040     *  <p>
041     *  Basically, rather than have all manner of client classes maintain their
042     *  own listener lists, add and remove listener methods, any class wanting
043     *  to attach listeners can simply request a delegate object to provide that
044     *  service. The delegate handles the listener list, the add and remove
045     *  listener methods. Firing events is then a matter of calling the
046     *  WikiEventManager's {@link #fireEvent(Object,WikiEvent)} method, where
047     *  the Object is the client. Prior to instantiating the event object, the
048     *  client can call {@link #isListening(Object)} to see there are any
049     *  listeners attached to its delegate.
050     *  </p>
051     *
052     *  <h3>Adding Listeners</h3>
053     *  <p>
054     *  Adding a WikiEventListener to an object is very simple:
055     *  </p>
056     *  <pre>
057     *      WikiEventManager.addWikiEventListener(object,listener);
058     *  </pre>
059     *
060     *  <h3>Removing Listeners</h3>
061     *  <p>
062     *  Removing a WikiEventListener from an object is very simple:
063     *  </p>
064     *  <pre>
065     *      WikiEventManager.removeWikiEventListener(object,listener);
066     *  </pre>
067     *  If you only have a reference to the listener, the following method
068     *  will remove it from any clients managed by the WikiEventManager:
069     *  <pre>
070     *      WikiEventManager.removeWikiEventListener(listener);
071     *  </pre>
072     *
073     *  <h3>Backward Compatibility: Replacing Existing <tt>fireEvent()</tt> Methods</h3>
074     *  <p>
075     *  Using one manager for all events processing permits consolidation of all event
076     *  listeners and their associated methods in one place rather than having them
077     *  attached to specific subcomponents of an application, and avoids a great deal
078     *  of event-related cut-and-paste code. Convenience methods that call the
079     *  WikiEventManager for event delegation can be written to maintain existing APIs.
080     *  </p>
081     *  <p>
082     *  For example, an existing <tt>fireEvent()</tt> method might look something like
083     *  this:
084     *  </p>
085     *  <pre>
086     *    protected final void fireEvent( WikiEvent event )
087     *    {
088     *        for ( Iterator it = m_listeners.iterator(); it.hasNext(); )
089     *        {
090     *            WikiEventListener listener = (WikiEventListener)it.next();
091     *            listener.actionPerformed(event);
092     *        }
093     *    }
094     *  </pre>
095     *  <p>
096     *  One disadvantage is that the above method is supplied with event objects,
097     *  which are created even when no listener exists for them. In a busy wiki
098     *  with many users unused/unnecessary event creation could be considerable.
099     *  Another advantage is that in addition to the iterator, there must be code
100     *  to support the addition and remove of listeners. The above could be
101     *  replaced with the below code (and with no necessary local support for
102     *  adding and removing listeners):
103     *  </p>
104     *  <pre>
105     *    protected final void fireEvent( int type )
106     *    {
107     *        if ( WikiEventManager.isListening(this) )
108     *        {
109     *            WikiEventManager.fireEvent(this,new WikiEngineEvent(this,type));
110     *        }
111     *    }
112     *  </pre>
113     *  <p>
114     *  This only needs to be customized to supply the specific parameters for
115     *  whatever WikiEvent you want to create.
116     *  </p>
117     *
118     *  <h3 id="preloading">Preloading Listeners</h3>
119     *  <p>
120     *  This may be used to create listeners for objects that don't yet exist,
121     *  particularly designed for embedded applications that need to be able
122     *  to listen for the instantiation of an Object, by maintaining a cache
123     *  of client-less WikiEvent sources that set their client upon being
124     *  popped from the cache. Each time any of the methods expecting a client
125     *  parameter is called with a null parameter it will preload an internal
126     *  cache with a client-less delegate object that will be popped and
127     *  returned in preference to creating a new object. This can have unwanted
128     *  side effects if there are multiple clients populating the cache with
129     *  listeners. The only check is for a Class match, so be aware if others
130     *  might be populating the client-less cache with listeners.
131     *  </p>
132     *  <h3>Listener lifecycle</h3>
133     *  <p>
134     *  Note that in most cases it is not necessary to remove a listener.
135     *  As of 2.4.97, the listeners are stored as WeakReferences, and will be
136     *  automatically cleaned at the next garbage collection, if you no longer
137     *  hold a reference to them.  Of course, until the garbage is collected,
138     *  your object might still be getting events, so if you wish to avoid that,
139     *  please remove it explicitly as described above.
140     *  </p>
141     * @since 2.4.20
142     */
143    public final class WikiEventManager
144    {
145        private static final Logger log = Logger.getLogger(WikiEventManager.class);
146    
147        /* If true, permits a WikiEventMonitor to be set. */
148        private static boolean c_permitMonitor = false;
149    
150        /* Optional listener to be used as all-event monitor. */
151        private static WikiEventListener c_monitor = null;
152    
153        /* The Map of client object to WikiEventDelegate. */
154        private final Map<Object, WikiEventDelegate> m_delegates = new HashMap<Object, WikiEventDelegate>();
155    
156        /* The Vector containing any preloaded WikiEventDelegates. */
157        private final Vector<WikiEventDelegate> m_preloadCache = new Vector<WikiEventDelegate>();
158    
159        /* Singleton instance of the WikiEventManager. */
160        private static WikiEventManager c_instance = null;
161    
162        // ............
163    
164        /**
165         *  Constructor for a WikiEventManager.
166         */
167        private WikiEventManager()
168        {
169            c_instance = this;
170            log.debug("instantiated WikiEventManager");
171        }
172    
173        /**
174         *  As this is a singleton class, this returns the single
175         *  instance of this class provided with the property file
176         *  filename and bit-wise application settings.
177         *
178         *  @return A shared instance of the WikiEventManager
179         */
180        public static WikiEventManager getInstance()
181        {
182            if( c_instance == null )
183            {
184                synchronized( WikiEventManager.class )
185                {
186                    WikiEventManager mgr = new WikiEventManager();
187                    // start up any post-instantiation services here
188                    return mgr;
189                }
190            }
191            return c_instance;
192        }
193    
194    
195        // public/API methods ......................................................
196    
197    
198        /**
199         *  Registers a WikiEventListener with a WikiEventDelegate for
200         *  the provided client object.
201         *
202         *  <h3>Monitor Listener</h3>
203         *
204         *  If <tt>client</tt> is a reference to the WikiEventManager class
205         *  itself and the compile-time flag {@link #c_permitMonitor} is true,
206         *  this attaches the listener as an all-event monitor, overwriting
207         *  any previous listener (hence returning true).
208         *  <p>
209         *  You can remove any existing monitor by either calling this method
210         *  with <tt>client</tt> as a reference to this class and the
211         *  <tt>listener</tt> parameter as null, or
212         *  {@link #removeWikiEventListener(Object,WikiEventListener)} with a
213         *  <tt>client</tt> as a reference to this class. The <tt>listener</tt>
214         *  parameter in this case is ignored.
215         *
216         * @param client   the client of the event source
217         * @param listener the event listener
218         * @return true if the listener was added (i.e., it was not already in the list and was added)
219         */
220        public static boolean addWikiEventListener(
221                Object client, WikiEventListener listener )
222        {
223            if ( client == WikiEventManager.class )
224            {
225                if ( c_permitMonitor ) c_monitor = listener;
226                return c_permitMonitor;
227            }
228            WikiEventDelegate delegate = getInstance().getDelegateFor(client);
229            return delegate.addWikiEventListener(listener);
230        }
231    
232    
233        /**
234         *  Un-registers a WikiEventListener with the WikiEventDelegate for
235         *  the provided client object.
236         *
237         * @param client   the client of the event source
238         * @param listener the event listener
239         * @return true if the listener was found and removed.
240         */
241        public static boolean removeWikiEventListener(
242                Object client, WikiEventListener listener )
243        {
244            if ( client == WikiEventManager.class )
245            {
246                c_monitor = null;
247                return true;
248            }
249            WikiEventDelegate delegate = getInstance().getDelegateFor(client);
250            return delegate.removeWikiEventListener(listener);
251        }
252    
253    
254        /**
255         *  Return the Set containing the WikiEventListeners attached to the
256         *  delegate for the supplied client. If there are no attached listeners,
257         *  returns an empty Iterator rather than null.  Note that this will
258         *  create a delegate for the client if it did not exist prior to the call.
259         *
260         *  <h3>NOTE</h3>
261         *  <p>
262         *  This method returns a Set rather than an Iterator because of the high
263         *  likelihood of the Set being modified while an Iterator might be active.
264         *  This returns an unmodifiable reference to the actual Set containing
265         *  the delegate's listeners. Any attempt to modify the Set will throw an
266         *  {@link java.lang.UnsupportedOperationException}. This method is not
267         *  synchronized and it should be understood that the composition of the
268         *  backing Set may change at any time.
269         *  </p>
270         *
271         * @param client   the client of the event source
272         * @return an unmodifiable Set containing the WikiEventListeners attached to the client
273         * @throws java.lang.UnsupportedOperationException  if any attempt is made to modify the Set
274         */
275        public static Set getWikiEventListeners( Object client )
276            throws UnsupportedOperationException
277        {
278            WikiEventDelegate delegate = getInstance().getDelegateFor(client);
279            return delegate.getWikiEventListeners();
280        }
281    
282    
283        /**
284         *  Un-registers a WikiEventListener from any WikiEventDelegate client managed by this
285         *  WikiEventManager. Since this is a one-to-one relation, the first match will be
286         *  returned upon removal; a true return value indicates the WikiEventListener was
287         *  found and removed.
288         *
289         * @param listener the event listener
290         * @return true if the listener was found and removed.
291         */
292        public static boolean removeWikiEventListener( WikiEventListener listener )
293        {
294            // get the Map.entry object for the entire Map, then check match on entry (listener)
295            WikiEventManager mgr = getInstance();
296            Map sources = mgr.getDelegates();
297            synchronized( sources )
298            {
299                // get an iterator over the Map.Enty objects in the map
300                Iterator it = sources.entrySet().iterator();
301                while( it.hasNext() )
302                {
303                    Map.Entry entry = (Map.Entry)it.next();
304                    // the entry value is the delegate
305                    WikiEventDelegate delegate = (WikiEventDelegate)entry.getValue();
306    
307                    // now see if we can remove the listener from the delegate
308                    // (delegate may be null because this is a weak reference)
309                    if( delegate != null && delegate.removeWikiEventListener(listener) )
310                    {
311                        return true; // was removed
312                    }
313                }
314            }
315            return false;
316        }
317    
318    
319        /**
320         *  Returns true if there are one or more listeners registered with
321         *  the provided client Object (undelegated event source). This locates
322         *  any delegate and checks to see if it has any listeners attached.
323         *
324         *  @param client the client Object
325         *  @return True, if there is a listener for this client object.
326         */
327        public static boolean isListening( Object client )
328        {
329            WikiEventDelegate source = getInstance().getDelegateFor(client);
330            return source != null ? source.isListening() : false ;
331        }
332    
333    
334        /**
335         *  Notify all listeners of the WikiEventDelegate having a registered
336         *  interest in change events of the supplied WikiEvent.
337         *
338         * @param client the client initiating the event.
339         * @param event  the WikiEvent to fire.
340         */
341        public static void fireEvent( Object client, WikiEvent event )
342        {
343            WikiEventDelegate source = getInstance().getDelegateFor(client);
344            if ( source != null ) source.fireEvent(event);
345            if ( c_monitor != null ) c_monitor.actionPerformed(event);
346        }
347    
348    
349        // private and utility methods .............................................
350    
351    
352        /**
353         *  Return the client-to-delegate Map.
354         */
355        private Map getDelegates()
356        {
357            return m_delegates;
358        }
359    
360    
361        /**
362         *  Returns a WikiEventDelegate for the provided client Object.
363         *  If the parameter is a class reference, will generate and return a
364         *  client-less WikiEventDelegate. If the parameter is not a Class and
365         *  the delegate cache contains any objects matching the Class of any
366         *  delegates in the cache, the first Class-matching delegate will be
367         *  used in preference to creating a new delegate.
368         *  If a null parameter is supplied, this will create a client-less
369         *  delegate that will attach to the first incoming client (i.e.,
370         *  there will be no Class-matching requirement).
371         *
372         * @param client   the client Object, or alternately a Class reference
373         * @return the WikiEventDelegate.
374         */
375        private WikiEventDelegate getDelegateFor( Object client )
376        {
377            synchronized( m_delegates )
378            {
379                if( client == null || client instanceof Class ) // then preload the cache
380                {
381                    WikiEventDelegate delegate = new WikiEventDelegate(client);
382                    m_preloadCache.add(delegate);
383                    m_delegates.put( client, delegate );
384                    return delegate;
385                }
386                else if( !m_preloadCache.isEmpty() )
387                {
388                    // then see if any of the cached delegates match the class of the incoming client
389                    for( int i = m_preloadCache.size()-1 ; i >= 0 ; i-- ) // start with most-recently added
390                    {
391                        WikiEventDelegate delegate = m_preloadCache.elementAt(i);
392                        if( delegate.getClientClass() == null
393                            || delegate.getClientClass().equals(client.getClass()) )
394                        {
395                            // we have a hit, so use it, but only on a client we haven't seen before
396                            if( !m_delegates.keySet().contains(client) )
397                            {
398                                m_preloadCache.remove(delegate);
399                                m_delegates.put( client, delegate );
400                                return delegate;
401                            }
402                        }
403                    }
404                }
405                // otherwise treat normally...
406                WikiEventDelegate delegate = m_delegates.get( client );
407                if( delegate == null )
408                {
409                    delegate = new WikiEventDelegate( client );
410                    m_delegates.put( client, delegate );
411                }
412                return delegate;
413            }
414        }
415    
416    
417        // .........................................................................
418    
419    
420        /**
421         *  Inner delegating class that manages event listener addition and
422         *  removal. Classes that generate events can obtain an instance of
423         *  this class from the WikiEventManager and delegate responsibility
424         *  to it. Interaction with this delegating class is done via the
425         *  methods of the {@link WikiEventDelegate} API.
426         *
427         * @since 2.4.20
428         */
429        private static final class WikiEventDelegate
430        {
431            /* A list of event listeners for this instance. */
432    
433            private ArrayList<WeakReference<WikiEventListener>> m_listenerList = new ArrayList<WeakReference<WikiEventListener>>();
434    
435            private Class  m_class  = null;
436    
437            /**
438             *  Constructor for an WikiEventDelegateImpl, provided
439             *  with the client Object it will service, or the Class
440             *  of client, the latter when used to preload a future
441             *  incoming delegate.
442             */
443            protected WikiEventDelegate( Object client )
444            {
445                if( client instanceof Class )
446                {
447                    m_class = (Class)client;
448                }
449            }
450    
451            /**
452             *  Returns the class of the client-less delegate, null if
453             *  this delegate is attached to a client Object.
454             */
455            protected Class getClientClass()
456            {
457                return m_class;
458            }
459    
460    
461            /**
462             *  Return an unmodifiable Set containing the WikiEventListeners of
463             *  this WikiEventDelegateImpl. If there are no attached listeners,
464             *  returns an empty Set rather than null.
465             *
466             * @return an unmodifiable Set containing this delegate's WikiEventListeners
467             * @throws java.lang.UnsupportedOperationException  if any attempt is made to modify the Set
468             */
469            public Set getWikiEventListeners()
470            {
471                synchronized( m_listenerList )
472                {
473                    TreeSet<WikiEventListener> set = new TreeSet<WikiEventListener>( new WikiEventListenerComparator() );
474    
475                    for( Iterator i = m_listenerList.iterator(); i.hasNext(); )
476                    {
477                        WikiEventListener l = (WikiEventListener) ((WeakReference)i.next()).get();
478    
479                        if( l != null )
480                        {
481                            set.add( l );
482                        }
483                    }
484    
485                    return Collections.unmodifiableSet(set);
486                }
487            }
488    
489    
490            /**
491             *  Adds <tt>listener</tt> as a listener for events fired by the WikiEventDelegate.
492             *
493             * @param listener   the WikiEventListener to be added
494             * @return true if the listener was added (i.e., it was not already in the list and was added)
495             */
496            public boolean addWikiEventListener( WikiEventListener listener )
497            {
498                synchronized( m_listenerList )
499                {
500                    return m_listenerList.add( new WeakReference<WikiEventListener>(listener) );
501                }
502            }
503    
504    
505            /**
506             *  Removes <tt>listener</tt> from the WikiEventDelegate.
507             *
508             * @param listener   the WikiEventListener to be removed
509             * @return true if the listener was removed (i.e., it was actually in the list and was removed)
510             */
511            public boolean removeWikiEventListener( WikiEventListener listener )
512            {
513                synchronized( m_listenerList )
514                {
515                    for( Iterator i = m_listenerList.iterator(); i.hasNext(); )
516                    {
517                        WikiEventListener l = (WikiEventListener) ((WeakReference)i.next()).get();
518    
519                        if( l == listener )
520                        {
521                            i.remove();
522                            return true;
523                        }
524                    }
525                }
526    
527                return false;
528            }
529    
530    
531            /**
532             *  Returns true if there are one or more listeners registered
533             *  with this instance.
534             */
535            public boolean isListening()
536            {
537                synchronized( m_listenerList )
538                {
539                    return !m_listenerList.isEmpty();
540                }
541            }
542    
543    
544            /**
545             *  Notify all listeners having a registered interest
546             *  in change events of the supplied WikiEvent.
547             */
548            public void fireEvent( WikiEvent event )
549            {
550                boolean needsCleanup = false;
551    
552                try
553                {
554                    synchronized( m_listenerList )
555                    {
556                        for( int i = 0; i < m_listenerList.size(); i++ )
557                        {
558                            WikiEventListener listener = (WikiEventListener) ((WeakReference)m_listenerList.get(i)).get();
559    
560                            if( listener != null )
561                            {
562                                listener.actionPerformed( event );
563                            }
564                            else
565                            {
566                                needsCleanup  = true;
567                            }
568                        }
569    
570                        //
571                        //  Remove all such listeners which have expired
572                        //
573                        if( needsCleanup )
574                        {
575                            for( int i = 0; i < m_listenerList.size(); i++ )
576                            {
577                                WeakReference w = m_listenerList.get(i);
578    
579                                if( w.get() == null ) m_listenerList.remove(i--);
580                            }
581                        }
582    
583                    }
584                }
585                catch( ConcurrentModificationException e )
586                {
587                    //
588                    //  We don't die, we just don't do notifications in that case.
589                    //
590                    log.info("Concurrent modification of event list; please report this.",e);
591                }
592    
593            }
594    
595        } // end inner class WikiEventDelegate
596    
597        private static class WikiEventListenerComparator implements Comparator<WikiEventListener>
598        {
599            // TODO: This method is a critical performance bottleneck
600            public int compare(WikiEventListener w0, WikiEventListener w1)
601            {
602                if( w1 == w0 || w0.equals(w1) ) return 0;
603    
604                return w1.hashCode() - w0.hashCode();
605            }
606        }
607    } // end org.apache.wiki.event.WikiEventManager