/*******************************************************************************
* Copyright (c) 2008 Scott Stanchfield
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*******************************************************************************/
package com.javadude.listenerlist;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* <p>Manages a set of listeners on behalf of another class.</p>
*
* TODO more detail and examples
*
* @param <ListenerInterface> The type of listeners we will track in this list. This must be an interface.
*/
public class ListenerList<ListenerInterface> {
public enum Exceptions {
/** Swallow all exceptions. All listeners will be notified and no exception will be thrown if any
* listeners throw exceptions. Exceptions will be logged if a logger is passed in when creating the
* listener list. */
SWALLOW,
/** Throw all exceptions. Exceptions thrown by any listener will immediately be thrown to the caller.
* Note that any listeners that have not yet been notified when an exception is thrown will
* not be notified.
*/
THROW,
/** Collect all exceptions. If any listeners throw an exception when being notified, the exception will
* be added to a list. All listeners will be notified, and zero or more may throw exceptions.
* After all listeners have been notified, a ListenerException will be thrown that contains all of the
* exceptions thrown.
*/
COLLECT
}
private Exceptions exceptionHandling;
// options for ExceptionHandling.LOG
private Logger logger;
private Level logLevel;
private List<ListenerInterface> listeners = new ArrayList<ListenerInterface>();
private ListenerInterface dynamicProxy;
private final Class<ListenerInterface> clazz;
/**
* Create a listener list without wrapping thrown exceptions. This list can be created using any exception mode.
* @param <ListenerInterface> The listener interface that we will be tracking.
* @param clazz The listener interface type
* @param exceptionHandling The exception handling mode. Can be SWALLOW, THROW or COLLECT.
* @return a new listener list instance
* @see Exceptions
*/
public static <ListenerInterface> ListenerList<ListenerInterface> create(Class<ListenerInterface> clazz, Exceptions exceptionHandling) {
return new ListenerList<ListenerInterface>(clazz, exceptionHandling, null, null);
}
public static <ListenerInterface> ListenerList<ListenerInterface> create(Class<ListenerInterface> clazz, Exceptions exceptionHandling, Logger logger) {
return new ListenerList<ListenerInterface>(clazz, exceptionHandling, logger, Level.SEVERE);
}
public static <ListenerInterface> ListenerList<ListenerInterface> create(Class<ListenerInterface> clazz, Exceptions exceptionHandling, Logger logger, Level logLevel) {
return new ListenerList<ListenerInterface>(clazz, exceptionHandling, logger, logLevel);
}
protected ListenerList(Class<ListenerInterface> clazz, Exceptions exceptionHandling, Logger logger, Level logLevel) {
if (!clazz.isInterface()) {
throw new IllegalArgumentException("Listener type must be an interface");
}
this.clazz = clazz;
this.exceptionHandling = exceptionHandling;
this.logger = logger;
this.logLevel = logLevel;
@SuppressWarnings("unchecked")
ListenerInterface proxy = (ListenerInterface) Proxy.newProxyInstance(clazz.getClassLoader(),
new Class<?>[] {clazz},
new ListenerInvocationHandler());
dynamicProxy = proxy;
}
public void clear() {
synchronized (listeners) {
listeners.clear();
}
}
public void add(ListenerInterface listener) {
synchronized (listeners) {
listeners.add(listener);
}
}
public void remove(ListenerInterface listener) {
synchronized (listeners) {
listeners.remove(listener);
}
}
public ListenerInterface fire() {
return dynamicProxy;
}
private class ListenerInvocationHandler implements InvocationHandler {
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
List<Throwable> exceptions = null;
if (exceptionHandling == Exceptions.COLLECT) {
exceptions = new ArrayList<Throwable>();
}
List<ListenerInterface> targets = null;
synchronized (listeners) {
targets = new ArrayList<ListenerInterface>(listeners);
}
try {
for (ListenerInterface listener : targets) {
ListenerList.this.invoke(listener, method, args, exceptions);
}
if (exceptions != null && !exceptions.isEmpty()) {
throw new ListenerException(exceptions);
}
} catch (ThreadDeath t) {
throw t;
} catch (Throwable t) {
if (exceptionHandling == Exceptions.THROW || exceptionHandling == Exceptions.COLLECT) {
throw t;
}
// else swallow exception
}
return null;
}
}
protected void invoke(ListenerInterface listener, Method method, Object[] args, List<Throwable> exceptions) throws Throwable {
try {
try {
method.invoke(listener, args);
} catch (InvocationTargetException t) {
throw t.getCause();
}
} catch (ThreadDeath t) {
throw t;
} catch (Throwable t) {
if (logger != null) {
logger.log(logLevel, "Error invoking listener method " + method.getName(), t); // TBD NLS
}
if (exceptions != null) {
exceptions.add(t);
}
if (exceptionHandling != Exceptions.COLLECT && exceptionHandling != Exceptions.SWALLOW) {
throw t;
}
// else swallow
}
}
@Override public String toString() {
return getClass().getName() + '[' + paramString() + ']';
}
public String paramString() {
return "listenerType=" + clazz + ", listeners=" + listeners;
}
}