1 /*
2  * Copyright (c) 2007, 2016, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 import java.awt.*;
27 import java.awt.event.*;
28 import java.beans.*;
29 import java.lang.reflect.*;
30 import java.util.*;
31 
32 import javax.swing.*;
33 
34 /**
35  * SwingTestHelper is a utility class for writing AWT/Swing regression
36  * tests that require interacting with the UI.  Typically such tests
37  * consist of executing a chunk of code, waiting on an event, executing
38  * more code ...  This is painful in that you typically have to use various
39  * invokeLaters and threading to handle that interaction.  SwingTestHelper
40  * strealines this process.
41  * <p>
42  * SwingTestHelper uses reflection to invoke all methods starting with
43  * the name <code>onEDT</code> on the EDT and all methods starting with
44  * <code>onBackgroundThread</code> on a background thread.  Between each method
45  * invocation all pending events on the EDT are processed.  The methods
46  * are first sorted based on an integer after the method names and invoked
47  * in that order.  For example, the following subclass:
48  * <pre>
49  * class Test extends SwingTestHelper {
50  *   private void onEDT10();
51  *   private void onBackgroundThread20();
52  *   private void onBackgroundThread30();
53  *   private void onEDT40();
54  *   private void onBackgroundThread50();
55  * }
56  * </pre>
57  * Will have the methods invoked in the order <code>onEDT10</code>,
58  * <code>onBackgroundThread20</code>, <code>onBackgroundThread30</code>,
59  * <code>onEDT40</code>, <code>onBackgroundThread50</code>.
60  * <p>
61  * If you're not happy with method mangling you can also use annotations.
62  * The following gives the same result as the previous example:
63  * <pre>
64  * class Test extends SwingTestHelper {
65  *   &#064;Test(10)
66  *   private void foo(); // Was onEDT10
67  *
68  *   &#064;Test(value=20, onEDT=false)
69  *   private void bar(); // Was onBackgroundThread20
70  *
71  *   &#064;Test(value=30, onEDT=false)
72  *   private void baz(); // Was onBackgroundThread30
73  *
74  *   &#064;Test(40)
75  *   private void zed(); // Was onEDT40
76  *
77  *   &#064;Test(value=50, onEDT=false)
78  *   private void onBackgroundThread50(); // Was onBackgroundThread50
79  * }
80  * </pre>
81  * <p>
82  * It is recommended that you increment the value in increments of
83  * 10.  This makes it easier to add methods at a later date without
84  * having to change all method names/annotations after the newly added
85  * method.
86  * <p>
87  * Between each of the methods, all pending events (native and Java)
88  * are processed.
89  * <p>
90  * Failure of the test is signaled by any method throwing
91  * an exception, directly invoking <code>fail</code> or one of the
92  * <code>assert</code> variants.  If no methods throw an exception the test is
93  * assumed to have passed.
94  * <p>
95  * Often times it is necessary to block until focus has been gained on a
96  * particular widget.  This can be handled by the
97  * <code>requestAndWaitForFocus</code> method.  It will invoke
98  * <code>requestFocus</code> and block the test (not the EDT) until focus
99  * has been granted to the widget.
100  * <p>
101  * Care must be taken when using <code>Robot</code> directly.  For
102  * example, it's tempting to flood <code>Robot</code> with events and
103  * assume they will be received after some delay.  Depending upon the
104  * machine you may need to increase the delay.  Instead it's
105  * preferrable to block test execution until the event has been
106  * received and processed.  This can be done using the method
107  * <code>waitForEvent</code>.  For example, to block until a key typed
108  * event has been processed do the following:
109  * <pre>
110  *   private void onEDT() {
111  *     robot.moveMouseTo(myComponent);
112  *     robot.mousePress(xxx);
113  *     robot.mouseRelease(xxx);
114  *     waitForEvent(myComponent, MouseEvent.MOUSE_RELEASED);
115  *   }
116  * </pre>
117  * <p>
118  * Waiting for focus and events are specific examples of a more
119  * general problem.  Often times you need the EDT to continue processing
120  * events, but want to block test execution until something happens.
121  * In the case of focus you want to block test execution until focus
122  * is gained.  The method <code>waitForCondition</code> can be used to
123  * block test execution until the supplied <code>Runnable</code> returns.  The
124  * <code>Runnable</code> is invoked on the background thread.
125  * <p>
126  * To use this class you will need to do the following:
127  * <ol>
128  * <li>Override the method <code>createContentPane</code>.  All of your logic
129  *     for setting up the test environment should go here.  This method is
130  *     invoked on the EDT.
131  * <li>Implement the necessary <code>onEDTXX</code> and
132  *     <code>onBackgroundThreadXXX</code> methods to do the actual testing.
133  * <li>Make your <code>main</code> method look like:
134  *     <code>new MySwingTestHelper().run(args)</code>.  This will block
135  *     until the test fails or succeeds.
136  * <li>To use this with jtreg you'll need to have something like:
137  *   <pre>
138  *     &#064;library ../../../regtesthelpers
139  *     &#064;build Test JRobot Assert SwingTestHelper
140  *     &#064;run main MySwingTestHelper
141  *     * </pre>
142  * </ol>
143  * <p>
144  * Here's a complete example:
145  * <pre>
146  * public class bug4852305 extends SwingTestHelper {
147  *     private JTable table;
148  *
149  *     public static void main(String[] args) throws Throwable {
150  *         new bug4852305().run(args);
151  *     }
152  *
153  *     protected Component createContentPane() {
154  *         DefaultTableModel model = new DefaultTableModel(1, 2);
155  *         model.setValueAt("x", 0, 0);
156  *         model.setValueAt("z", 0, 1);
157  *         table = new JTable(model);
158  *         table.setDefaultEditor(Object.class, new DefaultCellEditor(new JTextField()) {
159  *             public boolean isCellEditable(EventObject anEvent) {
160  *                 if ((anEvent instanceof KeyEvent) ||
161  *                         (anEvent instanceof ActionEvent)) {
162  *                     return false;
163  *                 }
164  *                 return true;
165  *             }
166  *         });
167  *         return new JScrollPane(table);
168  *     }
169  *
170  *     private void onEDT10() {
171  *         requestAndWaitForFocus(table);
172  *     }
173  *
174  *     private void onEDT20() {
175  *         robot.keyPress(KeyEvent.VK_A);
176  *         robot.keyRelease(KeyEvent.VK_A);
177  *         waitForEvent(table, KeyEvent.KEY_RELEASED);
178  *     }
179  *
180  *     private void onEDT30() {
181  *         if (table.isEditing()) {
182  *             fail("Should not be editing");
183  *         }
184  *     }
185  * }
186  * </pre>
187  *
188  *
189  * @author Scott Violet
190  */
191 public abstract class SwingTestHelper {
192     private static final String ON_EDT_METHOD_NAME = "onEDT";
193     private static final String IN_BACKGROUND_METHOD_NAME = "onBackgroundThread";
194 
195     // Whether or not we've installed a PropertyChangeListener on the
196     // KeyboardFocusManager
197     private boolean installedFocusListener;
198     // Component currently blocking on until focus has been received.
199     private Component componentWaitingForFocus;
200 
201     // Set to true when done.
202     private boolean done;
203     // If failed, this gives the exception.  Only the first exception is
204     // kept.
205     private Throwable error;
206 
207     // List of methods to invoke
208     private java.util.List<Method> methods;
209 
210     // The conditions the background thread is blocked on.
211     private java.util.List<Runnable> conditions;
212 
213     // Whether or not we've installed the AWTEventListener
214     private boolean installedEventListener;
215 
216     /**
217      * Instance of <code>Robot</code> returned from <code>createRobot</code>.
218      */
219     protected JRobot robot;
220 
221     /**
222      * <code>Window</code> returned from <code>createWindow</code>.
223      */
224     protected Window window;
225 
226     // Listens for the first paint event
227     private AWTEventListener paintListener;
228     // Whether or not we've received a paint event.
229     private boolean receivedFirstPaint;
230 
231     // used if the user wants to slow down method processing
232     private PauseCondition delay = null;
233 
234     private boolean showProgress;
235     private JProgressBar progBar;
236 
237 
SwingTestHelper()238     public SwingTestHelper() {
239         paintListener = new AWTEventListener() {
240             public void eventDispatched(AWTEvent ev) {
241                 if ((ev.getID() & PaintEvent.PAINT) != 0 &&
242                         ev.getSource() == window) {
243                     synchronized(SwingTestHelper.this) {
244                         if (receivedFirstPaint) {
245                             return;
246                         }
247                         receivedFirstPaint = true;
248                     }
249                     Toolkit.getDefaultToolkit().removeAWTEventListener(
250                                    paintListener);
251                     startControlLoop();
252                 }
253             }
254         };
255         Toolkit.getDefaultToolkit().addAWTEventListener(
256             paintListener, AWTEvent.PAINT_EVENT_MASK);
257     }
258 
259     /**
260      * Sets whether SwingTestHelper should use {@code SunToolkit.realSync}
261      * to wait for events to finish, or {@code Robot.waitForIdle}. The default
262      * is to use realSync.
263      * Nov 2014: no realSync any more, just robot.waitForIdle which actually
264      * _is_ realSync on all platforms but OS X (and thus cannot be used on EDT).
265      */
setUseRealSync(boolean useRealSync)266     public void setUseRealSync(boolean useRealSync) {
267         //NOOP
268     }
269 
270     /**
271      * Set the amount of time to delay between invoking methods in
272      * the control loop. Useful to slow down testing.
273      */
setDelay(int delay)274     protected void setDelay(int delay) {
275         if (delay <= 0) {
276             this.delay = null;
277         } else {
278             this.delay = new PauseCondition(delay);
279         }
280     }
281 
282     /**
283      * Sets whether or not progress through the list of methods is
284      * shown by a progress bar at the bottom of the window created
285      * by {@code createWindow}.
286      */
setShowProgress(boolean showProgress)287     protected void setShowProgress(boolean showProgress) {
288         this.showProgress = showProgress;
289     }
290 
291     /**
292      * Creates and returns the <code>Window</code> for the test.  This
293      * implementation returns a JFrame with a default close operation
294      * of <code>EXIT_ON_CLOSE</code>.  The <code>Component</code>
295      * returned from <code>createContentPane</code> is added the
296      * <code>JFrame</code> and the the frame is packed.
297      * <p>
298      * Typically you only need override <code>createContentPane</code>.
299      */
createWindow()300     protected Window createWindow() {
301         JFrame frame = new JFrame("Test: " + getClass().getName());
302         frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
303         frame.add(createContentPane());
304         if (showProgress) {
305             progBar = new JProgressBar();
306             progBar.setString("");
307             progBar.setStringPainted(true);
308             frame.add(progBar, BorderLayout.SOUTH);
309         }
310         frame.pack();
311         return frame;
312     }
313 
314     /**
315      * Returns the <code>Component</code> to place in the frame.
316      * Override this or the <code>createWindow</code> method.
317      */
createContentPane()318     protected Component createContentPane() {
319         return null;
320     }
321 
322     /**
323      * Invokes <code>requestFocus</code> on the passed in component (assuming
324      * it doesn't already have focus).  Test execution is blocked until focus
325      * has been gained on the component.  This method <b>must</b> be invoked
326      * on the EDT, if you do not invoke it from the edt the test will fail.
327      *
328      * @param c the <code>Component</code> to wait for focus on
329      */
requestAndWaitForFocus(Component c)330     protected void requestAndWaitForFocus(Component c) {
331         requestAndWaitForFocus(c, true);
332     }
333 
334     /**
335      * Blocks test execution until focus is gained on the component.
336      * This method <b>must</b> be invoked
337      * on the EDT, if you do not invoke it from the edt the test will fail.
338      */
waitForFocusGained(Component c)339     protected void waitForFocusGained(Component c) {
340         requestAndWaitForFocus(c, false);
341     }
342 
requestAndWaitForFocus(Component c, boolean requestFocus)343     private void requestAndWaitForFocus(Component c, boolean requestFocus) {
344         if (!EventQueue.isDispatchThread()) {
345             System.out.println(
346                 "requestAndWaitForFocus should be invoked on EDT");
347             throw new RuntimeException();
348         }
349         if (componentWaitingForFocus != null) {
350             System.out.println("Already waiting for focus");
351             throw new RuntimeException();
352         }
353         if (!installedFocusListener) {
354             installedFocusListener = true;
355             KeyboardFocusManager.getCurrentKeyboardFocusManager().
356                 addPropertyChangeListener(new FocusListener());
357         }
358         synchronized(this) {
359             if (c.hasFocus()) {
360                 return;
361             }
362             componentWaitingForFocus = c;
363         }
364         if (requestFocus) {
365             c.requestFocus();
366         }
367         waitForCondition(new FocusCondition());
368     }
369 
370     /**
371      * Blocks test execution until the specified event has been received.
372      * This method immediately returns and the EDT will continue to
373      * process events, but test execution is blocked until
374      * the event is received.
375      *
376      * @param event the event type to wait for
377      */
waitForEvent(int event)378     protected void waitForEvent(int event) {
379         waitForEvent(null, event);
380     }
381 
382     /**
383      * Blocks test execution until the specified event has been received.
384      * This method immediately returns and the EDT will continue to
385      * process events, but test execution is blocked until
386      * the event is received.
387      *
388      * @param target the <code>Component</code> to wait for the event on;
389      *               <code>null</code> indicates it does not matter which
390      *               component the event is received on
391      * @param event the event type to wait for
392      */
waitForEvent(Component target, int event)393     protected void waitForEvent(Component target, int event) {
394         waitForCondition(new EventCondition(target, event));
395         if (!installedEventListener) {
396             installedEventListener = true;
397             Toolkit.getDefaultToolkit().addAWTEventListener(
398                     new EventListener(), 0xFFFFFFFFFFFFFFFFl);
399         }
400     }
401 
402     /**
403      * Paused test execution for the specified amount of time.  The caller
404      * immediately returns and the EDT can process events.
405      *
406      * @param time the amount of time, in milliseconds, to pause for
407      */
pause(int time)408     protected void pause(int time) {
409         waitForCondition(new PauseCondition(time));
410     }
411 
412     /**
413      * Schedules a <code>Runnable</code> that will be processed in the
414      * background thread.  This method immediately returns, and the
415      * EDT is free to continue processing events. Test execution is
416      * blocked until the <code>Runnable</code> completes.
417      */
waitForCondition(Runnable runnable)418     protected void waitForCondition(Runnable runnable) {
419         synchronized(this) {
420             if (conditions == null) {
421                 conditions = new LinkedList<Runnable>();
422             }
423             conditions.add(runnable);
424         }
425     }
426 
427     /**
428      * Runs the test.  This method blocks the caller until the test
429      * fails or succeeds. Recognized arguments are:
430      * <p>
431      * "-exit": Causes main to exit when the test is done.
432      * "-showProg": Indicate the progress of the test with a
433      *              progress bar in the main window. Only works
434      *              if the test hasn't overridden {@code createWindow}.
435      * "-delay int": Sets the delay between executing methods.
436      *               Useful when you want to slow a test to watch it.
437      *
438      * @param args the arguments from main, it's ok to pass in null
439      */
run(String[] args)440     protected final void run(String[] args) throws Throwable {
441         boolean exit = false;
442         if (args != null) {
443             for (int i = 0; i < args.length; i++) {
444                 if (args[i].equals("-exit")) {
445                     exit = true;
446                 } else if (args[i].equals("-delay")) {
447                     try {
448                         setDelay(Integer.parseInt(args[++i]));
449                     } catch (NumberFormatException ne) {
450                         throw new RuntimeException("-delay requires an integer value");
451                     } catch (ArrayIndexOutOfBoundsException ae) {
452                         throw new RuntimeException("-delay requires an integer value");
453                     }
454                 } else if (args[i].equals("-showProg")) {
455                     setShowProgress(true);
456                 } else {
457                     throw new RuntimeException("Invalid argument \"" + args[i] + "\"");
458                 }
459             }
460         }
461 
462         createWindow0();
463         synchronized(this) {
464             while(!done) {
465                 wait();
466             }
467         }
468         if (exit) {
469             // Not in harness
470             if (error != null) {
471                 System.out.println("FAILED: " + error);
472                 error.printStackTrace();
473             }
474             System.exit(0);
475         }
476         if (error != null) {
477             throw error;
478         }
479     }
480 
481     /**
482      * Creates the window, on the EDT.
483      */
createWindow0()484     private void createWindow0() {
485         EventQueue.invokeLater(new Runnable() {
486             public void run() {
487                 window = createWindow();
488                 window.show();
489             }
490         });
491     }
492 
493     /**
494      * Initializes the progress bar if necessary.
495      */
initProgressBar(final int size)496     private void initProgressBar(final int size) {
497         EventQueue.invokeLater(new Runnable() {
498             public void run() {
499                 if (progBar != null) {
500                     progBar.setMaximum(size);
501                 }
502             }
503         });
504     }
505 
506     /**
507      * Starst the control loop.
508      */
startControlLoop()509     private void startControlLoop() {
510         robot = createRobot();
511         if (robot != null) {
512             calculateMethods();
513             initProgressBar(methods.size());
514             new Thread(new Runnable() {
515                 public void run() {
516                     controlLoop();
517                 }
518             }).start();
519         }
520     }
521 
522     /**
523      * Increment the progress bar.
524      */
nextProgress(final String name)525     private void nextProgress(final String name) {
526         EventQueue.invokeLater(new Runnable() {
527             public void run() {
528                 if (progBar != null) {
529                     progBar.setString(name);
530                     progBar.setValue(progBar.getValue() + 1);
531                 }
532             }
533         });
534     }
535 
currentCondition()536     private synchronized Runnable currentCondition() {
537         if (conditions != null && conditions.size() > 0) {
538             return conditions.get(0);
539         }
540         return null;
541     }
542 
nextCondition()543     private synchronized Runnable nextCondition() {
544         return conditions.remove(0);
545     }
546 
controlLoop()547     private void controlLoop() {
548         int methodIndex = 0;
549         while (methodIndex < methods.size()) {
550             // Wait for any pending conditions
551             Runnable condition;
552             while ((condition = currentCondition()) != null) {
553                 try {
554                     condition.run();
555                 } catch (Exception e) {
556                     fail(e);
557                     return;
558                 }
559                 waitForEDTToFinish();
560                 synchronized(this) {
561                     if (done) {
562                         return;
563                     }
564                 }
565                 // Advance to next condition
566                 nextCondition();
567             }
568 
569             // Let all events on the EDT finish
570             waitForEDTToFinish();
571 
572             if (delay != null) {
573                 delay.run();
574             }
575 
576             // Invoke the next method
577             Method method = methods.get(methodIndex++);
578             Test test = method.getAnnotation(Test.class);
579             boolean onEDT = true;
580             if (test != null) {
581                 onEDT = test.onEDT();
582             }
583             else if (!method.getName().startsWith(ON_EDT_METHOD_NAME)) {
584                 onEDT = false;
585             }
586             if (onEDT) {
587                 invokeOnEDT(method);
588             }
589             else {
590                 invoke(method);
591             }
592 
593             // Let all events on the EDT finish
594             waitForEDTToFinish();
595 
596             nextProgress(method.getName());
597 
598             // If done, stop.
599             synchronized(this) {
600                 if (done) {
601                     return;
602                 }
603             }
604         }
605 
606         // No more methods, if we get and done isn't true, set it true
607         // so that the main thread wakes up.
608         synchronized(this) {
609             if (!done) {
610                 done = true;
611                 notifyAll();
612             }
613         }
614     }
615 
waitForEDTToFinish()616     private void waitForEDTToFinish() {
617             robot.waitForIdle();
618         }
619 
invokeOnEDT(final Method method)620     private void invokeOnEDT(final Method method) {
621         try {
622             EventQueue.invokeAndWait(new Runnable() {
623                 public void run() {
624                     invoke(method);
625                 }
626             });
627         } catch (InvocationTargetException ite) {
628             fail(ite);
629         } catch (InterruptedException ie) {
630             fail(ie);
631         }
632     }
633 
invoke(Method method)634     private void invoke(Method method) {
635         System.out.println("invoking: " + method.getName());
636         try {
637             if (Modifier.isPrivate(method.getModifiers())) {
638                 method.setAccessible(true);
639             }
640             method.invoke(this);
641         } catch (Exception e) {
642             fail(e);
643         }
644     }
645 
646     // Determines the methods to execute.
calculateMethods()647     private void calculateMethods() {
648         // Using a Set avoids duplicating methods returned by both
649         // getMethods() and getDeclaredMethods().
650         HashSet<Method> allMethods = new HashSet<Method>();
651         allMethods.addAll(Arrays.asList(getClass().getMethods()));
652         allMethods.addAll(Arrays.asList(getClass().getDeclaredMethods()));
653 
654         methods = new ArrayList<Method>();
655         for (Method method : allMethods) {
656             Test test = method.getAnnotation(Test.class);
657             if (test != null) {
658                 methods.add(method);
659             }
660             else if (method.getName().startsWith(ON_EDT_METHOD_NAME)) {
661                 methods.add(method);
662             }
663             else if (method.getName().startsWith(IN_BACKGROUND_METHOD_NAME)) {
664                 methods.add(method);
665             }
666         }
667         Comparator<Method> comparator = new Comparator<Method>() {
668             public int compare(Method m1, Method m2) {
669                 int index1 = getIndex(m1);
670                 int index2 = getIndex(m2);
671                 return index1 - index2;
672             }
673             private int getIndex(Method m) {
674                 String name = m.getName();
675                 String indexAsString;
676                 Test test = m.getAnnotation(Test.class);
677                 if (test != null) {
678                     return test.value();
679                 }
680                 if (name.startsWith(ON_EDT_METHOD_NAME)) {
681                     indexAsString = name.substring(
682                             ON_EDT_METHOD_NAME.length());
683                 }
684                 else {
685                     indexAsString = name.substring(
686                             IN_BACKGROUND_METHOD_NAME.length());
687                 }
688                 if (indexAsString.length() == 0) {
689                     System.out.println(
690                             "onEDT and onBackgroundThread must be " +
691                             "followed by an integer specifying " +
692                             "order.");
693                     System.exit(0);
694                 }
695                 return Integer.parseInt(indexAsString);
696             }
697         };
698         Collections.sort(methods, comparator);
699     }
700 
701     /**
702      * Invoke if the test should be considered to have failed.  This will
703      * stop test execution.
704      */
fail(String reason)705     public void fail(String reason) {
706         fail(new RuntimeException(reason));
707     }
708 
709     /**
710      * Invoke if the test should be considered to have failed.  This will
711      * stop test execution.
712      */
fail(Throwable error)713     public void fail(Throwable error) {
714         synchronized(this) {
715             if (this.error == null) {
716                 if (error instanceof InvocationTargetException) {
717                     this.error = ((InvocationTargetException)error).
718                             getCause();
719                 }
720                 else {
721                     this.error = error;
722                 }
723                 this.done = true;
724                 notifyAll();
725             }
726         }
727     }
728 
729     /**
730      * Invoke to prematurely stop test execution while there are remaining
731      * methods.  You typically don't invoke this, instead if all methods have
732      * been executed and fail hasn't been invoked, the test is considered to
733      * have passed.
734      */
succeeded()735     public void succeeded() {
736         synchronized(this) {
737             this.done = true;
738             notifyAll();
739         }
740     }
741 
742     /**
743      * Creates and returns the Robot that will be used.  You generally don't
744      * need to override this.
745      */
createRobot()746     protected JRobot createRobot() {
747         JRobot robot = JRobot.getRobot(false);
748         return robot;
749     }
750 
751 
752     private class FocusListener implements PropertyChangeListener {
propertyChange(PropertyChangeEvent e)753         public void propertyChange(PropertyChangeEvent e) {
754             if (componentWaitingForFocus != null &&
755                     "focusOwner".equals(e.getPropertyName()) &&
756                     componentWaitingForFocus == e.getNewValue()) {
757                 synchronized(SwingTestHelper.this) {
758                     componentWaitingForFocus = null;
759                     SwingTestHelper.this.notifyAll();
760                 }
761             }
762         }
763     }
764 
765 
766     private class EventCondition implements Runnable {
767         private Component component;
768         private int eventID;
769         private boolean received;
770 
EventCondition(Component component, int eventID)771         EventCondition(Component component, int eventID) {
772             this.component = component;
773             this.eventID = eventID;
774         }
775 
getEventID()776         public int getEventID() {
777             return eventID;
778         }
779 
getComponent()780         public Component getComponent() {
781             return component;
782         }
783 
received()784         public void received() {
785             synchronized(SwingTestHelper.this) {
786                 this.received = true;
787                 SwingTestHelper.this.notifyAll();
788             }
789         }
790 
isWaiting()791         public boolean isWaiting() {
792             return !received;
793         }
794 
run()795         public void run() {
796             synchronized(SwingTestHelper.this) {
797                 while (!received) {
798                     try {
799                         SwingTestHelper.this.wait();
800                     } catch (InterruptedException ie) {
801                         fail(ie);
802                     }
803                 }
804             }
805         }
806     }
807 
808 
809     private class FocusCondition implements Runnable {
run()810         public void run() {
811             synchronized(SwingTestHelper.this) {
812                 while (componentWaitingForFocus != null) {
813                     try {
814                         SwingTestHelper.this.wait();
815                     } catch (InterruptedException ie) {
816                         fail(ie);
817                     }
818                 }
819             }
820         }
821     }
822 
823 
824     private class PauseCondition implements Runnable {
825         private int time;
PauseCondition(int time)826         PauseCondition(int time) {
827             this.time = time;
828         }
run()829         public void run() {
830             try {
831                 Thread.sleep(time);
832             } catch (InterruptedException ie) {
833                 fail(ie);
834             }
835         }
836     }
837 
838 
839     private class EventListener implements AWTEventListener {
eventDispatched(AWTEvent ev)840         public void eventDispatched(AWTEvent ev) {
841             int eventID = ev.getID();
842             synchronized (SwingTestHelper.this) {
843                 for (Runnable condition : conditions) {
844                     if (condition instanceof EventCondition) {
845                         EventCondition ec = (EventCondition)condition;
846                         if (ec.isWaiting()) {
847                             if (eventID == ec.getEventID() &&
848                                     (ec.getComponent() == null ||
849                                      ev.getSource() == ec.getComponent())) {
850                                 ec.received();
851                             }
852                             return;
853                         }
854                     }
855                     else {
856                         return;
857                     }
858                 }
859             }
860         }
861     }
862 }
863