/*
* Copyright 2003-2010 Tufts University Licensed under the
* Educational Community License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may
* obtain a copy of the License at
*
* http://www.osedu.org/licenses/ECL-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an "AS IS"
* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package tufts.vue;
import java.util.*;
import java.util.concurrent.*;
import tufts.Util;
import static tufts.Util.*;
/**
* This provides for generic global event delivery / change support.
*
* <p>The event object may be any object. One handler is intended to be created
* for each event type in the runtime. Any source can post an event,
* and all listeners will get the event.
*
* <p>The listener must be a subclass of EventHandler.Listener.
* This is designed so that the listener can be created
* as an empty subclass, providing only type information.
*
* <p>E.g.: <code>interface <i>MyListener</i> extends EventHandler.Listener<<i>MyEvent</i>>{}</code>
*
* <p>Or when the listener and event are static inner classes to an implementation class defining
* the event, a standard pattern can be used:
*
* <blockquote><code>
* public static class Event { ... }
* <br><br>
* public interface Listener extends EventHandler.Listener<Event>{}
* </code></blockquote>
*
* Using inner classes, this standard pattern defines <i>MyClass</i>.Listener, and
* all that remains is to define <i>MyClass</i>.Event.
*
* <p>Note that bothering to define the Listener class is uneeded in some cases:
* implementing classes can implement EventHandler.Listener<<i>type</i>>, although if done
* this way, due to type-erasure, a single class may only declare itself as implementing
* a single EventHandler.Listener of any kind. If a class is declared as implementing
* multiple EventHandler.Listener's, even with multiple <i>types</i>, once the type information is
* thrown away, it will look like a class being declared as implementing the same
* interface multiple times.
*
* <p>Instances of handlers for any new event type can be obtained or created on the fly
* by calling the static method getHandler for a given class/type, which will
* automatically create a new handler for the given event type if one doesn't exist.
*
* @author Scott Fraize 2008-06-17
* @version $Revision: 1.7 $ / $Date: 2010-02-03 19:17:41 $ / $Author: mike $
*/
// todo: see if we can subclass ActiveInstance from this to share code
// also: may want to rename something like EventDispatch or EventSource
public class EventHandler<E>
{
private static final org.apache.log4j.Logger Log = org.apache.log4j.Logger.getLogger(EventHandler.class);
public interface Listener<E> {
public void eventRaised(E event);
}
private static final Map<Class,EventHandler> AllEventHandlers = new HashMap();
//private static final List<Listener> ListenersForAllEvents = new CopyOnWriteArrayList();
public static void addListener(Class clazz, Listener listener) {
getHandler(clazz).addListener(listener);
}
public static void removeListener(Class clazz, Listener listener) {
getHandler(clazz).removeListener(listener);
}
public static EventHandler getHandler(Class type) {
EventHandler handler = null;
lock(null, "getHandler");
synchronized (AllEventHandlers) {
handler = AllEventHandlers.get(type);
if (handler == null)
handler = new EventHandler(type, true);
}
unlock(null, "getHandler");
return handler;
}
//protected static int ActiveInstance.depth = -1; // event delivery depth
//=============================================================================
protected final CopyOnWriteArrayList<Listener> mListeners = new CopyOnWriteArrayList();
protected final Class _itemType;
protected final String _itemTypeName; // for debug
private E _lastEvent;
private Object _lastSource;
private boolean _inNotify;
protected EventHandler(Class clazz, boolean track) {
_itemType = clazz;
_itemTypeName = "<" + _itemType.getName() + ">";
if (track) {
lock(clazz, "INIT");
synchronized (AllEventHandlers) {
if (AllEventHandlers.containsKey(_itemType)) {
// tho this is not ideal, the safest thing to do is blow away the old one,
// as it's likely this accidentally happened by a request for a generic
// listener before a specialized side-effecting type-handler was initiated.
// We copy over the listeners from the old handler if there were any.
tufts.Util.printStackTrace("ignoring prior active change handler for " + getClass() + " and taking over listeners");
mListeners.addAll(getHandler(_itemType).mListeners);
}
AllEventHandlers.put(_itemType, this);
}
unlock(clazz, "INIT");
}
if (DEBUG.INIT || DEBUG.EVENTS) Log.debug("created " + this);
}
public boolean hasListeners() {
return mListeners.size() > 0;
}
public synchronized void redeliver() {
raise(_lastSource, _lastEvent);
}
public synchronized void raise(final Object source, final E newEvent)
{
if (DEBUG.EVENTS) {
final String debug =
_itemTypeName
//+ "\n\tlastEvent: " + _lastEvent
+ TERM_GREEN
+ "\n\t event: " + newEvent
+ "\n\t source: " + sourceName(source)
//+ "\n\t thread: [" + Thread.currentThread().getName() + "]"
+ "\n\tlisteners: " + Util.tags(mListeners)
;
Log.debug(TERM_YELLOW + debug + TERM_CLEAR);
}
notifyListeners(source, newEvent);
}
protected void notifyListeners(final Object source, final E event)
{
if (_inNotify) {
tufts.Util.printStackTrace(this + " event loop! aborting delivery of: " + event);
return;
}
_inNotify = true;
try {
ActiveInstance.depth++;
if (mListeners.size() > 0)
notifyListenerList(source, event, mListeners);
// if (ListenersForAllEvents.size() > 0)
// notifyListenerList(this, source, event, ListenersForAllEvents);
} finally {
ActiveInstance.depth--;
_inNotify = false;
}
_lastEvent = event;
_lastSource = source;
}
protected void notifyListenerList(final Object source,
final E event,
final Collection<Listener> listenerList)
{
int count = 0;
for (Listener target : listenerList) {
count++;
if (source == target) {
if (DEBUG.EVENTS) outf(" %2dskipSrc %s -- %s\n", count, _itemTypeName, target);
continue;
} else
if (DEBUG.EVENTS) outf(" %2d notify %s -> %s\n", count, _itemTypeName, target);
try {
//-------------------------------------------------------
// dispatch the event
//-------------------------------------------------------
dispatch(target, event);
} catch (Throwable t) {
Util.printStackTrace(t, this + " exception notifying " + target + " with " + event);
}
}
}
/** override to provide complex dispatch */
protected void dispatch(Listener target, E event) {
target.eventRaised(event);
}
public void addListener(Listener listener) {
if (mListeners.addIfAbsent(listener)) {
if (DEBUG.EVENTS) Log.debug(String.format(TERM_GREEN + "%-50s added listener %s" + TERM_CLEAR, _itemTypeName, listener));
} else {
Log.warn(this + "; add: is already listening: " + listener);
if (DEBUG.EVENTS)
Util.printStackTrace(this + "; FYI: already listening: " + listener);
}
}
public void removeListener(Listener listener) {
if (mListeners.remove(listener)) {
if (DEBUG.Enabled)
outf(TERM_GREEN + "%-50s removed listener %s\n" + TERM_CLEAR, this, listener);
} else if (DEBUG.EVENTS) {
Log.warn(this + "; remove: didn't contain listener " + listener);
}
}
protected static String sourceName(Object s) {
if (s == null)
return "null";
else if (s instanceof ActiveEvent || s instanceof EventHandler)
return s.toString();
else
return Util.tags(s);
//return s.getClass().getName() + ":" + s;
}
protected static void lock(Object o, String msg) {
if (DEBUG.THREAD) System.err.println((o == null ? "EventHandler" : o) + " " + msg + " LOCK");
}
protected static void unlock(Object o, String msg) {
if (DEBUG.THREAD) System.err.println((o == null ? "EventHandler" : o) + " " + msg + " UNLOCK");
}
protected static void outf(String fmt, Object... args) {
for (int x = 0; x < ActiveInstance.depth; x++) System.out.print(" ");
System.out.format(fmt, args);
//Log.debug(String.format(fmt, args));
}
@Override
public String toString() {
return getClass().getSimpleName() + _itemTypeName;
}
}