1 /*
2  * Copyright (c) 2006, 2014, 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.
8  *
9  * This code is distributed in the hope that it will be useful, but WITHOUT
10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12  * version 2 for more details (a copy is included in the LICENSE file that
13  * accompanied this code).
14  *
15  * You should have received a copy of the GNU General Public License version
16  * 2 along with this work; if not, write to the Free Software Foundation,
17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18  *
19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20  * or visit www.oracle.com if you need additional information or have any
21  * questions.
22  */
23 
24 package test.java.awt.regtesthelpers;
25 /**
26  * <p>This class contains utilities useful for regression testing.
27  * <p>When using jtreg you would include this class into the build
28  * list via something like:
29  * <pre>
30      @library ../../../regtesthelpers
31      @build Util
32      @run main YourTest
33    </pre>
34  * Note that if you are about to create a test based on
35  * Applet-template, then put those lines into html-file, not in java-file.
36  * <p> And put an
37  * import test.java.awt.regtesthelpers.Util;
38  * into the java source of test.
39 */
40 
41 import java.awt.Component;
42 import java.awt.Frame;
43 import java.awt.Dialog;
44 import java.awt.Window;
45 import java.awt.Button;
46 import java.awt.Point;
47 import java.awt.Dimension;
48 import java.awt.Rectangle;
49 import java.awt.Robot;
50 import java.awt.Toolkit;
51 import java.awt.IllegalComponentStateException;
52 import java.awt.AWTException;
53 import java.awt.AWTEvent;
54 import java.awt.Color;
55 
56 import java.awt.event.InputEvent;
57 import java.awt.event.WindowAdapter;
58 import java.awt.event.WindowEvent;
59 import java.awt.event.ActionEvent;
60 import java.awt.event.FocusEvent;
61 import java.awt.event.WindowListener;
62 import java.awt.event.WindowFocusListener;
63 import java.awt.event.FocusListener;
64 import java.awt.event.ActionListener;
65 
66 import java.lang.reflect.Constructor;
67 import java.lang.reflect.Field;
68 import java.lang.reflect.InvocationTargetException;
69 import java.lang.reflect.Method;
70 
71 import java.security.PrivilegedAction;
72 import java.security.AccessController;
73 
74 import java.util.concurrent.atomic.AtomicBoolean;
75 
76 public final class Util {
Util()77     private Util() {} // this is a helper class with static methods :)
78 
79     private volatile static Robot robot;
80 
81     /*
82      * @throws RuntimeException when creation failed
83      */
createRobot()84     public static Robot createRobot() {
85         try {
86             if (robot == null) {
87                 robot = new Robot();
88             }
89             return robot;
90         } catch (AWTException e) {
91             throw new RuntimeException("Error: unable to create robot", e);
92         }
93     }
94 
95 
96     /**
97      * Makes the window visible and waits until it's shown.
98      */
showWindowWait(Window win)99     public static void showWindowWait(Window win) {
100         win.setVisible(true);
101         waitTillShown(win);
102     }
103 
104     /**
105      * Moves mouse pointer in the center of given {@code comp} component
106      * using {@code robot} parameter.
107      */
pointOnComp(final Component comp, final Robot robot)108     public static void pointOnComp(final Component comp, final Robot robot) {
109         Rectangle bounds = new Rectangle(comp.getLocationOnScreen(), comp.getSize());
110         robot.mouseMove(bounds.x + bounds.width / 2, bounds.y + bounds.height / 2);
111     }
112 
113     /**
114      * Moves mouse pointer in the center of a given {@code comp} component
115      * and performs a left mouse button click using the {@code robot} parameter
116      * with the {@code delay} delay between press and release.
117      */
clickOnComp(final Component comp, final Robot robot, int delay)118     public static void clickOnComp(final Component comp, final Robot robot, int delay) {
119         pointOnComp(comp, robot);
120         robot.delay(delay);
121         robot.mousePress(InputEvent.BUTTON1_MASK);
122         robot.delay(delay);
123         robot.mouseRelease(InputEvent.BUTTON1_MASK);
124     }
125 
126     /**
127      * Moves mouse pointer in the center of a given {@code comp} component
128      * and performs a left mouse button click using the {@code robot} parameter
129      * with the default delay between press and release.
130      */
clickOnComp(final Component comp, final Robot robot)131     public static void clickOnComp(final Component comp, final Robot robot) {
132         clickOnComp(comp, robot, 50);
133     }
134 
getTitlePoint(Window decoratedWindow)135     public static Point getTitlePoint(Window decoratedWindow) {
136         Point p = decoratedWindow.getLocationOnScreen();
137         Dimension d = decoratedWindow.getSize();
138         return new Point(p.x + (int)(d.getWidth()/2),
139                          p.y + (int)(decoratedWindow.getInsets().top/2));
140     }
141 
142     /*
143      * Clicks on a title of Frame/Dialog.
144      * WARNING: it may fail on some platforms when the window is not wide enough.
145      */
clickOnTitle(final Window decoratedWindow, final Robot robot)146     public static void clickOnTitle(final Window decoratedWindow, final Robot robot) {
147         if (decoratedWindow instanceof Frame || decoratedWindow instanceof Dialog) {
148             Point p = getTitlePoint(decoratedWindow);
149             robot.mouseMove(p.x, p.y);
150             robot.delay(50);
151             robot.mousePress(InputEvent.BUTTON1_MASK);
152             robot.delay(50);
153             robot.mouseRelease(InputEvent.BUTTON1_MASK);
154         }
155     }
156 
157     /**
158      * Tests whether screen pixel has the expected color performing several
159      * attempts. This method is useful for asynchronous window manager where
160      * it's impossible to determine when drawing actually takes place.
161      *
162      * @param x X position of pixel
163      * @param y Y position of pixel
164      * @param color expected color
165      * @param attempts number of attempts to undertake
166      * @param delay delay before each attempt
167      * @param robot a robot to use for retrieving pixel color
168      * @return true if pixel color matches the color expected, otherwise false
169      */
testPixelColor(int x, int y, final Color color, int attempts, int delay, final Robot robot)170     public static boolean testPixelColor(int x, int y, final Color color, int attempts, int delay, final Robot robot) {
171         while (attempts-- > 0) {
172             robot.delay(delay);
173             Color screen = robot.getPixelColor(x, y);
174             if (screen.equals(color)) {
175                 return true;
176             }
177         }
178         return false;
179     }
180 
181     /**
182      * Tests whether the area within boundaries has the expected color
183      * performing several attempts. This method is useful for asynchronous
184      * window manager where it's impossible to determine when drawing actually
185      * takes place.
186      *
187      * @param bounds position of area
188      * @param color expected color
189      * @param attempts number of attempts to undertake
190      * @param delay delay before each attempt
191      * @param robot a robot to use for retrieving pixel color
192      * @return true if area color matches the color expected, otherwise false
193      */
testBoundsColor(final Rectangle bounds, final Color color, int attempts, int delay, final Robot robot)194     public static boolean testBoundsColor(final Rectangle bounds, final Color color, int attempts, int delay, final Robot robot) {
195         int right = bounds.x + bounds.width - 1;
196         int bottom = bounds.y + bounds.height - 1;
197         while (attempts-- > 0) {
198             if (testPixelColor(bounds.x, bounds.y, color, 1, delay, robot)
199                 && testPixelColor(right, bounds.y, color, 1, 0, robot)
200                 && testPixelColor(right, bottom, color, 1, 0, robot)
201                 && testPixelColor(bounds.x, bottom, color, 1, 0, robot)) {
202                 return true;
203             }
204         }
205         return false;
206     }
207 
waitForIdle(Robot robot)208     public static void waitForIdle(Robot robot) {
209         if (robot == null) {
210             robot = createRobot();
211         }
212         robot.waitForIdle();
213     }
214 
215 
216     /*
217      * Waits for a notification and for a boolean condition to become true.
218      * The method returns when the above conditions are fullfilled or when the timeout
219      * occurs.
220      *
221      * @param condition the object to be notified and the booelan condition to wait for
222      * @param timeout the maximum time to wait in milliseconds
223      * @param catchExceptions if {@code true} the method catches InterruptedException
224      * @return the final boolean value of the {@code condition}
225      * @throws InterruptedException if the awaiting proccess has been interrupted
226      */
waitForConditionEx(final AtomicBoolean condition, long timeout)227     public static boolean waitForConditionEx(final AtomicBoolean condition, long timeout)
228       throws InterruptedException
229         {
230             synchronized (condition) {
231                 long startTime = System.currentTimeMillis();
232                 while (!condition.get()) {
233                     condition.wait(timeout);
234                     if (System.currentTimeMillis() - startTime >= timeout ) {
235                         break;
236                     }
237                 }
238             }
239             return condition.get();
240         }
241 
242     /*
243      * The same as {@code waitForConditionEx(AtomicBoolean, long)} except that it
244      * doesn't throw InterruptedException.
245      */
waitForCondition(final AtomicBoolean condition, long timeout)246     public static boolean waitForCondition(final AtomicBoolean condition, long timeout) {
247         try {
248             return waitForConditionEx(condition, timeout);
249         } catch (InterruptedException e) {
250             throw new RuntimeException("Error: unexpected exception caught!", e);
251         }
252     }
253 
254     /*
255      * The same as {@code waitForConditionEx(AtomicBoolean, long)} but without a timeout.
256      */
waitForConditionEx(final AtomicBoolean condition)257     public static void waitForConditionEx(final AtomicBoolean condition)
258       throws InterruptedException
259         {
260             synchronized (condition) {
261                 while (!condition.get()) {
262                     condition.wait();
263                 }
264             }
265         }
266 
267     /*
268      * The same as {@code waitForConditionEx(AtomicBoolean)} except that it
269      * doesn't throw InterruptedException.
270      */
waitForCondition(final AtomicBoolean condition)271     public static void waitForCondition(final AtomicBoolean condition) {
272         try {
273             waitForConditionEx(condition);
274         } catch (InterruptedException e) {
275             throw new RuntimeException("Error: unexpected exception caught!", e);
276         }
277     }
278 
waitTillShownEx(final Component comp)279     public static void waitTillShownEx(final Component comp) throws InterruptedException {
280         while (true) {
281             try {
282                 Thread.sleep(100);
283                 comp.getLocationOnScreen();
284                 break;
285             } catch (IllegalComponentStateException e) {}
286         }
287     }
waitTillShown(final Component comp)288     public static void waitTillShown(final Component comp) {
289         try {
290             waitTillShownEx(comp);
291         } catch (InterruptedException e) {
292             throw new RuntimeException("Error: unexpected exception caught!", e);
293         }
294     }
295 
296     /**
297      * Drags from one point to another with the specified mouse button pressed.
298      *
299      * @param robot a robot to use for moving the mouse, etc.
300      * @param startPoint a start point of the drag
301      * @param endPoint an end point of the drag
302      * @param button one of {@code InputEvent.BUTTON1_MASK},
303      *     {@code InputEvent.BUTTON2_MASK}, {@code InputEvent.BUTTON3_MASK}
304      *
305      * @throws IllegalArgumentException if {@code button} is not one of
306      *     {@code InputEvent.BUTTON1_MASK}, {@code InputEvent.BUTTON2_MASK},
307      *     {@code InputEvent.BUTTON3_MASK}
308      */
drag(Robot robot, Point startPoint, Point endPoint, int button)309     public static void drag(Robot robot, Point startPoint, Point endPoint, int button) {
310         if (!(button == InputEvent.BUTTON1_MASK || button == InputEvent.BUTTON2_MASK
311                 || button == InputEvent.BUTTON3_MASK))
312         {
313             throw new IllegalArgumentException("invalid mouse button");
314         }
315 
316         robot.mouseMove(startPoint.x, startPoint.y);
317         robot.mousePress(button);
318         try {
319             mouseMove(robot, startPoint, endPoint);
320         } finally {
321             robot.mouseRelease(button);
322         }
323     }
324 
325     /**
326      * Moves the mouse pointer from one point to another.
327      * Uses Bresenham's algorithm.
328      *
329      * @param robot a robot to use for moving the mouse
330      * @param startPoint a start point of the drag
331      * @param endPoint an end point of the drag
332      */
mouseMove(Robot robot, Point startPoint, Point endPoint)333     public static void mouseMove(Robot robot, Point startPoint, Point endPoint) {
334         int dx = endPoint.x - startPoint.x;
335         int dy = endPoint.y - startPoint.y;
336 
337         int ax = Math.abs(dx) * 2;
338         int ay = Math.abs(dy) * 2;
339 
340         int sx = signWOZero(dx);
341         int sy = signWOZero(dy);
342 
343         int x = startPoint.x;
344         int y = startPoint.y;
345 
346         int d = 0;
347 
348         if (ax > ay) {
349             d = ay - ax/2;
350             while (true){
351                 robot.mouseMove(x, y);
352                 robot.delay(50);
353 
354                 if (x == endPoint.x){
355                     return;
356                 }
357                 if (d >= 0){
358                     y = y + sy;
359                     d = d - ax;
360                 }
361                 x = x + sx;
362                 d = d + ay;
363             }
364         } else {
365             d = ax - ay/2;
366             while (true){
367                 robot.mouseMove(x, y);
368                 robot.delay(50);
369 
370                 if (y == endPoint.y){
371                     return;
372                 }
373                 if (d >= 0){
374                     x = x + sx;
375                     d = d - ay;
376                 }
377                 y = y + sy;
378                 d = d + ax;
379             }
380         }
381     }
382 
signWOZero(int i)383     private static int signWOZero(int i){
384         return (i > 0)? 1: -1;
385     }
386 
sign(int n)387     private static int sign(int n) {
388         return n < 0 ? -1 : n == 0 ? 0 : 1;
389     }
390 
391     /** Returns {@code WindowListener} instance that diposes {@code Window} on
392      *  "window closing" event.
393      *
394      * @return    the {@code WindowListener} instance that could be set
395      *            on a {@code Window}. After that
396      *            the {@code Window} is disposed when "window closed"
397      *            event is sent to the {@code Window}
398      */
getClosingWindowAdapter()399     public static WindowListener getClosingWindowAdapter() {
400         return new WindowAdapter () {
401             public void windowClosing(WindowEvent e) {
402                 e.getWindow().dispose();
403             }
404         };
405     }
406 
407     /*
408      * The values directly map to the ones of
409      * sun.awt.X11.XWM & sun.awt.motif.MToolkit classes.
410      */
411     public final static int
412         UNDETERMINED_WM = 1,
413         NO_WM = 2,
414         OTHER_WM = 3,
415         OPENLOOK_WM = 4,
416         MOTIF_WM = 5,
417         CDE_WM = 6,
418         ENLIGHTEN_WM = 7,
419         KDE2_WM = 8,
420         SAWFISH_WM = 9,
421         ICE_WM = 10,
422         METACITY_WM = 11,
423         COMPIZ_WM = 12,
424         LG3D_WM = 13,
425         CWM_WM = 14,
426         MUTTER_WM = 15;
427 
428     /*
429      * Returns -1 in case of not X Window or any problems.
430      */
431     public static int getWMID() {
432         Class clazz = null;
433         try {
434             if ("sun.awt.X11.XToolkit".equals(Toolkit.getDefaultToolkit().getClass().getName())) {
435                 clazz = Class.forName("sun.awt.X11.XWM");
436             } else if ("sun.awt.motif.MToolkit".equals(Toolkit.getDefaultToolkit().getClass().getName())) {
437                 clazz = Class.forName("sun.awt.motif.MToolkit");
438             }
439         } catch (ClassNotFoundException cnfe) {
440             cnfe.printStackTrace();
441         }
442         if (clazz == null) {
443             return -1;
444         }
445 
446         try {
447             final Class _clazz = clazz;
448             Method m_addExports = Class.forName("java.awt.Helper").getDeclaredMethod("addExports", String.class, java.lang.Module.class);
449             // No MToolkit anymore: nothing to do about it.
450             // We may be called from non-X11 system, and this permission cannot be delegated to a test.
451             m_addExports.invoke(null, "sun.awt.X11", Util.class.getModule());
452             Method m_getWMID = (Method)AccessController.doPrivileged(new PrivilegedAction() {
453                     public Object run() {
454                         try {
455                             Method method = _clazz.getDeclaredMethod("getWMID", new Class[] {});
456                             if (method != null) {
457                                 method.setAccessible(true);
458                             }
459                             return method;
460                         } catch (NoSuchMethodException e) {
461                             assert false;
462                         } catch (SecurityException e) {
463                             assert false;
464                         }
465                         return null;
466                     }
467                 });
468             return ((Integer)m_getWMID.invoke(null, new Object[] {})).intValue();
469         } catch (ClassNotFoundException cnfe) {
470             cnfe.printStackTrace();
471         } catch (NoSuchMethodException nsme) {
472             nsme.printStackTrace();
473         } catch (IllegalAccessException iae) {
474             iae.printStackTrace();
475         } catch (InvocationTargetException ite) {
476             ite.printStackTrace();
477         }
478         return -1;
479     }
480 
481     //Cleans all the references
482     public static void cleanUp() {
483         apListener = null;
484         fgListener = null;
485         wgfListener = null;
486     }
487 
488 
489     ////////////////////////////
490     // Some stuff to test focus.
491     ////////////////////////////
492 
493     private static WindowGainedFocusListener wgfListener = new WindowGainedFocusListener();
494     private static FocusGainedListener fgListener = new FocusGainedListener();
495     private static ActionPerformedListener apListener = new ActionPerformedListener();
496 
497     private abstract static class EventListener {
498         AtomicBoolean notifier = new AtomicBoolean(false);
499         Component comp;
500         boolean printEvent;
501 
502         public void listen(Component comp, boolean printEvent) {
503             this.comp = comp;
504             this.printEvent = printEvent;
505             notifier.set(false);
506             setListener(comp);
507         }
508 
509         public AtomicBoolean getNotifier() {
510             return notifier;
511         }
512 
513         abstract void setListener(Component comp);
514 
515         void printAndNotify(AWTEvent e) {
516             if (printEvent) {
517                 System.err.println(e);
518             }
519             synchronized (notifier) {
520                 notifier.set(true);
521                 notifier.notifyAll();
522             }
523         }
524     }
525 
526     private static class WindowGainedFocusListener extends EventListener implements WindowFocusListener {
527 
528         void setListener(Component comp) {
529             ((Window)comp).addWindowFocusListener(this);
530         }
531 
532         public void windowGainedFocus(WindowEvent e) {
533 
534             ((Window)comp).removeWindowFocusListener(this);
535             printAndNotify(e);
536         }
537 
538         public void windowLostFocus(WindowEvent e) {}
539     }
540 
541     private static class FocusGainedListener extends EventListener implements FocusListener {
542 
543         void setListener(Component comp) {
544             comp.addFocusListener(this);
545         }
546 
547         public void focusGained(FocusEvent e) {
548             comp.removeFocusListener(this);
549             printAndNotify(e);
550         }
551 
552         public void focusLost(FocusEvent e) {}
553     }
554 
555     private static class ActionPerformedListener extends EventListener implements ActionListener {
556 
557         void setListener(Component comp) {
558             ((Button)comp).addActionListener(this);
559         }
560 
561         public void actionPerformed(ActionEvent e) {
562             ((Button)comp).removeActionListener(this);
563             printAndNotify(e);
564         }
565     }
566 
567     private static boolean trackEvent(int eventID, Component comp, Runnable action, int time, boolean printEvent) {
568         EventListener listener = null;
569 
570         switch (eventID) {
571         case WindowEvent.WINDOW_GAINED_FOCUS:
572             listener = wgfListener;
573             break;
574         case FocusEvent.FOCUS_GAINED:
575             listener = fgListener;
576             break;
577         case ActionEvent.ACTION_PERFORMED:
578             listener = apListener;
579             break;
580         }
581 
582         listener.listen(comp, printEvent);
583         action.run();
584         return Util.waitForCondition(listener.getNotifier(), time);
585     }
586 
587     /*
588      * Tracks WINDOW_GAINED_FOCUS event for a window caused by an action.
589      * @param window the window to track the event for
590      * @param action the action to perform
591      * @param time the max time to wait for the event
592      * @param printEvent should the event received be printed or doesn't
593      * @return true if the event has been received, otherwise false
594      */
595     public static boolean trackWindowGainedFocus(Window window, Runnable action, int time, boolean printEvent) {
596         return trackEvent(WindowEvent.WINDOW_GAINED_FOCUS, window, action, time, printEvent);
597     }
598 
599     /*
600      * Tracks FOCUS_GAINED event for a component caused by an action.
601      * @see #trackWindowGainedFocus
602      */
603     public static boolean trackFocusGained(Component comp, Runnable action, int time, boolean printEvent) {
604         return trackEvent(FocusEvent.FOCUS_GAINED, comp, action, time, printEvent);
605     }
606 
607     /*
608      * Tracks ACTION_PERFORMED event for a button caused by an action.
609      * @see #trackWindowGainedFocus
610      */
611     public static boolean trackActionPerformed(Button button, Runnable action, int time, boolean printEvent) {
612         return trackEvent(ActionEvent.ACTION_PERFORMED, button, action, time, printEvent);
613     }
614 
615     /*
616      * Requests focus on the component provided and waits for the result.
617      * @return true if the component has been focused, false otherwise.
618      */
619     public static boolean focusComponent(Component comp, int time) {
620         return focusComponent(comp, time, false);
621     }
622     public static boolean focusComponent(final Component comp, int time, boolean printEvent) {
623         return trackFocusGained(comp,
624                                 new Runnable() {
625                                     public void run() {
626                                         comp.requestFocus();
627                                     }
628                                 },
629                                 time, printEvent);
630 
631     }
632 
633 
634     /**
635      * Invokes the <code>task</code> on the EDT thread.
636      *
637      * @return result of the <code>task</code>
638      */
639     public static <T> T invokeOnEDT(final java.util.concurrent.Callable<T> task) throws Exception {
640         final java.util.List<T> result = new java.util.ArrayList<T>(1);
641         final Exception[] exception = new Exception[1];
642 
643         javax.swing.SwingUtilities.invokeAndWait(new Runnable() {
644 
645             @Override
646             public void run() {
647                 try {
648                     result.add(task.call());
649                 } catch (Exception e) {
650                     exception[0] = e;
651                 }
652             }
653         });
654 
655         if (exception[0] != null) {
656             throw exception[0];
657         }
658 
659         return result.get(0);
660     }
661 
662 }
663