View Javadoc

1   /*
2    * Copyright (c) 1998, 2005 Gargoyle Software Inc. All rights reserved.
3    *
4    * Redistribution and use in source and binary forms, with or without
5    * modification, are permitted provided that the following conditions are met:
6    *
7    * 1. Redistributions of source code must retain the above copyright notice,
8    *    this list of conditions and the following disclaimer.
9    * 2. Redistributions in binary form must reproduce the above copyright notice,
10   *    this list of conditions and the following disclaimer in the documentation
11   *    and/or other materials provided with the distribution.
12   * 3. The end-user documentation included with the redistribution, if any, must
13   *    include the following acknowledgment:
14   *
15   *       "This product includes software developed by Gargoyle Software Inc.
16   *        (http://www.GargoyleSoftware.com/)."
17   *
18   *    Alternately, this acknowledgment may appear in the software itself, if
19   *    and wherever such third-party acknowledgments normally appear.
20   * 4. The name "Gargoyle Software" must not be used to endorse or promote
21   *    products derived from this software without prior written permission.
22   *    For written permission, please contact info@GargoyleSoftware.com.
23   * 5. Products derived from this software may not be called "GSBase", nor may
24   *    "GSBase" appear in their name, without prior written permission of
25   *    Gargoyle Software Inc.
26   *
27   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
28   * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
29   * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GARGOYLE
30   * SOFTWARE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
31   * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
32   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
33   * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
34   * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
35   * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
36   * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
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&lt;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 }