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
020package org.apache.wiki.event;
021
022import java.lang.ref.WeakReference;
023import java.util.*;
024
025import 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 */
143public 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