/*
* $Id: WeakEventListenerList.java 3190 2009-01-20 17:47:52Z kschaefe $
*
* Copyright 2006 Sun Microsystems, Inc., 4150 Network Circle,
* Santa Clara, California 95054, U.S.A. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
package org.jdesktop.swingx.event;
import java.io.Serializable;
import java.lang.ref.WeakReference;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.EventListener;
import java.util.List;
/**
* A class that holds a list of EventListeners. A single instance
* can be used to hold all listeners (of all types) for the instance
* using the list. It is the responsibility of the class using the
* EventListenerList to provide type-safe API (preferably conforming
* to the JavaBeans spec) and methods which dispatch event notification
* methods to appropriate Event Listeners on the list.
*
* The main benefit that this class provides is that it releases
* garbage collected listeners (internally uses weak references). <p>
*
* PENDING: serialization support
*
*
* Usage example:
* Say one is defining a class that sends out FooEvents, and one wants
* to allow users of the class to register FooListeners and receive
* notification when FooEvents occur. The following should be added
* to the class definition:
* <pre>
* EventListenerList listenerList = new EventListenerList();
* FooEvent fooEvent = null;
*
* public void addFooListener(FooListener l) {
* listenerList.add(FooListener.class, l);
* }
*
* public void removeFooListener(FooListener l) {
* listenerList.remove(FooListener.class, l);
* }
*
*
* // Notify all listeners that have registered interest for
* // notification on this event type. The event instance
* // is lazily created using the parameters passed into
* // the fire method.
*
*
* protected void fireFooXXX() {
* // Guaranteed to return a non-null array
* FooListener[] listeners = listenerList.getListeners(FooListener.class);
* // Process the listeners last to first, notifying
* // those that are interested in this event
* for (FooListener listener: listeners) {
* // Lazily create the event:
* if (fooEvent == null)
* fooEvent = new FooEvent(this);
* listener.fooXXX(fooEvent);
* }
* }
* }
* </pre>
* foo should be changed to the appropriate name, and fireFooXxx to the
* appropriate method name. One fire method should exist for each
* notification method in the FooListener interface.
* <p>
* <strong>Warning:</strong>
* Serialized objects of this class will not be compatible with
* future Swing releases. The current serialization support is
* appropriate for short term storage or RMI between applications running
* the same version of Swing. As of 1.4, support for long term storage
* of all JavaBeans<sup><font size="-2">TM</font></sup>
* has been added to the <code>java.beans</code> package.
* Please see {@link java.beans.XMLEncoder}.
*
* @version 1.37 11/17/05
* @author Georges Saab
* @author Hans Muller
* @author James Gosling
*/
public class WeakEventListenerList implements Serializable {
protected transient List<WeakReference<? extends EventListener>> weakReferences;
protected transient List<Class<? extends EventListener>> classes;
/**
* Passes back the event listener list as an array
* of ListenerType-listener pairs.
* As a side-effect, cleans out any
* garbage collected listeners before building the array.
*
* @return a array of listenerType-listener pairs.
*/
public Object[] getListenerList() {
List<? extends EventListener> listeners = cleanReferences();
Object[] result = new Object[listeners.size() * 2];
for (int i = 0; i < listeners.size(); i++) {
result[2*i + 1] = listeners.get(i);
result[2*i] = getClasses().get(i);
}
return result;
}
/**
* Returns a list of strongly referenced EventListeners. Removes
* internal weak references to garbage collected listeners.
*
* @return
*/
@SuppressWarnings("unchecked")
private synchronized <T extends EventListener> List<T> cleanReferences() {
List<T> listeners = new ArrayList<T>();
for (int i = getReferences().size() - 1; i >= 0; i--) {
Object listener = getReferences().get(i).get();
if (listener == null) {
getReferences().remove(i);
getClasses().remove(i);
} else {
listeners.add(0, (T) listener);
}
}
return listeners;
}
private List<WeakReference<? extends EventListener>> getReferences() {
if (weakReferences == null) {
weakReferences = new ArrayList<WeakReference<? extends EventListener>>();
}
return weakReferences;
}
private List<Class<? extends EventListener>> getClasses() {
if (classes == null) {
classes = new ArrayList<Class<? extends EventListener>>();
}
return classes;
}
/**
* Return an array of all the listeners of the given type.
* As a side-effect, cleans out any
* garbage collected listeners before building the array.
* @return all of the listeners of the specified type.
* @exception ClassCastException if the supplied class
* is not assignable to EventListener
*
* @since 1.3
*/
@SuppressWarnings("unchecked")
public <T extends EventListener> T[] getListeners(Class<T> t) {
List<T> liveListeners = cleanReferences();
List<T> listeners = new ArrayList<T>();
for (int i = 0; i < liveListeners.size(); i++) {
if (getClasses().get(i) == t) {
listeners.add(liveListeners.get(i));
}
}
T[] result = (T[])Array.newInstance(t, listeners.size());
return listeners.toArray(result);
}
/**
* Adds the listener as a listener of the specified type.
* As a side-effect, cleans out any garbage collected
* listeners before adding.
* @param t the type of the listener to be added
* @param l the listener to be added
*/
public synchronized <T extends EventListener> void add(Class<T> t, T l) {
if (l==null) {
// In an ideal world, we would do an assertion here
// to help developers know they are probably doing
// something wrong
return;
}
if (!t.isInstance(l)) {
throw new IllegalArgumentException("Listener " + l +
" is not of type " + t);
}
cleanReferences();
getReferences().add(new WeakReference<T>(l));
getClasses().add(t);
}
/**
* Removes the listener as a listener of the specified type.
* @param t the type of the listener to be removed
* @param l the listener to be removed
*/
public synchronized <T extends EventListener> void remove(Class<T> t, T l) {
if (l ==null) {
// In an ideal world, we would do an assertion here
// to help developers know they are probably doing
// something wrong
return;
}
if (!t.isInstance(l)) {
throw new IllegalArgumentException("Listener " + l +
" is not of type " + t);
}
for (int i = 0; i < getReferences().size(); i++) {
if (l.equals(getReferences().get(i).get()) &&
(t == getClasses().get(i))) {
getReferences().remove(i);
getClasses().remove(i);
break;
}
}
}
// // Serialization support.
// private void writeObject(ObjectOutputStream s) throws IOException {
// Object[] lList = listenerList;
// s.defaultWriteObject();
//
// // Save the non-null event listeners:
// for (int i = 0; i < lList.length; i+=2) {
// Class t = (Class)lList[i];
// EventListener l = (EventListener)lList[i+1];
// if ((l!=null) && (l instanceof Serializable)) {
// s.writeObject(t.getName());
// s.writeObject(l);
// }
// }
//
// s.writeObject(null);
// }
//
// private void readObject(ObjectInputStream s)
// throws IOException, ClassNotFoundException {
// listenerList = NULL_ARRAY;
// s.defaultReadObject();
// Object listenerTypeOrNull;
//
// while (null != (listenerTypeOrNull = s.readObject())) {
// ClassLoader cl = Thread.currentThread().getContextClassLoader();
// EventListener l = (EventListener)s.readObject();
// add((Class<EventListener>)Class.forName((String)listenerTypeOrNull, true, cl), l);
// }
// }
// /**
// * Returns a string representation of the EventListenerList.
// */
// public String toString() {
// Object[] lList = listenerList;
// String s = "EventListenerList: ";
// s += lList.length/2 + " listeners: ";
// for (int i = 0 ; i <= lList.length-2 ; i+=2) {
// s += " type " + ((Class)lList[i]).getName();
// s += " listener " + lList[i+1];
// }
// return s;
// }
}