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