Util.java revision 13901:b2a69d66dc65
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 24package 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 41import java.awt.Component; 42import java.awt.Frame; 43import java.awt.Dialog; 44import java.awt.Window; 45import java.awt.Button; 46import java.awt.Point; 47import java.awt.Dimension; 48import java.awt.Rectangle; 49import java.awt.Robot; 50import java.awt.Toolkit; 51import java.awt.IllegalComponentStateException; 52import java.awt.AWTException; 53import java.awt.AWTEvent; 54import java.awt.Color; 55 56import java.awt.event.InputEvent; 57import java.awt.event.WindowAdapter; 58import java.awt.event.WindowEvent; 59import java.awt.event.ActionEvent; 60import java.awt.event.FocusEvent; 61import java.awt.event.WindowListener; 62import java.awt.event.WindowFocusListener; 63import java.awt.event.FocusListener; 64import java.awt.event.ActionListener; 65 66import java.lang.reflect.Constructor; 67import java.lang.reflect.Field; 68import java.lang.reflect.InvocationTargetException; 69import java.lang.reflect.Method; 70 71import java.security.PrivilegedAction; 72import java.security.AccessController; 73 74import java.util.concurrent.atomic.AtomicBoolean; 75 76public 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 216 /* 217 * Waits for a notification and for a boolean condition to become true. 218 * The method returns when the above conditions are fullfilled or when the timeout 219 * occurs. 220 * 221 * @param condition the object to be notified and the booelan condition to wait for 222 * @param timeout the maximum time to wait in milliseconds 223 * @param catchExceptions if {@code true} the method catches InterruptedException 224 * @return the final boolean value of the {@code condition} 225 * @throws InterruptedException if the awaiting proccess has been interrupted 226 */ 227 public static boolean waitForConditionEx(final AtomicBoolean condition, long timeout) 228 throws InterruptedException 229 { 230 synchronized (condition) { 231 long startTime = System.currentTimeMillis(); 232 while (!condition.get()) { 233 condition.wait(timeout); 234 if (System.currentTimeMillis() - startTime >= timeout ) { 235 break; 236 } 237 } 238 } 239 return condition.get(); 240 } 241 242 /* 243 * The same as {@code waitForConditionEx(AtomicBoolean, long)} except that it 244 * doesn't throw InterruptedException. 245 */ 246 public static boolean waitForCondition(final AtomicBoolean condition, long timeout) { 247 try { 248 return waitForConditionEx(condition, timeout); 249 } catch (InterruptedException e) { 250 throw new RuntimeException("Error: unexpected exception caught!", e); 251 } 252 } 253 254 /* 255 * The same as {@code waitForConditionEx(AtomicBoolean, long)} but without a timeout. 256 */ 257 public static void waitForConditionEx(final AtomicBoolean condition) 258 throws InterruptedException 259 { 260 synchronized (condition) { 261 while (!condition.get()) { 262 condition.wait(); 263 } 264 } 265 } 266 267 /* 268 * The same as {@code waitForConditionEx(AtomicBoolean)} except that it 269 * doesn't throw InterruptedException. 270 */ 271 public static void waitForCondition(final AtomicBoolean condition) { 272 try { 273 waitForConditionEx(condition); 274 } catch (InterruptedException e) { 275 throw new RuntimeException("Error: unexpected exception caught!", e); 276 } 277 } 278 279 public static void waitTillShownEx(final Component comp) throws InterruptedException { 280 while (true) { 281 try { 282 Thread.sleep(100); 283 comp.getLocationOnScreen(); 284 break; 285 } catch (IllegalComponentStateException e) {} 286 } 287 } 288 public static void waitTillShown(final Component comp) { 289 try { 290 waitTillShownEx(comp); 291 } catch (InterruptedException e) { 292 throw new RuntimeException("Error: unexpected exception caught!", e); 293 } 294 } 295 296 /** 297 * Drags from one point to another with the specified mouse button pressed. 298 * 299 * @param robot a robot to use for moving the mouse, etc. 300 * @param startPoint a start point of the drag 301 * @param endPoint an end point of the drag 302 * @param button one of {@code InputEvent.BUTTON1_MASK}, 303 * {@code InputEvent.BUTTON2_MASK}, {@code InputEvent.BUTTON3_MASK} 304 * 305 * @throws IllegalArgumentException if {@code button} is not one of 306 * {@code InputEvent.BUTTON1_MASK}, {@code InputEvent.BUTTON2_MASK}, 307 * {@code InputEvent.BUTTON3_MASK} 308 */ 309 public static void drag(Robot robot, Point startPoint, Point endPoint, int button) { 310 if (!(button == InputEvent.BUTTON1_MASK || button == InputEvent.BUTTON2_MASK 311 || button == InputEvent.BUTTON3_MASK)) 312 { 313 throw new IllegalArgumentException("invalid mouse button"); 314 } 315 316 robot.mouseMove(startPoint.x, startPoint.y); 317 robot.mousePress(button); 318 try { 319 mouseMove(robot, startPoint, endPoint); 320 } finally { 321 robot.mouseRelease(button); 322 } 323 } 324 325 /** 326 * Moves the mouse pointer from one point to another. 327 * Uses Bresenham's algorithm. 328 * 329 * @param robot a robot to use for moving the mouse 330 * @param startPoint a start point of the drag 331 * @param endPoint an end point of the drag 332 */ 333 public static void mouseMove(Robot robot, Point startPoint, Point endPoint) { 334 int dx = endPoint.x - startPoint.x; 335 int dy = endPoint.y - startPoint.y; 336 337 int ax = Math.abs(dx) * 2; 338 int ay = Math.abs(dy) * 2; 339 340 int sx = signWOZero(dx); 341 int sy = signWOZero(dy); 342 343 int x = startPoint.x; 344 int y = startPoint.y; 345 346 int d = 0; 347 348 if (ax > ay) { 349 d = ay - ax/2; 350 while (true){ 351 robot.mouseMove(x, y); 352 robot.delay(50); 353 354 if (x == endPoint.x){ 355 return; 356 } 357 if (d >= 0){ 358 y = y + sy; 359 d = d - ax; 360 } 361 x = x + sx; 362 d = d + ay; 363 } 364 } else { 365 d = ax - ay/2; 366 while (true){ 367 robot.mouseMove(x, y); 368 robot.delay(50); 369 370 if (y == endPoint.y){ 371 return; 372 } 373 if (d >= 0){ 374 x = x + sx; 375 d = d - ay; 376 } 377 y = y + sy; 378 d = d + ax; 379 } 380 } 381 } 382 383 private static int signWOZero(int i){ 384 return (i > 0)? 1: -1; 385 } 386 387 private static int sign(int n) { 388 return n < 0 ? -1 : n == 0 ? 0 : 1; 389 } 390 391 /** Returns {@code WindowListener} instance that diposes {@code Window} on 392 * "window closing" event. 393 * 394 * @return the {@code WindowListener} instance that could be set 395 * on a {@code Window}. After that 396 * the {@code Window} is disposed when "window closed" 397 * event is sent to the {@code Window} 398 */ 399 public static WindowListener getClosingWindowAdapter() { 400 return new WindowAdapter () { 401 public void windowClosing(WindowEvent e) { 402 e.getWindow().dispose(); 403 } 404 }; 405 } 406 407 /* 408 * The values directly map to the ones of 409 * sun.awt.X11.XWM & sun.awt.motif.MToolkit classes. 410 */ 411 public final static int 412 UNDETERMINED_WM = 1, 413 NO_WM = 2, 414 OTHER_WM = 3, 415 OPENLOOK_WM = 4, 416 MOTIF_WM = 5, 417 CDE_WM = 6, 418 ENLIGHTEN_WM = 7, 419 KDE2_WM = 8, 420 SAWFISH_WM = 9, 421 ICE_WM = 10, 422 METACITY_WM = 11, 423 COMPIZ_WM = 12, 424 LG3D_WM = 13, 425 CWM_WM = 14, 426 MUTTER_WM = 15; 427 428 /* 429 * Returns -1 in case of not X Window or any problems. 430 */ 431 public static int getWMID() { 432 Class clazz = null; 433 try { 434 if ("sun.awt.X11.XToolkit".equals(Toolkit.getDefaultToolkit().getClass().getName())) { 435 clazz = Class.forName("sun.awt.X11.XWM"); 436 } else if ("sun.awt.motif.MToolkit".equals(Toolkit.getDefaultToolkit().getClass().getName())) { 437 clazz = Class.forName("sun.awt.motif.MToolkit"); 438 } 439 } catch (ClassNotFoundException cnfe) { 440 cnfe.printStackTrace(); 441 } 442 if (clazz == null) { 443 return -1; 444 } 445 446 try { 447 final Class _clazz = clazz; 448 Method m_addExports = Class.forName("java.awt.Helper").getDeclaredMethod("addExports", String.class, java.lang.reflect.Module.class); 449 // No MToolkit anymore: nothing to do about it. 450 // We may be called from non-X11 system, and this permission cannot be delegated to a test. 451 m_addExports.invoke(null, "sun.awt.X11", Util.class.getModule()); 452 Method m_getWMID = (Method)AccessController.doPrivileged(new PrivilegedAction() { 453 public Object run() { 454 try { 455 Method method = _clazz.getDeclaredMethod("getWMID", new Class[] {}); 456 if (method != null) { 457 method.setAccessible(true); 458 } 459 return method; 460 } catch (NoSuchMethodException e) { 461 assert false; 462 } catch (SecurityException e) { 463 assert false; 464 } 465 return null; 466 } 467 }); 468 return ((Integer)m_getWMID.invoke(null, new Object[] {})).intValue(); 469 } catch (ClassNotFoundException cnfe) { 470 cnfe.printStackTrace(); 471 } catch (NoSuchMethodException nsme) { 472 nsme.printStackTrace(); 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} 663