diff options
| author | Benjamin Culkin <scorpress@gmail.com> | 2024-05-19 17:56:33 -0400 |
|---|---|---|
| committer | Benjamin Culkin <scorpress@gmail.com> | 2024-05-19 17:56:33 -0400 |
| commit | aedc34d55462a75e329bbf342251ff6504cd117e (patch) | |
| tree | bcc8f1f2352582717b484df302aeea6696b8f000 /projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSNotificationCenter.java | |
Initial import from SVN
Diffstat (limited to 'projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSNotificationCenter.java')
| -rw-r--r-- | projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSNotificationCenter.java | 671 |
1 files changed, 671 insertions, 0 deletions
diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSNotificationCenter.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSNotificationCenter.java new file mode 100644 index 0000000..aaf8261 --- /dev/null +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSNotificationCenter.java @@ -0,0 +1,671 @@ +/* +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. <br><br> +* +* 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. + * + * + */ + |
