/*
* Copyright (c) 2008-2009, Piccolo2D project, http://piccolo2d.org
* Copyright (c) 1998-2008, University of Maryland
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted provided
* that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this list of conditions
* and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions
* and the following disclaimer in the documentation and/or other materials provided with the
* distribution.
*
* None of the name of the University of Maryland, the name of the Piccolo2D project, or the names of its
* contributors may be used to endorse or promote products derived from this software without specific
* prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
* This class PNotificationCenter center is derived from the class
* NSNotificationCenter from:
*
* Wotonomy: OpenStep design patterns for pure Java
* applications. Copyright (C) 2000 Blacksmith, Inc.
*/
package edu.umd.cs.piccolox.event;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
/**
* <b>PNotificationCenter</b> provides a way for objects that don't know about
* each other to communicate. It receives PNotification objects and broadcasts
* them to all interested listeners. Unlike standard Java events, the event
* listeners don't need to know about the event source, and the event source
* doesn't need to maintain the list of listeners.
* <p>
* Listeners of the notfications center are held by weak references. So the
* notfication center will not create garbage collection problems as standard
* java event listeners do.
* </p>
*
* @author Jesse Grosjean
*/
public class PNotificationCenter {
public static final Object NULL_MARKER = new Object();
protected static PNotificationCenter DEFAULT_CENTER;
protected HashMap listenersMap;
protected ReferenceQueue keyQueue;
public static PNotificationCenter defaultCenter() {
if (DEFAULT_CENTER == null) {
DEFAULT_CENTER = new PNotificationCenter();
}
return DEFAULT_CENTER;
}
private PNotificationCenter() {
listenersMap = new HashMap();
keyQueue = new ReferenceQueue();
}
// ****************************************************************
// Add Listener Methods
// ****************************************************************
/**
* Registers the 'listener' to receive notifications with the name
* 'notificationName' and/or containing 'object'. When a matching
* notification is posted the callBackMethodName message will be sent to the
* listener with a single PNotification argument. If notificationName is
* null then the listener will receive all notifications with an object
* matching 'object'. If 'object' is null the listener will receive all
* notifications with the name 'notificationName'.
*
* @return whether or not the listener has been added
* @throws SecurityException
*/
public boolean addListener(Object listener, String callbackMethodName, String notificationName, Object object)
throws SecurityException {
processKeyQueue();
Object name = notificationName;
Method method = null;
try {
method = listener.getClass().getMethod(callbackMethodName, new Class[] { PNotification.class });
}
catch (NoSuchMethodException e) {
return false;
}
int modifiers = method.getModifiers();
if (!Modifier.isPublic(modifiers)) {
return false;
}
if (name == null) {
name = NULL_MARKER;
}
if (object == null) {
object = NULL_MARKER;
}
Object key = new NotificationKey(name, object);
Object notificationTarget = new NotificationTarget(listener, method);
List list = (List) listenersMap.get(key);
if (list == null) {
list = new ArrayList();
listenersMap.put(new NotificationKey(name, object, keyQueue), list);
}
if (!list.contains(notificationTarget)) {
list.add(notificationTarget);
}
return true;
}
// ****************************************************************
// Remove Listener Methods
// ****************************************************************
/**
* Removes the listener so that it no longer recives notfications from this
* notfication center.
*/
public void removeListener(Object listener) {
processKeyQueue();
Iterator i = new LinkedList(listenersMap.keySet()).iterator();
while (i.hasNext()) {
removeListener(listener, i.next());
}
}
/**
* Removes the listeners as the listener of notifications matching
* notificationName and object. If listener is null all listeners matching
* notificationName and object are removed. If notificationName is null the
* listener will be removed from all notifications containing the object. If
* the object is null then the listener will be removed from all
* notifications matching notficationName.
*/
public void removeListener(Object listener, String notificationName, Object object) {
processKeyQueue();
List keys = matchingKeys(notificationName, object);
Iterator it = keys.iterator();
while (it.hasNext()) {
removeListener(listener, it.next());
}
}
// ****************************************************************
// Post PNotification Methods
// ****************************************************************
/**
* Post a new notfication with notificationName and object. The object is
* typically the object posting the notification. The object may be null.
*/
public void postNotification(String notificationName, Object object) {
postNotification(notificationName, object, null);
}
/**
* Creates a notification with the name notificationName, associates it with
* the object, and posts it to this notification center. The object is
* typically the object posting the notification. It may be nil.
*/
public void postNotification(String notificationName, Object object, Map userInfo) {
postNotification(new PNotification(notificationName, object, userInfo));
}
/**
* Post the notification to this notification center. Most often clients
* will instead use one of this classes convenience postNotifcations
* methods.
*/
public void postNotification(PNotification aNotification) {
List mergedListeners = new LinkedList();
List listenersList;
Object name = aNotification.getName();
Object object = aNotification.getObject();
if (name != null) {
if (object == null) {// object is null
listenersList = (List) listenersMap.get(new NotificationKey(name, NULL_MARKER));
if (listenersList != null) {
mergedListeners.addAll(listenersList);
}
}
else { // both are specified
listenersList = (List) listenersMap.get(new NotificationKey(name, object));
if (listenersList != null) {
mergedListeners.addAll(listenersList);
}
listenersList = (List) listenersMap.get(new NotificationKey(name, NULL_MARKER));
if (listenersList != null) {
mergedListeners.addAll(listenersList);
}
listenersList = (List) listenersMap.get(new NotificationKey(NULL_MARKER, object));
if (listenersList != null) {
mergedListeners.addAll(listenersList);
}
}
}
else if (object != null) { // name is null
listenersList = (List) listenersMap.get(new NotificationKey(NULL_MARKER, object));
if (listenersList != null) {
mergedListeners.addAll(listenersList);
}
}
Object key = new NotificationKey(NULL_MARKER, NULL_MARKER);
listenersList = (List) listenersMap.get(key);
if (listenersList != null) {
mergedListeners.addAll(listenersList);
}
dispatchNotifications(aNotification, mergedListeners);
}
private void dispatchNotifications(PNotification aNotification, List listeners) {
NotificationTarget listener;
Iterator it = listeners.iterator();
while (it.hasNext()) {
listener = (NotificationTarget) it.next();
if (listener.get() == null) {
it.remove();
}
else {
try {
listener.getMethod().invoke(listener.get(), new Object[] { aNotification });
}
catch (IllegalAccessException e) {
// it's impossible add listeners that are not public
}
catch (InvocationTargetException e) {
// Since this is how Swing handles Exceptions that get
// thrown on listeners, it's probably ok to do it here.
e.printStackTrace();
}
}
}
}
// ****************************************************************
// Implementation classes and methods
// ****************************************************************
protected List matchingKeys(String name, Object object) {
List result = new LinkedList();
NotificationKey searchKey = new NotificationKey(name, object);
Iterator it = listenersMap.keySet().iterator();
while (it.hasNext()) {
NotificationKey key = (NotificationKey) it.next();
if (searchKey.equals(key)) {
result.add(key);
}
}
return result;
}
protected void removeListener(Object listener, Object key) {
if (listener == null) {
listenersMap.remove(key);
return;
}
List list = (List) listenersMap.get(key);
if (list == null)
return;
Iterator it = list.iterator();
while (it.hasNext()) {
Object observer = ((NotificationTarget) it.next()).get();
if ((observer == null) || (listener == observer)) {
it.remove();
}
}
if (list.size() == 0) {
listenersMap.remove(key);
}
}
protected void processKeyQueue() {
NotificationKey key;
while ((key = (NotificationKey) keyQueue.poll()) != null) {
listenersMap.remove(key);
}
}
protected static class NotificationKey extends WeakReference {
private Object name;
private int hashCode;
public NotificationKey(Object aName, Object anObject) {
super(anObject);
name = aName;
hashCode = aName.hashCode() + anObject.hashCode();
}
public NotificationKey(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;
if (!(anObject instanceof NotificationKey))
return false;
NotificationKey key = (NotificationKey) anObject;
if (name != key.name && (name == null || !name.equals(key.name)))
return false;
Object object = get();
return object != null && object == key.get();
}
public String toString() {
return "[CompoundKey:" + name() + ":" + get() + "]";
}
}
protected static class NotificationTarget extends WeakReference {
protected int hashCode;
protected Method method;
public NotificationTarget(Object object, Method method) {
super(object);
hashCode = object.hashCode();
this.method = method;
}
public Method getMethod() {
return method;
}
public int hashCode() {
return hashCode;
}
public boolean equals(Object object) {
if (this == object)
return true;
if (!(object instanceof NotificationTarget))
return false;
NotificationTarget target = (NotificationTarget) object;
if (method != target.method && (method == null || !method.equals(target.method)))
return false;
Object o = get();
return (o != null) && (o == target.get());
}
public String toString() {
return "[CompoundValue:" + get() + ":" + getMethod().getName() + "]";
}
}
}