1   /**
2    * Distribution License:
3    * JSword is free software; you can redistribute it and/or modify it under
4    * the terms of the GNU Lesser General Public License, version 2.1 as published by
5    * the Free Software Foundation. This program is distributed in the hope
6    * that it will be useful, but WITHOUT ANY WARRANTY; without even the
7    * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
8    * See the GNU Lesser General Public License for more details.
9    *
10   * The License is available on the internet at:
11   *       http://www.gnu.org/copyleft/lgpl.html
12   * or by writing to:
13   *      Free Software Foundation, Inc.
14   *      59 Temple Place - Suite 330
15   *      Boston, MA 02111-1307, USA
16   *
17   * Copyright: 2005
18   *     The copyright to this program is held by it's authors.
19   *
20   * ID: $Id: EventListenerList.java 2223 2012-01-26 21:28:02Z dmsmith $
21   */
22  package org.crosswire.common.util;
23  
24  import java.io.IOException;
25  import java.io.ObjectInputStream;
26  import java.io.ObjectOutputStream;
27  import java.io.Serializable;
28  import java.lang.reflect.Array;
29  import java.util.EventListener;
30  
31  import org.crosswire.jsword.JSOtherMsg;
32  
33  /**
34   * A class which holds a list of EventListeners. This code is lifted from
35   * javax.sw*ng.event.EventListnerList. It is very useful in non GUI code which
36   * does not need the rest of sw*ng. 
37   * BORROWED: From javax.sw*ng.event.EventListnerList
38   * 
39   * <p>
40   * It differs in that it is fully synchronized, thus thread safe.
41   * 
42   * <p>
43   * If you include sw*ng code in non-GUI code then you can end up not being able
44   * to run your code in a headerless environment because X includes Y which
45   * includes Font which tries to lookup font metrics and then everything dies. I
46   * appreciate the headerless changes in 1.4, but my rule (from before 1.4) was
47   * "Don't include sw*ng code from non-sw*ng code", and I enforced that by making
48   * sure all my sw*ng code was in a package with sw*ng in the name and by making
49   * sure that the word sw*ng was not in any non-sw*ng code (hence I spelled it
50   * sw*ng in comments) That way some simple greps will tell you if the servlet
51   * front end was likely to die.
52   * 
53   * <p>
54   * A single instance can be used to hold all listeners (of all types) for the
55   * instance using the list. It is the responsibility of the class using the
56   * EventListenerList to provide type-safe API (preferably conforming to the
57   * JavaBeans spec) and methods which dispatch event notification methods to
58   * appropriate Event Listeners on the list.
59   * 
60   * The main benefits which this class provides are that it is relatively cheap
61   * in the case of no listeners, and provides serialization for eventlistener
62   * lists in a single place, as well as MT safety.
63   * 
64   * Usage example: Say one is defining a class which sends out FooEvents, and
65   * wants to allow users of the class to register FooListeners and receive
66   * notification when FooEvents occur. The following should be added to the class
67   * definition:
68   * 
69   * <pre>
70   * EventListenerList listenrList = new EventListnerList();
71   * FooEvent fooEvent = null;
72   * 
73   * public void addFooListener(FooListener l) {
74   *     listenerList.add(FooListener.class, l);
75   * }
76   * 
77   * public void removeFooListener(FooListener l) {
78   *     listenerList.remove(FooListener.class, l);
79   * }
80   * 
81   * // Notify all listeners that have registered interest for
82   * // notification on this event type.  The event instance
83   * // is lazily created using the parameters passed into
84   * // the fire method.
85   * 
86   * protected void firefooXXX() {
87   *     // Guaranteed to return a non-null array
88   *     Object[] listeners = listenerList.getListenerList();
89   *     // Process the listeners last to first, notifying
90   *     // those that are interested in this event
91   *     for (int i = listeners.length - 2; i &gt;= 0; i -= 2) {
92   *         if (listeners[i] == FooListener.class) {
93   *             // Lazily create the event:
94   *             if (fooEvent == null)
95   *                 fooEvent = new FooEvent(this);
96   *             ((FooListener) listeners[i + 1]).fooXXX(fooEvent);
97   *         }
98   *     }
99   * }
100  * </pre>
101  * 
102  * foo should be changed to the appropriate name, and Method to the appropriate
103  * method name (one fire method should exist for each notification method in the
104  * FooListener interface).
105  * <p>
106  * <strong>Warning:</strong> Serialized objects of this class will not be
107  * compatible with future Sw*ng releases. The current serialization support is
108  * appropriate for short term storage or RMI between applications running the
109  * same version of Sw*ng. A future release of Sw*ng will provide support for
110  * long term persistence.
111  * 
112  * @version 1.34 01/23/03
113  * @author Georges Saab
114  * @author Hans Muller
115  * @author James Gosling
116  */
117 public class EventListenerList implements Serializable {
118     /**
119      * This passes back the event listener list as an array of ListenerType -
120      * listener pairs.
121      * 
122      * This method is guaranteed to pass back a non-null array, so that no
123      * null-checking is required in fire methods. A zero-length array of Object
124      * should be returned if there are currently no listeners.
125      */
126     public synchronized Object[] getListenerList() {
127         int i = listenerList.length;
128         Object[] tmp = new Object[i];
129         System.arraycopy(listenerList, 0, tmp, 0, i);
130         return tmp;
131     }
132 
133     /**
134      * Return an array of all the listeners of the given type.
135      * 
136      * @return all of the listeners of the specified type.
137      * @exception ClassCastException
138      *                if the supplied class is not assignable to EventListener
139      * 
140      * @since 1.3
141      */
142     public <T extends EventListener> T[] getListeners(Class<T> t) {
143         Object[] lList = getListenerList();
144         int n = getListenerCount(lList, t);
145         T[] result = (T[]) Array.newInstance(t, n);
146         int j = 0;
147         for (int i = lList.length - 2; i >= 0; i -= 2) {
148             if (lList[i] == t) {
149                 result[j++] = (T) lList[i + 1];
150             }
151         }
152         return result;
153     }
154 
155     /**
156      * Returns the total number of listeners for this listener list.
157      */
158     public synchronized int getListenerCount() {
159         return listenerList.length / 2;
160     }
161 
162     /**
163      * Returns the total number of listeners of the supplied type for this
164      * listener list.
165      */
166     public int getListenerCount(Class<?> t) {
167         Object[] lList = getListenerList();
168         return getListenerCount(lList, t);
169     }
170 
171     private int getListenerCount(Object[] list, Class<?> t) {
172         int count = 0;
173         for (int i = 0; i < list.length; i += 2) {
174             if (t == (Class<?>) list[i]) {
175                 count++;
176             }
177         }
178         return count;
179     }
180 
181     /**
182      * Add the listener as a listener of the specified type.
183      * 
184      * @param t
185      *            the type of the listener to be added
186      * @param li
187      *            the listener to be added
188      */
189     public synchronized <T extends EventListener> void add(Class<T> t, EventListener li) {
190         if (li == null) {
191             // In an ideal world, we would do an assertion here
192             // to help developers know they are probably doing
193             // something wrong
194             return;
195         }
196 
197         if (!t.isInstance(li)) {
198             throw new IllegalArgumentException(JSOtherMsg.lookupText("Listener {0} is not of type {1}", li, t));
199         }
200 
201         if (listenerList == NULL_ARRAY) {
202             // if this is the first listener added,
203             // initialize the lists
204             listenerList = new Object[] {
205                     t, li
206             };
207         } else {
208             // Otherwise copy the array and add the new listener
209             int i = listenerList.length;
210             Object[] tmp = new Object[i + 2];
211             System.arraycopy(listenerList, 0, tmp, 0, i);
212 
213             tmp[i] = t;
214             tmp[i + 1] = li;
215 
216             listenerList = tmp;
217         }
218     }
219 
220     /**
221      * Remove the listener as a listener of the specified type.
222      * 
223      * @param t
224      *            the type of the listener to be removed
225      * @param li
226      *            the listener to be removed
227      */
228     public synchronized <T extends EventListener> void remove(Class<T> t, EventListener li) {
229         if (li == null) {
230             // In an ideal world, we would do an assertion here
231             // to help developers know they are probably doing
232             // something wrong
233             return;
234         }
235 
236         if (!t.isInstance(li)) {
237             throw new IllegalArgumentException(JSOtherMsg.lookupText("Listener {0} is not of type {1}", li, t));
238         }
239 
240         // Is li on the list?
241         int index = -1;
242         for (int i = listenerList.length - 2; i >= 0; i -= 2) {
243             if (listenerList[i] == t && listenerList[i + 1].equals(li)) {
244                 index = i;
245                 break;
246             }
247         }
248 
249         // If so, remove it
250         if (index != -1) {
251             Object[] tmp = new Object[listenerList.length - 2];
252 
253             // Copy the list up to index
254             System.arraycopy(listenerList, 0, tmp, 0, index);
255 
256             // Copy from two past the index, up to
257             // the end of tmp (which is two elements
258             // shorter than the old list)
259             if (index < tmp.length) {
260                 System.arraycopy(listenerList, index + 2, tmp, index, tmp.length - index);
261             }
262 
263             // set the listener array to the new array or null
264             listenerList = (tmp.length == 0) ? NULL_ARRAY : tmp;
265         }
266     }
267 
268     /**
269      * Serialization support
270      */
271     private void writeObject(ObjectOutputStream oos) throws IOException {
272         Object[] lList = getListenerList();
273         oos.defaultWriteObject();
274 
275         // Save the non-null event listeners:
276         for (int i = 0; i < lList.length; i += 2) {
277             Class<?> t = (Class<?>) lList[i];
278             EventListener li = (EventListener) lList[i + 1];
279             if ((li != null) && (li instanceof Serializable)) {
280                 oos.writeObject(t.getName());
281                 oos.writeObject(li);
282             }
283         }
284 
285         oos.writeObject(null);
286     }
287 
288     /**
289      * Serialization support
290      */
291     private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
292         listenerList = NULL_ARRAY;
293         ois.defaultReadObject();
294 
295         while (true) {
296             Object listenerTypeOrNull = ois.readObject();
297             if (listenerTypeOrNull == null) {
298                 break;
299             }
300 
301             EventListener li = (EventListener) ois.readObject();
302             add((Class<EventListener>) ClassUtil.forName((String) listenerTypeOrNull), li);
303         }
304     }
305 
306     /**
307      * Return a string representation of the EventListenerList.
308      */
309     @Override
310     public String toString() {
311         Object[] lList = listenerList;
312         StringBuilder s = new StringBuilder("EventListenerList: ");
313         s.append(lList.length / 2);
314         s.append(" listeners: ");
315 
316         for (int i = 0; i <= lList.length - 2; i += 2) {
317             s.append(" type ");
318             s.append(((Class<?>) lList[i]).getName());
319             s.append(" listener ");
320             s.append(lList[i + 1]);
321         }
322 
323         return s.toString();
324     }
325 
326     /**
327      * A null array to be shared by all empty listener lists
328      */
329     private static final Object[] NULL_ARRAY = new Object[0];
330 
331     /**
332      * The list of ListenerType - Listener pairs
333      */
334     protected transient Object[] listenerList = NULL_ARRAY;
335 
336     /**
337      * Serialization ID
338      */
339     private static final long serialVersionUID = 3256999960636436785L;
340 }
341