1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38 package com.gargoylesoftware.base.testing;
39
40 import java.lang.reflect.InvocationHandler;
41 import java.lang.reflect.InvocationTargetException;
42 import java.lang.reflect.Method;
43 import java.lang.reflect.Proxy;
44 import java.util.ArrayList;
45 import java.util.Collections;
46 import java.util.EventObject;
47 import java.util.HashMap;
48 import java.util.Iterator;
49 import java.util.List;
50 import java.util.Map;
51 import junit.framework.AssertionFailedError;
52
53 /***
54 * A testing class for catching and logging events.
55 * <pre>
56 * // Catch all events fired by JFrame
57 * final JFrame frame = new JFrame();
58 * final EventCatcher eventCatcher = new EventCatcher();
59 * eventCatcher.listenTo(frame);
60 *
61 * frame.show();
62 *
63 * for( int i=0; i<eventCatcher.size(); i++ ) {
64 * System.out.println(eventCatcher.getEventAt(i));
65 * }
66 * </pre>
67 * @version $Revision: 1.4 $
68 * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
69 */
70 public class EventCatcher {
71
72 /***
73 * An inner class to handle the various events.
74 */
75 private InvocationHandler invocationHandler_ = new InvocationHandler() {
76 public Object invoke( final Object proxy,
77 final Method method,
78 final Object[] args) {
79 final String methodName = method.getName();
80 final Object result;
81 if( methodName.equals("hashCode") ) {
82 result = new Integer(1);
83 }
84 else if( methodName.equals("equals") && args.length == 1 ) {
85 result = new Boolean(this == args[0]);
86 }
87 else {
88 synchronized(EventCatcher.this) {
89 eventRecords_.add( new EventCatcherRecord(method, (EventObject)args[0]) );
90 }
91 result = null;
92 }
93 return result;
94 }
95 };
96
97 private final List eventRecords_ = new ArrayList();
98
99 /***
100 * Create a new EventCatcher.
101 */
102 public EventCatcher() {
103 }
104
105
106 /***
107 * Return information about the event at the specified index.
108 *
109 * @param index The index.
110 * @return The record.
111 * @deprecated Use {@link #getEventCatcherRecordAt(int)} instead
112 */
113 public synchronized EventCatcherRecord get( final int index ) {
114 return (EventCatcherRecord)eventRecords_.get(index);
115 }
116
117
118 /***
119 * Return the number of events that have been caught.
120 * @return the number of events that have been caught.
121 * @deprecated Use {@link #getEventCount()} instead
122 */
123 public synchronized int size() {
124 return eventRecords_.size();
125 }
126
127
128 /***
129 * Return a listener object that will log all fired events. This listener
130 * should be used when you want to only listen for one kind of events on a bean.
131 * If you want to listen to all events then you should just call {@link #listenTo(Object)}
132 * <pre>
133 * // Catch all window events
134 * final ObjectCatcher objectCatcher = new ObjectCatcher();
135 * final JFrame frame = new JFrame();
136 *
137 * frame.addWindowListener( (WindowListener)objectCatcher.getListener(WindowListener.class) );
138 * </pre>
139 *
140 * @param clazz The listener interface that we need to support.
141 * @return A listener.
142 */
143 public Object getListener( final Class clazz ) {
144 return Proxy.newProxyInstance( getClass().getClassLoader(),
145 new Class[]{ clazz },
146 invocationHandler_ );
147 }
148
149
150 /***
151 * Register the event catcher as a listener for all events that this object fires.
152 * <pre>
153 * // Catch all events fired by JFrame
154 * final ObjectCatcher objectCatcher = new ObjectCatcher();
155 * final JFrame frame = new JFrame();
156 *
157 * eventCatcher.listenTo(frame);
158 * </pre>
159 *
160 * @param object The object that we will be listening to.
161 * @throws IllegalAccessException If we do not have authorization to call
162 * the respective addXXXListener() method
163 * @throws InvocationTargetException If an exception is thrown during the
164 * call to the addXXXListener() method
165 */
166 public void listenTo( final Object object )
167 throws
168 IllegalAccessException,
169 InvocationTargetException {
170
171 final Method methods[] = object.getClass().getMethods();
172
173 String methodName;
174 Class parameterClasses[];
175 final Map addMethods = new HashMap(89);
176
177 int i;
178 for( i=0; i<methods.length; i++ ) {
179 methodName = methods[i].getName();
180 if( methodName.startsWith("add") && methodName.endsWith("Listener") ) {
181 parameterClasses = methods[i].getParameterTypes();
182 if( parameterClasses.length == 1 ) {
183 addMethods.put( methods[i], parameterClasses[0] );
184 }
185 }
186 }
187
188 Map.Entry entry;
189 Object proxy;
190 Method method;
191
192 final Iterator iterator = addMethods.entrySet().iterator();
193 while( iterator.hasNext() ) {
194 entry = (Map.Entry)iterator.next();
195 method = (Method)entry.getKey();
196 proxy = getListener( (Class)entry.getValue() );
197 method.invoke( object, new Object[]{proxy} );
198 }
199 }
200
201
202 /***
203 * Return the event at the specified index.
204 * @param index The index
205 * @return The event at that index.
206 */
207 public synchronized EventObject getEventAt( final int index ) {
208 return getEventCatcherRecordAt(index).getEvent();
209 }
210
211
212 /***
213 * Return the record at the specified index. The record will contain the event and
214 * assorted information about the event.
215 * @param index The index
216 * @return The record at that index.
217 */
218 public synchronized EventCatcherRecord getEventCatcherRecordAt( final int index ) {
219 return (EventCatcherRecord)eventRecords_.get(index);
220 }
221
222
223 /***
224 * Return the number of events that have been collected so far.
225 *
226 * @return The number of events.
227 */
228 public synchronized int getEventCount() {
229 return eventRecords_.size();
230 }
231
232
233 /***
234 * Return an immutable list containing all the events collected so far.
235 *
236 * @return A list of collected events.
237 */
238 public synchronized List getEvents() {
239 final List list = new ArrayList(eventRecords_.size());
240 final Iterator iterator = eventRecords_.iterator();
241 while( iterator.hasNext() ) {
242 final EventCatcherRecord record = (EventCatcherRecord)iterator.next();
243 list.add( record.getEvent() );
244 }
245
246 return Collections.unmodifiableList(list);
247 }
248
249
250 /***
251 * Throw away all the currently collected events.
252 */
253 public synchronized void clear() {
254 eventRecords_.clear();
255 }
256
257
258 /***
259 * Compare the specified events against the actual collected event to see if they
260 * appear to be the same. Refer to {@link TestUtil#appearsEqual(Object,Object)}
261 * for an explanation of "appearing" to be the same.
262 *
263 * @param expectedEvents The events that we expect to have been collected.
264 */
265 public synchronized void assertEventsAppearEquals( final List expectedEvents ) {
266 final List actualEvents = getEvents();
267 final int eventCount = actualEvents.size();
268 if( expectedEvents.size() != eventCount ) {
269 throw new AssertionFailedError("Different number of events: expected="
270 +expectedEvents+" actual="+actualEvents);
271 }
272
273 for( int i=0; i<eventCount; i++ ) {
274 if( TestUtil.appearsEqual( expectedEvents.get(i), actualEvents.get(i) ) == false ) {
275 throw new AssertionFailedError("Different events: expected="
276 +expectedEvents+" actual="+actualEvents);
277 }
278 }
279 }
280 }