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 {
  77     private Util() {} // this is a helper class with static methods :)
  78 
  79     /*
  80      * @throws RuntimeException when creation failed
  81      */
  82     public static Robot createRobot() {
  83         try {
  84             return new Robot();
  85         } catch (AWTException e) {
  86             throw new RuntimeException("Error: unable to create robot", e);
  87         }
  88     }
  89 
  90 
  91     /**
  92      * Makes the window visible and waits until it's shown.
  93      */
  94     public static void showWindowWait(Window win) {
  95         win.setVisible(true);
  96         waitTillShown(win);
  97     }
  98 
  99     /**
 100      * Moves mouse pointer in the center of given {@code comp} component
 101      * using {@code robot} parameter.
 102      */
 103     public static void pointOnComp(final Component comp, final Robot robot) {
 104         Rectangle bounds = new Rectangle(comp.getLocationOnScreen(), comp.getSize());
 105         robot.mouseMove(bounds.x + bounds.width / 2, bounds.y + bounds.height / 2);
 106     }
 107 
 108     /**
 109      * Moves mouse pointer in the center of a given {@code comp} component
 110      * and performs a left mouse button click using the {@code robot} parameter
 111      * with the {@code delay} delay between press and release.
 112      */
 113     public static void clickOnComp(final Component comp, final Robot robot, int delay) {
 114         pointOnComp(comp, robot);
 115         robot.delay(delay);
 116         robot.mousePress(InputEvent.BUTTON1_MASK);
 117         robot.delay(delay);
 118         robot.mouseRelease(InputEvent.BUTTON1_MASK);
 119     }
 120 
 121     /**
 122      * Moves mouse pointer in the center of a given {@code comp} component
 123      * and performs a left mouse button click using the {@code robot} parameter
 124      * with the default delay between press and release.
 125      */
 126     public static void clickOnComp(final Component comp, final Robot robot) {
 127         clickOnComp(comp, robot, 50);
 128     }
 129 
 130     public static Point getTitlePoint(Window decoratedWindow) {
 131         Point p = decoratedWindow.getLocationOnScreen();
 132         Dimension d = decoratedWindow.getSize();
 133         return new Point(p.x + (int)(d.getWidth()/2),
 134                          p.y + (int)(decoratedWindow.getInsets().top/2));
 135     }
 136 
 137     /*
 138      * Clicks on a title of Frame/Dialog.
 139      * WARNING: it may fail on some platforms when the window is not wide enough.
 140      */
 141     public static void clickOnTitle(final Window decoratedWindow, final Robot robot) {
 142         if (decoratedWindow instanceof Frame || decoratedWindow instanceof Dialog) {
 143             Point p = getTitlePoint(decoratedWindow);
 144             robot.mouseMove(p.x, p.y);
 145             robot.delay(50);
 146             robot.mousePress(InputEvent.BUTTON1_MASK);
 147             robot.delay(50);
 148             robot.mouseRelease(InputEvent.BUTTON1_MASK);
 149         }
 150     }
 151 
 152     /**
 153      * Tests whether screen pixel has the expected color performing several
 154      * attempts. This method is useful for asynchronous window manager where
 155      * it's impossible to determine when drawing actually takes place.
 156      *
 157      * @param x X position of pixel
 158      * @param y Y position of pixel
 159      * @param color expected color
 160      * @param attempts number of attempts to undertake
 161      * @param delay delay before each attempt
 162      * @param robot a robot to use for retrieving pixel color
 163      * @return true if pixel color matches the color expected, otherwise false
 164      */
 165     public static boolean testPixelColor(int x, int y, final Color color, int attempts, int delay, final Robot robot) {
 166         while (attempts-- > 0) {
 167             robot.delay(delay);
 168             Color screen = robot.getPixelColor(x, y);
 169             if (screen.equals(color)) {
 170                 return true;
 171             }
 172         }
 173         return false;
 174     }
 175 
 176     /**
 177      * Tests whether the area within boundaries has the expected color
 178      * performing several attempts. This method is useful for asynchronous
 179      * window manager where it's impossible to determine when drawing actually
 180      * takes place.
 181      *
 182      * @param bounds position of area
 183      * @param color expected color
 184      * @param attempts number of attempts to undertake
 185      * @param delay delay before each attempt
 186      * @param robot a robot to use for retrieving pixel color
 187      * @return true if area color matches the color expected, otherwise false
 188      */
 189     public static boolean testBoundsColor(final Rectangle bounds, final Color color, int attempts, int delay, final Robot robot) {
 190         int right = bounds.x + bounds.width - 1;
 191         int bottom = bounds.y + bounds.height - 1;
 192         while (attempts-- > 0) {
 193             if (testPixelColor(bounds.x, bounds.y, color, 1, delay, robot)
 194                 && testPixelColor(right, bounds.y, color, 1, 0, robot)
 195                 && testPixelColor(right, bottom, color, 1, 0, robot)
 196                 && testPixelColor(bounds.x, bottom, color, 1, 0, robot)) {
 197                 return true;
 198             }
 199         }
 200         return false;
 201     }
 202 
 203     public static void waitForIdle(final Robot robot) {
 204         robot.waitForIdle();
 205     }
 206 
 207     public static Field getField(final Class klass, final String fieldName) {
 208         return AccessController.doPrivileged(new PrivilegedAction<Field>() {
 209             public Field run() {
 210                 try {
 211                     Field field = klass.getDeclaredField(fieldName);
 212                     assert (field != null);
 213                     field.setAccessible(true);
 214                     return field;
 215                 } catch (SecurityException se) {
 216                     throw new RuntimeException("Error: unexpected exception caught!", se);
 217                 } catch (NoSuchFieldException nsfe) {
 218                     throw new RuntimeException("Error: unexpected exception caught!", nsfe);
 219                 }
 220             }
 221         });
 222     }
 223 
 224     /*
 225      * Waits for a notification and for a boolean condition to become true.
 226      * The method returns when the above conditions are fullfilled or when the timeout
 227      * occurs.
 228      *
 229      * @param condition the object to be notified and the booelan condition to wait for
 230      * @param timeout the maximum time to wait in milliseconds
 231      * @param catchExceptions if {@code true} the method catches InterruptedException
 232      * @return the final boolean value of the {@code condition}
 233      * @throws InterruptedException if the awaiting proccess has been interrupted
 234      */
 235     public static boolean waitForConditionEx(final AtomicBoolean condition, long timeout)
 236       throws InterruptedException
 237         {
 238             synchronized (condition) {
 239                 long startTime = System.currentTimeMillis();
 240                 while (!condition.get()) {
 241                     condition.wait(timeout);
 242                     if (System.currentTimeMillis() - startTime >= timeout ) {
 243                         break;
 244                     }
 245                 }
 246             }
 247             return condition.get();
 248         }
 249 
 250     /*
 251      * The same as {@code waitForConditionEx(AtomicBoolean, long)} except that it
 252      * doesn't throw InterruptedException.
 253      */
 254     public static boolean waitForCondition(final AtomicBoolean condition, long timeout) {
 255         try {
 256             return waitForConditionEx(condition, timeout);
 257         } catch (InterruptedException e) {
 258             throw new RuntimeException("Error: unexpected exception caught!", e);
 259         }
 260     }
 261 
 262     /*
 263      * The same as {@code waitForConditionEx(AtomicBoolean, long)} but without a timeout.
 264      */
 265     public static void waitForConditionEx(final AtomicBoolean condition)
 266       throws InterruptedException
 267         {
 268             synchronized (condition) {
 269                 while (!condition.get()) {
 270                     condition.wait();
 271                 }
 272             }
 273         }
 274 
 275     /*
 276      * The same as {@code waitForConditionEx(AtomicBoolean)} except that it
 277      * doesn't throw InterruptedException.
 278      */
 279     public static void waitForCondition(final AtomicBoolean condition) {
 280         try {
 281             waitForConditionEx(condition);
 282         } catch (InterruptedException e) {
 283             throw new RuntimeException("Error: unexpected exception caught!", e);
 284         }
 285     }
 286 
 287     public static void waitTillShownEx(final Component comp) throws InterruptedException {
 288         while (true) {
 289             try {
 290                 Thread.sleep(100);
 291                 comp.getLocationOnScreen();
 292                 break;
 293             } catch (IllegalComponentStateException e) {}
 294         }
 295     }
 296     public static void waitTillShown(final Component comp) {
 297         try {
 298             waitTillShownEx(comp);
 299         } catch (InterruptedException e) {
 300             throw new RuntimeException("Error: unexpected exception caught!", e);
 301         }
 302     }
 303 
 304     /**
 305      * Drags from one point to another with the specified mouse button pressed.
 306      *
 307      * @param robot a robot to use for moving the mouse, etc.
 308      * @param startPoint a start point of the drag
 309      * @param endPoint an end point of the drag
 310      * @param button one of {@code InputEvent.BUTTON1_MASK},
 311      *     {@code InputEvent.BUTTON2_MASK}, {@code InputEvent.BUTTON3_MASK}
 312      *
 313      * @throws IllegalArgumentException if {@code button} is not one of
 314      *     {@code InputEvent.BUTTON1_MASK}, {@code InputEvent.BUTTON2_MASK},
 315      *     {@code InputEvent.BUTTON3_MASK}
 316      */
 317     public static void drag(Robot robot, Point startPoint, Point endPoint, int button) {
 318         if (!(button == InputEvent.BUTTON1_MASK || button == InputEvent.BUTTON2_MASK
 319                 || button == InputEvent.BUTTON3_MASK))
 320         {
 321             throw new IllegalArgumentException("invalid mouse button");
 322         }
 323 
 324         robot.mouseMove(startPoint.x, startPoint.y);
 325         robot.mousePress(button);
 326         try {
 327             mouseMove(robot, startPoint, endPoint);
 328         } finally {
 329             robot.mouseRelease(button);
 330         }
 331     }
 332 
 333     /**
 334      * Moves the mouse pointer from one point to another.
 335      * Uses Bresenham's algorithm.
 336      *
 337      * @param robot a robot to use for moving the mouse
 338      * @param startPoint a start point of the drag
 339      * @param endPoint an end point of the drag
 340      */
 341     public static void mouseMove(Robot robot, Point startPoint, Point endPoint) {
 342         int dx = endPoint.x - startPoint.x;
 343         int dy = endPoint.y - startPoint.y;
 344 
 345         int ax = Math.abs(dx) * 2;
 346         int ay = Math.abs(dy) * 2;
 347 
 348         int sx = signWOZero(dx);
 349         int sy = signWOZero(dy);
 350 
 351         int x = startPoint.x;
 352         int y = startPoint.y;
 353 
 354         int d = 0;
 355 
 356         if (ax > ay) {
 357             d = ay - ax/2;
 358             while (true){
 359                 robot.mouseMove(x, y);
 360                 robot.delay(50);
 361 
 362                 if (x == endPoint.x){
 363                     return;
 364                 }
 365                 if (d >= 0){
 366                     y = y + sy;
 367                     d = d - ax;
 368                 }
 369                 x = x + sx;
 370                 d = d + ay;
 371             }
 372         } else {
 373             d = ax - ay/2;
 374             while (true){
 375                 robot.mouseMove(x, y);
 376                 robot.delay(50);
 377 
 378                 if (y == endPoint.y){
 379                     return;
 380                 }
 381                 if (d >= 0){
 382                     x = x + sx;
 383                     d = d - ay;
 384                 }
 385                 y = y + sy;
 386                 d = d + ax;
 387             }
 388         }
 389     }
 390 
 391     private static int signWOZero(int i){
 392         return (i > 0)? 1: -1;
 393     }
 394 
 395     private static int sign(int n) {
 396         return n < 0 ? -1 : n == 0 ? 0 : 1;
 397     }
 398 
 399     /** Returns {@code WindowListener} instance that diposes {@code Window} on
 400      *  "window closing" event.
 401      *
 402      * @return    the {@code WindowListener} instance that could be set
 403      *            on a {@code Window}. After that
 404      *            the {@code Window} is disposed when "window closed"
 405      *            event is sent to the {@code Window}
 406      */
 407     public static WindowListener getClosingWindowAdapter() {
 408         return new WindowAdapter () {
 409             public void windowClosing(WindowEvent e) {
 410                 e.getWindow().dispose();
 411             }
 412         };
 413     }
 414 
 415     /*
 416      * The values directly map to the ones of
 417      * sun.awt.X11.XWM & sun.awt.motif.MToolkit classes.
 418      */
 419     public final static int
 420         UNDETERMINED_WM = 1,
 421         NO_WM = 2,
 422         OTHER_WM = 3,
 423         OPENLOOK_WM = 4,
 424         MOTIF_WM = 5,
 425         CDE_WM = 6,
 426         ENLIGHTEN_WM = 7,
 427         KDE2_WM = 8,
 428         SAWFISH_WM = 9,
 429         ICE_WM = 10,
 430         METACITY_WM = 11,
 431         COMPIZ_WM = 12,
 432         LG3D_WM = 13,
 433         CWM_WM = 14,
 434         MUTTER_WM = 15;
 435 
 436     /*
 437      * Returns -1 in case of not X Window or any problems.
 438      */
 439     public static int getWMID() {
 440         Class clazz = null;
 441         try {
 442             if ("sun.awt.X11.XToolkit".equals(Toolkit.getDefaultToolkit().getClass().getName())) {
 443                 clazz = Class.forName("sun.awt.X11.XWM");
 444             } else if ("sun.awt.motif.MToolkit".equals(Toolkit.getDefaultToolkit().getClass().getName())) {
 445                 clazz = Class.forName("sun.awt.motif.MToolkit");
 446             }
 447         } catch (ClassNotFoundException cnfe) {
 448             cnfe.printStackTrace();
 449         }
 450         if (clazz == null) {
 451             return -1;
 452         }
 453 
 454         try {
 455             final Class _clazz = clazz;
 456             Method m_getWMID = (Method)AccessController.doPrivileged(new PrivilegedAction() {
 457                     public Object run() {
 458                         try {
 459                             Method method = _clazz.getDeclaredMethod("getWMID", new Class[] {});
 460                             if (method != null) {
 461                                 method.setAccessible(true);
 462                             }
 463                             return method;
 464                         } catch (NoSuchMethodException e) {
 465                             assert false;
 466                         } catch (SecurityException e) {
 467                             assert false;
 468                         }
 469                         return null;
 470                     }
 471                 });
 472             return ((Integer)m_getWMID.invoke(null, new Object[] {})).intValue();
 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 }