1/* 2 * Copyright (c) 1997, 2016, 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 */ 23package org.netbeans.jemmy; 24 25import java.awt.AWTEvent; 26import java.awt.EventQueue; 27import java.awt.Toolkit; 28import java.awt.event.InvocationEvent; 29import java.lang.reflect.InvocationTargetException; 30 31/** 32 * 33 * Provides functionality to work with java.awt.EventQueue empty. 34 * 35 * <BR><BR>Timeouts used: <BR> 36 * QueueTool.WaitQueueEmptyTimeout - timeout to wait queue emptied<BR> 37 * QueueTool.QueueCheckingDelta - time delta to check result<BR> 38 * QueueTool.LockTimeout - time to wait queue locked after lock action has been 39 * put there<BR> 40 * QueueTool.InvocationTimeout - time for action was put into queue to be 41 * started<BR> 42 * QueueTool.MaximumLockingTime - maximum time to lock queue.<br> 43 * 44 * @see Timeouts 45 * 46 * @author Alexandre Iline (alexandre.iline@oracle.com) 47 * 48 */ 49public class QueueTool implements Outputable, Timeoutable { 50 51 private final static long WAIT_QUEUE_EMPTY_TIMEOUT = 180000; 52 private final static long QUEUE_CHECKING_DELTA = 10; 53 private final static long LOCK_TIMEOUT = 180000; 54 private final static long MAXIMUM_LOCKING_TIME = 180000; 55 private final static long INVOCATION_TIMEOUT = 180000; 56 57 private static JemmyQueue jemmyQueue = null; 58 59 private TestOut output; 60 private Timeouts timeouts; 61 private Locker locker; 62 private Waiter<String, Void> lockWaiter; 63 64 /** 65 * Constructor. 66 */ 67 public QueueTool() { 68 locker = new Locker(); 69 lockWaiter = new Waiter<String, Void>(new Waitable<String, Void>() { 70 @Override 71 public String actionProduced(Void obj) { 72 return locker.isLocked() ? "" : null; 73 } 74 75 @Override 76 public String getDescription() { 77 return "Event queue to be locked"; 78 } 79 80 @Override 81 public String toString() { 82 return "QueueTool.Waiter{" + getDescription() + '}'; 83 } 84 }); 85 setOutput(JemmyProperties.getProperties().getOutput()); 86 setTimeouts(JemmyProperties.getProperties().getTimeouts()); 87 } 88 89 /** 90 * Returns system EventQueue. 91 * 92 * @return system EventQueue. 93 */ 94 public static EventQueue getQueue() { 95 return Toolkit.getDefaultToolkit().getSystemEventQueue(); 96 } 97 98 /** 99 * Map to {@code EventQueue.isDispatchThread()}. 100 * 101 * @return true if this thread is the AWT dispatching thread. 102 */ 103 public static boolean isDispatchThread() { 104 return EventQueue.isDispatchThread(); 105 } 106 107 /** 108 * Checks if system event queue is empty. 109 * 110 * @return true if EventQueue is empty. 111 */ 112 public static boolean checkEmpty() { 113 return getQueue().peekEvent() == null; 114 } 115 116 /** 117 * Shortcuts event if 118 * {@code ((JemmyProperties.getCurrentDispatchingModel() & JemmyProperties.SHORTCUT_MODEL_MASK) != 0)} 119 * and if executed in the dispatch thread. Otherwise posts event. 120 * 121 * @param event Event to dispatch. 122 */ 123 public static void processEvent(AWTEvent event) { 124 if ((JemmyProperties.getCurrentDispatchingModel() 125 & JemmyProperties.SHORTCUT_MODEL_MASK) != 0) { 126 installQueue(); 127 } 128 if ((JemmyProperties.getCurrentDispatchingModel() 129 & JemmyProperties.SHORTCUT_MODEL_MASK) != 0 130 && isDispatchThread()) { 131 shortcutEvent(event); 132 } else { 133 postEvent(event); 134 } 135 } 136 137 /** 138 * Simply posts events into the system event queue. 139 * 140 * @param event Event to dispatch. 141 */ 142 public static void postEvent(AWTEvent event) { 143 getQueue().postEvent(event); 144 } 145 146 /** 147 * Dispatches event ahead of all events staying in the event queue. 148 * 149 * @param event an event to be shortcut. 150 */ 151 public static void shortcutEvent(AWTEvent event) { 152 installQueue(); 153 jemmyQueue.shortcutEvent(event); 154 } 155 156 /** 157 * Installs own Jemmy EventQueue implementation. The method is executed in 158 * dispatchmode only. 159 * 160 * @see #uninstallQueue 161 */ 162 public static void installQueue() { 163 if (jemmyQueue == null) { 164 jemmyQueue = new JemmyQueue(); 165 } 166 jemmyQueue.install(); 167 } 168 169 /** 170 * Uninstalls own Jemmy EventQueue implementation. 171 * 172 * @see #installQueue 173 */ 174 public static void uninstallQueue() { 175 if (jemmyQueue != null) { 176 jemmyQueue.uninstall(); 177 } 178 } 179 180 static { 181 Timeouts.initDefault("QueueTool.WaitQueueEmptyTimeout", WAIT_QUEUE_EMPTY_TIMEOUT); 182 Timeouts.initDefault("QueueTool.QueueCheckingDelta", QUEUE_CHECKING_DELTA); 183 Timeouts.initDefault("QueueTool.LockTimeout", LOCK_TIMEOUT); 184 Timeouts.initDefault("QueueTool.InvocationTimeout", INVOCATION_TIMEOUT); 185 Timeouts.initDefault("QueueTool.MaximumLockingTime", MAXIMUM_LOCKING_TIME); 186 } 187 188 /** 189 * Defines current timeouts. 190 * 191 * @param ts ?t? A collection of timeout assignments. 192 * @see org.netbeans.jemmy.Timeouts 193 * @see org.netbeans.jemmy.Timeoutable 194 * @see #getTimeouts 195 */ 196 @Override 197 public void setTimeouts(Timeouts ts) { 198 timeouts = ts; 199 lockWaiter.setTimeouts(getTimeouts().cloneThis()); 200 } 201 202 /** 203 * Return current timeouts. 204 * 205 * @return the collection of current timeout assignments. 206 * @see org.netbeans.jemmy.Timeouts 207 * @see org.netbeans.jemmy.Timeoutable 208 * @see #setTimeouts 209 */ 210 @Override 211 public Timeouts getTimeouts() { 212 return timeouts; 213 } 214 215 /** 216 * Defines print output streams or writers. 217 * 218 * @param out Identify the streams or writers used for print output. 219 * @see org.netbeans.jemmy.Outputable 220 * @see org.netbeans.jemmy.TestOut 221 * @see #getOutput 222 */ 223 @Override 224 public void setOutput(TestOut out) { 225 output = out; 226 lockWaiter.setOutput(output.createErrorOutput()); 227 } 228 229 /** 230 * Returns print output streams or writers. 231 * 232 * @return an object that contains references to objects for printing to 233 * output and err streams. 234 * @see org.netbeans.jemmy.Outputable 235 * @see org.netbeans.jemmy.TestOut 236 * @see #setOutput 237 */ 238 @Override 239 public TestOut getOutput() { 240 return output; 241 } 242 243 /** 244 * Waits for system event queue empty. Uses 245 * "QueueTool.WaitQueueEmptyTimeout" milliseconds to wait. 246 * 247 * @throws TimeoutExpiredException 248 */ 249 public void waitEmpty() { 250 Waiter<String, Void> waiter = new Waiter<>(new Waitable<String, Void>() { 251 @Override 252 public String actionProduced(Void obj) { 253 if (checkEmpty()) { 254 return "Empty"; 255 } 256 return null; 257 } 258 259 @Override 260 public String getDescription() { 261 return "Wait event queue empty"; 262 } 263 264 @Override 265 public String toString() { 266 return "waitEmpty.Waiter{" + getDescription() + '}'; 267 } 268 }); 269 waiter.setTimeoutsToCloneOf(timeouts, "QueueTool.WaitQueueEmptyTimeout"); 270 waiter.setOutput(output); 271 try { 272 waiter.waitAction(null); 273 } catch (TimeoutExpiredException e) { 274 final AWTEvent event = getQueue().peekEvent(); 275 // if event != null run toString in dispatch thread 276 String eventToString = (event == null) ? "null" : invokeSmoothly( 277 new QueueTool.QueueAction<String>("event.toString()") { 278 @Override 279 public String launch() { 280 return event.toString(); 281 } 282 } 283 ); 284 getOutput().printErrLine("Event at the top of stack: " + eventToString); 285 throw (e); 286 } catch (InterruptedException e) { 287 output.printStackTrace(e); 288 } 289 } 290 291 /** 292 * Waits for system event queue be empty for {@code emptyTime} 293 * milliseconds. Uses "QueueTool.WaitQueueEmptyTimeout" milliseconds to 294 * wait. 295 * 296 * @param emptyTime time for the queue to stay empty. 297 * @throws TimeoutExpiredException 298 */ 299 public void waitEmpty(long emptyTime) { 300 301 StayingEmptyWaiter waiter = new StayingEmptyWaiter(emptyTime); 302 waiter.setTimeoutsToCloneOf(timeouts, "QueueTool.WaitQueueEmptyTimeout"); 303 waiter.setOutput(output); 304 try { 305 waiter.waitAction(null); 306 } catch (TimeoutExpiredException e) { 307 final AWTEvent event = getQueue().peekEvent(); 308 String eventToString = (event == null) ? "null" : invokeSmoothly( 309 new QueueTool.QueueAction<String>("event.toString()") { 310 @Override 311 public String launch() { 312 return event.toString(); 313 } 314 } 315 ); 316 getOutput().printErrLine("Event at the top of stack: " + eventToString); 317 throw (e); 318 } catch (InterruptedException e) { 319 output.printStackTrace(e); 320 } 321 } 322 323 /** 324 * Invokes action through EventQueue. Does not wait for it execution. 325 * 326 * @param action an action to be invoked. 327 */ 328 public void invoke(QueueAction<?> action) { 329 output.printTrace("Invoking \"" + action.getDescription() + "\" action through event queue"); 330 EventQueue.invokeLater(action); 331 } 332 333 /** 334 * Invokes runnable through EventQueue. Does not wait for it execution. 335 * 336 * @param runnable a runnable to be invoked. 337 * @return QueueAction instance which can be use for execution monitoring. 338 * @see QueueTool.QueueAction 339 */ 340 public QueueAction<Void> invoke(Runnable runnable) { 341 QueueAction<Void> result = new RunnableRunnable(runnable); 342 invoke(result); 343 return result; 344 } 345 346 /** 347 * Invokes action through EventQueue. Does not wait for it execution. 348 * 349 * @param action an action to be invoked. 350 * @param param {@code action.launch(Object)} method parameter. 351 * @return QueueAction instance which can be use for execution monitoring. 352 * @see QueueTool.QueueAction 353 */ 354 public <R, P> QueueAction<R> invoke(Action<R, P> action, P param) { 355 QueueAction<R> result = new ActionRunnable<>(action, param); 356 invoke(result); 357 return result; 358 } 359 360 /** 361 * Being executed outside of AWT dispatching thread, invokes an action 362 * through the event queue. Otherwise executes {@code action.launch()} 363 * method directly. 364 * 365 * @param action anaction to be executed. 366 * @return Action result. 367 */ 368 public <R> R invokeSmoothly(QueueAction<R> action) { 369 if (!EventQueue.isDispatchThread()) { 370 return invokeAndWait(action); 371 } else { 372 try { 373 return action.launch(); 374 } catch (Exception e) { 375 throw (new JemmyException("Exception in " + action.getDescription(), e)); 376 } 377 } 378 } 379 380 /** 381 * Being executed outside of AWT dispatching thread, invokes a runnable 382 * through the event queue. Otherwise executes {@code runnable.run()} 383 * method directly. 384 * 385 * @param runnable a runnable to be executed. 386 */ 387 public void invokeSmoothly(Runnable runnable) { 388 if (!EventQueue.isDispatchThread()) { 389 invokeAndWait(runnable); 390 } else { 391 runnable.run(); 392 } 393 } 394 395 /** 396 * Being executed outside of AWT dispatching thread, invokes an action 397 * through the event queue. Otherwise executes 398 * {@code action.launch(Object)} method directly. 399 * 400 * @param action anaction to be executed. 401 * @param param an action parameter 402 * @return Action result. 403 */ 404 public <R, P> R invokeSmoothly(Action<R, P> action, P param) { 405 if (!EventQueue.isDispatchThread()) { 406 return invokeAndWait(action, param); 407 } else { 408 return action.launch(param); 409 } 410 } 411 412 /** 413 * Invokes action through EventQueue. Waits for it execution. 414 * 415 * @param action an action to be invoked. 416 * @return a result of action 417 * @throws TimeoutExpiredException if action was not executed in 418 * "QueueTool.InvocationTimeout" milliseconds. 419 */ 420 public <R> R invokeAndWait(QueueAction<R> action) { 421 422 class JemmyInvocationLock { 423 } 424 Object lock = new JemmyInvocationLock(); 425 InvocationEvent event 426 = new JemmyInvocationEvent(Toolkit.getDefaultToolkit(), 427 action, 428 lock, 429 true); 430 try { 431 synchronized (lock) { 432 getQueue().postEvent(event); 433 while (!action.getFinished()) { 434 lock.wait(); 435 } 436 } 437 } catch (InterruptedException e) { 438 throw (new JemmyException("InterruptedException during " 439 + action.getDescription() 440 + " execution", e)); 441 } 442 if (action.getException() != null) { 443 throw (new JemmyException("Exception in " + action.getDescription(), 444 action.getException())); 445 } 446 if (event.getException() != null) { 447 throw (new JemmyException("Exception in " + action.getDescription(), 448 event.getException())); 449 } 450 return action.getResult(); 451 } 452 453 public static final class JemmyInvocationEvent extends InvocationEvent { 454 455 private static final long serialVersionUID = 42L; 456 457 public JemmyInvocationEvent(Object source, Runnable runnable, 458 Object notifier, boolean catchThrowables) { 459 super(source, runnable, notifier, catchThrowables); 460 } 461 } 462 463 /** 464 * Invokes runnable through EventQueue. Waits for it execution. 465 * 466 * @param runnable a runnable to be invoked. 467 * @throws TimeoutExpiredException if runnable was not executed in 468 * "QueueTool.InvocationTimeout" milliseconds. 469 */ 470 public void invokeAndWait(Runnable runnable) { 471 invokeAndWait(new RunnableRunnable(runnable)); 472 } 473 474 /** 475 * Invokes action through EventQueue. Waits for it execution. May throw 476 * TimeoutExpiredException if action was not executed in 477 * "QueueTool.InvocationTimeout" milliseconds. 478 * 479 * @param action an action to be invoked. 480 * @param param action.launch(Object method parameter. 481 * @return a result of action 482 * @throws TimeoutExpiredException if action was not executed in 483 * "QueueTool.InvocationTimeout" milliseconds. 484 */ 485 public <R, P> R invokeAndWait(Action<R, P> action, P param) { 486 return invokeAndWait(new ActionRunnable<>(action, param)); 487 } 488 489 /** 490 * Locks EventQueue. Locking will be automatically aborted after 491 * "QueueTool.MaximumLockingTime" milliseconds. 492 * 493 * @see #unlock() 494 * @throws TimeoutExpiredException 495 */ 496 public void lock() { 497 output.printTrace("Locking queue."); 498 invoke(locker); 499 try { 500 lockWaiter. 501 getTimeouts(). 502 setTimeout("Waiter.WaitingTime", 503 timeouts. 504 getTimeout("QueueTool.LockTimeout")); 505 lockWaiter. 506 getTimeouts(). 507 setTimeout("Waiter.TimeDelta", 508 timeouts. 509 getTimeout("QueueTool.QueueCheckingDelta")); 510 lockWaiter.waitAction(null); 511 } catch (InterruptedException e) { 512 output.printStackTrace(e); 513 } 514 } 515 516 /** 517 * Unlocks EventQueue. 518 * 519 * @see #lock() 520 */ 521 public void unlock() { 522 output.printTrace("Unlocking queue."); 523 locker.setLocked(false); 524 } 525 526 /** 527 * Locks event queue for "time" milliseconds. Returns immediately after 528 * locking. 529 * 530 * @param time a time to lock the queue for. 531 */ 532 public void lock(long time) { 533 output.printTrace("Locking queue for " + Long.toString(time) + " milliseconds"); 534 lock(); 535 invoke(new UnlockPostponer(time)); 536 } 537 538 /** 539 * Sais if last locking was expired. 540 * 541 * @return true if last locking had beed expired. 542 */ 543 public boolean wasLockingExpired() { 544 return locker.expired; 545 } 546 547 /** 548 * Action to be executed through event queue. Even if it was executed without 549 * waiting by {@code invoke(QueueAction)} execution process can be 550 * monitored by {@code getResult()}, {@code getException()}, 551 * {@code getFinished()} methods. 552 */ 553 public static abstract class QueueAction<R> implements Runnable { 554 555 private volatile boolean finished; 556 private Exception exception; 557 private R result; 558 private String description; 559 560 /** 561 * Constructor. 562 * 563 * @param description a description. 564 */ 565 public QueueAction(String description) { 566 this.description = description; 567 finished = false; 568 exception = null; 569 result = null; 570 } 571 572 /** 573 * Method to implement action functionality. 574 * 575 * @return an Object - action result 576 * @throws Exception 577 */ 578 public abstract R launch() 579 throws Exception; 580 581 /** 582 */ 583 @Override 584 public final void run() { 585 finished = false; 586 exception = null; 587 result = null; 588 try { 589 result = launch(); 590 } catch (Exception e) { 591 exception = e; 592 } finally { 593 finished = true; 594 } 595 } 596 597 /** 598 * Action description. 599 * 600 * @return the description. 601 */ 602 public String getDescription() { 603 return description; 604 } 605 606 /** 607 * Returns action result if action has already been finished, null 608 * otherwise. 609 * 610 * @return an action result. 611 */ 612 public R getResult() { 613 return result; 614 } 615 616 /** 617 * Returns exception occured during action execution (if any). 618 * 619 * @return the Exception happened inside {@code launch()} method. 620 */ 621 public Exception getException() { 622 return exception; 623 } 624 625 /** 626 * Informs whether action has been finished or not. 627 * 628 * @return true if this action have been finished 629 */ 630 public boolean getFinished() { 631 return finished; 632 } 633 634 @Override 635 public String toString() { 636 return "QueueAction{description=" + description + ", result=" + result + ", finished=" + finished + ", exception=" + exception + '}'; 637 } 638 } 639 640 private static class JemmyQueue extends EventQueue { 641 642 private boolean installed = false; 643 644 public void shortcutEvent(AWTEvent event) { 645 super.dispatchEvent(event); 646 } 647 648 @Override 649 protected void dispatchEvent(AWTEvent event) { 650 //it's necessary to catch exception here. 651 //because test might already fail by timeout 652 //but generated events are still in stack 653 try { 654 super.dispatchEvent(event); 655 } catch (Exception e) { 656 //the exceptions should be printed into 657 //Jemmy output - not System.out 658 JemmyProperties.getCurrentOutput().printStackTrace(e); 659 } 660 } 661 662 public synchronized void install() { 663 if (!installed) { 664 getQueue().push(this); 665 installed = true; 666 } 667 } 668 669 public synchronized void uninstall() { 670 if (installed) { 671 pop(); 672 installed = false; 673 } 674 } 675 } 676 677 private class EventWaiter implements Runnable { 678 679 boolean empty = true; 680 long emptyTime; 681 682 public EventWaiter(long emptyTime) { 683 this.emptyTime = emptyTime; 684 } 685 686 @Override 687 public void run() { 688 long startTime = System.currentTimeMillis(); 689 while ((empty = checkEmpty()) 690 && (System.currentTimeMillis() - startTime) < emptyTime) { 691 timeouts.sleep("QueueTool.QueueCheckingDelta"); 692 } 693 } 694 } 695 696 private class StayingEmptyWaiter extends Waiter<String, Void> { 697 698 long emptyTime; 699 700 public StayingEmptyWaiter(long emptyTime) { 701 this.emptyTime = emptyTime; 702 } 703 704 @Override 705 public String actionProduced(Void obj) { 706 try { 707 EventWaiter eventWaiter = new EventWaiter(emptyTime); 708 EventQueue.invokeAndWait(eventWaiter); 709 if (eventWaiter.empty 710 && timeFromStart() <= super.getTimeouts().getTimeout("Waiter.WaitingTime")) { 711 return "Reached"; 712 } 713 } catch (InterruptedException | InvocationTargetException e) { 714 output.printStackTrace(e); 715 } 716 return null; 717 } 718 719 @Override 720 public String getDescription() { 721 return "Wait event queue staying empty for " + emptyTime; 722 } 723 724 @Override 725 public String toString() { 726 return "StayingEmptyWaiter{" + "emptyTime=" + emptyTime + '}'; 727 } 728 } 729 730 private class ActionRunnable<R, P> extends QueueAction<R> { 731 732 Action<R, P> action; 733 P param; 734 735 public ActionRunnable(Action<R, P> action, P param) { 736 super(action.getDescription()); 737 this.action = action; 738 this.param = param; 739 } 740 741 @Override 742 public R launch() throws Exception { 743 return action.launch(param); 744 } 745 } 746 747 private class RunnableRunnable extends QueueAction<Void> { 748 749 Runnable action; 750 751 public RunnableRunnable(Runnable action) { 752 super("Runnable"); 753 this.action = action; 754 } 755 756 @Override 757 public Void launch() throws Exception { 758 action.run(); 759 return null; 760 } 761 } 762 763 private class Locker extends QueueAction<Void> { 764 765 volatile boolean locked = false; 766 long wholeTime, deltaTime; 767 boolean expired; 768 769 public Locker() { 770 super("Event queue locking"); 771 } 772 773 @Override 774 public Void launch() { 775 wholeTime = timeouts.getTimeout("QueueTool.MaximumLockingTime"); 776 deltaTime = timeouts.getTimeout("QueueTool.QueueCheckingDelta"); 777 setLocked(true); 778 expired = false; 779 long startTime = System.currentTimeMillis(); 780 while (isLocked()) { 781 try { 782 Thread.sleep(deltaTime); 783 } catch (InterruptedException e) { 784 getOutput().printStackTrace(e); 785 } 786 if (System.currentTimeMillis() - startTime > wholeTime) { 787 getOutput().printLine("Locking has been expired!"); 788 expired = true; 789 break; 790 } 791 } 792 return null; 793 } 794 795 public void setLocked(boolean locked) { 796 this.locked = locked; 797 } 798 799 public boolean isLocked() { 800 return locked; 801 } 802 } 803 804 private class UnlockPostponer implements Runnable { 805 806 long time; 807 808 public UnlockPostponer(long time) { 809 this.time = time; 810 } 811 812 @Override 813 public void run() { 814 new Timeout("", time).sleep(); 815 unlock(); 816 } 817 } 818} 819