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