/******************************************************************************* * Copyright (c) 2004, 2012 BREDEX GmbH. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * BREDEX GmbH - initial API and implementation and/or initial documentation *******************************************************************************/ package org.eclipse.jubula.rc.swing.driver; import java.awt.Robot; import java.awt.Toolkit; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Arrays; import java.util.List; import org.apache.commons.lang.ArrayUtils; import org.eclipse.jubula.rc.common.exception.RobotException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * This class is based on the code snippet posted on * http://stackoverflow.com/questions/11042979/does-java-awt-robot-waitforidle-wait-for-events-to-be-dispatched */ public class EventFlusher { /** logger */ private static final Logger LOG = LoggerFactory.getLogger(EventFlusher.class); /** the toolkit class name */ private static final String TOOLKIT_CLASS_NAME = "sun.awt.SunToolkit"; //$NON-NLS-1$ /** the native sync queue method */ private Method m_syncNativeQueue; /** indicates whether the method zero arguments (Java 6) or not (Java 7) */ private boolean m_isSyncNativeQueueZeroArguments; /** the robot to use */ private final Robot m_robot; /** the flush timeout to use */ private final long m_flushTimeout; /** * indicates whether the default toolkit is compatible to the required * toolkit implementation for native event flushing */ private boolean m_isCompatibleToolkit = false; /** * Constructor * * @param robot * the robot * @param flushTimeout * the flush timeout */ public EventFlusher(Robot robot, long flushTimeout) { m_robot = robot; m_flushTimeout = flushTimeout; m_syncNativeQueue = null; m_isSyncNativeQueueZeroArguments = true; try { Class sunToolkitClass = Class.forName(TOOLKIT_CLASS_NAME); if (sunToolkitClass.isAssignableFrom(Toolkit .getDefaultToolkit().getClass())) { m_isCompatibleToolkit = true; } // Since it's a protected method, we have to iterate over declared // methods and setAccessible. Method[] methods = sunToolkitClass.getDeclaredMethods(); for (int i = 0; i < methods.length; i++) { Method method = methods[i]; String name = method.getName(); if ("syncNativeQueue".equals(name)) { //$NON-NLS-1$ List parameterTypes = Arrays.asList( method.getParameterTypes()); if (Arrays.asList(new Object[] { long.class }) .equals(parameterTypes)) { m_isSyncNativeQueueZeroArguments = false; } else if (parameterTypes.isEmpty() && null == m_syncNativeQueue) { m_isSyncNativeQueueZeroArguments = true; } else { continue; } m_syncNativeQueue = method; m_syncNativeQueue.setAccessible(true); } } } catch (SecurityException e) { throw new RobotException(e); } catch (ClassNotFoundException e) { throw new RobotException(e); } } /** * Block until Swing has dispatched events caused by the Robot or user. * * <p> * It is based on {@link SunToolkit#realSync()}. Use that method if you want * to try to wait for everything to settle down (e.g. if an event listener * calls {@link java.awt.Component#requestFocus()}, * {@link SwingUtilities#invokeLater(Runnable)}, or * {@link javax.swing.Timer}, realSync will block until all of those are * done, or throw exception after trying). The disadvantage of realSync is * that it throws {@link SunToolkit.InfiniteLoop} when the queues don't * become idle after 20 tries. * * <p> * Use this method if you only want to wait until the direct event listeners * have been called. For example, if you need to simulate a user click * followed by a stream input, then you can ensure that they will reach the * program under test in the right order: * * <pre> * robot.mousePress(InputEvent.BUTTON1); * EventFlusher.flush(); * writer.write("done with press"); * </pre> * * @see {@link java.awt.Robot#waitForIdle()} is no good; does not wait for * OS input events to get to the Java process. * @see {@link SunToolkit#realSync()} tries 20 times to wait for queues to * settle and then throws exception. In contrast, flushInputEvents does * not wait for queues to settle, just to flush what's already on them * once. * @see {@link java.awt.Toolkit#sync()} flushes graphics pipeline but not * input events. */ public void flush() { // 1) SunToolkit.syncNativeQueue: block until the operating system // delivers Robot or user events to the process. if (m_isCompatibleToolkit && m_syncNativeQueue != null) { Toolkit toolkit = Toolkit.getDefaultToolkit(); try { if (m_isSyncNativeQueueZeroArguments) { // java 1.6 m_syncNativeQueue.invoke(toolkit, ArrayUtils.EMPTY_OBJECT_ARRAY); } else { // java 1.7 m_syncNativeQueue.invoke(toolkit, new Object[] { m_flushTimeout }); } } catch (IllegalArgumentException e) { LOG.error("Error occurred while invoking syncNativeQueue.", e); //$NON-NLS-1$ } catch (IllegalAccessException e) { LOG.error("Error occurred while invoking syncNativeQueue.", e); //$NON-NLS-1$ } catch (InvocationTargetException e) { LOG.error("Error occurred while invoking syncNativeQueue.", e); //$NON-NLS-1$ } } // 2) SunToolkit.flushPendingEvents: block until the Toolkit thread // (aka AWT-XAWT, AWT-AppKit, or AWT-Windows) delivers enqueued events // to the EventQueue // // + // // 3) SwingUtilities.invokeAndWait: block until the Swing thread (aka // AWT-EventQueue-0) has dispatched all the enqueued input events. m_robot.waitForIdle(); } }