Note: this is the old code, kept for TwoThe's answer. See below for new code.
public class EventListener
{
/*
* An EventListener pairs a class (EventObject or a subtype) with a method on an object.
* It can hold either a strong or a weak reference to the object, and defaults weak.
* It uses reflection to call the specified method.
*/
Object source;
Method handler = null;
Class E;
WeakReference<Object> sourceReference;
public EventListener(Class<? extends EventObject> E, Object source,
String handlingMethod, boolean weakReference)
{
if (weakReference) {
this.source = null;
sourceReference = new WeakReference(source);
} else {
this.source = source;
sourceReference = null;
}
/*
* This structure sets up the call to the specified method on the specified
* object. If no method is found for the specified class to listen to,
* superclasses are tried until either a valid method is found or EventObject
* is reached with no match.
*/
Class C = E;
while (EventObject.class.isAssignableFrom(C) && handler == null) {
try {
this.handler = source.getClass().getMethod(handlingMethod, C);
} catch (NoSuchMethodException e) {
C = C.getSuperclass();
}
}
if (handler == null) {
throw new IllegalArgumentException("No method with the signature: "
+ handlingMethod + "("
+ E.getSimpleName() + ") found.");
}
this.E = E;
}
public EventListener(Class<? extends EventObject> E, Object source,
String handlingMethod)
{
this(E, source, handlingMethod, true);
}
void handleEvent(EventObject event) //package protected because it should only be
//called by EventManager's dispatchEvent().
{
if (source == null) { //source == null iff using weak references.
if (sourceReference.get() == null) { //referenced object garbage collected,
//delete listener.
deregisterListener();
return;
}
try {
handler.invoke(sourceReference.get(), event);
} catch (IllegalAccessException | IllegalArgumentException |
InvocationTargetException e) {
throw new IllegalArgumentException(e.getMessage(), e.getCause());
}
} else {
try {
handler.invoke(source, event);
} catch (IllegalAccessException | IllegalArgumentException |
InvocationTargetException e) {
throw new IllegalArgumentException(e.getMessage(), e.getCause());
}
}
}
public void registerListener() //registers a listener with the manager.
//Reference to listener is required to deregister later.
{
EventManager.getInstance().addListener(E, this);
}
public void deregisterListener()
{
EventManager.getInstance().removeListener(E, this);
}
}
New code:
EventManager.java
public final class EventManager //The manager holds all the EventListeners, and dispatches events among them.
{
private static EventManager INSTANCE = new EventManager(); //Singleton pattern, as multiple managers is nonsensical.
private HashMap<Class<? extends EventObject>, LinkedList<EventListener>> listeners;
private int lockCount = 0;
private LinkedList<ListenerStub> delayedListeners;
public static EventManager getInstance()
{
return INSTANCE;
}
private EventManager()
{
listeners = new HashMap();
delayedListeners = new LinkedList();
}
/*Adds a listener if there's nothing using the set of listeners, or delays it
* until the usage is finished if something is.
* Package protected because nothing other than EventListener's registerListener()
* should call it. Eqivalent statements apply for removeListener() below.
*/
void addListener(Class<? extends EventObject> clazz, EventListener listener)
{
if (lockCount == 0) {
realAddListener(clazz, listener);
} else {
delayListenerOperation(clazz, listener, true);
}
}
private void realAddListener(Class<? extends EventObject> clazz, EventListener listener)
{
if (!listeners.containsKey(clazz)) {
listeners.put(clazz, new LinkedList<EventListener>());
}
LinkedList<EventListener> list = listeners.get(clazz);
list.add(listener);
System.out.println("added");
}
void removeListener(Class<? extends EventObject> clazz, EventListener listener)
{
if (lockCount == 0) {
realRemoveListener(clazz, listener);
} else {
delayListenerOperation(clazz, listener, false);
}
}
private void realRemoveListener(Class<? extends EventObject> clazz, EventListener listener)
{
LinkedList<EventListener> list = listeners.get(clazz);
list.remove(listener);
System.out.println("removed");
}
private void delayListenerOperation(Class<? extends EventObject> clazz, EventListener listener, boolean add)
{
delayedListeners.add(new ListenerStub(clazz, listener, add));
System.out.println("delayed");
}
public void dispatchEvent(EventObject event)
{
ListenerLock l = new ListenerLock();
dispatchEvent(event, event.getClass()); //Hiding recursive call from user
l.release();
if (event instanceof ResolvableEventObject) {
((ResolvableEventObject) event).resolve();
//ResolvableEventObject is my class, it's just an EventObject with a
//resolve() method.
}
}
private void dispatchEvent(EventObject event, Class clazz)
{
LinkedList<EventListener> list = listeners.get(clazz);
if (list != null) {
for (int i = 0; i < list.size(); i++) {
EventListener listener = list.get(i);
listener.handleEvent(event);
}
}
if (EventObject.class.isAssignableFrom(clazz.getSuperclass())) {
dispatchEvent(event, clazz.getSuperclass());
}
}
private void resolveDelayedListeners()
{
for (ListenerStub listenerStub : delayedListeners) {
if (listenerStub.isAdd()) {
realAddListener(listenerStub.getListenerClass(), listenerStub.getListener());
} else {
realRemoveListener(listenerStub.getListenerClass(), listenerStub.getListener());
}
}
}
private class ListenerStub /*
* This class is used to hold the data of the listeners to be removed
* until the lock on the listener set is released.
*/
{
private Class<? extends EventObject> clazz;
private EventListener listener;
private boolean add;
ListenerStub(Class<? extends EventObject> clazz, EventListener listener, boolean add)
{
this.clazz = clazz;
this.listener = listener;
this.add = add;
}
public Class<? extends EventObject> getListenerClass()
{
return clazz;
}
public EventListener getListener()
{
return listener;
}
public boolean isAdd()
{
return add;
}
}
public class ListenerLock
/*
* This is a basic lock that is used to prevent modifications to the list of
* listeners. It releases when told, or when finalized if forgotten.
*/
{
boolean released = false;
ListenerLock()
{
lockCount++;
}
public void release()
{
if (!released) {
lockCount = Math.min(0, lockCount - 1);
released = true;
if (lockCount == 0) {
resolveDelayedListeners();
}
}
}
protected void finalize() throws Throwable
{
super.finalize();
release();
}
}
}
EventListener.java
public final class EventListener
{
/*
* An EventListener pairs a class (EventObject or a subtype) with a method on an object.
* It can hold either a strong or a weak reference to the object, and defaults weak.
* It uses reflection to call the specified method.
*/
Object source = null;
Method handler = null;
Class clazz;
WeakReference<Object> sourceReference;
public EventListener(Class<? extends EventObject> clazz, Object source,
String handlingMethod, boolean weakReference)
{
if (weakReference) {
this.source = null;
sourceReference = new WeakReference(source);
} else {
this.source = source;
sourceReference = null;
}
/*
* This structure sets up the call to the specified method on the specified
* object. If no method is found for the specified class to listen to,
* superclasses are tried until either a valid method is found or EventObject
* is reached with no match.
*/
Class C = clazz;
while (EventObject.class.isAssignableFrom(C) && handler == null) {
try {
this.handler = source.getClass().getMethod(handlingMethod, C);
} catch (NoSuchMethodException e) {
C = C.getSuperclass();
}
}
if (handler == null) {
throw new IllegalArgumentException("No method with the signature: "
+ handlingMethod + "("
+ clazz.getSimpleName() + ") found.");
}
this.clazz = clazz;
}
public EventListener(Class<? extends EventObject> clazz, Object source,
String handlingMethod)
{
this(clazz, source, handlingMethod, true);
}
void handleEvent(EventObject event) //package protected because it should only be
//called by EventManager's dispatchEvent().
{
if (source == null) { //source == null iff using weak references.
if (sourceReference.get() == null) { //referenced object garbage collected, delete listener.
deregisterListener();
return;
}
try {
handler.invoke(sourceReference.get(), event);
} catch (IllegalAccessException | IllegalArgumentException |
InvocationTargetException e) {
throw new IllegalArgumentException(e.getMessage(), e.getCause());
}
} else {
try {
handler.invoke(source, event);
} catch (IllegalAccessException | IllegalArgumentException |
InvocationTargetException e) {
throw new IllegalArgumentException(e.getMessage(), e.getCause());
}
}
}
public void registerListener() //registers a listener with the manager. Reference to listener
//is required to deregister later.
{
EventManager.getInstance().addListener(clazz, this);
}
public void deregisterListener()
{
EventManager.getInstance().removeListener(clazz, this);
}
}