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