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