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 }