001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      https://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018package org.apache.commons.lang3.event;
019
020import java.io.ByteArrayOutputStream;
021import java.io.IOException;
022import java.io.ObjectInputStream;
023import java.io.ObjectOutputStream;
024import java.io.Serializable;
025import java.lang.reflect.InvocationHandler;
026import java.lang.reflect.InvocationTargetException;
027import java.lang.reflect.Method;
028import java.lang.reflect.Proxy;
029import java.util.ArrayList;
030import java.util.List;
031import java.util.Objects;
032import java.util.concurrent.CopyOnWriteArrayList;
033
034import org.apache.commons.lang3.ArrayUtils;
035import org.apache.commons.lang3.SerializationUtils;
036import org.apache.commons.lang3.Validate;
037import org.apache.commons.lang3.exception.ExceptionUtils;
038import org.apache.commons.lang3.function.FailableConsumer;
039
040/**
041 * Manages a list of event listeners of a given generic type. This class provides {@link #addListener(Object)} and {@link #removeListener(Object)} methods for
042 * managing listeners, as well as a {@link #fire()} method for firing events to the listeners.
043 *
044 * <p>
045 * For example, to support ActionEvents:
046 * </p>
047 *
048 * <pre>{@code
049 * public class MyActionEventSource {
050 *
051 *     private EventListenerSupport<ActionListener> actionListeners = EventListenerSupport.create(ActionListener.class);
052 *
053 *     public void someMethodThatFiresAction() {
054 *         ActionEvent e = new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "something");
055 *         actionListeners.fire().actionPerformed(e);
056 *     }
057 * }
058 * }</pre>
059 * <p>
060 * Events are fired
061 * <p>
062 * Serializing an {@link EventListenerSupport} instance will result in any non-{@link Serializable} listeners being silently dropped.
063 * </p>
064 *
065 * @param <L> The type of event listener that is supported by this proxy.
066 * @since 3.0
067 */
068public class EventListenerSupport<L> implements Serializable {
069
070    /**
071     * Invokes listeners through {@link #invoke(Object, Method, Object[])} in the order added to the underlying {@link List}.
072     */
073    protected class ProxyInvocationHandler implements InvocationHandler {
074
075        private final FailableConsumer<Throwable, IllegalAccessException> handler;
076
077        /**
078         * Constructs a new instance.
079         */
080        public ProxyInvocationHandler() {
081            this(ExceptionUtils::rethrow);
082        }
083
084        /**
085         * Constructs a new instance.
086         *
087         * @param handler Handles Throwables.
088         * @since 3.15.0
089         */
090        public ProxyInvocationHandler(final FailableConsumer<Throwable, IllegalAccessException> handler) {
091            this.handler = Objects.requireNonNull(handler, "handler");
092        }
093
094        /**
095         * Handles an exception thrown by a listener. By default rethrows the given Throwable.
096         *
097         * @param t The Throwable
098         * @throws IllegalAccessException Thrown by the listener.
099         * @throws IllegalArgumentException Thrown by the listener.
100         * @throws InvocationTargetException Thrown by the listener.
101         * @since 3.15.0
102         */
103        protected void handle(final Throwable t) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
104            handler.accept(t);
105        }
106
107        /**
108         * Propagates the method call to all registered listeners in place of the proxy listener object.
109         * <p>
110         * Calls listeners in the order added to the underlying {@link List}.
111         * </p>
112         *
113         * @param unusedProxy The proxy object representing a listener on which the invocation was called; not used
114         * @param method The listener method that will be called on all of the listeners.
115         * @param args event arguments to propagate to the listeners.
116         * @return The result of the method call
117         * @throws InvocationTargetException Thrown if an error occurs.
118         * @throws IllegalArgumentException Thrown if an error occurs.
119         * @throws IllegalAccessException Thrown if an error occurs.
120         */
121        @Override
122        public Object invoke(final Object unusedProxy, final Method method, final Object[] args)
123                throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
124            for (final L listener : listeners) {
125                try {
126                    method.invoke(listener, args);
127                } catch (final Throwable t) {
128                    handle(t);
129                }
130            }
131            return null;
132        }
133    }
134
135    /** Serialization version */
136    private static final long serialVersionUID = 3593265990380473632L;
137
138    /**
139     * Creates an EventListenerSupport object which supports the specified
140     * listener type.
141     *
142     * @param <T> The type of the listener interface
143     * @param listenerInterface The type of listener interface that will receive
144     *        events posted using this class.
145     *
146     * @return An EventListenerSupport object which supports the specified
147     *         listener type.
148     *
149     * @throws NullPointerException Thrown if {@code listenerInterface} is
150     *         {@code null}.
151     * @throws IllegalArgumentException Thrown if {@code listenerInterface} is
152     *         not an interface.
153     */
154    public static <T> EventListenerSupport<T> create(final Class<T> listenerInterface) {
155        return new EventListenerSupport<>(listenerInterface);
156    }
157
158    /**
159     * 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.
160     */
161    private List<L> listeners = new CopyOnWriteArrayList<>();
162
163    /**
164     * The proxy representing the collection of listeners. Calls to this proxy object will be sent to all registered listeners.
165     */
166    private transient L proxy;
167
168    /**
169     * Empty typed array for #getListeners().
170     */
171    private transient L[] prototypeArray;
172
173    /**
174     * Constructs a new EventListenerSupport instance.
175     * <p>
176     * This constructor is needed for serialization.
177     * </p>
178     */
179    private EventListenerSupport() {
180    }
181
182    /**
183     * Constructs an EventListenerSupport object which supports the provided
184     * listener interface.
185     *
186     * @param listenerInterface The type of listener interface that will receive
187     *        events posted using this class.
188     *
189     * @throws NullPointerException Thrown if {@code listenerInterface} is
190     *         {@code null}.
191     * @throws IllegalArgumentException Thrown if {@code listenerInterface} is
192     *         not an interface.
193     */
194    public EventListenerSupport(final Class<L> listenerInterface) {
195        this(listenerInterface, Thread.currentThread().getContextClassLoader());
196    }
197
198    /**
199     * Constructs an EventListenerSupport object which supports the provided
200     * listener interface using the specified class loader to create the JDK
201     * dynamic proxy.
202     *
203     * @param listenerInterface The listener interface.
204     * @param classLoader       The class loader.
205     * @throws NullPointerException Thrown if {@code listenerInterface} or
206     *         {@code classLoader} is {@code null}.
207     * @throws IllegalArgumentException Thrown if {@code listenerInterface} is
208     *         not an interface.
209     */
210    public EventListenerSupport(final Class<L> listenerInterface, final ClassLoader classLoader) {
211        this();
212        Objects.requireNonNull(listenerInterface, "listenerInterface");
213        Objects.requireNonNull(classLoader, "classLoader");
214        Validate.isTrue(listenerInterface.isInterface(), "Class %s is not an interface", listenerInterface.getName());
215        initializeTransientFields(listenerInterface, classLoader);
216    }
217
218    /**
219     * Adds an event listener.
220     * <p>
221     * Listeners are called in the order added.
222     * </p>
223     *
224     * @param listener The event listener (may not be {@code null}).
225     * @throws NullPointerException Thrown if {@code listener} is {@code null}.
226     */
227    public void addListener(final L listener) {
228        addListener(listener, true);
229    }
230
231    /**
232     * Adds an event listener. Will not add a pre-existing listener object to the list if {@code allowDuplicate} is false.
233     * <p>
234     * Listeners are called in the order added.
235     * </p>
236     *
237     * @param listener       The event listener (may not be {@code null}).
238     * @param allowDuplicate The flag for determining if duplicate listener objects are allowed to be registered.
239     *
240     * @throws NullPointerException Thrown if {@code listener} is {@code null}.
241     * @since 3.5
242     */
243    public void addListener(final L listener, final boolean allowDuplicate) {
244        Objects.requireNonNull(listener, "listener");
245        if (allowDuplicate || !listeners.contains(listener)) {
246            listeners.add(listener);
247        }
248    }
249
250    /**
251     * Creates the {@link InvocationHandler} responsible for calling
252     * to the managed listeners. Subclasses can override to provide custom behavior.
253     *
254     * @return ProxyInvocationHandler
255     */
256    protected InvocationHandler createInvocationHandler() {
257        return new ProxyInvocationHandler();
258    }
259
260    /**
261     * Creates the proxy object.
262     *
263     * @param listenerInterface The class of the listener interface
264     * @param classLoader The class loader to be used
265     */
266    private void createProxy(final Class<L> listenerInterface, final ClassLoader classLoader) {
267        proxy = listenerInterface.cast(Proxy.newProxyInstance(classLoader, new Class[] { listenerInterface }, createInvocationHandler()));
268    }
269
270    /**
271     * Returns a proxy object which can be used to call listener methods on all
272     * of the registered event listeners. All calls made to this proxy will be
273     * forwarded to all registered listeners.
274     *
275     * @return A proxy object which can be used to call listener methods on all
276     * of the registered event listeners
277     */
278    public L fire() {
279        return proxy;
280    }
281
282    /**
283     * Gets the number of registered listeners.
284     *
285     * @return The number of registered listeners.
286     */
287    int getListenerCount() {
288        return listeners.size();
289    }
290
291    /**
292     * Gets an array containing the currently registered listeners.
293     * Modification to this array's elements will have no effect on the
294     * {@link EventListenerSupport} instance.
295     *
296     * @return L[]
297     */
298    public L[] getListeners() {
299        return listeners.toArray(prototypeArray);
300    }
301
302    /**
303     * Initializes transient fields.
304     *
305     * @param listenerInterface The class of the listener interface
306     * @param classLoader The class loader to be used
307     */
308    private void initializeTransientFields(final Class<L> listenerInterface, final ClassLoader classLoader) {
309        // Will throw CCE here if not correct
310        this.prototypeArray = ArrayUtils.newInstance(listenerInterface, 0);
311        createProxy(listenerInterface, classLoader);
312    }
313
314    /**
315     * Deserializes the next object into this instance.
316     *
317     * @param objectInputStream The input stream.
318     * @throws IOException Thrown if an IO error occurs.
319     * @throws ClassNotFoundException Thrown if the class cannot be resolved.
320     */
321    private void readObject(final ObjectInputStream objectInputStream) throws IOException, ClassNotFoundException {
322        @SuppressWarnings("unchecked") // Will throw CCE here if not correct
323        final L[] srcListeners = (L[]) objectInputStream.readObject();
324        SerializationUtils.requireNonNull(srcListeners, "srcListeners"); // fail-fast with a better message
325        this.listeners = new CopyOnWriteArrayList<>(srcListeners);
326        final Class<L> listenerInterface = ArrayUtils.getComponentType(srcListeners);
327        initializeTransientFields(listenerInterface, Thread.currentThread().getContextClassLoader());
328    }
329
330    /**
331     * Removes an event listener.
332     *
333     * @param listener The event listener (may not be {@code null}).
334     * @throws NullPointerException Thrown if {@code listener} is
335     *         {@code null}.
336     */
337    public void removeListener(final L listener) {
338        listeners.remove(Objects.requireNonNull(listener, "listener"));
339    }
340
341    /**
342     * Serializes this instance onto the given ObjectOutputStream.
343     *
344     * @param objectOutputStream The output stream
345     * @throws IOException Thrown if an IO error occurs
346     */
347    private void writeObject(final ObjectOutputStream objectOutputStream) throws IOException {
348        final ArrayList<L> serializableListeners = new ArrayList<>();
349        // Don't just rely on instanceof Serializable:
350        ObjectOutputStream testObjectOutputStream = new ObjectOutputStream(new ByteArrayOutputStream());
351        for (final L listener : listeners) {
352            try {
353                testObjectOutputStream.writeObject(listener);
354                serializableListeners.add(listener);
355            } catch (final IOException exception) {
356                //recreate test stream in case of indeterminate state
357                testObjectOutputStream = new ObjectOutputStream(new ByteArrayOutputStream());
358            }
359        }
360        // We can reconstitute everything we need from an array of our listeners,
361        // which has the additional advantage of typically requiring less storage than a list:
362        objectOutputStream.writeObject(serializableListeners.toArray(prototypeArray));
363    }
364}