| EventListenerList.java |
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 >= 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