package com.yoursway.utils; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; /** * This class is a thread safe list that is designed for storing lists of * listeners. The implementation is optimized for minimal memory footprint, * frequent reads and infrequent writes. Modification of the list is * synchronized and relatively expensive, while accessing the listeners is very * fast. Readers are given access to the underlying array data structure for * reading, with the trust that they will not modify the underlying array. * <p> * <b>dottedmag</b>: underlying implementation changed to ArrayList, so this * might be a bit slower now. * <p> * <a name="same">A listener list handles the <i>same</i> listener being added * multiple times, and tolerates removal of listeners that are the same as other * listeners in the list. For this purpose, listeners can be compared with each * other using either equality or identity, as specified in the list * constructor. * </p> * <p> * This class is typed and modified version of * org.eclipse.core.runtime.ListenerList * </p> * <p> * The recommended code sequence for notifying all registered listeners of say, * <code>FooListener.eventHappened</code>, is: * * <pre> * for (FooListener listener : myListeners) * listener.eventHappened(event); * </pre> * * </p> */ public final class Listeners<T> implements Iterable<T> { /** * Mode constant (value 0) indicating that listeners should be considered * the <a href="#same">same</a> if they are equal. */ public static final int EQUALITY = 0; /** * Mode constant (value 1) indicating that listeners should be considered * the <a href="#same">same</a> if they are identical. */ public static final int IDENTITY = 1; /** * Indicates the comparison mode used to determine if two listeners are * equivalent */ private final boolean identity = true; /** * The list of listeners. Initially empty but initialized to an array of * size capacity the first time a listener is added. Maintains invariant: * listeners != null */ private volatile List<T> listeners = Collections.emptyList(); /** * Creates a listener list in which listeners are compared using identity. */ public Listeners() { } /** * Adds a listener to this list. This method has no effect if the <a * href="#same">same</a> listener is already registered. * * @param listener * the non-<code>null</code> listener to add */ public synchronized <L extends T> void add(L listener) { if (listener == null) throw new NullPointerException("listener is null"); for (T existingListener : listeners) if (identity ? listener == existingListener : listener.equals(existingListener)) return; List<T> newListeners = new ArrayList<T>(listeners.size() + 1); newListeners.addAll(listeners); newListeners.add(listener); //atomic assignment listeners = newListeners; } /** * Returns an array containing all the registered listeners. The resulting * array is unaffected by subsequent adds or removes. If there are no * listeners registered, the result is an empty array. Use this method when * notifying listeners, so that any modifications to the listener list * during the notification will have no effect on the notification itself. * <p> * Note: Callers of this method <b>must not</b> modify the returned array. * * @return the list of registered listeners */ public Iterator<T> iterator() { return listeners.iterator(); } /** * Returns whether this listener list is empty. * * @return <code>true</code> if there are no registered listeners, and * <code>false</code> otherwise */ public boolean isEmpty() { return listeners.isEmpty(); } /** * Removes a listener from this list. Has no effect if the <a * href="#same">same</a> listener was not already registered. * * @param listener * the non-<code>null</code> listener to remove */ public synchronized void remove(T listener) { if (listener == null) throw new NullPointerException("listener is null"); // Optimization if (listeners.isEmpty()) ; else if (listeners.size() == 1) { if (isSame(listeners.get(0), listener)) listeners = Collections.emptyList(); } else { List<T> newListeners = new ArrayList<T>(listeners.size()); for (T existingListener : listeners) if (!isSame(existingListener, listener)) newListeners.add(existingListener); listeners = newListeners; } } private boolean isSame(T a, T b) { return identity ? a == b : a.equals(b); } /** * Returns the number of registered listeners. * * @return the number of registered listeners */ public int size() { return listeners.size(); } /** * Removes all listeners from this list. */ public synchronized void clear() { listeners = Collections.emptyList(); } public static <T> Listeners<T> newListenersByIdentity() { return new Listeners<T>(); } }