All Downloads are FREE. Search and download functionalities are using the official Maven repository.

edu.umd.cs.piccolox.event.PNotificationCenter Maven / Gradle / Ivy

There is a newer version: 3.0.1
Show newest version
/*
 * Copyright (c) 2008-2011, 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;

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

* Listeners of the notifications center are held by weak references. So the * notification center will not create garbage collection problems as standard * java event listeners do. *

* * @author Jesse Grosjean */ public class PNotificationCenter { /** Used as a place holder for null names or objects. */ public static final Object NULL_MARKER = new Object(); /** Singleton instance of the notification center. */ protected static volatile PNotificationCenter DEFAULT_CENTER; /** A map of listeners keyed by NotificationKey objects. */ protected HashMap listenersMap; /** A queue of NotificationKeys that are available to be garbage collected. */ protected ReferenceQueue keyQueue; /** * Singleton accessor for the PNotificationCenter. * * @return singleton instance of PNotificationCenter */ public static PNotificationCenter defaultCenter() { if (DEFAULT_CENTER == null) { DEFAULT_CENTER = new PNotificationCenter(); } return DEFAULT_CENTER; } private PNotificationCenter() { listenersMap = new HashMap(); keyQueue = new ReferenceQueue(); } /** * 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'. * * @param listener object to be notified of notifications * @param callbackMethodName method to be invoked on the listener * @param notificationName name of notifications to filter on * @param object source of notification messages that this listener is * interested in * @return true if listener has been added */ public boolean addListener(final Object listener, final String callbackMethodName, final String notificationName, final Object object) { processKeyQueue(); final Object name = nullify(notificationName); final Object sanitizedObject = nullify(object); final Method method = extractCallbackMethod(listener, callbackMethodName); if (method == null) { return false; } final NotificationKey key = new NotificationKey(name, sanitizedObject); final NotificationTarget notificationTarget = new NotificationTarget(listener, method); List list = (List) listenersMap.get(key); if (list == null) { list = new ArrayList(); listenersMap.put(new NotificationKey(name, sanitizedObject, keyQueue), list); } if (!list.contains(notificationTarget)) { list.add(notificationTarget); } return true; } private Method extractCallbackMethod(final Object listener, final String methodName) { Method method = null; try { Class[] classes = new Class[1]; classes[0] = PNotification.class; method = listener.getClass().getMethod(methodName, classes); } catch (final NoSuchMethodException e) { return null; } final int modifiers = method.getModifiers(); if (!Modifier.isPublic(modifiers)) { return null; } return method; } /** * Sanitizes the object reference by returning NULL_MARKER if the object is * null. * * @param object object to sanitize * * @return NULL_MARKER is object is null, otherwise object */ private Object nullify(final Object object) { if (object == null) { return NULL_MARKER; } return object; } // **************************************************************** // Remove Listener Methods // **************************************************************** /** * Removes the listener so that it no longer receives notfications from this * notification center. * * @param listener listener to be removed from this notification center */ public void removeListener(final Object listener) { processKeyQueue(); final Iterator i = new LinkedList(listenersMap.keySet()).iterator(); while (i.hasNext()) { removeListener(listener, i.next()); } } /** * Unregisters the listener as a listener for the specified kind of * notification. * * 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. * * @param listener listener to be removed * @param notificationName name of notifications or null for all * @param object notification source or null for all */ public void removeListener(final Object listener, final String notificationName, final Object object) { processKeyQueue(); final List keys = matchingKeys(notificationName, object); final Iterator it = keys.iterator(); while (it.hasNext()) { removeListener(listener, it.next()); } } // **************************************************************** // Post PNotification Methods // **************************************************************** /** * Post a new notification with notificationName and object. The object is * typically the object posting the notification. The object may be null. * * @param notificationName name of notification to post * @param object source of the notification, null signifies unknown */ public void postNotification(final String notificationName, final 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 null. * * @param notificationName name of notification being posted * @param object source of the notification, may be null * @param properties properties associated with the notification */ public void postNotification(final String notificationName, final Object object, final Map properties) { postNotification(new PNotification(notificationName, object, properties)); } /** * Post the notification to this notification center. Most often clients * will instead use one of this classes convenience postNotifcations * methods. * * @param notification notification to be dispatched to appropriate * listeners */ public void postNotification(final PNotification notification) { final List mergedListeners = new LinkedList(); final Object name = notification.getName(); final Object object = notification.getObject(); if (name != null && object != null) { fillWithMatchingListeners(name, object, mergedListeners); fillWithMatchingListeners(null, object, mergedListeners); fillWithMatchingListeners(name, null, mergedListeners); } else if (name != null) { fillWithMatchingListeners(name, null, mergedListeners); } else if (object != null) { fillWithMatchingListeners(null, object, mergedListeners); } fillWithMatchingListeners(null, null, mergedListeners); dispatchNotifications(notification, mergedListeners); } /** * Adds all listeners that are registered to receive notifications to the * end of the list provided. * * @param notificationName name of the notification being emitted * @param object source of the notification * @param listeners list to append listeners to */ private void fillWithMatchingListeners(final Object notificationName, final Object object, final List listeners) { final Object key = new NotificationKey(nullify(notificationName), nullify(object)); final List globalListeners = (List) listenersMap.get(key); if (globalListeners != null) { listeners.addAll(globalListeners); } } private void dispatchNotifications(final PNotification notification, final List listeners) { NotificationTarget listener; final Iterator listenerIterator = listeners.iterator(); while (listenerIterator.hasNext()) { listener = (NotificationTarget) listenerIterator.next(); if (listener.get() == null) { listenerIterator.remove(); } else { notifyListener(notification, listener); } } } private void notifyListener(final PNotification notification, final NotificationTarget listener) { try { Object[] objects = new Object[1]; objects[0] = notification; listener.getMethod().invoke(listener.get(), objects); } catch (final IllegalAccessException e) { throw new RuntimeException("Impossible Situation: invoking inaccessible method on listener", e); } catch (final InvocationTargetException e) { throw new RuntimeException(e); } } /** * Returns a list of keys with the given name and object. * * @param name name of key * @param object key associated with the object * * @return list of matching keys */ protected List matchingKeys(final String name, final Object object) { final List result = new LinkedList(); final NotificationKey searchKey = new NotificationKey(name, object); final Iterator it = listenersMap.keySet().iterator(); while (it.hasNext()) { final NotificationKey key = (NotificationKey) it.next(); if (searchKey.equals(key)) { result.add(key); } } return result; } /** * Removes the given listener from receiving notifications with the given * key. * * @param listener the listener being unregistered * @param key the key that identifies the listener */ protected void removeListener(final Object listener, final Object key) { if (listener == null) { listenersMap.remove(key); return; } final List list = (List) listenersMap.get(key); if (list == null) { return; } final Iterator it = list.iterator(); while (it.hasNext()) { final Object observer = ((NotificationTarget) it.next()).get(); if (observer == null || listener == observer) { it.remove(); } } if (list.size() == 0) { listenersMap.remove(key); } } /** * Iterates over available keys in the key queue and removes the queue from * the listener map. */ protected void processKeyQueue() { NotificationKey key; while ((key = (NotificationKey) keyQueue.poll()) != null) { listenersMap.remove(key); } } /** * Represents a notification type from a particular object. */ protected static class NotificationKey extends WeakReference { private final Object name; private final int hashCode; /** * Creates a notification key with the provided name associated to the * object given. * * @param name name of notification * @param object associated object */ public NotificationKey(final Object name, final Object object) { super(object); this.name = name; hashCode = name.hashCode() + object.hashCode(); } /** * Creates a notification key with the provided name associated with the * provided object. * * @param name name of notification * @param object associated object * @param queue ReferenceQueue in which this NotificationKey will be * appended once it has been cleared to be garbage collected */ public NotificationKey(final Object name, final Object object, final ReferenceQueue queue) { super(object, queue); this.name = name; hashCode = name.hashCode() + object.hashCode(); } /** * Returns name of notification this key represents. * * @return name of notification */ public Object name() { return name; } /** {@inheritDoc} */ public int hashCode() { return hashCode; } /** * Two keys are equal if they have the same name and are associated with * the same object and conform to all other equals rules. * * @param anObject object being tested for equivalence to this * NotificationKey * * @return true if this object is logically equivalent to the one passed * in */ public boolean equals(final Object anObject) { if (this == anObject) { return true; } if (!(anObject instanceof NotificationKey)) { return false; } final NotificationKey key = (NotificationKey) anObject; if (name != key.name && (name == null || !name.equals(key.name))) { return false; } final Object object = get(); return object != null && object == key.get(); } /** * Returns a nice string representation of this notification key. * * @return string representation of this notification key */ public String toString() { return "[CompoundKey:" + name() + ":" + get() + "]"; } } /** * A NotificationTarget is a method on a particular object that can be * invoked. */ protected static class NotificationTarget extends WeakReference { /** Cached hashcode value computed at construction time. */ protected int hashCode; /** Method to be invoked on the object. */ protected Method method; /** * Creates a notification target representing the method on the * particular object provided. * * @param object object on which method can be invoked * @param method method to be invoked */ public NotificationTarget(final Object object, final Method method) { super(object); hashCode = object.hashCode() + method.hashCode(); this.method = method; } /** * Returns the method that will be invoked on the listener object. * * @return method to be invoked with notification is to be dispatched */ public Method getMethod() { return method; } /** * Returns hash code for this notification target. * * @return hash code */ public int hashCode() { return hashCode; } /** * Returns true if this object is logically equivalent to the one passed * in. For this to happen they must have the same method and object. * * @param object object being tested for logical equivalency to this one * * @return true if logically equivalent */ public boolean equals(final Object object) { if (this == object) { return true; } if (!(object instanceof NotificationTarget)) { return false; } final NotificationTarget target = (NotificationTarget) object; if (method != target.method && (method == null || !method.equals(target.method))) { return false; } final Object o = get(); return o != null && o == target.get(); } /** * Returns a string representation of this NotificationTarget for * debugging purposes. * * @return string representation */ public String toString() { return "[CompoundValue:" + get() + ":" + getMethod().getName() + "]"; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy