/*
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;
}
/**
* Adds 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;
}
CompoundKey key = new CompoundKey(name, anObject);
CompoundValue value = new CompoundValue(anObserver, aSelector);
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<>();
CompoundKey key;
List 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(observerList);
}
observerList = observers.get(new CompoundKey(name, NullMarker));
if (observerList != null) {
mergedList.addAll(observerList);
}
observerList = observers.get(new CompoundKey(NullMarker, object));
if (observerList != null) {
mergedList.addAll(observerList);
}
} else { // object is null
observerList = observers.get(new CompoundKey(name, NullMarker));
if (observerList != null) {
mergedList.addAll(observerList);
}
}
} else if (object != null) { // name is null
observerList = observers.get(new CompoundKey(NullMarker, object));
if (observerList != null) {
mergedList.addAll(observerList);
}
}
key = new CompoundKey(NullMarker, NullMarker);
observerList = observers.get(key);
if (observerList != null) {
mergedList.addAll(observerList);
}
CompoundValue value;
Iterator it = mergedList.iterator();
while (it.hasNext()) {
value = 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 = observers.get(key);
if (list == null)
return;
// remove specified observer from list
Object observer;
Iterator it = list.iterator();
while (it.hasNext()) {
observer = 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