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    }