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