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