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 }