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    }