package util; import java.io.*; import java.util.*; /** * Helper class that supports Listeners which themselves can be listened to. * * <p>The main intent is to support garbage collection by avoiding cyclic links between * the listener and its event source. Listeners that use ListenerHelper (and implement * HelpableListener) will be registered with their event source only as long as there * are any listeners registered with them. They will however guarantee up-to-date-ness * indepent of whether a listener is registered with them or not, by polling their * model whenever a query is performed against them and they have no listeners.</p> * * <p>For this mechanism to work properly such listeners must abide by the following * rules:</p> * * <ul> * <li>They must implement HelpableListener.</li> * <li>They must use an instance of ListenerHelper to manage their list of listeners.</li> * <li>At the start of any query method that depends on the listener's own event * source they are to call {@link #needModelUpdate}</li> * </ul> * * <p>As the Swing serialization is buggy, this class will serialize only listeners that implement * {@link SerializableListener}. For this purpose, however, all methods in * {@link javax.swing.event.EventListenerList} need to be overridden.</p> * * @see HelpableListener * @see #needModelUpdate * * @author Steffen Zschaler * @version 2.0 02/06/1999 * @since v2.0 */ public class ListenerHelper extends javax.swing.event.EventListenerList { /** * A null array to be shared by all empty listener lists */ private final static Object[] NULL_ARRAY = new Object[0]; /** * The list of ListenerType - Listener pairs. We need to redefine this as it would otherwise be stored by * the standard {@link javax.swing.event.EventListenerList} mechanism. * * PENDING (sz9): Need to replace direct references to listeners by SoftReferences, in order to avoid * cycles and thereby memory leaks. Example: Stocks listen to their catalogs in order to maintain * referential integrity. They also store a pointer to their catalog in order to be able to reference * it. A cycle is created, which means that creating lots of temporary stocks is a potential memory * problem. */ protected transient Object[] aoListeners = NULL_ARRAY; /** * The listener that owns this listener helper. * * @serial */ protected HelpableListener m_hlOwner; /** * Construct a new ListenerHelper. Using this constructor will only use the improved serialization support, * but not the subscribe/unsubscribe functionality. */ public ListenerHelper() { this (null); } /** * Create a new ListenerHelper. * * @param hlOwner the HelpableListener that is associated to this ListenerHelper. */ public ListenerHelper (HelpableListener hlOwner) { super(); m_hlOwner = hlOwner; } /** * This passes back the event listener list as an array * of ListenerType - listener pairs. Note that for * performance reasons, this implementation passes back * the actual data structure in which the listner data * is stored internally! * This method is guaranteed to pass back a non-null * array, so that no null-checking is required in * fire methods. A zero-length array of Object should * be returned if there are currently no listeners. * * WARNING!!! Absolutely NO modification of * the data contained in this array should be made -- if * any such manipulation is necessary, it should be done * on a copy of the array returned rather than the array * itself. */ public Object[] getListenerList() { return aoListeners; } /** * Return the total number of listeners for this listenerlist */ public int getListenerCount() { return aoListeners.length / 2; } /** * Return the total number of listeners of the supplied type * for this listenerlist. */ public int getListenerCount(Class t) { int count = 0; Object[] lList = aoListeners; for (int i = 0; i < lList.length; i += 2) { if (t == ((Class) lList[i])) { count++; } } return count; } /** * Add the listener as a listener of the specified type. * * @param t the type of the listener to be added * @param l the listener to be added */ public synchronized void add (Class t, EventListener l) { if (!t.isInstance(l)) { throw new IllegalArgumentException ("Listener " + l + " is not of type " + t); } if (l ==null) { throw new IllegalArgumentException("Listener " + l + " is null"); } if (aoListeners == NULL_ARRAY) { // if this is the first listener added, // initialize the lists aoListeners = new Object[] { t, l }; // .. and have the owner subscribe. if (m_hlOwner != null) { m_hlOwner.subscribe(); // a last time so that we start with the most accurate model possible m_hlOwner.updateModel(); } } else { // Otherwise copy the array and add the new listener int i = aoListeners.length; Object[] tmp = new Object[i+2]; System.arraycopy(aoListeners, 0, tmp, 0, i); tmp[i] = t; tmp[i+1] = l; aoListeners = tmp; } } /** * Remove 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 void remove(Class t, EventListener l) { if (!t.isInstance(l)) { throw new IllegalArgumentException("Listener " + l + " is not of type " + t); } if (l ==null) { throw new IllegalArgumentException("Listener " + l + " is null"); } // Is l on the list? int index = -1; for (int i = aoListeners.length-2; i >= 0; i -= 2) { if ((aoListeners[i] == t) && (aoListeners[i+1].equals(l))) { index = i; break; } } // If so, remove it if (index != -1) { Object[] tmp = new Object[aoListeners.length-2]; // Copy the list up to index System.arraycopy(aoListeners, 0, tmp, 0, index); // Copy from two past the index, up to // the end of tmp (which is two elements // shorter than the old list) if (index < tmp.length) { System.arraycopy(aoListeners, index + 2, tmp, index, tmp.length - index); } // set the listener array to the new array or null aoListeners = (tmp.length == 0) ? NULL_ARRAY : tmp; if (tmp.length == 0) { if (m_hlOwner != null) { m_hlOwner.unsubscribe(); } } } } /** * Make sure, the owner's model is up to date. If necessary call * {@link util.HelpableListener#updateModel} in the owner. */ public void needModelUpdate() { if (getListenerCount() == 0) { if (m_hlOwner != null) { m_hlOwner.updateModel(); } } } /** * A SerializableListenerHelper will store all the listeners that implement {@link SerializableListener}. * * @serialData tuples of (<classname>, <listener>). */ private void writeObject(ObjectOutputStream s) throws IOException { Object[] lList = aoListeners; 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 SerializableListener)) { s.writeObject(t.getName()); s.writeObject(l); } } s.writeObject(null); } /** * A SerializableListenerHelper will store all the listeners that implement {@link SerializableListener}. */ private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException { aoListeners = NULL_ARRAY; s.defaultReadObject(); Object listenerTypeOrNull; while (null != (listenerTypeOrNull = s.readObject())) { EventListener l = (EventListener) s.readObject(); add(Class.forName ((String) listenerTypeOrNull), l); } } /** * Return a string representation of the SerializableListenerHelper. */ public String toString() { Object[] lList = aoListeners; String s = "SerializableListenerHelper: "; 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; } }