001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018 package org.apache.commons.lang3.event;
019
020 import java.io.ByteArrayOutputStream;
021 import java.io.IOException;
022 import java.io.ObjectInputStream;
023 import java.io.ObjectOutputStream;
024 import java.io.Serializable;
025 import java.lang.reflect.Array;
026 import java.lang.reflect.InvocationHandler;
027 import java.lang.reflect.Method;
028 import java.lang.reflect.Proxy;
029 import java.util.ArrayList;
030 import java.util.List;
031 import java.util.concurrent.CopyOnWriteArrayList;
032
033 import org.apache.commons.lang3.Validate;
034
035 /**
036 * An EventListenerSupport object can be used to manage a list of event
037 * listeners of a particular type. The class provides
038 * {@link #addListener(Object)} and {@link #removeListener(Object)} methods
039 * for registering listeners, as well as a {@link #fire()} method for firing
040 * events to the listeners.
041 *
042 * <p/>
043 * To use this class, suppose you want to support ActionEvents. You would do:
044 * <code><pre>
045 * public class MyActionEventSource
046 * {
047 * private EventListenerSupport<ActionListener> actionListeners =
048 * EventListenerSupport.create(ActionListener.class);
049 *
050 * public void someMethodThatFiresAction()
051 * {
052 * ActionEvent e = new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "somethingCool");
053 * actionListeners.fire().actionPerformed(e);
054 * }
055 * }
056 * </pre></code>
057 *
058 * Serializing an {@link EventListenerSupport} instance will result in any
059 * non-{@link Serializable} listeners being silently dropped.
060 *
061 * @param <L> the type of event listener that is supported by this proxy.
062 *
063 * @since 3.0
064 * @version $Id: EventListenerSupport.java 1082302 2011-03-16 21:08:27Z oheger $
065 */
066 public class EventListenerSupport<L> implements Serializable {
067
068 /** Serialization version */
069 private static final long serialVersionUID = 3593265990380473632L;
070
071 /**
072 * The list used to hold the registered listeners. This list is
073 * intentionally a thread-safe copy-on-write-array so that traversals over
074 * the list of listeners will be atomic.
075 */
076 private List<L> listeners = new CopyOnWriteArrayList<L>();
077
078 /**
079 * The proxy representing the collection of listeners. Calls to this proxy
080 * object will sent to all registered listeners.
081 */
082 private transient L proxy;
083
084 /**
085 * Empty typed array for #getListeners().
086 */
087 private transient L[] prototypeArray;
088
089 /**
090 * Creates an EventListenerSupport object which supports the specified
091 * listener type.
092 *
093 * @param <T> the type of the listener interface
094 * @param listenerInterface the type of listener interface that will receive
095 * events posted using this class.
096 *
097 * @return an EventListenerSupport object which supports the specified
098 * listener type.
099 *
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(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(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(Class<L> listenerInterface, 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(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(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(ObjectOutputStream objectOutputStream) throws IOException {
221 ArrayList<L> serializableListeners = new ArrayList<L>();
222
223 // don't just rely on instanceof Serializable:
224 ObjectOutputStream testObjectOutputStream = new ObjectOutputStream(new ByteArrayOutputStream());
225 for (L listener : listeners) {
226 try {
227 testObjectOutputStream.writeObject(listener);
228 serializableListeners.add(listener);
229 } catch (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(ObjectInputStream objectInputStream) throws IOException, ClassNotFoundException {
248 @SuppressWarnings("unchecked")
249 L[] listeners = (L[]) objectInputStream.readObject();
250
251 this.listeners = new CopyOnWriteArrayList<L>(listeners);
252
253 @SuppressWarnings("unchecked")
254 Class<L> listenerInterface = (Class<L>) listeners.getClass().getComponentType();
255
256 initializeTransientFields(listenerInterface, Thread.currentThread().getContextClassLoader());
257 }
258
259 /**
260 * Initialize transient fields.
261 * @param listenerInterface the class of the listener interface
262 * @param classLoader the class loader to be used
263 */
264 private void initializeTransientFields(Class<L> listenerInterface, ClassLoader classLoader) {
265 @SuppressWarnings("unchecked")
266 L[] array = (L[]) Array.newInstance(listenerInterface, 0);
267 this.prototypeArray = array;
268 createProxy(listenerInterface, classLoader);
269 }
270
271 /**
272 * Create the proxy object.
273 * @param listenerInterface the class of the listener interface
274 * @param classLoader the class loader to be used
275 */
276 private void createProxy(Class<L> listenerInterface, ClassLoader classLoader) {
277 proxy = listenerInterface.cast(Proxy.newProxyInstance(classLoader,
278 new Class[] { listenerInterface }, createInvocationHandler()));
279 }
280
281 /**
282 * Create the {@link InvocationHandler} responsible for broadcasting calls
283 * to the managed listeners. Subclasses can override to provide custom behavior.
284 * @return ProxyInvocationHandler
285 */
286 protected InvocationHandler createInvocationHandler() {
287 return new ProxyInvocationHandler();
288 }
289
290 /**
291 * An invocation handler used to dispatch the event(s) to all the listeners.
292 */
293 protected class ProxyInvocationHandler implements InvocationHandler {
294 /** Serialization version */
295 private static final long serialVersionUID = 1L;
296
297 /**
298 * Propagates the method call to all registered listeners in place of
299 * the proxy listener object.
300 *
301 * @param proxy the proxy object representing a listener on which the
302 * invocation was called.
303 * @param method the listener method that will be called on all of the
304 * listeners.
305 * @param args event arguments to propagate to the listeners.
306 * @return the result of the method call
307 * @throws Throwable if an error occurs
308 */
309 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
310 for (L listener : listeners) {
311 method.invoke(listener, args);
312 }
313 return null;
314 }
315 }
316 }