View Javadoc

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 }