001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s): Alexandre Iline.
025: *
026: * The Original Software is the Jemmy library.
027: * The Initial Developer of the Original Software is Alexandre Iline.
028: * All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: *
041: *
042: *
043: * $Id$ $Revision$ $Date$
044: *
045: */
046:
047: package org.netbeans.jemmy;
048:
049: import java.awt.AWTEvent;
050: import java.awt.EventQueue;
051: import java.awt.Toolkit;
052:
053: import java.awt.event.InvocationEvent;
054:
055: import java.util.Hashtable;
056:
057: import java.lang.reflect.InvocationTargetException;
058:
059: /**
060: *
061: * Provides functionality to work with java.awt.EventQueue empty.
062: *
063: * <BR><BR>Timeouts used: <BR>
064: * QueueTool.WaitQueueEmptyTimeout - timeout to wait queue emptied<BR>
065: * QueueTool.QueueCheckingDelta - time delta to check result<BR>
066: * QueueTool.LockTimeout - time to wait queue locked after lock action has been put there<BR>
067: * QueueTool.InvocationTimeout - time for action was put into queue to be started<BR>
068: * QueueTool.MaximumLockingTime - maximum time to lock queue.<br>
069: *
070: * @see Timeouts
071: *
072: * @author Alexandre Iline (alexandre.iline@sun.com)
073: *
074: */
075: public class QueueTool implements Outputable, Timeoutable {
076:
077: private final static long WAIT_QUEUE_EMPTY_TIMEOUT = 180000;
078: private final static long QUEUE_CHECKING_DELTA = 10;
079: private final static long LOCK_TIMEOUT = 180000;
080: private final static long MAXIMUM_LOCKING_TIME = 180000;
081: private final static long INVOCATION_TIMEOUT = 180000;
082:
083: private static JemmyQueue jemmyQueue = null;
084:
085: private TestOut output;
086: private Timeouts timeouts;
087: private Locker locker;
088: private Waiter lockWaiter;
089:
090: /**
091: * Constructor.
092: */
093: public QueueTool() {
094: locker = new Locker();
095: lockWaiter = new Waiter(new Waitable() {
096: public Object actionProduced(Object obj) {
097: return (locker.isLocked() ? "" : null);
098: }
099:
100: public String getDescription() {
101: return ("Event queue to be locked");
102: }
103: });
104: setOutput(JemmyProperties.getProperties().getOutput());
105: setTimeouts(JemmyProperties.getProperties().getTimeouts());
106: }
107:
108: /**
109: * Returns system EventQueue.
110: * @return system EventQueue.
111: */
112: public static EventQueue getQueue() {
113: return (Toolkit.getDefaultToolkit().getSystemEventQueue());
114: }
115:
116: /**
117: * Map to <code>EventQueue.isDispatchThread()</code>.
118: * @return true if this thread is the AWT dispatching thread.
119: */
120: public static boolean isDispatchThread() {
121: return (getQueue().isDispatchThread());
122: }
123:
124: /**
125: * Checks if system event queue is empty.
126: * @return true if EventQueue is empty.
127: */
128: public static boolean checkEmpty() {
129: return (getQueue().peekEvent() == null);
130: }
131:
132: /**
133: * Shortcuts event if
134: * <code>((JemmyProperties.getCurrentDispatchingModel() & JemmyProperties.SHORTCUT_MODEL_MASK) != 0)</code>
135: * and if executed in the dispatch thread.
136: * Otherwise posts event.
137: * @param event Event to dispatch.
138: */
139: public static void processEvent(AWTEvent event) {
140: if ((JemmyProperties.getCurrentDispatchingModel() & JemmyProperties.SHORTCUT_MODEL_MASK) != 0) {
141: installQueue();
142: }
143: if ((JemmyProperties.getCurrentDispatchingModel() & JemmyProperties.SHORTCUT_MODEL_MASK) != 0
144: && isDispatchThread()) {
145: shortcutEvent(event);
146: } else {
147: postEvent(event);
148: }
149: }
150:
151: /**
152: * Simply posts events into the system event queue.
153: * @param event Event to dispatch.
154: */
155: public static void postEvent(AWTEvent event) {
156: getQueue().postEvent(event);
157: }
158:
159: /**
160: * Dispatches event ahead of all events staying in the event queue.
161: * @param event an event to be shortcut.
162: */
163: public static void shortcutEvent(AWTEvent event) {
164: installQueue();
165: jemmyQueue.shortcutEvent(event);
166: }
167:
168: /**
169: * Installs own Jemmy EventQueue implementation.
170: * The method is executed in dispatchmode only.
171: * @see #uninstallQueue
172: */
173: public static void installQueue() {
174: if (jemmyQueue == null) {
175: jemmyQueue = new JemmyQueue();
176: }
177: jemmyQueue.install();
178: }
179:
180: /**
181: * Uninstalls own Jemmy EventQueue implementation.
182: * @see #installQueue
183: */
184: public static void uninstallQueue() {
185: if (jemmyQueue != null) {
186: jemmyQueue.uninstall();
187: }
188: }
189:
190: static {
191: Timeouts.initDefault("QueueTool.WaitQueueEmptyTimeout",
192: WAIT_QUEUE_EMPTY_TIMEOUT);
193: Timeouts.initDefault("QueueTool.QueueCheckingDelta",
194: QUEUE_CHECKING_DELTA);
195: Timeouts.initDefault("QueueTool.LockTimeout", LOCK_TIMEOUT);
196: Timeouts.initDefault("QueueTool.InvocationTimeout",
197: INVOCATION_TIMEOUT);
198: Timeouts.initDefault("QueueTool.MaximumLockingTime",
199: MAXIMUM_LOCKING_TIME);
200: }
201:
202: /**
203: * Defines current timeouts.
204: *
205: * @param ts ?t? A collection of timeout assignments.
206: * @see org.netbeans.jemmy.Timeouts
207: * @see org.netbeans.jemmy.Timeoutable
208: * @see #getTimeouts
209: */
210: public void setTimeouts(Timeouts ts) {
211: timeouts = ts;
212: lockWaiter.setTimeouts(getTimeouts().cloneThis());
213: }
214:
215: /**
216: * Return current timeouts.
217: * @return the collection of current timeout assignments.
218: * @see org.netbeans.jemmy.Timeouts
219: * @see org.netbeans.jemmy.Timeoutable
220: * @see #setTimeouts
221: */
222: public Timeouts getTimeouts() {
223: return (timeouts);
224: }
225:
226: /**
227: * Defines print output streams or writers.
228: * @param out Identify the streams or writers used for print output.
229: * @see org.netbeans.jemmy.Outputable
230: * @see org.netbeans.jemmy.TestOut
231: * @see #getOutput
232: */
233: public void setOutput(TestOut out) {
234: output = out;
235: lockWaiter.setOutput(output.createErrorOutput());
236: }
237:
238: /**
239: * Returns print output streams or writers.
240: * @return an object that contains references to objects for
241: * printing to output and err streams.
242: * @see org.netbeans.jemmy.Outputable
243: * @see org.netbeans.jemmy.TestOut
244: * @see #setOutput
245: */
246: public TestOut getOutput() {
247: return (output);
248: }
249:
250: /**
251: * Waits for system event queue empty.
252: * Uses "QueueTool.WaitQueueEmptyTimeout" milliseconds to wait.
253: * @throws TimeoutExpiredException
254: */
255: public void waitEmpty() {
256: Waiter waiter = new Waiter(new Waitable() {
257: public Object actionProduced(Object obj) {
258: if (checkEmpty()) {
259: return ("Empty");
260: }
261: return (null);
262: }
263:
264: public String getDescription() {
265: return ("Wait event queue empty");
266: }
267: });
268: waiter.setTimeouts(timeouts.cloneThis());
269: waiter.getTimeouts().setTimeout("Waiter.WaitingTime",
270: timeouts.getTimeout("QueueTool.WaitQueueEmptyTimeout"));
271: waiter.setOutput(output);
272: try {
273: waiter.waitAction(null);
274: } catch (TimeoutExpiredException e) {
275: final AWTEvent event = getQueue().peekEvent();
276: // if event != null run toString in dispatch thread
277: String eventToString = (event == null) ? "null"
278: : (String) invokeSmoothly(new QueueTool.QueueAction(
279: "event.toString()") {
280: public Object launch() {
281: return event.toString();
282: }
283: });
284: getOutput().printErrLine(
285: "Event at the top of stack: " + eventToString);
286: throw (e);
287: } catch (InterruptedException e) {
288: output.printStackTrace(e);
289: }
290: }
291:
292: /**
293: * Waits for system event queue be empty for <code>emptyTime</code> milliseconds.
294: * Uses "QueueTool.WaitQueueEmptyTimeout" milliseconds to wait.
295: *
296: * @throws TimeoutExpiredException
297: * @param emptyTime time for the queue to stay empty.
298: */
299: public void waitEmpty(long emptyTime) {
300:
301: StayingEmptyWaiter waiter = new StayingEmptyWaiter(emptyTime);
302: waiter.setTimeouts(timeouts.cloneThis());
303: waiter.getTimeouts().setTimeout("Waiter.WaitingTime",
304: timeouts.getTimeout("QueueTool.WaitQueueEmptyTimeout"));
305: waiter.setOutput(output);
306: try {
307: waiter.waitAction(null);
308: } catch (TimeoutExpiredException e) {
309: final AWTEvent event = getQueue().peekEvent();
310: String eventToString = (event == null) ? "null"
311: : (String) invokeSmoothly(new QueueTool.QueueAction(
312: "event.toString()") {
313: public Object launch() {
314: return event.toString();
315: }
316: });
317: getOutput().printErrLine(
318: "Event at the top of stack: " + eventToString);
319: throw (e);
320: } catch (InterruptedException e) {
321: output.printStackTrace(e);
322: }
323: }
324:
325: /**
326: * Invokes action through EventQueue.
327: * Does not wait for it execution.
328: * @param action an action to be invoked.
329: */
330: public void invoke(QueueAction action) {
331: output.printTrace("Invoking \"" + action.getDescription()
332: + "\" action through event queue");
333: EventQueue.invokeLater(action);
334: }
335:
336: /**
337: * Invokes runnable through EventQueue.
338: * Does not wait for it execution.
339: * @param runnable a runnable to be invoked.
340: * @return QueueAction instance which can be use for execution monitoring.
341: * @see QueueTool.QueueAction
342: */
343: public QueueAction invoke(Runnable runnable) {
344: QueueAction result = new RunnableRunnable(runnable);
345: invoke(result);
346: return (result);
347: }
348:
349: /**
350: * Invokes action through EventQueue.
351: * Does not wait for it execution.
352: * @param action an action to be invoked.
353: * @param param <code>action.launch(Object)</code> method parameter.
354: * @return QueueAction instance which can be use for execution monitoring.
355: * @see QueueTool.QueueAction
356: */
357: public QueueAction invoke(Action action, Object param) {
358: QueueAction result = new ActionRunnable(action, param);
359: invoke(result);
360: return (result);
361: }
362:
363: /**
364: * Being executed outside of AWT dispatching thread,
365: * invokes an action through the event queue.
366: * Otherwise executes <code>action.launch()</code> method
367: * directly.
368: * @param action anaction to be executed.
369: * @return Action result.
370: */
371: public Object invokeSmoothly(QueueAction action) {
372: if (!getQueue().isDispatchThread()) {
373: return (invokeAndWait(action));
374: } else {
375: try {
376: return (action.launch());
377: } catch (Exception e) {
378: throw (new JemmyException("Exception in "
379: + action.getDescription(), e));
380: }
381: }
382: }
383:
384: /**
385: * Being executed outside of AWT dispatching thread,
386: * invokes a runnable through the event queue.
387: * Otherwise executes <code>runnable.run()</code> method
388: * directly.
389: * @param runnable a runnable to be executed.
390: */
391: public void invokeSmoothly(Runnable runnable) {
392: if (!getQueue().isDispatchThread()) {
393: invokeAndWait(runnable);
394: } else {
395: runnable.run();
396: }
397: }
398:
399: /**
400: * Being executed outside of AWT dispatching thread,
401: * invokes an action through the event queue.
402: * Otherwise executes <code>action.launch(Object)</code> method
403: * directly.
404: * @param action anaction to be executed.
405: * @param param an action parameter
406: * @return Action result.
407: */
408: public Object invokeSmoothly(Action action, Object param) {
409: if (!getQueue().isDispatchThread()) {
410: return (invokeAndWait(action, param));
411: } else {
412: return (action.launch(param));
413: }
414: }
415:
416: /**
417: * Invokes action through EventQueue.
418: * Waits for it execution.
419: * @param action an action to be invoked.
420: * @return a result of action
421: * @throws TimeoutExpiredException if action
422: * was not executed in "QueueTool.InvocationTimeout" milliseconds.
423: */
424: public Object invokeAndWait(QueueAction action) {
425:
426: class JemmyInvocationLock {
427: }
428: Object lock = new JemmyInvocationLock();
429: InvocationEvent event = new InvocationEvent(Toolkit
430: .getDefaultToolkit(), action, lock, true);
431: try {
432: synchronized (lock) {
433: getQueue().postEvent(event);
434: lock.wait();
435: }
436: } catch (InterruptedException e) {
437: throw (new JemmyException("InterruptedException during "
438: + action.getDescription() + " execution", e));
439: }
440: if (action.getException() != null) {
441: throw (new JemmyException("Exception in "
442: + action.getDescription(), action.getException()));
443: }
444: if (event.getException() != null) {
445: throw (new JemmyException("Exception in "
446: + action.getDescription(), event.getException()));
447: }
448: return (action.getResult());
449: }
450:
451: /**
452: * Invokes runnable through EventQueue.
453: * Waits for it execution.
454: * @param runnable a runnable to be invoked.
455: * @throws TimeoutExpiredException if runnable
456: * was not executed in "QueueTool.InvocationTimeout" milliseconds.
457: */
458: public void invokeAndWait(Runnable runnable) {
459: invokeAndWait(new RunnableRunnable(runnable));
460: }
461:
462: /**
463: * Invokes action through EventQueue.
464: * Waits for it execution.
465: * May throw TimeoutExpiredException if action
466: * was not executed in "QueueTool.InvocationTimeout" milliseconds.
467: * @param action an action to be invoked.
468: * @param param action.launch(Object method parameter.
469: * @return a result of action
470: * @throws TimeoutExpiredException if action
471: * was not executed in "QueueTool.InvocationTimeout" milliseconds.
472: */
473: public Object invokeAndWait(Action action, Object param) {
474: return (invokeAndWait(new ActionRunnable(action, param)));
475: }
476:
477: /**
478: * Locks EventQueue.
479: * Locking will be automatically aborted after
480: * "QueueTool.MaximumLockingTime" milliseconds.
481: * @see #unlock()
482: * @throws TimeoutExpiredException
483: */
484: public void lock() {
485: output.printTrace("Locking queue.");
486: invoke(locker);
487: try {
488: lockWaiter.getTimeouts().setTimeout("Waiter.WaitingTime",
489: timeouts.getTimeout("QueueTool.LockTimeout"));
490: lockWaiter
491: .getTimeouts()
492: .setTimeout(
493: "Waiter.TimeDelta",
494: timeouts
495: .getTimeout("QueueTool.QueueCheckingDelta"));
496: lockWaiter.waitAction(null);
497: } catch (InterruptedException e) {
498: output.printStackTrace(e);
499: }
500: }
501:
502: /**
503: * Unlocks EventQueue.
504: * @see #lock()
505: */
506: public void unlock() {
507: output.printTrace("Unlocking queue.");
508: locker.setLocked(false);
509: }
510:
511: /**
512: * Locks event queue for "time" milliseconds.
513: * Returns immediately after locking.
514: * @param time a time to lock the queue for.
515: */
516: public void lock(long time) {
517: output.printTrace("Locking queue for " + Long.toString(time)
518: + " milliseconds");
519: lock();
520: invoke(new UnlockPostponer(time));
521: }
522:
523: /**
524: * Sais if last locking was expired.
525: * @return true if last locking had beed expired.
526: */
527: public boolean wasLockingExpired() {
528: return (locker.expired);
529: }
530:
531: /**
532: * Action to be excuted through event queue.
533: * Even if it was executed without waiting by <code>invoke(QueueAction)</code>
534: * execution process can be monitored by <code>getResult()</code>,
535: * <code>getException()</code>, <code>getFinished()</code> methods.
536: */
537: public static abstract class QueueAction implements Runnable {
538: private boolean finished;
539: private Exception exception;
540: private Object result;
541: private String description;
542:
543: /**
544: * Constructor.
545: * @param description a description.
546: */
547: public QueueAction(String description) {
548: this .description = description;
549: finished = false;
550: exception = null;
551: result = null;
552: }
553:
554: /**
555: * Method to implement action functionality.
556: * @return an Object - action result
557: * @throws Exception
558: */
559: public abstract Object launch() throws Exception;
560:
561: /**
562: */
563: public final void run() {
564: finished = false;
565: exception = null;
566: result = null;
567: try {
568: result = launch();
569: } catch (Exception e) {
570: exception = e;
571: }
572: finished = true;
573: }
574:
575: /**
576: * Action description.
577: * @return the description.
578: */
579: public String getDescription() {
580: return (description);
581: }
582:
583: /**
584: * Returns action result if action has already been finished,
585: * null otherwise.
586: * @return an action result.
587: */
588: public Object getResult() {
589: return (result);
590: }
591:
592: /**
593: * Returns exception occured during action execution (if any).
594: * @return the Exception happened inside <code>launch()</code> method.
595: */
596: public Exception getException() {
597: return (exception);
598: }
599:
600: /**
601: * Informs whether action has been finished or not.
602: * @return true if this action have been finished
603: */
604: public boolean getFinished() {
605: return (finished);
606: }
607: }
608:
609: private static class JemmyQueue extends EventQueue {
610: private boolean installed = false;
611:
612: public void shortcutEvent(AWTEvent event) {
613: super .dispatchEvent(event);
614: }
615:
616: protected void dispatchEvent(AWTEvent event) {
617: //it's necessary to catch exception here.
618: //because test might already fail by timeout
619: //but generated events are still in stack
620: try {
621: super .dispatchEvent(event);
622: } catch (Exception e) {
623: //the exceptions should be printed into
624: //Jemmy output - not System.out
625: JemmyProperties.getCurrentOutput().printStackTrace(e);
626: }
627: }
628:
629: public synchronized void install() {
630: if (!installed) {
631: getQueue().push(this );
632: installed = true;
633: }
634: }
635:
636: public synchronized void uninstall() {
637: if (installed) {
638: pop();
639: installed = false;
640: }
641: }
642: }
643:
644: private class EventWaiter implements Runnable {
645: boolean empty = true;
646: long emptyTime;
647:
648: public EventWaiter(long emptyTime) {
649: this .emptyTime = emptyTime;
650: }
651:
652: public void run() {
653: long startTime = System.currentTimeMillis();
654: while ((empty = checkEmpty())
655: && (System.currentTimeMillis() - startTime) < emptyTime) {
656: timeouts.sleep("QueueTool.QueueCheckingDelta");
657: }
658: }
659: }
660:
661: private class StayingEmptyWaiter extends Waiter {
662: long emptyTime;
663:
664: public StayingEmptyWaiter(long emptyTime) {
665: this .emptyTime = emptyTime;
666: }
667:
668: public Object actionProduced(Object obj) {
669: try {
670: EventWaiter eventWaiter = new EventWaiter(emptyTime);
671: EventQueue.invokeAndWait(eventWaiter);
672: if (eventWaiter.empty
673: && timeFromStart() <= super .getTimeouts()
674: .getTimeout("Waiter.WaitingTime")) {
675: return ("Reached");
676: }
677: } catch (InterruptedException e) {
678: output.printStackTrace(e);
679: } catch (InvocationTargetException e) {
680: output.printStackTrace(e);
681: }
682: return (null);
683: }
684:
685: public String getDescription() {
686: return ("Wait event queue staying empty for " + Long
687: .toString(emptyTime));
688: }
689: }
690:
691: private class ActionRunnable extends QueueAction {
692: Action action;
693: Object param;
694:
695: public ActionRunnable(Action action, Object param) {
696: super (action.getDescription());
697: this .action = action;
698: this .param = param;
699: }
700:
701: public Object launch() throws Exception {
702: return (action.launch(param));
703: }
704: }
705:
706: private class RunnableRunnable extends QueueAction {
707: Runnable action;
708:
709: public RunnableRunnable(Runnable action) {
710: super ("Runnable");
711: this .action = action;
712: }
713:
714: public Object launch() throws Exception {
715: action.run();
716: return (null);
717: }
718: }
719:
720: private class Locker extends QueueAction {
721: boolean locked = false;
722: long wholeTime, deltaTime;
723: boolean expired;
724:
725: public Locker() {
726: super ("Event queue locking");
727: }
728:
729: public Object launch() {
730: wholeTime = timeouts
731: .getTimeout("QueueTool.MaximumLockingTime");
732: deltaTime = timeouts
733: .getTimeout("QueueTool.QueueCheckingDelta");
734: setLocked(true);
735: expired = false;
736: long startTime = System.currentTimeMillis();
737: while (isLocked()) {
738: try {
739: Thread.sleep(deltaTime);
740: } catch (InterruptedException e) {
741: getOutput().printStackTrace(e);
742: }
743: if (System.currentTimeMillis() - startTime > wholeTime) {
744: getOutput().printLine("Locking has been expired!");
745: expired = true;
746: break;
747: }
748: }
749: return (null);
750: }
751:
752: public void setLocked(boolean locked) {
753: this .locked = locked;
754: }
755:
756: public boolean isLocked() {
757: return (locked);
758: }
759: }
760:
761: private class UnlockPostponer implements Runnable {
762: long time;
763:
764: public UnlockPostponer(long time) {
765: this .time = time;
766: }
767:
768: public void run() {
769: new Timeout("", time).sleep();
770: unlock();
771: }
772: }
773: }
|