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