View Javadoc
1   /**
2    * Redistribution and use of this software and associated documentation ("Software"), with or
3    * without modification, are permitted provided that the following conditions are met:
4    *
5    * 1. Redistributions of source code must retain copyright statements and notices. Redistributions
6    * must also contain a copy of this document.
7    *
8    * 2. Redistributions in binary form must reproduce the above copyright notice, this list of
9    * conditions and the following disclaimer in the documentation and/or other materials provided with
10   * the distribution.
11   *
12   * 3. The name "Exolab" must not be used to endorse or promote products derived from this Software
13   * without prior written permission of Intalio, Inc. For written permission, please contact
14   * info@exolab.org.
15   *
16   * 4. Products derived from this Software may not be called "Exolab" nor may "Exolab" appear in
17   * their names without prior written permission of Intalio, Inc. Exolab is a registered trademark of
18   * Intalio, Inc.
19   *
20   * 5. Due credit should be given to the Exolab Project (http://www.exolab.org/).
21   *
22   * THIS SOFTWARE IS PROVIDED BY INTALIO, INC. AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESSED OR
23   * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
24   * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL INTALIO, INC. OR ITS
25   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26   * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27   * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
28   * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
29   * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30   *
31   *
32   * Contribution(s):
33   *
34   * - Jeff Norris, Jeff.Norris@jpl.nasa.gov - Original Author
35   *
36   * $Id$
37   */
38  
39  package org.exolab.castor.util;
40  
41  import java.io.IOException;
42  import java.io.ObjectInputStream;
43  import java.io.ObjectOutputStream;
44  import java.io.Serializable;
45  import java.util.EventListener;
46  
47  /**
48   * <p>
49   * This class is an efficient repository for EventListeners based on javax.swing.EventListenerList.
50   * </p>
51   *
52   * <p>
53   * This modification of javax.swing.EventListenerList retains the core functionality of that class
54   * but changes the basic API and adds a few more features, as summarized below:
55   * <p>
56   * 
57   * <ol>
58   * <li>javax.swing.EventListenerList requires all listeners to be added in conjunction with a class
59   * object that identified the type of the listener. This implementation's add methods simply take
60   * the listener object.
61   *
62   * <li>The listener list returned from javax.swing.EventListenerList had to be iterated over in a
63   * cumbersome manner because the listeners were stored along with their Class objects in the array.
64   * Since listener classes are not stored in this listener list, the returned listener array can be
65   * iterated over normally (1 element at a time).
66   *
67   * <li>The remove method in javax.swing.EventListenerList had return type "void". This
68   * implementation's remove method returns true if the specified listener was found in the listener
69   * array and false otherwise.
70   *
71   * <li>This implementation adds {@link #add(EventListener, int)}, which allows the addition of a
72   * listener at a specific position in the array.
73   *
74   * <li>The add and remove methods in this implementation throw IllegalArgumentExceptions when their
75   * arguments are null.
76   * </ol>
77   *
78   * <p>
79   * As is the case with javax.swing.EventListenerList, this class provides multi-threaded safety
80   * through a copy-on-modify strategy. It is optimized to provide high performance when events are
81   * being fired, with slightly slower performance than the Collection API when listeners are being
82   * added and removed. Like its predecessor, this class will never return a null array from
83   * getListenerList.
84   * </p>
85   *
86   * <p>
87   * The most important thing to keep in mind when using this class is that the array returned by
88   * getListenerList is the actual internal array of this class and MUST NOT BE MODIFIED UNDER ANY
89   * CIRCUMSTANCES. Below is an example of how to use this class, borrowed (and slightly modified)
90   * from the javax.swing.EventListenerList documentation:
91   * </p>
92   *
93   * <p>
94   * Usage example: Say one is defining a class that sends out FooEvents, and one wants to allow users
95   * of the class to register FooListeners and receive notification when FooEvents occur. The
96   * following should be added to the class definition:
97   * </p>
98   * 
99   * <pre>
100  * EventListenerList listenerList = new EventListenerList();
101  * FooEvent fooEvent = null;
102  *
103  * public void addFooListener(FooListener l) {
104  *   listenerList.add(l);
105  * }
106  *
107  * public void removeFooListener(FooListener l) {
108  *   listenerList.remove(l);
109  * }
110  *
111  * // Notify all listeners that have registered interest for
112  * // notification on this event type. The event instance
113  * // is lazily created using the parameters passed into
114  * // the fire method.
115  *
116  * protected void fireFooXXX() {
117  *   // Guaranteed to return a non-null array
118  *   EventListener[] listeners = listenerList.getListenerList();
119  *   // Process the listeners last to first, notifying
120  *   // those that are interested in this event
121  *   for (int i = 0; i &lt; listeners.length; i++) {
122  *     // Lazily create the event:
123  *     if (fooEvent == null)
124  *       fooEvent = new FooEvent(this);
125  *     ((FooListener) listeners[i]).fooXXX(fooEvent);
126  *   }
127  * }
128  * </pre>
129  *
130  * <p>
131  * foo should be changed to the appropriate name, and fireFooXxx to the appropriate method name. One
132  * fire method should exist for each notification method in the FooListener interface.
133  * </p>
134  *
135  * <p>
136  * The authors of javax.swing.EventListenerList are Georges Saab, Hans Muller, and James Gosling.
137  * </p>
138  *
139  * @author <a href="mailto:Jeff.Norris@jpl.nasa.gov">Jeff Norris</a>
140  * @version $Revision$ $Date: 2005-12-13 14:58:48 -0700 (Tue, 13 Dec 2005) $
141  */
142 public class EventListenerList implements Serializable {
143   /** SerialVersionUID */
144   private static final long serialVersionUID = 4472874989562384564L;
145 
146   /**
147    * A null array to be shared by all empty listener lists
148    */
149   private final static EventListener[] NULL_ARRAY = new EventListener[0];
150 
151   /**
152    * The internal list of listeners that is returned from getListenerList
153    */
154   protected transient EventListener[] listenerList = NULL_ARRAY;
155 
156   /**
157    * <p>
158    * Passes back the event listener list as an array of EventListeners.
159    * </p>
160    *
161    * <p>
162    * Note that for performance reasons, this implementation passes back the actual data structure in
163    * which the listener data is stored internally! This method is guaranteed to pass back a non-null
164    * array, so that no null-checking is required in fire methods. A zero-length array of Object will
165    * be returned if there are currently no listeners.
166    * </p>
167    * 
168    * WARNING!!! Absolutely NO modification of the data contained in this array should be made -- if
169    * any such manipulation is necessary, it should be done on a copy of the array returned rather
170    * than the array itself.
171    */
172   public EventListener[] getListenerList() {
173     return listenerList;
174   }
175 
176   /**
177    * <p>
178    * Returns the total number of listeners in this listener list.
179    * </p>
180    *
181    */
182   public int getListenerCount() {
183     return listenerList.length;
184   }
185 
186   /**
187    * <p>
188    * Adds the listener to the end of the listener list.
189    * </p>
190    *
191    * @param newListener the listener to be added
192    * @exception IllegalArgumentException if the specified newListener is null
193    */
194   public synchronized void add(EventListener newListener) {
195 
196     if (newListener == null)
197       throw new IllegalArgumentException("Listener to add must not be null.");
198 
199     if (listenerList == NULL_ARRAY) {
200       // if this is the first listener added,
201       // initialize the lists
202       listenerList = new EventListener[] {newListener};
203     } else {
204       // Otherwise copy the array and add the new listener
205       int oldLength = listenerList.length;
206       EventListener[] tmp = new EventListener[oldLength + 1];
207       System.arraycopy(listenerList, 0, tmp, 0, oldLength);
208 
209       tmp[oldLength] = newListener;
210 
211       listenerList = tmp;
212     }
213   }
214 
215   /**
216    * <p>
217    * Adds the listener at the specified index in the listener list.
218    * </p>
219    *
220    * @param newListener the listener to be added
221    * @exception IllegalArgumentException if the specified newListener is null, or the specified
222    *            index is less than zero or greater than the length of the listener list array.
223    */
224   public synchronized void add(EventListener newListener, int index) {
225     if (newListener == null)
226       throw new IllegalArgumentException("Listener to add must not be null.");
227 
228     if ((index < 0) || (index > listenerList.length))
229       throw new IllegalArgumentException("Index to add listener (" + index
230           + ") is out of bounds. List length is " + listenerList.length);
231 
232     if (listenerList == NULL_ARRAY) {
233       // if this is the first listener added, initialize the lists
234       listenerList = new EventListener[] {newListener};
235     } else {
236       // Otherwise copy the array and add the new listener
237       int oldLength = listenerList.length;
238       EventListener[] tmp = new EventListener[oldLength + 1];
239       // Copy up to the index where the new listener should go
240       System.arraycopy(listenerList, 0, tmp, 0, index);
241       // Skip a cell and copy the rest of the list
242       System.arraycopy(listenerList, index, tmp, index + 1, oldLength - index);
243       // Insert the new listener
244       tmp[index] = newListener;
245 
246       listenerList = tmp;
247     }
248   }
249 
250   /**
251    * Removes the listener as a listener of the specified type.
252    *
253    * @param listenerToRemove the listener to be removed
254    * @exception IllegalArgumentException if the specified listener is null
255    */
256   public synchronized boolean remove(EventListener listenerToRemove) {
257     if (listenerToRemove == null)
258       throw new IllegalArgumentException("Listener to remove must " + "not be null.");
259     // Is listenerToRemove on the list?
260     int index = -1;
261     for (int i = listenerList.length - 1; i >= 0; i--) {
262       if (listenerList[i].equals(listenerToRemove) == true) {
263         index = i;
264         break;
265       }
266     }
267 
268     // If so, remove it
269     if (index != -1) {
270       EventListener[] tmp = new EventListener[listenerList.length - 1];
271       // Copy the list up to index
272       System.arraycopy(listenerList, 0, tmp, 0, index);
273       // Copy from one past the index, up to the end of tmp (which is
274       // one element shorter than the old list)
275       if (index < tmp.length)
276         System.arraycopy(listenerList, index + 1, tmp, index, tmp.length - index);
277       // set the listener array to the new array or null
278       listenerList = (tmp.length == 0) ? NULL_ARRAY : tmp;
279       return true;
280     }
281     return false;
282   }
283 
284   // Serialization support.
285   private void writeObject(ObjectOutputStream s) throws IOException {
286     Object[] lList = listenerList;
287     s.defaultWriteObject();
288 
289     // Save the non-null event listeners:
290     for (int i = 0; i < lList.length; i += 1) {
291       EventListener l = (EventListener) lList[i];
292       if ((l != null) && (l instanceof Serializable)) {
293         s.writeObject(l);
294       }
295     }
296 
297     s.writeObject(null);
298   }
299 
300   private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException {
301     listenerList = NULL_ARRAY;
302     s.defaultReadObject();
303     EventListener listenerOrNull;
304 
305     while (null != (listenerOrNull = (EventListener) s.readObject())) {
306       add(listenerOrNull);
307     }
308   }
309 
310   /**
311    * Returns a string representation of the EventListenerList.
312    */
313   public String toString() {
314     Object[] lList = listenerList;
315     String s = "EventListenerList: ";
316     s += lList.length + " listeners: ";
317     for (int i = 0; i < lList.length; i++) {
318       s += " listener " + lList[i + 1];
319     }
320     return s;
321   }
322 
323 } // -- class: EventListenerList