/* * Copyright (c) 1997, 2016, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.netbeans.jemmy; import java.awt.AWTEvent; import java.awt.Toolkit; import java.awt.event.AWTEventListener; import java.lang.ref.Reference; import java.lang.ref.WeakReference; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.Vector; /** * * Provides methods to check last dispatched events, to wait for events of * specific types, or to guarantee that events of specific types are not * dispatched during some time frame. * <BR><BR> * All possible listeners are added during this class initialization in case if * "jemmy.event_listening" system property is not equal to "no", so, by default, * all events are listened. * * Uses timeouts:<BR> * EventTool.WaitEventTimeout - time to wait for AWT events.<BR> * EventTool.WaitNoEventTimeout - when checking for the absence of incoming AWT * events.<BR> * EventTool.EventCheckingDelta - time delta between checks for AWT events. * * @author Alexandre Iline (alexandre.iline@oracle.com) */ public class EventTool implements Timeoutable, Outputable { private static final long WAIT_EVENT_TIMEOUT = 60000; private static final long WAIT_NO_EVENT_TIMEOUT = 180000; private static final long EVENT_CHECKING_DELTA = 10; private static ListenerSet listenerSet; private static long currentEventMask = 0; private TestOut output; private Timeouts timeouts; /** * Constructor. */ public EventTool() { setOutput(JemmyProperties.getProperties().getOutput()); setTimeouts(JemmyProperties.getProperties().getTimeouts()); } /** * Returns time of the last dispatched event under mask. * * @param eventMask Events types to be searched. * {@code AWTEvent.*_EVENT_MASK} fields combination. * @return time in milliseconds * @see #addListeners(long) */ public static long getLastEventTime(long eventMask) { return listenerSet.getLastEventTime(eventMask); } /** * Returns last dispatched event under mask. * * @param eventMask Events types to be searched. * {@code AWTEvent.*_EVENT_MASK} fields combination. * @return AWTEvent * @see #addListeners(long) */ public static AWTEvent getLastEvent(long eventMask) { return listenerSet.getLastEvent(eventMask); } /** * Returns time of the last dispatched event. * * @return time in milliseconds * @see #addListeners(long) */ public static long getLastEventTime() { return getLastEventTime(listenerSet.getTheWholeMask()); } /** * Returns last dispatched event. * * @return AWTEvent * @see #addListeners(long) */ public static AWTEvent getLastEvent() { return getLastEvent(listenerSet.getTheWholeMask()); } /** * Adds listeners to listen events under mask. Invokes * {@code removeListeners()} first, so any event history is lost. * * @param eventMask Mask to listen events under. * {@code AWTEvent.*_EVENT_MASK} fields combination. * @see #addListeners() * @see #removeListeners() */ public static void addListeners(long eventMask) { removeListeners(); listenerSet.addListeners(eventMask); currentEventMask = eventMask; } /** * Adds listeners to listen all types of events. Invokes * {@code removeListeners()} first, so any event history is lost. This * method is invoked during static section of this class. * * @see #addListeners(long) * @see #removeListeners() * @see #getTheWholeEventMask() */ public static void addListeners() { addListeners(listenerSet.getTheWholeMask()); } /** * Removes all listeners. * * @see #addListeners(long) * @see #addListeners() */ public static void removeListeners() { listenerSet.removeListeners(); } /** * Returns event mask last time used by {@code addListeners(long)} * method. In case if {@code addListeners()} method was used last, * {@code getTheWholeEventMask() } result is returned. * * @return a long representing the current event mask value * @see #getTheWholeEventMask() */ public static long getCurrentEventMask() { return currentEventMask; } /** * Returns a combination of all {@code AWTEvent.*_EVENT_MASK} fields.. * * @return a combination of all {@code AWTEvent.*_EVENT_MASK} fields. */ public static long getTheWholeEventMask() { return listenerSet.getTheWholeMask(); } static { Timeouts.initDefault("EventTool.WaitEventTimeout", WAIT_EVENT_TIMEOUT); Timeouts.initDefault("EventTool.WaitNoEventTimeout", WAIT_NO_EVENT_TIMEOUT); Timeouts.initDefault("EventTool.EventCheckingDelta", EVENT_CHECKING_DELTA); listenerSet = new ListenerSet(); if (System.getProperty("jemmy.event_listening") == null || !System.getProperty("jemmy.event_listening").equals("no")) { listenerSet.addListeners(); } } /** * Defines current timeouts. * * @param ts ?t? A collection of timeout assignments. * @see org.netbeans.jemmy.Timeouts * @see org.netbeans.jemmy.Timeoutable * @see #getTimeouts */ @Override public void setTimeouts(Timeouts ts) { timeouts = ts; } /** * Return current timeouts. * * @return the collection of current timeout assignments. * @see org.netbeans.jemmy.Timeouts * @see org.netbeans.jemmy.Timeoutable * @see #setTimeouts */ @Override public Timeouts getTimeouts() { return timeouts; } /** * Defines print output streams or writers. * * @param out Identify the streams or writers used for print output. * @see org.netbeans.jemmy.Outputable * @see org.netbeans.jemmy.TestOut * @see #getOutput */ @Override public void setOutput(TestOut out) { output = out; } /** * Returns print output streams or writers. * * @return an object that contains references to objects for printing to * output and err streams. * @see org.netbeans.jemmy.Outputable * @see org.netbeans.jemmy.TestOut * @see #setOutput */ @Override public TestOut getOutput() { return output; } /** * Waits for the first event under mask. Waits during * {@code EventTool.WaitEventTimeout} milliseconds. * * @param eventMask Mask to wait events under. * {@code AWTEvent.*_EVENT_MASK} fields combination. * @return an AWTEvent object * @see #waitEvent() * @throws TimeoutExpiredException */ public AWTEvent waitEvent(long eventMask) { return (waitEvent(eventMask, timeouts.getTimeout("EventTool.WaitEventTimeout"), output.createErrorOutput())); } /** * Waits for the first event. Waits during * {@code EventTool.WaitEventTimeout} milliseconds. * * @return an AWTEvent object * @see #waitEvent(long) * @see #getTheWholeEventMask() * @throws TimeoutExpiredException */ public AWTEvent waitEvent() { return waitEvent(listenerSet.getTheWholeMask()); } /** * Check that no event under mask will be dispatched during time specified. * * @param eventMask Mask to wait events under. * {@code AWTEvent.*_EVENT_MASK} fields combination. * @param waitTime Quiet time (millisecons). * @return true if no event ahs found. * @see #checkNoEvent(long) */ public boolean checkNoEvent(long eventMask, long waitTime) { return checkNoEvent(eventMask, waitTime, output); } /** * Check that no event will be dispatched during time specified. * * @param waitTime Quiet time (millisecons). * @return true if no event ahs found. * @see #checkNoEvent(long, long) * @see #getTheWholeEventMask() */ public boolean checkNoEvent(long waitTime) { return checkNoEvent(listenerSet.getTheWholeMask(), waitTime); } /** * During {@code EventTool.WaitNoEventTimeout} time waits for true * result of checkNoEvent(long, long) method. * * @param eventMask Mask to wait events under. * {@code AWTEvent.*_EVENT_MASK} fields combination. * @param waitTime Quiet time (millisecons). * @see #checkNoEvent(long, long) * @see #waitNoEvent(long) * @throws TimeoutExpiredException */ public void waitNoEvent(long eventMask, long waitTime) { NoEventWaiter waiter = new NoEventWaiter(eventMask, waitTime); waiter.setTimeouts(timeouts.cloneThis()); waiter.getTimeouts(). setTimeout("Waiter.WaitingTime", timeouts.getTimeout("EventTool.WaitNoEventTimeout")); waiter.getTimeouts(). setTimeout("Waiter.TimeDelta", timeouts.getTimeout("EventTool.EventCheckingDelta")); try { waiter.waitAction(null); } catch (InterruptedException e) { output.printStackTrace(e); } } /** * During {@code EventTool.WaitNoEventTimeout} time waits for true * result of {@code checkNoEvent(long)} method. * * @param waitTime Quiet time (millisecons). * @see #checkNoEvent(long) * @see #waitNoEvent(long, long) * @throws TimeoutExpiredException */ public void waitNoEvent(long waitTime) { ListenerSet ls = listenerSet; if (ls != null) { // surprisingly this field can be null in case of massive // garbage collecting efforts like in NbTestCase.assertGC waitNoEvent(ls.getTheWholeMask(), waitTime); } } private AWTEvent waitEvent(long eventMask, long waitTime, TestOut waiterOutput) { EventWaiter waiter = new EventWaiter(eventMask); waiter.setTimeouts(timeouts.cloneThis()); waiter.setOutput(waiterOutput); waiter.getTimeouts(). setTimeout("Waiter.WaitingTime", waitTime); waiter.getTimeouts(). setTimeout("Waiter.TimeDelta", timeouts.getTimeout("EventTool.EventCheckingDelta")); try { return waiter.waitAction(null); } catch (InterruptedException e) { output.printStackTrace(e); return null; } } private boolean checkNoEvent(long eventMask, long waitTime, TestOut waiterOutput) { try { AWTEvent event = waitEvent(eventMask, waitTime, TestOut.getNullOutput()); waiterOutput.printLine("AWT event was produced during waiting: "); // used instead of event.toString() because it is not thread safe waiterOutput.printLine(event.getClass().getName()); return false; } catch (TimeoutExpiredException e) { return true; } } private static class EventType implements AWTEventListener { long eventMask; long eventTime; private Reference<AWTEvent> eventRef; public EventType(long eventMask) { this.eventMask = eventMask; eventRef = new WeakReference<>(null); eventTime = -1; } @Override public void eventDispatched(AWTEvent event) { eventRef = new WeakReference<>(event); eventTime = System.currentTimeMillis(); } public AWTEvent getEvent() { return eventRef.get(); } public long getTime() { return eventTime; } public long getEventMask() { return eventMask; } } private static class ListenerSet { private Vector<EventType> eventTypes; private long theWholeMask; public ListenerSet() { eventTypes = new Vector<>(); try { Class<?> eventClass = Class.forName("java.awt.AWTEvent"); Field[] fields = eventClass.getFields(); theWholeMask = 0; long eventMask; for (Field field : fields) { if ((field.getModifiers() & (Modifier.PUBLIC | Modifier.STATIC)) != 0 && field.getType().equals(Long.TYPE) && field.getName().endsWith("_EVENT_MASK")) { eventMask = (Long) field.get(null); eventTypes.add(new EventType(eventMask)); theWholeMask = theWholeMask | eventMask; } } } catch (ClassNotFoundException | IllegalAccessException e) { JemmyProperties.getCurrentOutput().printStackTrace(e); } } public void addListeners(long eventMask) { Toolkit dtk = Toolkit.getDefaultToolkit(); for (EventType et : eventTypes) { if ((et.getEventMask() & eventMask) != 0) { dtk.addAWTEventListener(et, et.getEventMask()); } } } public void addListeners() { addListeners(getTheWholeMask()); } public void removeListeners() { Toolkit dtk = Toolkit.getDefaultToolkit(); for (EventType eventType : eventTypes) { dtk.removeAWTEventListener(eventType); } } public long getTheWholeMask() { return theWholeMask; } public long getLastEventTime(long eventMask) { EventType et = getLastEventType(eventMask); return (et == null) ? -1 : et.getTime(); } public AWTEvent getLastEvent(long eventMask) { EventType et = getLastEventType(eventMask); return (et == null) ? null : et.getEvent(); } private EventType getLastEventType(long eventMask) { long maxTime = -1; EventType maxType = null; for (EventType et : eventTypes) { if ((eventMask & et.getEventMask()) != 0 && et.getTime() > maxTime) { maxType = et; maxTime = maxType.getTime(); } } return maxType; } } private static class EventWaiter extends Waiter<AWTEvent, Void> { long eventMask; long startTime; public EventWaiter(long eventMask) { this.eventMask = eventMask; startTime = getLastEventTime(eventMask); } @Override public AWTEvent actionProduced(Void obj) { EventType et = listenerSet.getLastEventType(eventMask); if (et != null && et.getTime() > startTime) { return et.getEvent(); } else { return null; } } @Override public String getDescription() { return ("Last event under " + Long.toString(eventMask, 2) + " event mask"); } @Override public String toString() { return "EventWaiter{" + "eventMask=" + Long.toString(eventMask, 2) + ", startTime=" + startTime + '}'; } } private class NoEventWaiter extends Waiter<String, Void> { long eventMask; long waitTime; public NoEventWaiter(long eventMask, long waitTime) { this.eventMask = eventMask; this.waitTime = waitTime; } @Override public String actionProduced(Void obj) { return (checkNoEvent(eventMask, waitTime, TestOut.getNullOutput()) ? "Reached!" : null); } @Override public String getDescription() { return ("No event under " + Long.toString(eventMask, 2) + " event mask during " + Long.toString(waitTime) + " milliseconds"); } @Override public String toString() { return "NoEventWaiter{" + "eventMask=" + Long.toString(eventMask, 2) + ", waitTime=" + waitTime + '}'; } } }