| /* |
| Licensed to the Apache Software Foundation (ASF) under one |
| or more contributor license agreements. See the NOTICE file |
| distributed with this work for additional information |
| regarding copyright ownership. The ASF licenses this file |
| to you under the Apache License, Version 2.0 (the |
| "License"); you may not use this file except in compliance |
| with the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, |
| software distributed under the License is distributed on an |
| "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| KIND, either express or implied. See the License for the |
| specific language governing permissions and limitations |
| under the License. |
| */ |
| |
| package org.apache.wiki.event; |
| |
| import org.apache.log4j.Logger; |
| |
| import java.lang.ref.WeakReference; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.ConcurrentModificationException; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.TreeSet; |
| import java.util.Vector; |
| |
| /** |
| * A singleton class that manages the addition and removal of WikiEvent listeners to a event source, as well as the firing of events |
| * to those listeners. An "event source" is the object delegating its event handling to an inner delegating class supplied by this |
| * manager. The class being serviced is considered a "client" of the delegate. The WikiEventManager operates across any number of |
| * simultaneously-existing Engines since it manages all delegation on a per-object basis. Anything that might fire a WikiEvent |
| * (or any of its subclasses) can be a client. |
| * </p> |
| * |
| * <h3>Using a Delegate for Event Listener Management</h3> |
| * <p> |
| * Basically, rather than have all manner of client classes maintain their own listener lists, add and remove listener methods, any |
| * class wanting to attach listeners can simply request a delegate object to provide that service. The delegate handles the listener |
| * list, the add and remove listener methods. Firing events is then a matter of calling the WikiEventManager's |
| * {@link #fireEvent(Object,WikiEvent)} method, where the Object is the client. Prior to instantiating the event object, the |
| * client can call {@link #isListening(Object)} to see there are any listeners attached to its delegate. |
| * </p> |
| * |
| * <h3>Adding Listeners</h3> |
| * <p> |
| * Adding a WikiEventListener to an object is very simple: |
| * </p> |
| * <pre> |
| * WikiEventManager.addWikiEventListener( object, listener ); |
| * </pre> |
| * |
| * <h3>Removing Listeners</h3> |
| * <p> |
| * Removing a WikiEventListener from an object is very simple: |
| * </p> |
| * <pre> |
| * WikiEventManager.removeWikiEventListener( object, listener ); |
| * </pre> |
| * If you only have a reference to the listener, the following method will remove it from any clients managed by the WikiEventManager: |
| * <pre> |
| * WikiEventManager.removeWikiEventListener( listener ); |
| * </pre> |
| * |
| * <h3>Backward Compatibility: Replacing Existing <tt>fireEvent()</tt> Methods</h3> |
| * <p> |
| * Using one manager for all events processing permits consolidation of all event listeners and their associated methods in one place |
| * rather than having them attached to specific subcomponents of an application, and avoids a great deal of event-related cut-and-paste |
| * code. Convenience methods that call the WikiEventManager for event delegation can be written to maintain existing APIs. |
| * </p> |
| * <p> |
| * For example, an existing <tt>fireEvent()</tt> method might look something like this: |
| * </p> |
| * <pre> |
| * protected final void fireEvent( WikiEvent event ) { |
| * for( WikiEventListener listener : m_listeners ) { |
| * listener.actionPerformed( event ); |
| * } |
| * } |
| * </pre> |
| * <p> |
| * One disadvantage is that the above method is supplied with event objects, which are created even when no listener exists for them. |
| * In a busy wiki with many users unused/unnecessary event creation could be considerable. Another advantage is that in addition to |
| * the iterator, there must be code to support the addition and remove of listeners. The above could be replaced with the below code |
| * (and with no necessary local support for adding and removing listeners): |
| * </p> |
| * <pre> |
| * protected final void fireEvent( int type ) { |
| * if( WikiEventManager.isListening( this ) ) { |
| * WikiEventManager.fireEvent( this, new WikiEngineEvent( this, type ) ); |
| * } |
| * } |
| * </pre> |
| * <p> |
| * This only needs to be customized to supply the specific parameters for whatever WikiEvent you want to create. |
| * </p> |
| * |
| * <h3 id="preloading">Preloading Listeners</h3> |
| * <p> |
| * This may be used to create listeners for objects that don't yet exist, particularly designed for embedded applications that need |
| * to be able to listen for the instantiation of an Object, by maintaining a cache of client-less WikiEvent sources that set their |
| * client upon being popped from the cache. Each time any of the methods expecting a client parameter is called with a null parameter |
| * it will preload an internal cache with a client-less delegate object that will be popped and returned in preference to creating a |
| * new object. This can have unwanted side effects if there are multiple clients populating the cache with listeners. The only check |
| * is for a Class match, so be aware if others might be populating the client-less cache with listeners. |
| * </p> |
| * <h3>Listener lifecycle</h3> |
| * <p> |
| * Note that in most cases it is not necessary to remove a listener. As of 2.4.97, the listeners are stored as WeakReferences, and |
| * will be automatically cleaned at the next garbage collection, if you no longer hold a reference to them. Of course, until the |
| * garbage is collected, your object might still be getting events, so if you wish to avoid that, please remove it explicitly as |
| * described above. |
| * </p> |
| * @since 2.4.20 |
| */ |
| public final class WikiEventManager { |
| |
| private static final Logger log = Logger.getLogger(WikiEventManager.class); |
| |
| /* If true, permits a WikiEventMonitor to be set. */ |
| private static boolean c_permitMonitor = false; |
| |
| /* Optional listener to be used as all-event monitor. */ |
| private static WikiEventListener c_monitor = null; |
| |
| /* The Map of client object to WikiEventDelegate. */ |
| private final Map< Object, WikiEventDelegate > m_delegates = new HashMap<>(); |
| |
| /* The Vector containing any preloaded WikiEventDelegates. */ |
| private final Vector< WikiEventDelegate > m_preloadCache = new Vector<>(); |
| |
| /* Singleton instance of the WikiEventManager. */ |
| private static WikiEventManager c_instance = null; |
| |
| /** Constructor for a WikiEventManager. */ |
| private WikiEventManager() { |
| c_instance = this; |
| log.debug( "instantiated WikiEventManager" ); |
| } |
| |
| /** |
| * As this is a singleton class, this returns the single instance of this class provided with the property file |
| * filename and bit-wise application settings. |
| * |
| * @return A shared instance of the WikiEventManager |
| */ |
| public static WikiEventManager getInstance() { |
| if( c_instance == null ) { |
| synchronized( WikiEventManager.class ) { |
| return new WikiEventManager(); |
| // start up any post-instantiation services here |
| } |
| } |
| return c_instance; |
| } |
| |
| // public/API methods ...................................................... |
| |
| /** |
| * Registers a WikiEventListener with a WikiEventDelegate for the provided client object. |
| * |
| * <h3>Monitor Listener</h3> |
| * |
| * If <tt>client</tt> is a reference to the WikiEventManager class itself and the compile-time flag {@link #c_permitMonitor} is true, |
| * this attaches the listener as an all-event monitor, overwriting any previous listener (hence returning true). |
| * <p> |
| * You can remove any existing monitor by either calling this method with <tt>client</tt> as a reference to this class and the |
| * <tt>listener</tt> parameter as null, or {@link #removeWikiEventListener(Object,WikiEventListener)} with a <tt>client</tt> |
| * as a reference to this class. The <tt>listener</tt> parameter in this case is ignored. |
| * |
| * @param client the client of the event source |
| * @param listener the event listener |
| * @return true if the listener was added (i.e., it was not already in the list and was added) |
| */ |
| public static boolean addWikiEventListener( final Object client, final WikiEventListener listener ) { |
| if( client == WikiEventManager.class ) { |
| if ( c_permitMonitor ) { |
| c_monitor = listener; |
| } |
| return c_permitMonitor; |
| } |
| final WikiEventDelegate delegate = getInstance().getDelegateFor(client); |
| return delegate.addWikiEventListener(listener); |
| } |
| |
| /** |
| * Un-registers a WikiEventListener with the WikiEventDelegate for the provided client object. |
| * |
| * @param client the client of the event source |
| * @param listener the event listener |
| * @return true if the listener was found and removed. |
| */ |
| public static boolean removeWikiEventListener( final Object client, final WikiEventListener listener ) { |
| if ( client == WikiEventManager.class ) { |
| c_monitor = null; |
| return true; |
| } |
| final WikiEventDelegate delegate = getInstance().getDelegateFor(client); |
| return delegate.removeWikiEventListener(listener); |
| } |
| |
| /** |
| * Return the Set containing the WikiEventListeners attached to the delegate for the supplied client. If there are no |
| * attached listeners, returns an empty Iterator rather than null. Note that this will create a delegate for the client |
| * if it did not exist prior to the call. |
| * |
| * <h3>NOTE</h3> |
| * <p> |
| * This method returns a Set rather than an Iterator because of the high likelihood of the Set being modified while an |
| * Iterator might be active. This returns an unmodifiable reference to the actual Set containing the delegate's listeners. |
| * Any attempt to modify the Set will throw an {@link java.lang.UnsupportedOperationException}. This method is not |
| * synchronized and it should be understood that the composition of the backing Set may change at any time. |
| * </p> |
| * |
| * @param client the client of the event source |
| * @return an unmodifiable Set containing the WikiEventListeners attached to the client |
| * @throws java.lang.UnsupportedOperationException if any attempt is made to modify the Set |
| */ |
| public static Set<WikiEventListener> getWikiEventListeners( final Object client ) throws UnsupportedOperationException { |
| final WikiEventDelegate delegate = getInstance().getDelegateFor(client); |
| return delegate.getWikiEventListeners(); |
| } |
| |
| /** |
| * Un-registers a WikiEventListener from any WikiEventDelegate client managed by this WikiEventManager. Since this is a one-to-one |
| * relation, the first match will be returned upon removal; a true return value indicates the WikiEventListener was found and removed. |
| * |
| * @param listener the event listener |
| * @return true if the listener was found and removed. |
| */ |
| public static boolean removeWikiEventListener( final WikiEventListener listener ) { |
| // get the Map.entry object for the entire Map, then check match on entry (listener) |
| final WikiEventManager mgr = getInstance(); |
| final Map< Object, WikiEventDelegate > sources = mgr.getDelegates(); |
| synchronized( sources ) { |
| // get an iterator over the Map.Enty objects in the map |
| for( final Map.Entry< Object, WikiEventDelegate > entry : sources.entrySet() ) { |
| // the entry value is the delegate |
| final WikiEventDelegate delegate = entry.getValue(); |
| |
| // now see if we can remove the listener from the delegate (delegate may be null because this is a weak reference) |
| if( delegate != null && delegate.removeWikiEventListener( listener ) ) { |
| return true; // was removed |
| } |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Returns true if there are one or more listeners registered with the provided client Object (undelegated event source). This locates |
| * any delegate and checks to see if it has any listeners attached. |
| * |
| * @param client the client Object |
| * @return True, if there is a listener for this client object. |
| */ |
| public static boolean isListening( final Object client ) { |
| return getInstance().getDelegateFor( client ).isListening(); |
| } |
| |
| /** |
| * Notify all listeners of the WikiEventDelegate having a registered interest in change events of the supplied WikiEvent. |
| * |
| * @param client the client initiating the event. |
| * @param event the WikiEvent to fire. |
| */ |
| public static void fireEvent( final Object client, final WikiEvent event ) { |
| final WikiEventDelegate source = getInstance().getDelegateFor( client ); |
| source.fireEvent( event ); |
| if( c_monitor != null ) { |
| c_monitor.actionPerformed( event ); |
| } |
| } |
| |
| // private and utility methods ............................................. |
| |
| /** |
| * Return the client-to-delegate Map. |
| */ |
| private Map< Object, WikiEventDelegate > getDelegates() { |
| return m_delegates; |
| } |
| |
| /** |
| * Returns a WikiEventDelegate for the provided client Object. If the parameter is a class reference, will generate and return a |
| * client-less WikiEventDelegate. If the parameter is not a Class and the delegate cache contains any objects matching the Class |
| * of any delegates in the cache, the first Class-matching delegate will be used in preference to creating a new delegate. |
| * If a null parameter is supplied, this will create a client-less delegate that will attach to the first incoming client (i.e., |
| * there will be no Class-matching requirement). |
| * |
| * @param client the client Object, or alternately a Class reference |
| * @return the WikiEventDelegate. |
| */ |
| private WikiEventDelegate getDelegateFor( final Object client ) { |
| synchronized( m_delegates ) { |
| if( client == null || client instanceof Class ) { // then preload the cache |
| final WikiEventDelegate delegate = new WikiEventDelegate( client ); |
| m_preloadCache.add( delegate ); |
| m_delegates.put( client, delegate ); |
| return delegate; |
| } else if( !m_preloadCache.isEmpty() ) { |
| // then see if any of the cached delegates match the class of the incoming client |
| for( int i = m_preloadCache.size()-1 ; i >= 0 ; i-- ) { // start with most-recently added |
| final WikiEventDelegate delegate = m_preloadCache.elementAt( i ); |
| if( delegate.getClientClass() == null || delegate.getClientClass().equals( client.getClass() ) ) { |
| // we have a hit, so use it, but only on a client we haven't seen before |
| if( !m_delegates.containsKey( client ) ) { |
| m_preloadCache.remove( delegate ); |
| m_delegates.put( client, delegate ); |
| return delegate; |
| } |
| } |
| } |
| } |
| // otherwise treat normally... |
| WikiEventDelegate delegate = m_delegates.get( client ); |
| if( delegate == null ) { |
| delegate = new WikiEventDelegate( client ); |
| m_delegates.put( client, delegate ); |
| } |
| return delegate; |
| } |
| } |
| |
| |
| // ......................................................................... |
| |
| /** |
| * Inner delegating class that manages event listener addition and removal. Classes that generate events can obtain an instance of |
| * this class from the WikiEventManager and delegate responsibility to it. Interaction with this delegating class is done via the |
| * methods of the {@link WikiEventDelegate} API. |
| * |
| * @since 2.4.20 |
| */ |
| private static final class WikiEventDelegate { |
| |
| /* A list of event listeners for this instance. */ |
| private final ArrayList< WeakReference< WikiEventListener > > m_listenerList = new ArrayList<>(); |
| private Class< ? > m_class = null; |
| |
| /** |
| * Constructor for an WikiEventDelegateImpl, provided with the client Object it will service, or the Class |
| * of client, the latter when used to preload a future incoming delegate. |
| */ |
| protected WikiEventDelegate( final Object client ) { |
| if( client instanceof Class ) { |
| m_class = ( Class< ? > )client; |
| } |
| } |
| |
| /** |
| * Returns the class of the client-less delegate, null if this delegate is attached to a client Object. |
| */ |
| protected Class< ? > getClientClass() { |
| return m_class; |
| } |
| |
| /** |
| * Return an unmodifiable Set containing the WikiEventListeners of this WikiEventDelegateImpl. If there are no attached listeners, |
| * returns an empty Set rather than null. |
| * |
| * @return an unmodifiable Set containing this delegate's WikiEventListeners |
| * @throws java.lang.UnsupportedOperationException if any attempt is made to modify the Set |
| */ |
| public Set< WikiEventListener > getWikiEventListeners() { |
| synchronized( m_listenerList ) { |
| final TreeSet< WikiEventListener > set = new TreeSet<>( new WikiEventListenerComparator() ); |
| for( final WeakReference< WikiEventListener > wikiEventListenerWeakReference : m_listenerList ) { |
| final WikiEventListener l = wikiEventListenerWeakReference.get(); |
| if( l != null ) { |
| set.add( l ); |
| } |
| } |
| |
| return Collections.unmodifiableSet( set ); |
| } |
| } |
| |
| /** |
| * Adds <tt>listener</tt> as a listener for events fired by the WikiEventDelegate. |
| * |
| * @param listener the WikiEventListener to be added |
| * @return true if the listener was added (i.e., it was not already in the list and was added) |
| */ |
| public boolean addWikiEventListener( final WikiEventListener listener ) { |
| synchronized( m_listenerList ) { |
| return m_listenerList.add( new WeakReference<>(listener) ); |
| } |
| } |
| |
| /** |
| * Removes <tt>listener</tt> from the WikiEventDelegate. |
| * |
| * @param listener the WikiEventListener to be removed |
| * @return true if the listener was removed (i.e., it was actually in the list and was removed) |
| */ |
| public boolean removeWikiEventListener( final WikiEventListener listener ) { |
| synchronized( m_listenerList ) { |
| for( final Iterator< WeakReference< WikiEventListener > > i = m_listenerList.iterator(); i.hasNext(); ) { |
| final WikiEventListener l = i.next().get(); |
| if( l == listener ) { |
| i.remove(); |
| return true; |
| } |
| } |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Returns true if there are one or more listeners registered with this instance. |
| */ |
| public boolean isListening() { |
| synchronized( m_listenerList ) { |
| return !m_listenerList.isEmpty(); |
| } |
| } |
| |
| /** |
| * Notify all listeners having a registered interest in change events of the supplied WikiEvent. |
| */ |
| public void fireEvent( final WikiEvent event ) { |
| boolean needsCleanup = false; |
| try { |
| synchronized( m_listenerList ) { |
| for( final WeakReference< WikiEventListener > wikiEventListenerWeakReference : m_listenerList ) { |
| final WikiEventListener listener = wikiEventListenerWeakReference.get(); |
| if( listener != null ) { |
| listener.actionPerformed( event ); |
| } else { |
| needsCleanup = true; |
| } |
| } |
| |
| // Remove all such listeners which have expired |
| if( needsCleanup ) { |
| for( int i = 0; i < m_listenerList.size(); i++ ) { |
| final WeakReference< WikiEventListener > w = m_listenerList.get( i ); |
| if( w.get() == null ) { |
| m_listenerList.remove(i--); |
| } |
| } |
| } |
| |
| } |
| } catch( final ConcurrentModificationException e ) { |
| // We don't die, we just don't do notifications in that case. |
| log.info( "Concurrent modification of event list; please report this.", e ); |
| } |
| } |
| } |
| |
| private static class WikiEventListenerComparator implements Comparator< WikiEventListener > { |
| // TODO: This method is a critical performance bottleneck |
| @Override |
| public int compare( final WikiEventListener w0, final WikiEventListener w1 ) { |
| if( w1 == w0 || w0.equals( w1 ) ) { |
| return 0; |
| } |
| |
| return w1.hashCode() - w0.hashCode(); |
| } |
| } |
| |
| } |