    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.lang.reflect.InvocationHandler;
    021    import java.lang.reflect.Method;
    022    import java.lang.reflect.Proxy;
    023    import java.util.List;
    024    import java.util.concurrent.CopyOnWriteArrayList;
    025
    026    import org.apache.commons.lang3.Validate;
    027
    028    /**
    029     * An EventListenerSupport object can be used to manage a list of event
    030     * listeners of a particular type. The class provides
    031     * {@link #addListener(Object)} and {@link #removeListener(Object)} methods
    032     * for registering listeners, as well as a {@link #fire()} method for firing
    033     * events to the listeners.
    034     *
    035     * <p/>
    036     * To use this class, suppose you want to support ActionEvents.  You would do:
    037     * <code><pre>
    038     * public class MyActionEventSource
    039     * {
    040     *   private EventListenerSupport<ActionListener> actionListeners =
    041     *       EventListenerSupport.create(ActionListener.class);
    042     *
    043     *   public void someMethodThatFiresAction()
    044     *   {
    045     *     ActionEvent e = new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "somethingCool");
    046     *     actionListeners.fire().actionPerformed(e);
    047     *   }
    048     * }
    049     * </pre></code>
    050     *
    051     * @param <L> the type of event listener that is supported by this proxy.
    052     *
    053     * @since 3.0
    054     * @version $Id: EventListenerSupport.java 978864 2010-07-24 12:49:38Z jcarman $
    055     */
    056    public class EventListenerSupport<L>
    057    {
    058        /**
    059        * The list used to hold the registered listeners. This list is
    060        * intentionally a thread-safe copy-on-write-array so that traversals over
    061        * the list of listeners will be atomic.
    062        */
    063        private final List<L> listeners = new CopyOnWriteArrayList<L>();
    064
    065        /**
    066         * The proxy representing the collection of listeners. Calls to this proxy
    067         * object will sent to all registered listeners.
    068         */
    069        private final L proxy;
    070
    071        /**
    072         * Creates an EventListenerSupport object which supports the specified
    073         * listener type.
    074         *
    075         * @param listenerInterface the type of listener interface that will receive
    076         *        events posted using this class.
    077         *
    078         * @return an EventListenerSupport object which supports the specified
    079         *         listener type.
    080         *
    081         * @throws NullPointerException if <code>listenerInterface</code> is
    082         *         <code>null</code>.
    083         * @throws IllegalArgumentException if <code>listenerInterface</code> is
    084         *         not an interface.
    085         */
    086        public static <T> EventListenerSupport<T> create(Class<T> listenerInterface)
    087        {
    088            return new EventListenerSupport<T>(listenerInterface);
    089        }
    090
    091        /**
    092         * Creates an EventListenerSupport object which supports the provided
    093         * listener interface.
    094         *
    095         * @param listenerInterface the type of listener interface that will receive
    096         *        events posted using this class.
    097         *
    098         * @throws NullPointerException if <code>listenerInterface</code> is
    099         *         <code>null</code>.
    100         * @throws IllegalArgumentException if <code>listenerInterface</code> is
    101         *         not an interface.
    102         */
    103        public EventListenerSupport(Class<L> listenerInterface)
    104        {
    105            this(listenerInterface, Thread.currentThread().getContextClassLoader());
    106        }
    107
    108        /**
    109         * Creates an EventListenerSupport object which supports the provided
    110         * listener interface using the specified class loader to create the JDK
    111         * dynamic proxy.
    112         *
    113         * @param listenerInterface the listener interface.
    114         * @param classLoader       the class loader.
    115         *
    116         * @throws NullPointerException if <code>listenerInterface</code> or
    117         *         <code>classLoader</code> is <code>null</code>.
    118         * @throws IllegalArgumentException if <code>listenerInterface</code> is
    119         *         not an interface.
    120         */
    121        public EventListenerSupport(Class<L> listenerInterface, ClassLoader classLoader)
    122        {
    123            Validate.notNull(listenerInterface, "Listener interface cannot be null.");
    124            Validate.notNull(classLoader, "ClassLoader cannot be null.");
    125            Validate.isTrue(listenerInterface.isInterface(),
    126                "Class {0} is not an interface",
    127                listenerInterface.getName());
    128            proxy = listenerInterface.cast(Proxy.newProxyInstance(classLoader,
    129                    new Class[]{listenerInterface},
    130                    new ProxyInvocationHandler()));
    131        }
    132
    133        /**
    134         * Returns a proxy object which can be used to call listener methods on all
    135         * of the registered event listeners. All calls made to this proxy will be
    136         * forwarded to all registered listeners.
    137         *
    138         * @return a proxy object which can be used to call listener methods on all
    139         * of the registered event listeners
    140         */
    141        public L fire()
    142        {
    143            return proxy;
    144        }
    145
    146    //**********************************************************************************************************************
    147    // Other Methods
    148    //**********************************************************************************************************************
    149
    150        /**
    151         * Registers an event listener.
    152         *
    153         * @param listener the event listener (may not be <code>null</code>).
    154         *
    155         * @throws NullPointerException if <code>listener</code> is
    156         *         <code>null</code>.
    157         */
    158        public void addListener(L listener)
    159        {
    160            Validate.notNull(listener, "Listener object cannot be null.");
    161            listeners.add(listener);
    162        }
    163
    164        /**
    165         * Returns the number of registered listeners.
    166         *
    167         * @return the number of registered listeners.
    168         */
    169        int getListenerCount()
    170        {
    171            return listeners.size();
    172        }
    173
    174        /**
    175         * Unregisters an event listener.
    176         *
    177         * @param listener the event listener (may not be <code>null</code>).
    178         *
    179         * @throws NullPointerException if <code>listener</code> is
    180         *         <code>null</code>.
    181         */
    182        public void removeListener(L listener)
    183        {
    184            Validate.notNull(listener, "Listener object cannot be null.");
    185            listeners.remove(listener);
    186        }
    187
    188        /**
    189         * An invocation handler used to dispatch the event(s) to all the listeners.
    190         */
    191        private class ProxyInvocationHandler implements InvocationHandler
    192        {
    193            /**
    194             * Propagates the method call to all registered listeners in place of
    195             * the proxy listener object.
    196             *
    197             * @param proxy the proxy object representing a listener on which the
    198             *        invocation was called.
    199             * @param method the listener method that will be called on all of the
    200             *        listeners.
    201             * @param args event arguments to propogate to the listeners.
    202             */
    203            public Object invoke(Object proxy, Method method, Object[] args)
    204                throws Throwable
    205            {
    206                for (L listener : listeners)
    207                {
    208                    method.invoke(listener, args);
    209                }
    210                return null;
    211            }
    212        }
    213    }