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