1 /*
   2  * Copyright (c) 2006, 2007, 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 
  55 import java.awt.event.InputEvent;
  56 import java.awt.event.WindowAdapter;
  57 import java.awt.event.WindowEvent;
  58 import java.awt.event.ActionEvent;
  59 import java.awt.event.FocusEvent;
  60 import java.awt.event.WindowListener;
  61 import java.awt.event.WindowFocusListener;
  62 import java.awt.event.FocusListener;
  63 import java.awt.event.ActionListener;
  64 
  65 import java.awt.peer.FramePeer;
  66 
  67 import java.lang.reflect.Constructor;
  68 import java.lang.reflect.Field;
  69 import java.lang.reflect.InvocationTargetException;
  70 import java.lang.reflect.Method;
  71 
  72 import java.security.PrivilegedAction;
  73 import java.security.AccessController;
  74 
  75 import java.util.concurrent.atomic.AtomicBoolean;
  76 
  77 public final class Util {
  78     private Util() {} // this is a helper class with static methods :)
  79 
  80     /*
  81      * @throws RuntimeException when creation failed
  82      */
  83     public static Robot createRobot() {
  84         try {
  85             return new Robot();
  86         } catch (AWTException e) {
  87             throw new RuntimeException("Error: unable to create robot", e);
  88         }
  89     }
  90 
  91     public static Frame createEmbeddedFrame(final Frame embedder)
  92         throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException, NoSuchMethodException,
  93                InstantiationException, InvocationTargetException
  94     {
  95         Toolkit tk = Toolkit.getDefaultToolkit();
  96         FramePeer frame_peer = (FramePeer) embedder.getPeer();
  97         System.out.println("frame's peer = " + frame_peer);
  98         if ("sun.awt.windows.WToolkit".equals(tk.getClass().getName())) {
  99             Class comp_peer_class =
 100                 Class.forName("sun.awt.windows.WComponentPeer");
 101             System.out.println("comp peer class = " + comp_peer_class);
 102             Field hwnd_field = comp_peer_class.getDeclaredField("hwnd");
 103             hwnd_field.setAccessible(true);
 104             System.out.println("hwnd_field =" + hwnd_field);
 105             long hwnd = hwnd_field.getLong(frame_peer);
 106             System.out.println("hwnd = " + hwnd);
 107 
 108             Class clazz = Class.forName("sun.awt.windows.WEmbeddedFrame");
 109             Constructor constructor = clazz.getConstructor (new Class [] {Long.TYPE});
 110             return (Frame) constructor.newInstance (new Object[] {hwnd});
 111         } else if ("sun.awt.X11.XToolkit".equals(tk.getClass().getName())) {
 112             Class x_base_window_class = Class.forName("sun.awt.X11.XBaseWindow");
 113             System.out.println("x_base_window_class = " + x_base_window_class);
 114             Method get_window = x_base_window_class.getMethod("getWindow", new Class[0]);
 115             System.out.println("get_window = " + get_window);
 116             long window = (Long) get_window.invoke(frame_peer, new Object[0]);
 117             System.out.println("window = " + window);
 118             Class clazz = Class.forName("sun.awt.X11.XEmbeddedFrame");
 119             Constructor constructor = clazz.getConstructor (new Class [] {Long.TYPE, Boolean.TYPE});
 120             return (Frame) constructor.newInstance (new Object[] {window, true});
 121         }
 122 
 123         throw new RuntimeException("Unexpected toolkit - " + tk);
 124     }
 125 
 126     /**
 127      * Makes the window visible and waits until it's shown.
 128      */
 129     public static void showWindowWait(Window win) {
 130         win.setVisible(true);
 131         waitTillShown(win);
 132     }
 133 
 134     /**
 135      * Moves mouse pointer in the center of given {@code comp} component
 136      * using {@code robot} parameter.
 137      */
 138     public static void pointOnComp(final Component comp, final Robot robot) {
 139         Rectangle bounds = new Rectangle(comp.getLocationOnScreen(), comp.getSize());
 140         robot.mouseMove(bounds.x + bounds.width / 2, bounds.y + bounds.height / 2);
 141     }
 142 
 143     /**
 144      * Moves mouse pointer in the center of a given {@code comp} component
 145      * and performs a left mouse button click using the {@code robot} parameter
 146      * with the {@code delay} delay between press and release.
 147      */
 148     public static void clickOnComp(final Component comp, final Robot robot, int delay) {
 149         pointOnComp(comp, robot);
 150         robot.delay(delay);
 151         robot.mousePress(InputEvent.BUTTON1_MASK);
 152         robot.delay(delay);
 153         robot.mouseRelease(InputEvent.BUTTON1_MASK);
 154     }
 155 
 156     /**
 157      * Moves mouse pointer in the center of a given {@code comp} component
 158      * and performs a left mouse button click using the {@code robot} parameter
 159      * with the default delay between press and release.
 160      */
 161     public static void clickOnComp(final Component comp, final Robot robot) {
 162         clickOnComp(comp, robot, 50);
 163     }
 164 
 165     /*
 166      * Clicks on a title of Frame/Dialog.
 167      * WARNING: it may fail on some platforms when the window is not wide enough.
 168      */
 169     public static void clickOnTitle(final Window decoratedWindow, final Robot robot) {
 170         Point p = decoratedWindow.getLocationOnScreen();
 171         Dimension d = decoratedWindow.getSize();
 172 
 173         if (decoratedWindow instanceof Frame || decoratedWindow instanceof Dialog) {
 174             robot.mouseMove(p.x + (int)(d.getWidth()/2), p.y + (int)decoratedWindow.getInsets().top/2);
 175             robot.delay(50);
 176             robot.mousePress(InputEvent.BUTTON1_MASK);
 177             robot.delay(50);
 178             robot.mouseRelease(InputEvent.BUTTON1_MASK);
 179         }
 180     }
 181 
 182     public static void waitForIdle(final Robot robot) {
 183         // we do not use robot for now, use SunToolkit.realSync() instead
 184         ((sun.awt.SunToolkit)Toolkit.getDefaultToolkit()).realSync();
 185     }
 186 
 187     public static Field getField(final Class klass, final String fieldName) {
 188         return AccessController.doPrivileged(new PrivilegedAction<Field>() {
 189             public Field run() {
 190                 try {
 191                     Field field = klass.getDeclaredField(fieldName);
 192                     assert (field != null);
 193                     field.setAccessible(true);
 194                     return field;
 195                 } catch (SecurityException se) {
 196                     throw new RuntimeException("Error: unexpected exception caught!", se);
 197                 } catch (NoSuchFieldException nsfe) {
 198                     throw new RuntimeException("Error: unexpected exception caught!", nsfe);
 199                 }
 200             }
 201         });
 202     }
 203 
 204     /*
 205      * Waits for a notification and for a boolean condition to become true.
 206      * The method returns when the above conditions are fullfilled or when the timeout
 207      * occurs.
 208      *
 209      * @param condition the object to be notified and the booelan condition to wait for
 210      * @param timeout the maximum time to wait in milliseconds
 211      * @param catchExceptions if {@code true} the method catches InterruptedException
 212      * @return the final boolean value of the {@code condition}
 213      * @throws InterruptedException if the awaiting proccess has been interrupted
 214      */
 215     public static boolean waitForConditionEx(final AtomicBoolean condition, long timeout)
 216       throws InterruptedException
 217         {
 218             synchronized (condition) {
 219                 long startTime = System.currentTimeMillis();
 220                 while (!condition.get()) {
 221                     condition.wait(timeout);
 222                     if (System.currentTimeMillis() - startTime >= timeout ) {
 223                         break;
 224                     }
 225                 }
 226             }
 227             return condition.get();
 228         }
 229 
 230     /*
 231      * The same as {@code waitForConditionEx(AtomicBoolean, long)} except that it
 232      * doesn't throw InterruptedException.
 233      */
 234     public static boolean waitForCondition(final AtomicBoolean condition, long timeout) {
 235         try {
 236             return waitForConditionEx(condition, timeout);
 237         } catch (InterruptedException e) {
 238             throw new RuntimeException("Error: unexpected exception caught!", e);
 239         }
 240     }
 241 
 242     /*
 243      * The same as {@code waitForConditionEx(AtomicBoolean, long)} but without a timeout.
 244      */
 245     public static void waitForConditionEx(final AtomicBoolean condition)
 246       throws InterruptedException
 247         {
 248             synchronized (condition) {
 249                 while (!condition.get()) {
 250                     condition.wait();
 251                 }
 252             }
 253         }
 254 
 255     /*
 256      * The same as {@code waitForConditionEx(AtomicBoolean)} except that it
 257      * doesn't throw InterruptedException.
 258      */
 259     public static void waitForCondition(final AtomicBoolean condition) {
 260         try {
 261             waitForConditionEx(condition);
 262         } catch (InterruptedException e) {
 263             throw new RuntimeException("Error: unexpected exception caught!", e);
 264         }
 265     }
 266 
 267     public static void waitTillShownEx(final Component comp) throws InterruptedException {
 268         while (true) {
 269             try {
 270                 Thread.sleep(100);
 271                 comp.getLocationOnScreen();
 272                 break;
 273             } catch (IllegalComponentStateException e) {}
 274         }
 275     }
 276     public static void waitTillShown(final Component comp) {
 277         try {
 278             waitTillShownEx(comp);
 279         } catch (InterruptedException e) {
 280             throw new RuntimeException("Error: unexpected exception caught!", e);
 281         }
 282     }
 283 
 284     /**
 285      * Drags from one point to another with the specified mouse button pressed.
 286      *
 287      * @param robot a robot to use for moving the mouse, etc.
 288      * @param startPoint a start point of the drag
 289      * @param endPoint an end point of the drag
 290      * @param button one of {@code InputEvent.BUTTON1_MASK},
 291      *     {@code InputEvent.BUTTON2_MASK}, {@code InputEvent.BUTTON3_MASK}
 292      *
 293      * @throws IllegalArgumentException if {@code button} is not one of
 294      *     {@code InputEvent.BUTTON1_MASK}, {@code InputEvent.BUTTON2_MASK},
 295      *     {@code InputEvent.BUTTON3_MASK}
 296      */
 297     public static void drag(Robot robot, Point startPoint, Point endPoint, int button) {
 298         if (!(button == InputEvent.BUTTON1_MASK || button == InputEvent.BUTTON2_MASK
 299                 || button == InputEvent.BUTTON3_MASK))
 300         {
 301             throw new IllegalArgumentException("invalid mouse button");
 302         }
 303 
 304         robot.mouseMove(startPoint.x, startPoint.y);
 305         robot.mousePress(button);
 306         try {
 307             mouseMove(robot, startPoint, endPoint);
 308         } finally {
 309             robot.mouseRelease(button);
 310         }
 311     }
 312 
 313     /**
 314      * Moves the mouse pointer from one point to another.
 315      * Uses Bresenham's algorithm.
 316      *
 317      * @param robot a robot to use for moving the mouse
 318      * @param startPoint a start point of the drag
 319      * @param endPoint an end point of the drag
 320      */
 321     public static void mouseMove(Robot robot, Point startPoint, Point endPoint) {
 322         int dx = endPoint.x - startPoint.x;
 323         int dy = endPoint.y - startPoint.y;
 324 
 325         int ax = Math.abs(dx) * 2;
 326         int ay = Math.abs(dy) * 2;
 327 
 328         int sx = signWOZero(dx);
 329         int sy = signWOZero(dy);
 330 
 331         int x = startPoint.x;
 332         int y = startPoint.y;
 333 
 334         int d = 0;
 335 
 336         if (ax > ay) {
 337             d = ay - ax/2;
 338             while (true){
 339                 robot.mouseMove(x, y);
 340                 robot.delay(50);
 341 
 342                 if (x == endPoint.x){
 343                     return;
 344                 }
 345                 if (d >= 0){
 346                     y = y + sy;
 347                     d = d - ax;
 348                 }
 349                 x = x + sx;
 350                 d = d + ay;
 351             }
 352         } else {
 353             d = ax - ay/2;
 354             while (true){
 355                 robot.mouseMove(x, y);
 356                 robot.delay(50);
 357 
 358                 if (y == endPoint.y){
 359                     return;
 360                 }
 361                 if (d >= 0){
 362                     x = x + sx;
 363                     d = d - ay;
 364                 }
 365                 y = y + sy;
 366                 d = d + ax;
 367             }
 368         }
 369     }
 370 
 371     private static int signWOZero(int i){
 372         return (i > 0)? 1: -1;
 373     }
 374 
 375     private static int sign(int n) {
 376         return n < 0 ? -1 : n == 0 ? 0 : 1;
 377     }
 378 
 379     /** Returns {@code WindowListener} instance that diposes {@code Window} on
 380      *  "window closing" event.
 381      *
 382      * @return    the {@code WindowListener} instance that could be set
 383      *            on a {@code Window}. After that
 384      *            the {@code Window} is disposed when "window closed"
 385      *            event is sent to the {@code Window}
 386      */
 387     public static WindowListener getClosingWindowAdapter() {
 388         return new WindowAdapter () {
 389             public void windowClosing(WindowEvent e) {
 390                 e.getWindow().dispose();
 391             }
 392         };
 393     }
 394 
 395     /*
 396      * The values directly map to the ones of
 397      * sun.awt.X11.XWM & sun.awt.motif.MToolkit classes.
 398      */
 399     public final static int
 400         UNDETERMINED_WM = 1,
 401         NO_WM = 2,
 402         OTHER_WM = 3,
 403         OPENLOOK_WM = 4,
 404         MOTIF_WM = 5,
 405         CDE_WM = 6,
 406         ENLIGHTEN_WM = 7,
 407         KDE2_WM = 8,
 408         SAWFISH_WM = 9,
 409         ICE_WM = 10,
 410         METACITY_WM = 11,
 411         COMPIZ_WM = 12,
 412         LG3D_WM = 13;
 413 
 414     /*
 415      * Returns -1 in case of not X Window or any problems.
 416      */
 417     public static int getWMID() {
 418         Class clazz = null;
 419         try {
 420             if ("sun.awt.X11.XToolkit".equals(Toolkit.getDefaultToolkit().getClass().getName())) {
 421                 clazz = Class.forName("sun.awt.X11.XWM");
 422             } else if ("sun.awt.motif.MToolkit".equals(Toolkit.getDefaultToolkit().getClass().getName())) {
 423                 clazz = Class.forName("sun.awt.motif.MToolkit");
 424             }
 425         } catch (ClassNotFoundException cnfe) {
 426             cnfe.printStackTrace();
 427         }
 428         if (clazz == null) {
 429             return -1;
 430         }
 431 
 432         try {
 433             final Class _clazz = clazz;
 434             Method m_getWMID = (Method)AccessController.doPrivileged(new PrivilegedAction() {
 435                     public Object run() {
 436                         try {
 437                             Method method = _clazz.getDeclaredMethod("getWMID", new Class[] {});
 438                             if (method != null) {
 439                                 method.setAccessible(true);
 440                             }
 441                             return method;
 442                         } catch (NoSuchMethodException e) {
 443                             assert false;
 444                         } catch (SecurityException e) {
 445                             assert false;
 446                         }
 447                         return null;
 448                     }
 449                 });
 450             return ((Integer)m_getWMID.invoke(null, new Object[] {})).intValue();
 451         } catch (IllegalAccessException iae) {
 452             iae.printStackTrace();
 453         } catch (InvocationTargetException ite) {
 454             ite.printStackTrace();
 455         }
 456         return -1;
 457     }
 458 
 459 
 460     ////////////////////////////
 461     // Some stuff to test focus.
 462     ////////////////////////////
 463 
 464     private static WindowGainedFocusListener wgfListener = new WindowGainedFocusListener();
 465     private static FocusGainedListener fgListener = new FocusGainedListener();
 466     private static ActionPerformedListener apListener = new ActionPerformedListener();
 467 
 468     private abstract static class EventListener {
 469         AtomicBoolean notifier = new AtomicBoolean(false);
 470         Component comp;
 471         boolean printEvent;
 472 
 473         public void listen(Component comp, boolean printEvent) {
 474             this.comp = comp;
 475             this.printEvent = printEvent;
 476             notifier.set(false);
 477             setListener(comp);
 478         }
 479 
 480         public AtomicBoolean getNotifier() {
 481             return notifier;
 482         }
 483 
 484         abstract void setListener(Component comp);
 485 
 486         void printAndNotify(AWTEvent e) {
 487             if (printEvent) {
 488                 System.err.println(e);
 489             }
 490             synchronized (notifier) {
 491                 notifier.set(true);
 492                 notifier.notifyAll();
 493             }
 494         }
 495     }
 496 
 497     private static class WindowGainedFocusListener extends EventListener implements WindowFocusListener {
 498 
 499         void setListener(Component comp) {
 500             ((Window)comp).addWindowFocusListener(this);
 501         }
 502 
 503         public void windowGainedFocus(WindowEvent e) {
 504 
 505             ((Window)comp).removeWindowFocusListener(this);
 506             printAndNotify(e);
 507         }
 508 
 509         public void windowLostFocus(WindowEvent e) {}
 510     }
 511 
 512     private static class FocusGainedListener extends EventListener implements FocusListener {
 513 
 514         void setListener(Component comp) {
 515             comp.addFocusListener(this);
 516         }
 517 
 518         public void focusGained(FocusEvent e) {
 519             comp.removeFocusListener(this);
 520             printAndNotify(e);
 521         }
 522 
 523         public void focusLost(FocusEvent e) {}
 524     }
 525 
 526     private static class ActionPerformedListener extends EventListener implements ActionListener {
 527 
 528         void setListener(Component comp) {
 529             ((Button)comp).addActionListener(this);
 530         }
 531 
 532         public void actionPerformed(ActionEvent e) {
 533             ((Button)comp).removeActionListener(this);
 534             printAndNotify(e);
 535         }
 536     }
 537 
 538     private static boolean trackEvent(int eventID, Component comp, Runnable action, int time, boolean printEvent) {
 539         EventListener listener = null;
 540 
 541         switch (eventID) {
 542         case WindowEvent.WINDOW_GAINED_FOCUS:
 543             listener = wgfListener;
 544             break;
 545         case FocusEvent.FOCUS_GAINED:
 546             listener = fgListener;
 547             break;
 548         case ActionEvent.ACTION_PERFORMED:
 549             listener = apListener;
 550             break;
 551         }
 552 
 553         listener.listen(comp, printEvent);
 554         action.run();
 555         return Util.waitForCondition(listener.getNotifier(), time);
 556     }
 557 
 558     /*
 559      * Tracks WINDOW_GAINED_FOCUS event for a window caused by an action.
 560      * @param window the window to track the event for
 561      * @param action the action to perform
 562      * @param time the max time to wait for the event
 563      * @param printEvent should the event received be printed or doesn't
 564      * @return true if the event has been received, otherwise false
 565      */
 566     public static boolean trackWindowGainedFocus(Window window, Runnable action, int time, boolean printEvent) {
 567         return trackEvent(WindowEvent.WINDOW_GAINED_FOCUS, window, action, time, printEvent);
 568     }
 569 
 570     /*
 571      * Tracks FOCUS_GAINED event for a component caused by an action.
 572      * @see #trackWindowGainedFocus
 573      */
 574     public static boolean trackFocusGained(Component comp, Runnable action, int time, boolean printEvent) {
 575         return trackEvent(FocusEvent.FOCUS_GAINED, comp, action, time, printEvent);
 576     }
 577 
 578     /*
 579      * Tracks ACTION_PERFORMED event for a button caused by an action.
 580      * @see #trackWindowGainedFocus
 581      */
 582     public static boolean trackActionPerformed(Button button, Runnable action, int time, boolean printEvent) {
 583         return trackEvent(ActionEvent.ACTION_PERFORMED, button, action, time, printEvent);
 584     }
 585 
 586     /*
 587      * Requests focus on the component provided and waits for the result.
 588      * @return true if the component has been focused, false otherwise.
 589      */
 590     public static boolean focusComponent(Component comp, int time) {
 591         return focusComponent(comp, time, false);
 592     }
 593     public static boolean focusComponent(final Component comp, int time, boolean printEvent) {
 594         return trackFocusGained(comp,
 595                                 new Runnable() {
 596                                     public void run() {
 597                                         comp.requestFocus();
 598                                     }
 599                                 },
 600                                 time, printEvent);
 601 
 602     }
 603 }