/* Wotonomy: OpenStep design patterns for pure Java applications. Copyright (C) 2000 Blacksmith, Inc. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, see http://www.gnu.org */ package net.wotonomy.foundation; import java.lang.ref.ReferenceQueue; import java.lang.ref.WeakReference; import java.util.Hashtable; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Vector; import net.wotonomy.foundation.internal.WotonomyException; /** * NSNotificationCenter broadcasts NSNotifications to registered observers. * Observers can register for all notifications of a specific type, or all * notifications about a specific object, or both. Observers specify the method * that will be called when they are notified. A global notification center can * be accessed with defaultCenter(), but other centers can be created and used * independently of the default center.
*
* * This implementation uses weak references for observers and observables. The * advantage to this approach is that you do not need to explicitly unregister * observers or observables; they will be unregistered when they are * garbage-collected. Note that you will need to retain a reference to any * objects you register or they may become unregistered if no other object * references them. * * @author michael@mpowers.net * @author $Author: cgruber $ * @version $Revision: 893 $ */ public class NSNotificationCenter { /** * Null marker class simplifies equals() logic for CompoundKey class below. */ public static final Object NullMarker = new Object(); private static NSNotificationCenter defaultCenter = null; /** * A Map of (name,object) pairs to a List of (observer,selector) pairs. */ private Hashtable observers; // thread-safe /** * Default constructor creates a new notification center. */ public NSNotificationCenter() { observers = new Hashtable(); } /** * Returns the system default center, creating one if it has not yet been * created. */ static public NSNotificationCenter defaultCenter() { if (defaultCenter == null) { defaultCenter = new NSNotificationCenter(); } return defaultCenter; } /** * Addes the specified observer to the notification queue for notifications with * the specified name or the specified object or both. * * @param anObserver The observer that wishes to be notified. * @param aSelector The selector that will be invoked. Must have exactly * one argument, to which a notification will be passed. * @param notificationName The name of the notifications for which the observer * will be notified. If null, will notify only based on * matching anObject. * @param anObject The object of the notifications for which the * observer will be notified. If null, will notify only * based on matching notificationName. */ public void addObserver(Object anObserver, NSSelector aSelector, String notificationName, Object anObject) { // remove freed objects processKeyQueue(); Object name = notificationName; if (name == null) { name = NullMarker; } if (anObject == null) { anObject = NullMarker; } Object key = new CompoundKey(name, anObject); Object value = new CompoundValue(anObserver, aSelector); List list = (List) observers.get(key); if (list == null) { // create new list with value and put it in map list = new Vector(); // thread-safe list.add(value); observers.put(new CompoundKey(name, anObject, keyQueue), list); } else { // add only if not already in list if (!list.contains(value)) { list.add(value); } } } /** * Posts the specified notification. Notifies all registered observers that * match either the notification name or the notification object, or both. * * @param aNotification The notification that will be passed to the observers * selector. */ public void postNotification(NSNotification aNotification) { List mergedList = new LinkedList(); Object key, observerList; Object name = aNotification.name(); Object object = aNotification.object(); if (name != null) { if (object != null) { // both are specified observerList = observers.get(new CompoundKey(name, object)); if (observerList != null) { mergedList.addAll((List) observerList); } observerList = observers.get(new CompoundKey(name, NullMarker)); if (observerList != null) { mergedList.addAll((List) observerList); } observerList = observers.get(new CompoundKey(NullMarker, object)); if (observerList != null) { mergedList.addAll((List) observerList); } } else { // object is null observerList = observers.get(new CompoundKey(name, NullMarker)); if (observerList != null) { mergedList.addAll((List) observerList); } } } else if (object != null) { // name is null observerList = observers.get(new CompoundKey(NullMarker, object)); if (observerList != null) { mergedList.addAll((List) observerList); } } key = new CompoundKey(NullMarker, NullMarker); observerList = observers.get(key); if (observerList != null) { mergedList.addAll((List) observerList); } CompoundValue value; Iterator it = mergedList.iterator(); while (it.hasNext()) { value = (CompoundValue) it.next(); if (value.get() == null) { it.remove(); } else { try { value.selector().invoke(value.get(), new Object[] { aNotification }); } catch (Exception exc) { WotonomyException w = new WotonomyException( "Error notifying object: " + value.get() + " : " + aNotification, exc); // throw w; w.printStackTrace(); postNotification("Error notifying object", this, new NSDictionary("exception", w)); } } } } /** * Posts a notification created from the specified name and object. Calls * postNotification( NSNotification ). * * @param notificationName a String key to distinguish this notification. * @param anObject any object, by convention this is the originator of * the notification. */ public void postNotification(String notificationName, Object anObject) { postNotification(new NSNotification(notificationName, anObject)); } /** * Posts a notification created from the specified name, object, and info. Calls * postNotification( NSNotification ). * * @param notificationName a String key to distinguish this notification. * @param anObject any object, by convention this is the originator of * the notification. * @param userInfo a Map containing information specific to the * originator of the notification and that may be of * interest to a knowledgable observer. */ public void postNotification(String notificationName, Object anObject, Map userInfo) { postNotification(new NSNotification(notificationName, anObject, userInfo)); } /** * Unregisters the specified observer from all notification queues for which it * is registered. * * @param anObserver The observer to be unregistered. */ public void removeObserver(Object anObserver) { // remove freed objects processKeyQueue(); Iterator it = new LinkedList(observers.keySet()).iterator(); while (it.hasNext()) { removeObserver(anObserver, it.next()); } } /** * Unregisters the specified observer from all notifications queues associated * with the specified name or object or both. * * @param anObserver The observer to be unregistered, if null will * unregister all observers for the specified * notification name and object. * @param notificationName The name of the notification for which the observer * will be unregistered, if null will unregister the * specified observer for all notifications with the * specified object. * @param anObject The object for the notification for which the * observer will be unregistered, if null will * unregister the specified observer for all objects * with the specified notification. */ public void removeObserver(Object anObserver, String notificationName, Object anObject) { // remove freed objects processKeyQueue(); // get key matches List keys = matchingKeys(notificationName, anObject); // remove specified observer from each matching key Iterator it = keys.iterator(); while (it.hasNext()) { removeObserver(anObserver, it.next()); } } /** * Returns all keys that match the specified name and object, but in this case * null parameters are considered wildcards. Pass NullMarkers if you want to * explicitly match nulls. */ private List matchingKeys(String name, Object object) { List result = new LinkedList(); boolean willAdd; CompoundKey key; Iterator it = observers.keySet().iterator(); while (it.hasNext()) { key = (CompoundKey) it.next(); willAdd = false; if ((name == null) || (name == key.name())) { if ((object == null) || (object == key.get())) { willAdd = true; } } if (willAdd) { result.add(key); } } return result; } /** * Removes the specified observer from the list referenced by the specified key * in the observer map. */ private void removeObserver(Object anObserver, Object key) { // if observer null, remove all observers for key if (anObserver == null) { observers.remove(key); return; } List list = (List) observers.get(key); if (list == null) return; // remove specified observer from list Object observer; Iterator it = list.iterator(); while (it.hasNext()) { observer = ((CompoundValue) it.next()).get(); if ((observer == null) || (anObserver == observer)) { // remove if match or freed object it.remove(); // do not return; process entire list } } if (list.size() == 0) { observers.remove(key); } } /* Reference queues for cleared WeakKeys */ private ReferenceQueue keyQueue = new ReferenceQueue(); /** * Removes any keys whose object has been garbage collected. (Garbage collected * values are removed as they are encountered.) */ private void processKeyQueue() { CompoundKey ck; while ((ck = (CompoundKey) keyQueue.poll()) != null) { // System.out.println( "EOObserverCenter.processQueue: removing object" ); observers.remove(ck); } } /** * Key combining a name with an object. The object is weakly referenced, and * keys are deallocated by reference queue. equals() compares by reference. */ private static class CompoundKey extends WeakReference { private Object name; private int hashCode; /** * Creates compound key. Neither name nor object may be null. Use NullMarker to * represent null in either name or object. */ public CompoundKey(Object aName, Object anObject) { super(anObject); name = aName; hashCode = aName.hashCode() + anObject.hashCode(); } /** * Creates compound key with queue. Neither name nor object may be null. Use * NullMarker to represent null in either name or object. */ public CompoundKey(Object aName, Object anObject, ReferenceQueue aQueue) { super(anObject, aQueue); name = aName; hashCode = aName.hashCode() + anObject.hashCode(); } public Object name() { return name; } public int hashCode() { return hashCode; } public boolean equals(Object anObject) { if (this == anObject) return true; // assumes only used with other compound keys CompoundKey key = (CompoundKey) anObject; if (name == key.name || (name != null && name.equals(key.name))) { Object object = get(); if (object != null) { // compares by reference if (object == (key.get())) { return true; } } } return false; } public String toString() { return "[CompoundKey:" + name() + ":" + get() + "]"; } } /** * Value combining an object with a selector. The object is weakly referenced, * and null values are not allowed. */ private static class CompoundValue extends WeakReference { private NSSelector selector; private int hashCode; public CompoundValue(Object anObject, NSSelector aSelector) { super(anObject); hashCode = anObject.hashCode(); selector = aSelector; } public NSSelector selector() { return selector; } public int hashCode() { return hashCode; } public boolean equals(Object anObject) { if (this == anObject) return true; // assumes only used with other compound values CompoundValue value = (CompoundValue) anObject; if (selector == value.selector || (selector != null && selector.equals(value.selector))) { Object object = get(); if (object != null) { if (object == value.get()) { return true; } } } return false; } public String toString() { return "[CompoundValue:" + get() + ":" + selector().name() + "]"; } } /* * public static void main( String[] argv ) { Object aSource = "aSource"; Object * bSource = "bSource"; * * Object oneTest = new OneTest(); Object twoTest = new TwoTest(); NSSelector * notifyMeOnce = new NSSelector( "notifyMeOnce", new Class[] { * NSNotification.class } ); NSSelector notifyMeTwice = new NSSelector( * "notifyMeTwice", new Class[] { NSNotification.class } ); * * NSNotificationCenter.defaultCenter().addObserver( oneTest, notifyMeOnce, * "aMessage", null ); * * NSNotificationCenter.defaultCenter().addObserver( oneTest, notifyMeOnce, * null, aSource ); * * NSNotificationCenter.defaultCenter().addObserver( twoTest, notifyMeOnce, * "aMessage", aSource ); * * NSNotificationCenter.defaultCenter().addObserver( twoTest, notifyMeTwice, * null, null ); * * NSNotificationCenter.defaultCenter().postNotification( "aMessage", aSource ); * System.out.println(); NSNotificationCenter.defaultCenter().postNotification( * "aMessage", bSource ); System.out.println(); * NSNotificationCenter.defaultCenter().postNotification( "bMessage", aSource ); * System.out.println(); NSNotificationCenter.defaultCenter().postNotification( * "bMessage", bSource ); System.out.println( "---" ); * * NSNotificationCenter.defaultCenter().removeObserver( oneTest, null, aSource * ); * * NSNotificationCenter.defaultCenter().postNotification( "aMessage", aSource ); * System.out.println(); NSNotificationCenter.defaultCenter().postNotification( * "aMessage", bSource ); System.out.println(); * NSNotificationCenter.defaultCenter().postNotification( "bMessage", aSource ); * System.out.println(); NSNotificationCenter.defaultCenter().postNotification( * "bMessage", bSource ); System.out.println( "---" ); * * NSNotificationCenter.defaultCenter().removeObserver( null ); * * NSNotificationCenter.defaultCenter().postNotification( "aMessage", aSource ); * System.out.println(); NSNotificationCenter.defaultCenter().postNotification( * "aMessage", bSource ); System.out.println(); * NSNotificationCenter.defaultCenter().postNotification( "bMessage", aSource ); * System.out.println(); NSNotificationCenter.defaultCenter().postNotification( * "bMessage", bSource ); System.out.println( "---" ); } * * static private class OneTest { public void notifyMeOnce( NSNotification * aNotification ) { System.out.println( "OneTest.notifyMeOnce: " + * aNotification ); } } * * static private class TwoTest { public void notifyMeOnce( NSNotification * aNotification ) { System.out.println( "TwoTest.notifyMeOnce: " + * aNotification ); } public void notifyMeTwice( NSNotification aNotification ) * { System.out.println( "TwoTest.notifyMeTwice: " + aNotification ); } } */ } /* * $Log$ Revision 1.2 2006/02/16 13:15:00 cgruber Check in all sources in * eclipse-friendly maven-enabled packages. * * Revision 1.11 2003/06/03 14:51:15 mpowers Added commented-out println for * debugging. * * Revision 1.10 2003/03/27 21:46:00 mpowers Better handling for null parameters * on subscribe. Better handling for null parameters on post. * * Revision 1.9 2003/01/28 19:44:38 mpowers Now comparing strings by value not * reference. * * Revision 1.8 2001/06/29 16:14:23 mpowers Fixed a javac compiler error that * jikes allowed: shoe's on the other foot! * * Revision 1.7 2001/06/07 22:09:03 mpowers Exceptions during a notification are * no longer being thrown so we can assure that all notifications get handled. * Instead, we're printing stack traces... * * Revision 1.6 2001/04/09 21:41:50 mpowers Better debugging. * * Revision 1.5 2001/03/15 21:09:06 mpowers Fixed notifications with null * objects. * * Revision 1.4 2001/02/21 21:18:34 mpowers Clarified need to retain references. * * Revision 1.3 2001/02/21 18:31:07 mpowers Finished and tested implementation * of NSNotificationCenter. * * Revision 1.1.1.1 2000/12/21 15:47:39 mpowers Contributing wotonomy. * * Revision 1.3 2000/12/20 16:25:38 michael Added log to all files. * * */