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