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 }