/** * Redistribution and use of this software and associated documentation * ("Software"), with or without modification, are permitted provided * that the following conditions are met: * * 1. Redistributions of source code must retain copyright * statements and notices. Redistributions must also contain a * copy of this document. * * 2. Redistributions in binary form must reproduce the * above copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * 3. The name "Exolab" must not be used to endorse or promote * products derived from this Software without prior written * permission of Intalio, Inc. For written permission, * please contact info@exolab.org. * * 4. Products derived from this Software may not be called "Exolab" * nor may "Exolab" appear in their names without prior written * permission of Intalio, Inc. Exolab is a registered * trademark of Intalio, Inc. * * 5. Due credit should be given to the Exolab Project * (http://www.exolab.org/). * * THIS SOFTWARE IS PROVIDED BY INTALIO, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL * INTALIO, INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. * * * Contribution(s): * * - Jeff Norris, Jeff.Norris@jpl.nasa.gov * - Original Author * * $Id$ */ package org.exolab.castor.util; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.util.EventListener; /** * <p>This class is an efficient repository for EventListeners based * on javax.swing.EventListenerList.</p> * * <p>This modification of javax.swing.EventListenerList retains the * core functionality of that class but changes the basic API and adds * a few more features, as summarized below:<p> * * <ol> * <li>javax.swing.EventListenerList requires all listeners to be * added in conjunction with a class object that identified the type * of the listener. This implementation's add methods simply take the * listener object. * * <li>The listener list returned from javax.swing.EventListenerList * had to be iterated over in a cumbersome manner because the * listeners were stored along with their Class objects in the array. * Since listener classes are not stored in this listener list, the * returned listener array can be iterated over normally (1 element at * a time). * * <li>The remove method in javax.swing.EventListenerList had return * type "void". This implementation's remove method returns true if * the specified listener was found in the listener array and false * otherwise. * * <li>This implementation adds {@link #add(EventListener, int)}, * which allows the addition of a listener at a specific position in * the array. * * <li>The add and remove methods in this implementation throw * IllegalArgumentExceptions when their arguments are null. * </ol> * * <p>As is the case with javax.swing.EventListenerList, this class * provides multi-threaded safety through a copy-on-modify strategy. * It is optimized to provide high performance when events are being * fired, with slightly slower performance than the Collection API * when listeners are being added and removed. Like its predecessor, * this class will never return a null array from getListenerList.</p> * * <p>The most important thing to keep in mind when using this class * is that the array returned by getListenerList is the actual * internal array of this class and MUST NOT BE MODIFIED UNDER ANY * CIRCUMSTANCES. Below is an example of how to use this class, * borrowed (and slightly modified) from the * javax.swing.EventListenerList documentation:</p> * * <p>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:</p> * <pre> * EventListenerList listenerList = new EventListenerList(); * FooEvent fooEvent = null; * * public void addFooListener(FooListener l) { * listenerList.add(l); * } * * public void removeFooListener(FooListener l) { * listenerList.remove(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 * EventListener[] listeners = listenerList.getListenerList(); * // Process the listeners last to first, notifying * // those that are interested in this event * for (int i = 0 ; i < listeners.length ; i++) { * // Lazily create the event: * if (fooEvent == null) * fooEvent = new FooEvent(this); * ((FooListener)listeners[i]).fooXXX(fooEvent); * } * } * </pre> * * <p>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> * * <p>The authors of javax.swing.EventListenerList are Georges Saab, * Hans Muller, and James Gosling.</p> * * @author <a href="mailto:Jeff.Norris@jpl.nasa.gov">Jeff Norris</a> * @version $Revision$ $Date: 2005-12-13 14:58:48 -0700 (Tue, 13 Dec 2005) $ */ public class EventListenerList implements Serializable { /** SerialVersionUID */ private static final long serialVersionUID = 4472874989562384564L; /** * A null array to be shared by all empty listener lists */ private final static EventListener[] NULL_ARRAY = new EventListener[0]; /** * The internal list of listeners that is returned from * getListenerList */ protected transient EventListener[] listenerList = NULL_ARRAY; /** * <p>Passes back the event listener list as an array of * EventListeners.</p> * * <p>Note that for performance reasons, this implementation passes * back the actual data structure in which the listener 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 will be returned if * there are currently no listeners.</p> * * 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 EventListener[] getListenerList() { return listenerList; } /** * <p>Returns the total number of listeners in this listener * list.</p> * */ public int getListenerCount() { return listenerList.length; } /** * <p>Adds the listener to the end of the listener list.</p> * * @param newListener the listener to be added * @exception IllegalArgumentException if the specified newListener * is null */ public synchronized void add(EventListener newListener) { if (newListener==null) throw new IllegalArgumentException("Listener to add must not be null."); if (listenerList == NULL_ARRAY) { // if this is the first listener added, // initialize the lists listenerList = new EventListener[] { newListener }; } else { // Otherwise copy the array and add the new listener int oldLength = listenerList.length; EventListener[] tmp = new EventListener[oldLength+1]; System.arraycopy(listenerList, 0, tmp, 0, oldLength); tmp[oldLength] = newListener; listenerList = tmp; } } /** * <p>Adds the listener at the specified index in the listener * list.</p> * * @param newListener the listener to be added * @exception IllegalArgumentException if the specified newListener * is null, or the specified index is less than zero or greater than * the length of the listener list array. */ public synchronized void add(EventListener newListener, int index) { if (newListener==null) throw new IllegalArgumentException("Listener to add must not be null."); if ((index < 0) || (index > listenerList.length)) throw new IllegalArgumentException("Index to add listener (" + index + ") is out of bounds. List length is " + listenerList.length); if (listenerList == NULL_ARRAY) { // if this is the first listener added, initialize the lists listenerList = new EventListener[] { newListener }; } else { // Otherwise copy the array and add the new listener int oldLength = listenerList.length; EventListener[] tmp = new EventListener[oldLength+1]; // Copy up to the index where the new listener should go System.arraycopy(listenerList, 0, tmp, 0, index); // Skip a cell and copy the rest of the list System.arraycopy(listenerList, index, tmp, index+1, oldLength-index); // Insert the new listener tmp[index] = newListener; listenerList = tmp; } } /** * Removes the listener as a listener of the specified type. * * @param listenerToRemove the listener to be removed * @exception IllegalArgumentException if the specified listener is * null */ public synchronized boolean remove(EventListener listenerToRemove) { if (listenerToRemove ==null) throw new IllegalArgumentException("Listener to remove must " + "not be null."); // Is listenerToRemove on the list? int index = -1; for (int i = listenerList.length-1; i>=0; i--) { if (listenerList[i].equals(listenerToRemove) == true) { index = i; break; } } // If so, remove it if (index != -1) { EventListener[] tmp = new EventListener[listenerList.length-1]; // Copy the list up to index System.arraycopy(listenerList, 0, tmp, 0, index); // Copy from one past the index, up to the end of tmp (which is // one element shorter than the old list) if (index < tmp.length) System.arraycopy(listenerList, index+1, tmp, index, tmp.length - index); // set the listener array to the new array or null listenerList = (tmp.length == 0) ? NULL_ARRAY : tmp; return true; } return false; } // 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+=1) { EventListener l = (EventListener)lList[i]; if ((l!=null) && (l instanceof Serializable)) { s.writeObject(l); } } s.writeObject(null); } private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException { listenerList = NULL_ARRAY; s.defaultReadObject(); EventListener listenerOrNull; while (null != (listenerOrNull = (EventListener)s.readObject())) { add(listenerOrNull); } } /** * Returns a string representation of the EventListenerList. */ public String toString() { Object[] lList = listenerList; String s = "EventListenerList: "; s += lList.length + " listeners: "; for (int i = 0 ; i < lList.length ; i++) { s += " listener " + lList[i+1]; } return s; } } //-- class: EventListenerList