    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
    018    package org.apache.commons.lang3.event;
    019
    020    import java.io.ByteArrayOutputStream;
    021    import java.io.IOException;
    022    import java.io.ObjectInputStream;
    023    import java.io.ObjectOutputStream;
    024    import java.io.Serializable;
    025    import java.lang.reflect.Array;
    026    import java.lang.reflect.InvocationHandler;
    027    import java.lang.reflect.Method;
    028    import java.lang.reflect.Proxy;
    029    import java.util.ArrayList;
    030    import java.util.List;
    031    import java.util.concurrent.CopyOnWriteArrayList;
    032
    033    import org.apache.commons.lang3.Validate;
    034
    035    /**
    036     * An EventListenerSupport object can be used to manage a list of event
    037     * listeners of a particular type. The class provides
    038     * {@link #addListener(Object)} and {@link #removeListener(Object)} methods
    039     * for registering listeners, as well as a {@link #fire()} method for firing
    040     * events to the listeners.
    041     *
    042     * <p/>
    043     * To use this class, suppose you want to support ActionEvents.  You would do:
    044     * <code><pre>
    045     * public class MyActionEventSource
    046     * {
    047     *   private EventListenerSupport<ActionListener> actionListeners =
    048     *       EventListenerSupport.create(ActionListener.class);
    049     *
    050     *   public void someMethodThatFiresAction()
    051     *   {
    052     *     ActionEvent e = new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "somethingCool");
    053     *     actionListeners.fire().actionPerformed(e);
    054     *   }
    055     * }
    056     * </pre></code>
    057     *
    058     * Serializing an {@link EventListenerSupport} instance will result in any
    059     * non-{@link Serializable} listeners being silently dropped.
    060     *
    061     * @param <L> the type of event listener that is supported by this proxy.
    062     *
    063     * @since 3.0
    064     * @version $Id: EventListenerSupport.java 1082302 2011-03-16 21:08:27Z oheger $
    065     */
    066    public class EventListenerSupport<L> implements Serializable {
    067
    068        /** Serialization version */
    069        private static final long serialVersionUID = 3593265990380473632L;
    070
    071        /**
    072         * The list used to hold the registered listeners. This list is
    073         * intentionally a thread-safe copy-on-write-array so that traversals over
    074         * the list of listeners will be atomic.
    075         */
    076        private List<L> listeners = new CopyOnWriteArrayList<L>();
    077
    078        /**
    079         * The proxy representing the collection of listeners. Calls to this proxy
    080         * object will sent to all registered listeners.
    081         */
    082        private transient L proxy;
    083
    084        /**
    085         * Empty typed array for #getListeners().
    086         */
    087        private transient L[] prototypeArray;
    088
    089        /**
    090         * Creates an EventListenerSupport object which supports the specified
    091         * listener type.
    092         *
    093         * @param <T> the type of the listener interface
    094         * @param listenerInterface the type of listener interface that will receive
    095         *        events posted using this class.
    096         *
    097         * @return an EventListenerSupport object which supports the specified
    098         *         listener type.
    099         *
    100         * @throws NullPointerException if <code>listenerInterface</code> is
    101         *         <code>null</code>.
    102         * @throws IllegalArgumentException if <code>listenerInterface</code> is
    103         *         not an interface.
    104         */
    105        public static <T> EventListenerSupport<T> create(Class<T> listenerInterface) {
    106            return new EventListenerSupport<T>(listenerInterface);
    107        }
    108
    109        /**
    110         * Creates an EventListenerSupport object which supports the provided
    111         * listener interface.
    112         *
    113         * @param listenerInterface the type of listener interface that will receive
    114         *        events posted using this class.
    115         *
    116         * @throws NullPointerException if <code>listenerInterface</code> is
    117         *         <code>null</code>.
    118         * @throws IllegalArgumentException if <code>listenerInterface</code> is
    119         *         not an interface.
    120         */
    121        public EventListenerSupport(Class<L> listenerInterface) {
    122            this(listenerInterface, Thread.currentThread().getContextClassLoader());
    123        }
    124
    125        /**
    126         * Creates an EventListenerSupport object which supports the provided
    127         * listener interface using the specified class loader to create the JDK
    128         * dynamic proxy.
    129         *
    130         * @param listenerInterface the listener interface.
    131         * @param classLoader       the class loader.
    132         *
    133         * @throws NullPointerException if <code>listenerInterface</code> or
    134         *         <code>classLoader</code> is <code>null</code>.
    135         * @throws IllegalArgumentException if <code>listenerInterface</code> is
    136         *         not an interface.
    137         */
    138        public EventListenerSupport(Class<L> listenerInterface, ClassLoader classLoader) {
    139            this();
    140            Validate.notNull(listenerInterface, "Listener interface cannot be null.");
    141            Validate.notNull(classLoader, "ClassLoader cannot be null.");
    142            Validate.isTrue(listenerInterface.isInterface(), "Class {0} is not an interface",
    143                    listenerInterface.getName());
    144            initializeTransientFields(listenerInterface, classLoader);
    145        }
    146
    147        /**
    148         * Create a new EventListenerSupport instance.
    149         * Serialization-friendly constructor.
    150         */
    151        private EventListenerSupport() {
    152        }
    153
    154        /**
    155         * Returns a proxy object which can be used to call listener methods on all
    156         * of the registered event listeners. All calls made to this proxy will be
    157         * forwarded to all registered listeners.
    158         *
    159         * @return a proxy object which can be used to call listener methods on all
    160         * of the registered event listeners
    161         */
    162        public L fire() {
    163            return proxy;
    164        }
    165
    166    //**********************************************************************************************************************
    167    // Other Methods
    168    //**********************************************************************************************************************
    169
    170        /**
    171         * Registers an event listener.
    172         *
    173         * @param listener the event listener (may not be <code>null</code>).
    174         *
    175         * @throws NullPointerException if <code>listener</code> is
    176         *         <code>null</code>.
    177         */
    178        public void addListener(L listener) {
    179            Validate.notNull(listener, "Listener object cannot be null.");
    180            listeners.add(listener);
    181        }
    182
    183        /**
    184         * Returns the number of registered listeners.
    185         *
    186         * @return the number of registered listeners.
    187         */
    188        int getListenerCount() {
    189            return listeners.size();
    190        }
    191
    192        /**
    193         * Unregisters an event listener.
    194         *
    195         * @param listener the event listener (may not be <code>null</code>).
    196         *
    197         * @throws NullPointerException if <code>listener</code> is
    198         *         <code>null</code>.
    199         */
    200        public void removeListener(L listener) {
    201            Validate.notNull(listener, "Listener object cannot be null.");
    202            listeners.remove(listener);
    203        }
    204
    205        /**
    206         * Get an array containing the currently registered listeners.
    207         * Modification to this array's elements will have no effect on the
    208         * {@link EventListenerSupport} instance.
    209         * @return L[]
    210         */
    211        public L[] getListeners() {
    212            return listeners.toArray(prototypeArray);
    213        }
    214
    215        /**
    216         * Serialize.
    217         * @param objectOutputStream the output stream
    218         * @throws IOException if an IO error occurs
    219         */
    220        private void writeObject(ObjectOutputStream objectOutputStream) throws IOException {
    221            ArrayList<L> serializableListeners = new ArrayList<L>();
    222
    223            // don't just rely on instanceof Serializable:
    224            ObjectOutputStream testObjectOutputStream = new ObjectOutputStream(new ByteArrayOutputStream());
    225            for (L listener : listeners) {
    226                try {
    227                    testObjectOutputStream.writeObject(listener);
    228                    serializableListeners.add(listener);
    229                } catch (IOException exception) {
    230                    //recreate test stream in case of indeterminate state
    231                    testObjectOutputStream = new ObjectOutputStream(new ByteArrayOutputStream());
    232                }
    233            }
    234            /*
    235             * we can reconstitute everything we need from an array of our listeners,
    236             * which has the additional advantage of typically requiring less storage than a list:
    237             */
    238            objectOutputStream.writeObject(serializableListeners.toArray(prototypeArray));
    239        }
    240
    241        /**
    242         * Deserialize.
    243         * @param objectInputStream the input stream
    244         * @throws IOException if an IO error occurs
    245         * @throws ClassNotFoundException if the class cannot be resolved
    246         */
    247        private void readObject(ObjectInputStream objectInputStream) throws IOException, ClassNotFoundException {
    248            @SuppressWarnings("unchecked")
    249            L[] listeners = (L[]) objectInputStream.readObject();
    250
    251            this.listeners = new CopyOnWriteArrayList<L>(listeners);
    252
    253            @SuppressWarnings("unchecked")
    254            Class<L> listenerInterface = (Class<L>) listeners.getClass().getComponentType();
    255
    256            initializeTransientFields(listenerInterface, Thread.currentThread().getContextClassLoader());
    257        }
    258
    259        /**
    260         * Initialize transient fields.
    261         * @param listenerInterface the class of the listener interface
    262         * @param classLoader the class loader to be used
    263         */
    264        private void initializeTransientFields(Class<L> listenerInterface, ClassLoader classLoader) {
    265            @SuppressWarnings("unchecked")
    266            L[] array = (L[]) Array.newInstance(listenerInterface, 0);
    267            this.prototypeArray = array;
    268            createProxy(listenerInterface, classLoader);
    269        }
    270
    271        /**
    272         * Create the proxy object.
    273         * @param listenerInterface the class of the listener interface
    274         * @param classLoader the class loader to be used
    275         */
    276        private void createProxy(Class<L> listenerInterface, ClassLoader classLoader) {
    277            proxy = listenerInterface.cast(Proxy.newProxyInstance(classLoader,
    278                    new Class[] { listenerInterface }, createInvocationHandler()));
    279        }
    280
    281        /**
    282         * Create the {@link InvocationHandler} responsible for broadcasting calls
    283         * to the managed listeners.  Subclasses can override to provide custom behavior.
    284         * @return ProxyInvocationHandler
    285         */
    286        protected InvocationHandler createInvocationHandler() {
    287            return new ProxyInvocationHandler();
    288        }
    289
    290        /**
    291         * An invocation handler used to dispatch the event(s) to all the listeners.
    292         */
    293        protected class ProxyInvocationHandler implements InvocationHandler {
    294            /** Serialization version */
    295            private static final long serialVersionUID = 1L;
    296
    297            /**
    298             * Propagates the method call to all registered listeners in place of
    299             * the proxy listener object.
    300             *
    301             * @param proxy the proxy object representing a listener on which the
    302             *        invocation was called.
    303             * @param method the listener method that will be called on all of the
    304             *        listeners.
    305             * @param args event arguments to propagate to the listeners.
    306             * @return the result of the method call
    307             * @throws Throwable if an error occurs
    308             */
    309            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    310                for (L listener : listeners) {
    311                    method.invoke(listener, args);
    312                }
    313                return null;
    314            }
    315        }
    316    }