1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18 package org.apache.commons.lang3.event;
19
20 import java.io.ByteArrayOutputStream;
21 import java.io.IOException;
22 import java.io.ObjectInputStream;
23 import java.io.ObjectOutputStream;
24 import java.io.Serializable;
25 import java.lang.reflect.Array;
26 import java.lang.reflect.InvocationHandler;
27 import java.lang.reflect.Method;
28 import java.lang.reflect.Proxy;
29 import java.util.ArrayList;
30 import java.util.List;
31 import java.util.concurrent.CopyOnWriteArrayList;
32
33 import org.apache.commons.lang3.Validate;
34
35 /**
36 * An EventListenerSupport object can be used to manage a list of event
37 * listeners of a particular type. The class provides
38 * {@link #addListener(Object)} and {@link #removeListener(Object)} methods
39 * for registering listeners, as well as a {@link #fire()} method for firing
40 * events to the listeners.
41 *
42 * <p/>
43 * To use this class, suppose you want to support ActionEvents. You would do:
44 * <code><pre>
45 * public class MyActionEventSource
46 * {
47 * private EventListenerSupport<ActionListener> actionListeners =
48 * EventListenerSupport.create(ActionListener.class);
49 *
50 * public void someMethodThatFiresAction()
51 * {
52 * ActionEvent e = new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "somethingCool");
53 * actionListeners.fire().actionPerformed(e);
54 * }
55 * }
56 * </pre></code>
57 *
58 * Serializing an {@link EventListenerSupport} instance will result in any
59 * non-{@link Serializable} listeners being silently dropped.
60 *
61 * @param <L> the type of event listener that is supported by this proxy.
62 *
63 * @since 3.0
64 * @version $Id: EventListenerSupport.java 1436770 2013-01-22 07:09:45Z ggregory $
65 */
66 public class EventListenerSupport<L> implements Serializable {
67
68 /** Serialization version */
69 private static final long serialVersionUID = 3593265990380473632L;
70
71 /**
72 * The list used to hold the registered listeners. This list is
73 * intentionally a thread-safe copy-on-write-array so that traversals over
74 * the list of listeners will be atomic.
75 */
76 private List<L> listeners = new CopyOnWriteArrayList<L>();
77
78 /**
79 * The proxy representing the collection of listeners. Calls to this proxy
80 * object will sent to all registered listeners.
81 */
82 private transient L proxy;
83
84 /**
85 * Empty typed array for #getListeners().
86 */
87 private transient L[] prototypeArray;
88
89 /**
90 * Creates an EventListenerSupport object which supports the specified
91 * listener type.
92 *
93 * @param <T> the type of the listener interface
94 * @param listenerInterface the type of listener interface that will receive
95 * events posted using this class.
96 *
97 * @return an EventListenerSupport object which supports the specified
98 * listener type.
99 *
100 * @throws NullPointerException if <code>listenerInterface</code> is
101 * <code>null</code>.
102 * @throws IllegalArgumentException if <code>listenerInterface</code> is
103 * not an interface.
104 */
105 public static <T> EventListenerSupport<T> create(final Class<T> listenerInterface) {
106 return new EventListenerSupport<T>(listenerInterface);
107 }
108
109 /**
110 * Creates an EventListenerSupport object which supports the provided
111 * listener interface.
112 *
113 * @param listenerInterface the type of listener interface that will receive
114 * events posted using this class.
115 *
116 * @throws NullPointerException if <code>listenerInterface</code> is
117 * <code>null</code>.
118 * @throws IllegalArgumentException if <code>listenerInterface</code> is
119 * not an interface.
120 */
121 public EventListenerSupport(final Class<L> listenerInterface) {
122 this(listenerInterface, Thread.currentThread().getContextClassLoader());
123 }
124
125 /**
126 * Creates an EventListenerSupport object which supports the provided
127 * listener interface using the specified class loader to create the JDK
128 * dynamic proxy.
129 *
130 * @param listenerInterface the listener interface.
131 * @param classLoader the class loader.
132 *
133 * @throws NullPointerException if <code>listenerInterface</code> or
134 * <code>classLoader</code> is <code>null</code>.
135 * @throws IllegalArgumentException if <code>listenerInterface</code> is
136 * not an interface.
137 */
138 public EventListenerSupport(final Class<L> listenerInterface, final ClassLoader classLoader) {
139 this();
140 Validate.notNull(listenerInterface, "Listener interface cannot be null.");
141 Validate.notNull(classLoader, "ClassLoader cannot be null.");
142 Validate.isTrue(listenerInterface.isInterface(), "Class {0} is not an interface",
143 listenerInterface.getName());
144 initializeTransientFields(listenerInterface, classLoader);
145 }
146
147 /**
148 * Create a new EventListenerSupport instance.
149 * Serialization-friendly constructor.
150 */
151 private EventListenerSupport() {
152 }
153
154 /**
155 * Returns a proxy object which can be used to call listener methods on all
156 * of the registered event listeners. All calls made to this proxy will be
157 * forwarded to all registered listeners.
158 *
159 * @return a proxy object which can be used to call listener methods on all
160 * of the registered event listeners
161 */
162 public L fire() {
163 return proxy;
164 }
165
166 //**********************************************************************************************************************
167 // Other Methods
168 //**********************************************************************************************************************
169
170 /**
171 * Registers an event listener.
172 *
173 * @param listener the event listener (may not be <code>null</code>).
174 *
175 * @throws NullPointerException if <code>listener</code> is
176 * <code>null</code>.
177 */
178 public void addListener(final L listener) {
179 Validate.notNull(listener, "Listener object cannot be null.");
180 listeners.add(listener);
181 }
182
183 /**
184 * Returns the number of registered listeners.
185 *
186 * @return the number of registered listeners.
187 */
188 int getListenerCount() {
189 return listeners.size();
190 }
191
192 /**
193 * Unregisters an event listener.
194 *
195 * @param listener the event listener (may not be <code>null</code>).
196 *
197 * @throws NullPointerException if <code>listener</code> is
198 * <code>null</code>.
199 */
200 public void removeListener(final L listener) {
201 Validate.notNull(listener, "Listener object cannot be null.");
202 listeners.remove(listener);
203 }
204
205 /**
206 * Get an array containing the currently registered listeners.
207 * Modification to this array's elements will have no effect on the
208 * {@link EventListenerSupport} instance.
209 * @return L[]
210 */
211 public L[] getListeners() {
212 return listeners.toArray(prototypeArray);
213 }
214
215 /**
216 * Serialize.
217 * @param objectOutputStream the output stream
218 * @throws IOException if an IO error occurs
219 */
220 private void writeObject(final ObjectOutputStream objectOutputStream) throws IOException {
221 final ArrayList<L> serializableListeners = new ArrayList<L>();
222
223 // don't just rely on instanceof Serializable:
224 ObjectOutputStream testObjectOutputStream = new ObjectOutputStream(new ByteArrayOutputStream());
225 for (final L listener : listeners) {
226 try {
227 testObjectOutputStream.writeObject(listener);
228 serializableListeners.add(listener);
229 } catch (final IOException exception) {
230 //recreate test stream in case of indeterminate state
231 testObjectOutputStream = new ObjectOutputStream(new ByteArrayOutputStream());
232 }
233 }
234 /*
235 * we can reconstitute everything we need from an array of our listeners,
236 * which has the additional advantage of typically requiring less storage than a list:
237 */
238 objectOutputStream.writeObject(serializableListeners.toArray(prototypeArray));
239 }
240
241 /**
242 * Deserialize.
243 * @param objectInputStream the input stream
244 * @throws IOException if an IO error occurs
245 * @throws ClassNotFoundException if the class cannot be resolved
246 */
247 private void readObject(final ObjectInputStream objectInputStream) throws IOException, ClassNotFoundException {
248 @SuppressWarnings("unchecked") // Will throw CCE here if not correct
249 final
250 L[] listeners = (L[]) objectInputStream.readObject();
251
252 this.listeners = new CopyOnWriteArrayList<L>(listeners);
253
254 @SuppressWarnings("unchecked") // Will throw CCE here if not correct
255 final
256 Class<L> listenerInterface = (Class<L>) listeners.getClass().getComponentType();
257
258 initializeTransientFields(listenerInterface, Thread.currentThread().getContextClassLoader());
259 }
260
261 /**
262 * Initialize transient fields.
263 * @param listenerInterface the class of the listener interface
264 * @param classLoader the class loader to be used
265 */
266 private void initializeTransientFields(final Class<L> listenerInterface, final ClassLoader classLoader) {
267 @SuppressWarnings("unchecked") // Will throw CCE here if not correct
268 final
269 L[] array = (L[]) Array.newInstance(listenerInterface, 0);
270 this.prototypeArray = array;
271 createProxy(listenerInterface, classLoader);
272 }
273
274 /**
275 * Create the proxy object.
276 * @param listenerInterface the class of the listener interface
277 * @param classLoader the class loader to be used
278 */
279 private void createProxy(final Class<L> listenerInterface, final ClassLoader classLoader) {
280 proxy = listenerInterface.cast(Proxy.newProxyInstance(classLoader,
281 new Class[] { listenerInterface }, createInvocationHandler()));
282 }
283
284 /**
285 * Create the {@link InvocationHandler} responsible for broadcasting calls
286 * to the managed listeners. Subclasses can override to provide custom behavior.
287 * @return ProxyInvocationHandler
288 */
289 protected InvocationHandler createInvocationHandler() {
290 return new ProxyInvocationHandler();
291 }
292
293 /**
294 * An invocation handler used to dispatch the event(s) to all the listeners.
295 */
296 protected class ProxyInvocationHandler implements InvocationHandler {
297
298 /**
299 * Propagates the method call to all registered listeners in place of
300 * the proxy listener object.
301 *
302 * @param proxy the proxy object representing a listener on which the
303 * invocation was called.
304 * @param method the listener method that will be called on all of the
305 * listeners.
306 * @param args event arguments to propagate to the listeners.
307 * @return the result of the method call
308 * @throws Throwable if an error occurs
309 */
310 @Override
311 public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
312 for (final L listener : listeners) {
313 method.invoke(listener, args);
314 }
315 return null;
316 }
317 }
318 }