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   * <p>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   * </p>
42   *
43   * <p>
44   * To use this class, suppose you want to support ActionEvents.  You would do:
45   * </p>
46   * <pre><code>
47   * public class MyActionEventSource
48   * {
49   *   private EventListenerSupport&lt;ActionListener&gt; actionListeners =
50   *       EventListenerSupport.create(ActionListener.class);
51   *
52   *   public void someMethodThatFiresAction()
53   *   {
54   *     ActionEvent e = new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "somethingCool");
55   *     actionListeners.fire().actionPerformed(e);
56   *   }
57   * }
58   * </code></pre>
59   *
60   * <p>
61   * Serializing an {@link EventListenerSupport} instance will result in any
62   * non-{@link Serializable} listeners being silently dropped.
63   * </p>
64   *
65   * @param <L> the type of event listener that is supported by this proxy.
66   *
67   * @since 3.0
68   */
69  public class EventListenerSupport<L> implements Serializable {
70  
71      /** Serialization version */
72      private static final long serialVersionUID = 3593265990380473632L;
73  
74      /**
75       * The list used to hold the registered listeners. This list is
76       * intentionally a thread-safe copy-on-write-array so that traversals over
77       * the list of listeners will be atomic.
78       */
79      private List<L> listeners = new CopyOnWriteArrayList<L>();
80  
81      /**
82       * The proxy representing the collection of listeners. Calls to this proxy
83       * object will sent to all registered listeners.
84       */
85      private transient L proxy;
86  
87      /**
88       * Empty typed array for #getListeners().
89       */
90      private transient L[] prototypeArray;
91  
92      /**
93       * Creates an EventListenerSupport object which supports the specified
94       * listener type.
95       *
96       * @param <T> the type of the listener interface
97       * @param listenerInterface the type of listener interface that will receive
98       *        events posted using this class.
99       *
100      * @return an EventListenerSupport object which supports the specified
101      *         listener type.
102      *
103      * @throws NullPointerException if <code>listenerInterface</code> is
104      *         <code>null</code>.
105      * @throws IllegalArgumentException if <code>listenerInterface</code> is
106      *         not an interface.
107      */
108     public static <T> EventListenerSupport<T> create(final Class<T> listenerInterface) {
109         return new EventListenerSupport<T>(listenerInterface);
110     }
111 
112     /**
113      * Creates an EventListenerSupport object which supports the provided
114      * listener interface.
115      *
116      * @param listenerInterface the type of listener interface that will receive
117      *        events posted using this class.
118      *
119      * @throws NullPointerException if <code>listenerInterface</code> is
120      *         <code>null</code>.
121      * @throws IllegalArgumentException if <code>listenerInterface</code> is
122      *         not an interface.
123      */
124     public EventListenerSupport(final Class<L> listenerInterface) {
125         this(listenerInterface, Thread.currentThread().getContextClassLoader());
126     }
127 
128     /**
129      * Creates an EventListenerSupport object which supports the provided
130      * listener interface using the specified class loader to create the JDK
131      * dynamic proxy.
132      *
133      * @param listenerInterface the listener interface.
134      * @param classLoader       the class loader.
135      *
136      * @throws NullPointerException if <code>listenerInterface</code> or
137      *         <code>classLoader</code> is <code>null</code>.
138      * @throws IllegalArgumentException if <code>listenerInterface</code> is
139      *         not an interface.
140      */
141     public EventListenerSupport(final Class<L> listenerInterface, final ClassLoader classLoader) {
142         this();
143         Validate.notNull(listenerInterface, "Listener interface cannot be null.");
144         Validate.notNull(classLoader, "ClassLoader cannot be null.");
145         Validate.isTrue(listenerInterface.isInterface(), "Class {0} is not an interface",
146                 listenerInterface.getName());
147         initializeTransientFields(listenerInterface, classLoader);
148     }
149 
150     /**
151      * Create a new EventListenerSupport instance.
152      * Serialization-friendly constructor.
153      */
154     private EventListenerSupport() {
155     }
156 
157     /**
158      * Returns a proxy object which can be used to call listener methods on all
159      * of the registered event listeners. All calls made to this proxy will be
160      * forwarded to all registered listeners.
161      *
162      * @return a proxy object which can be used to call listener methods on all
163      * of the registered event listeners
164      */
165     public L fire() {
166         return proxy;
167     }
168 
169 //**********************************************************************************************************************
170 // Other Methods
171 //**********************************************************************************************************************
172 
173     /**
174      * Registers an event listener.
175      *
176      * @param listener the event listener (may not be <code>null</code>).
177      *
178      * @throws NullPointerException if <code>listener</code> is
179      *         <code>null</code>.
180      */
181     public void addListener(final L listener) {
182         addListener(listener, true);
183     }
184 
185     /**
186      * Registers an event listener.  Will not add a pre-existing listener
187      * object to the list if <code>allowDuplicate</code> is false.
188      *
189      * @param listener the event listener (may not be <code>null</code>).
190      * @param allowDuplicate the flag for determining if duplicate listener
191      * objects are allowed to be registered.
192      *
193      * @throws NullPointerException if <code>listener</code> is
194      * <code>null</code>.
195      */
196     public void addListener(final L listener, boolean allowDuplicate) {
197         Validate.notNull(listener, "Listener object cannot be null.");
198         if (allowDuplicate) {
199             listeners.add(listener);
200         } else if (!listeners.contains(listener)) {
201             listeners.add(listener);
202         }
203     }
204 
205     /**
206      * Returns the number of registered listeners.
207      *
208      * @return the number of registered listeners.
209      */
210     int getListenerCount() {
211         return listeners.size();
212     }
213 
214     /**
215      * Unregisters an event listener.
216      *
217      * @param listener the event listener (may not be <code>null</code>).
218      *
219      * @throws NullPointerException if <code>listener</code> is
220      *         <code>null</code>.
221      */
222     public void removeListener(final L listener) {
223         Validate.notNull(listener, "Listener object cannot be null.");
224         listeners.remove(listener);
225     }
226 
227     /**
228      * Get an array containing the currently registered listeners.
229      * Modification to this array's elements will have no effect on the
230      * {@link EventListenerSupport} instance.
231      * @return L[]
232      */
233     public L[] getListeners() {
234         return listeners.toArray(prototypeArray);
235     }
236 
237     /**
238      * Serialize.
239      * @param objectOutputStream the output stream
240      * @throws IOException if an IO error occurs
241      */
242     private void writeObject(final ObjectOutputStream objectOutputStream) throws IOException {
243         final ArrayList<L> serializableListeners = new ArrayList<L>();
244 
245         // don't just rely on instanceof Serializable:
246         ObjectOutputStream testObjectOutputStream = new ObjectOutputStream(new ByteArrayOutputStream());
247         for (final L listener : listeners) {
248             try {
249                 testObjectOutputStream.writeObject(listener);
250                 serializableListeners.add(listener);
251             } catch (final IOException exception) {
252                 //recreate test stream in case of indeterminate state
253                 testObjectOutputStream = new ObjectOutputStream(new ByteArrayOutputStream());
254             }
255         }
256         /*
257          * we can reconstitute everything we need from an array of our listeners,
258          * which has the additional advantage of typically requiring less storage than a list:
259          */
260         objectOutputStream.writeObject(serializableListeners.toArray(prototypeArray));
261     }
262 
263     /**
264      * Deserialize.
265      * @param objectInputStream the input stream
266      * @throws IOException if an IO error occurs
267      * @throws ClassNotFoundException if the class cannot be resolved
268      */
269     private void readObject(final ObjectInputStream objectInputStream) throws IOException, ClassNotFoundException {
270         @SuppressWarnings("unchecked") // Will throw CCE here if not correct
271         final
272         L[] srcListeners = (L[]) objectInputStream.readObject();
273 
274         this.listeners = new CopyOnWriteArrayList<L>(srcListeners);
275 
276         @SuppressWarnings("unchecked") // Will throw CCE here if not correct
277         final
278         Class<L> listenerInterface = (Class<L>) srcListeners.getClass().getComponentType();
279 
280         initializeTransientFields(listenerInterface, Thread.currentThread().getContextClassLoader());
281     }
282 
283     /**
284      * Initialize transient fields.
285      * @param listenerInterface the class of the listener interface
286      * @param classLoader the class loader to be used
287      */
288     private void initializeTransientFields(final Class<L> listenerInterface, final ClassLoader classLoader) {
289         @SuppressWarnings("unchecked") // Will throw CCE here if not correct
290         final
291         L[] array = (L[]) Array.newInstance(listenerInterface, 0);
292         this.prototypeArray = array;
293         createProxy(listenerInterface, classLoader);
294     }
295 
296     /**
297      * Create the proxy object.
298      * @param listenerInterface the class of the listener interface
299      * @param classLoader the class loader to be used
300      */
301     private void createProxy(final Class<L> listenerInterface, final ClassLoader classLoader) {
302         proxy = listenerInterface.cast(Proxy.newProxyInstance(classLoader,
303                 new Class[] { listenerInterface }, createInvocationHandler()));
304     }
305 
306     /**
307      * Create the {@link InvocationHandler} responsible for broadcasting calls
308      * to the managed listeners.  Subclasses can override to provide custom behavior.
309      * @return ProxyInvocationHandler
310      */
311     protected InvocationHandler createInvocationHandler() {
312         return new ProxyInvocationHandler();
313     }
314 
315     /**
316      * An invocation handler used to dispatch the event(s) to all the listeners.
317      */
318     protected class ProxyInvocationHandler implements InvocationHandler {
319 
320         /**
321          * Propagates the method call to all registered listeners in place of
322          * the proxy listener object.
323          *
324          * @param unusedProxy the proxy object representing a listener on which the
325          *        invocation was called; not used
326          * @param method the listener method that will be called on all of the
327          *        listeners.
328          * @param args event arguments to propagate to the listeners.
329          * @return the result of the method call
330          * @throws Throwable if an error occurs
331          */
332         @Override
333         public Object invoke(final Object unusedProxy, final Method method, final Object[] args) throws Throwable {
334             for (final L listener : listeners) {
335                 method.invoke(listener, args);
336             }
337             return null;
338         }
339     }
340 }