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     private volatile static Robot robot;
  80 
  81     /*
  82      * @throws RuntimeException when creation failed
  83      */
  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      */
  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      */
 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      */
 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      */
 131     public static void clickOnComp(final Component comp, final Robot robot) {
 132         clickOnComp(comp, robot, 50);
 133     }
 134 
 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      */
 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      */
 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      */
 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 
 208     public static void waitForIdle(Robot robot) {
 209         if (robot == null) {
 210             robot = createRobot();
 211         }
 212         robot.waitForIdle();
 213     }
 214 
 215     public static Field getField(final Class klass, final String fieldName) {
 216         return AccessController.doPrivileged(new PrivilegedAction<Field>() {
 217             public Field run() {
 218                 try {
 219                     Field field = klass.getDeclaredField(fieldName);
 220                     assert (field != null);
 221                     field.setAccessible(true);
 222                     return field;
 223                 } catch (SecurityException se) {
 224                     throw new RuntimeException("Error: unexpected exception caught!", se);
 225                 } catch (NoSuchFieldException nsfe) {
 226                     throw new RuntimeException("Error: unexpected exception caught!", nsfe);
 227                 }
 228             }
 229         });
 230     }
 231 
 232     /*
 233      * Waits for a notification and for a boolean condition to become true.
 234      * The method returns when the above conditions are fullfilled or when the timeout
 235      * occurs.
 236      *
 237      * @param condition the object to be notified and the booelan condition to wait for
 238      * @param timeout the maximum time to wait in milliseconds
 239      * @param catchExceptions if {@code true} the method catches InterruptedException
 240      * @return the final boolean value of the {@code condition}
 241      * @throws InterruptedException if the awaiting proccess has been interrupted
 242      */
 243     public static boolean waitForConditionEx(final AtomicBoolean condition, long timeout)
 244       throws InterruptedException
 245         {
 246             synchronized (condition) {
 247                 long startTime = System.currentTimeMillis();
 248                 while (!condition.get()) {
 249                     condition.wait(timeout);
 250                     if (System.currentTimeMillis() - startTime >= timeout ) {
 251                         break;
 252                     }
 253                 }
 254             }
 255             return condition.get();
 256         }
 257 
 258     /*
 259      * The same as {@code waitForConditionEx(AtomicBoolean, long)} except that it
 260      * doesn't throw InterruptedException.
 261      */
 262     public static boolean waitForCondition(final AtomicBoolean condition, long timeout) {
 263         try {
 264             return waitForConditionEx(condition, timeout);
 265         } catch (InterruptedException e) {
 266             throw new RuntimeException("Error: unexpected exception caught!", e);
 267         }
 268     }
 269 
 270     /*
 271      * The same as {@code waitForConditionEx(AtomicBoolean, long)} but without a timeout.
 272      */
 273     public static void waitForConditionEx(final AtomicBoolean condition)
 274       throws InterruptedException
 275         {
 276             synchronized (condition) {
 277                 while (!condition.get()) {
 278                     condition.wait();
 279                 }
 280             }
 281         }
 282 
 283     /*
 284      * The same as {@code waitForConditionEx(AtomicBoolean)} except that it
 285      * doesn't throw InterruptedException.
 286      */
 287     public static void waitForCondition(final AtomicBoolean condition) {
 288         try {
 289             waitForConditionEx(condition);
 290         } catch (InterruptedException e) {
 291             throw new RuntimeException("Error: unexpected exception caught!", e);
 292         }
 293     }
 294 
 295     public static void waitTillShownEx(final Component comp) throws InterruptedException {
 296         while (true) {
 297             try {
 298                 Thread.sleep(100);
 299                 comp.getLocationOnScreen();
 300                 break;
 301             } catch (IllegalComponentStateException e) {}
 302         }
 303     }
 304     public static void waitTillShown(final Component comp) {
 305         try {
 306             waitTillShownEx(comp);
 307         } catch (InterruptedException e) {
 308             throw new RuntimeException("Error: unexpected exception caught!", e);
 309         }
 310     }
 311 
 312     /**
 313      * Drags from one point to another with the specified mouse button pressed.
 314      *
 315      * @param robot a robot to use for moving the mouse, etc.
 316      * @param startPoint a start point of the drag
 317      * @param endPoint an end point of the drag
 318      * @param button one of {@code InputEvent.BUTTON1_MASK},
 319      *     {@code InputEvent.BUTTON2_MASK}, {@code InputEvent.BUTTON3_MASK}
 320      *
 321      * @throws IllegalArgumentException if {@code button} is not one of
 322      *     {@code InputEvent.BUTTON1_MASK}, {@code InputEvent.BUTTON2_MASK},
 323      *     {@code InputEvent.BUTTON3_MASK}
 324      */
 325     public static void drag(Robot robot, Point startPoint, Point endPoint, int button) {
 326         if (!(button == InputEvent.BUTTON1_MASK || button == InputEvent.BUTTON2_MASK
 327                 || button == InputEvent.BUTTON3_MASK))
 328         {
 329             throw new IllegalArgumentException("invalid mouse button");
 330         }
 331 
 332         robot.mouseMove(startPoint.x, startPoint.y);
 333         robot.mousePress(button);
 334         try {
 335             mouseMove(robot, startPoint, endPoint);
 336         } finally {
 337             robot.mouseRelease(button);
 338         }
 339     }
 340 
 341     /**
 342      * Moves the mouse pointer from one point to another.
 343      * Uses Bresenham's algorithm.
 344      *
 345      * @param robot a robot to use for moving the mouse
 346      * @param startPoint a start point of the drag
 347      * @param endPoint an end point of the drag
 348      */
 349     public static void mouseMove(Robot robot, Point startPoint, Point endPoint) {
 350         int dx = endPoint.x - startPoint.x;
 351         int dy = endPoint.y - startPoint.y;
 352 
 353         int ax = Math.abs(dx) * 2;
 354         int ay = Math.abs(dy) * 2;
 355 
 356         int sx = signWOZero(dx);
 357         int sy = signWOZero(dy);
 358 
 359         int x = startPoint.x;
 360         int y = startPoint.y;
 361 
 362         int d = 0;
 363 
 364         if (ax > ay) {
 365             d = ay - ax/2;
 366             while (true){
 367                 robot.mouseMove(x, y);
 368                 robot.delay(50);
 369 
 370                 if (x == endPoint.x){
 371                     return;
 372                 }
 373                 if (d >= 0){
 374                     y = y + sy;
 375                     d = d - ax;
 376                 }
 377                 x = x + sx;
 378                 d = d + ay;
 379             }
 380         } else {
 381             d = ax - ay/2;
 382             while (true){
 383                 robot.mouseMove(x, y);
 384                 robot.delay(50);
 385 
 386                 if (y == endPoint.y){
 387                     return;
 388                 }
 389                 if (d >= 0){
 390                     x = x + sx;
 391                     d = d - ay;
 392                 }
 393                 y = y + sy;
 394                 d = d + ax;
 395             }
 396         }
 397     }
 398 
 399     private static int signWOZero(int i){
 400         return (i > 0)? 1: -1;
 401     }
 402 
 403     private static int sign(int n) {
 404         return n < 0 ? -1 : n == 0 ? 0 : 1;
 405     }
 406 
 407     /** Returns {@code WindowListener} instance that diposes {@code Window} on
 408      *  "window closing" event.
 409      *
 410      * @return    the {@code WindowListener} instance that could be set
 411      *            on a {@code Window}. After that
 412      *            the {@code Window} is disposed when "window closed"
 413      *            event is sent to the {@code Window}
 414      */
 415     public static WindowListener getClosingWindowAdapter() {
 416         return new WindowAdapter () {
 417             public void windowClosing(WindowEvent e) {
 418                 e.getWindow().dispose();
 419             }
 420         };
 421     }
 422 
 423     /*
 424      * The values directly map to the ones of
 425      * sun.awt.X11.XWM & sun.awt.motif.MToolkit classes.
 426      */
 427     public final static int
 428         UNDETERMINED_WM = 1,
 429         NO_WM = 2,
 430         OTHER_WM = 3,
 431         OPENLOOK_WM = 4,
 432         MOTIF_WM = 5,
 433         CDE_WM = 6,
 434         ENLIGHTEN_WM = 7,
 435         KDE2_WM = 8,
 436         SAWFISH_WM = 9,
 437         ICE_WM = 10,
 438         METACITY_WM = 11,
 439         COMPIZ_WM = 12,
 440         LG3D_WM = 13,
 441         CWM_WM = 14,
 442         MUTTER_WM = 15;
 443 
 444     /*
 445      * Returns -1 in case of not X Window or any problems.
 446      */
 447     public static int getWMID() {
 448         Class clazz = null;
 449         try {
 450             if ("sun.awt.X11.XToolkit".equals(Toolkit.getDefaultToolkit().getClass().getName())) {
 451                 clazz = Class.forName("sun.awt.X11.XWM");
 452             } else if ("sun.awt.motif.MToolkit".equals(Toolkit.getDefaultToolkit().getClass().getName())) {
 453                 clazz = Class.forName("sun.awt.motif.MToolkit");
 454             }
 455         } catch (ClassNotFoundException cnfe) {
 456             cnfe.printStackTrace();
 457         }
 458         if (clazz == null) {
 459             return -1;
 460         }
 461 
 462         try {
 463             final Class _clazz = clazz;
 464             Method m_getWMID = (Method)AccessController.doPrivileged(new PrivilegedAction() {
 465                     public Object run() {
 466                         try {
 467                             Method method = _clazz.getDeclaredMethod("getWMID", new Class[] {});
 468                             if (method != null) {
 469                                 method.setAccessible(true);
 470                             }
 471                             return method;
 472                         } catch (NoSuchMethodException e) {
 473                             assert false;
 474                         } catch (SecurityException e) {
 475                             assert false;
 476                         }
 477                         return null;
 478                     }
 479                 });
 480             return ((Integer)m_getWMID.invoke(null, new Object[] {})).intValue();
 481         } catch (IllegalAccessException iae) {
 482             iae.printStackTrace();
 483         } catch (InvocationTargetException ite) {
 484             ite.printStackTrace();
 485         }
 486         return -1;
 487     }
 488 
 489     //Cleans all the references
 490     public static void cleanUp() {
 491         apListener = null;
 492         fgListener = null;
 493         wgfListener = null;
 494     }
 495 
 496 
 497     ////////////////////////////
 498     // Some stuff to test focus.
 499     ////////////////////////////
 500 
 501     private static WindowGainedFocusListener wgfListener = new WindowGainedFocusListener();
 502     private static FocusGainedListener fgListener = new FocusGainedListener();
 503     private static ActionPerformedListener apListener = new ActionPerformedListener();
 504 
 505     private abstract static class EventListener {
 506         AtomicBoolean notifier = new AtomicBoolean(false);
 507         Component comp;
 508         boolean printEvent;
 509 
 510         public void listen(Component comp, boolean printEvent) {
 511             this.comp = comp;
 512             this.printEvent = printEvent;
 513             notifier.set(false);
 514             setListener(comp);
 515         }
 516 
 517         public AtomicBoolean getNotifier() {
 518             return notifier;
 519         }
 520 
 521         abstract void setListener(Component comp);
 522 
 523         void printAndNotify(AWTEvent e) {
 524             if (printEvent) {
 525                 System.err.println(e);
 526             }
 527             synchronized (notifier) {
 528                 notifier.set(true);
 529                 notifier.notifyAll();
 530             }
 531         }
 532     }
 533 
 534     private static class WindowGainedFocusListener extends EventListener implements WindowFocusListener {
 535 
 536         void setListener(Component comp) {
 537             ((Window)comp).addWindowFocusListener(this);
 538         }
 539 
 540         public void windowGainedFocus(WindowEvent e) {
 541 
 542             ((Window)comp).removeWindowFocusListener(this);
 543             printAndNotify(e);
 544         }
 545 
 546         public void windowLostFocus(WindowEvent e) {}
 547     }
 548 
 549     private static class FocusGainedListener extends EventListener implements FocusListener {
 550 
 551         void setListener(Component comp) {
 552             comp.addFocusListener(this);
 553         }
 554 
 555         public void focusGained(FocusEvent e) {
 556             comp.removeFocusListener(this);
 557             printAndNotify(e);
 558         }
 559 
 560         public void focusLost(FocusEvent e) {}
 561     }
 562 
 563     private static class ActionPerformedListener extends EventListener implements ActionListener {
 564 
 565         void setListener(Component comp) {
 566             ((Button)comp).addActionListener(this);
 567         }
 568 
 569         public void actionPerformed(ActionEvent e) {
 570             ((Button)comp).removeActionListener(this);
 571             printAndNotify(e);
 572         }
 573     }
 574 
 575     private static boolean trackEvent(int eventID, Component comp, Runnable action, int time, boolean printEvent) {
 576         EventListener listener = null;
 577 
 578         switch (eventID) {
 579         case WindowEvent.WINDOW_GAINED_FOCUS:
 580             listener = wgfListener;
 581             break;
 582         case FocusEvent.FOCUS_GAINED:
 583             listener = fgListener;
 584             break;
 585         case ActionEvent.ACTION_PERFORMED:
 586             listener = apListener;
 587             break;
 588         }
 589 
 590         listener.listen(comp, printEvent);
 591         action.run();
 592         return Util.waitForCondition(listener.getNotifier(), time);
 593     }
 594 
 595     /*
 596      * Tracks WINDOW_GAINED_FOCUS event for a window caused by an action.
 597      * @param window the window to track the event for
 598      * @param action the action to perform
 599      * @param time the max time to wait for the event
 600      * @param printEvent should the event received be printed or doesn't
 601      * @return true if the event has been received, otherwise false
 602      */
 603     public static boolean trackWindowGainedFocus(Window window, Runnable action, int time, boolean printEvent) {
 604         return trackEvent(WindowEvent.WINDOW_GAINED_FOCUS, window, action, time, printEvent);
 605     }
 606 
 607     /*
 608      * Tracks FOCUS_GAINED event for a component caused by an action.
 609      * @see #trackWindowGainedFocus
 610      */
 611     public static boolean trackFocusGained(Component comp, Runnable action, int time, boolean printEvent) {
 612         return trackEvent(FocusEvent.FOCUS_GAINED, comp, action, time, printEvent);
 613     }
 614 
 615     /*
 616      * Tracks ACTION_PERFORMED event for a button caused by an action.
 617      * @see #trackWindowGainedFocus
 618      */
 619     public static boolean trackActionPerformed(Button button, Runnable action, int time, boolean printEvent) {
 620         return trackEvent(ActionEvent.ACTION_PERFORMED, button, action, time, printEvent);
 621     }
 622 
 623     /*
 624      * Requests focus on the component provided and waits for the result.
 625      * @return true if the component has been focused, false otherwise.
 626      */
 627     public static boolean focusComponent(Component comp, int time) {
 628         return focusComponent(comp, time, false);
 629     }
 630     public static boolean focusComponent(final Component comp, int time, boolean printEvent) {
 631         return trackFocusGained(comp,
 632                                 new Runnable() {
 633                                     public void run() {
 634                                         comp.requestFocus();
 635                                     }
 636                                 },
 637                                 time, printEvent);
 638 
 639     }
 640 
 641 
 642     /**
 643      * Invokes the <code>task</code> on the EDT thread.
 644      *
 645      * @return result of the <code>task</code>
 646      */
 647     public static <T> T invokeOnEDT(final java.util.concurrent.Callable<T> task) throws Exception {
 648         final java.util.List<T> result = new java.util.ArrayList<T>(1);
 649         final Exception[] exception = new Exception[1];
 650 
 651         javax.swing.SwingUtilities.invokeAndWait(new Runnable() {
 652 
 653             @Override
 654             public void run() {
 655                 try {
 656                     result.add(task.call());
 657                 } catch (Exception e) {
 658                     exception[0] = e;
 659                 }
 660             }
 661         });
 662 
 663         if (exception[0] != null) {
 664             throw exception[0];
 665         }
 666 
 667         return result.get(0);
 668     }
 669 
 670 }