/* 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. * * */