org.eclipse.jubula.rc.swt.driver.RobotSwtImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.jubula.rc.swt.driver.RobotSwtImpl.java

Source

/*******************************************************************************
 * Copyright (c) 2004, 2010 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.swt.driver;

import java.awt.event.InputEvent;
import java.awt.image.BufferedImage;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Properties;
import java.util.Set;

import javax.swing.KeyStroke;

import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.Validate;
import org.eclipse.jubula.rc.common.AUTServer;
import org.eclipse.jubula.rc.common.CompSystemConstants;
import org.eclipse.jubula.rc.common.driver.ClickOptions;
import org.eclipse.jubula.rc.common.driver.ClickOptions.ClickModifier;
import org.eclipse.jubula.rc.common.driver.IEventMatcher;
import org.eclipse.jubula.rc.common.driver.IEventThreadQueuer;
import org.eclipse.jubula.rc.common.driver.IRobot;
import org.eclipse.jubula.rc.common.driver.IRobotEventConfirmer;
import org.eclipse.jubula.rc.common.driver.IRobotEventInterceptor;
import org.eclipse.jubula.rc.common.driver.IRobotFactory;
import org.eclipse.jubula.rc.common.driver.IRunnable;
import org.eclipse.jubula.rc.common.driver.InterceptorOptions;
import org.eclipse.jubula.rc.common.driver.MouseMovementStrategy;
import org.eclipse.jubula.rc.common.driver.RobotTiming;
import org.eclipse.jubula.rc.common.exception.OsNotSupportedException;
import org.eclipse.jubula.rc.common.exception.RobotException;
import org.eclipse.jubula.rc.common.exception.StepExecutionException;
import org.eclipse.jubula.rc.common.logger.AutServerLogger;
import org.eclipse.jubula.rc.common.util.LocalScreenshotUtil;
import org.eclipse.jubula.rc.common.util.WorkaroundUtil;
import org.eclipse.jubula.rc.swt.SwtAUTServer;
import org.eclipse.jubula.rc.swt.tester.SwtApplicationTester;
import org.eclipse.jubula.rc.swt.utils.SwtKeyCodeConverter;
import org.eclipse.jubula.rc.swt.utils.SwtPointUtil;
import org.eclipse.jubula.rc.swt.utils.SwtUtils;
import org.eclipse.jubula.tools.constants.InputConstants;
import org.eclipse.jubula.tools.constants.StringConstants;
import org.eclipse.jubula.tools.i18n.I18n;
import org.eclipse.jubula.tools.messagehandling.MessageIDs;
import org.eclipse.jubula.tools.objects.event.EventFactory;
import org.eclipse.jubula.tools.objects.event.TestErrorEvent;
import org.eclipse.jubula.tools.utils.EnvironmentUtils;
import org.eclipse.jubula.tools.utils.TimeUtil;
import org.eclipse.swt.SWT;
import org.eclipse.swt.SWTError;
import org.eclipse.swt.SWTException;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.ScrollBar;
import org.eclipse.swt.widgets.Scrollable;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Widget;

/**
 * <p>
 * AWT/Swing implementation of the <code>IRobot</code> interface. It
 * uses the {@link java.awt.Robot} to move the mouse and perform clicks. Any
 * mouse move or click is intercepted and confirmed using the appropriate
 * AWT/Swing implementations of
 * {@link org.eclipse.jubula.rc.swt.driver.IRobotEventInterceptor}and
 * {@link org.eclipse.jubula.rc.swt.driver.IRobotEventConfirmer}.
 * </p>
 * 
 * <p>
 * The <code>click()</code> and <code>move()</code> implementations expect
 * that the graphics component is of type {@link java.awt.Component}and the
 * constraints object is <code>null</code> or of type
 * {@link java.awt.Rectangle}.
 * </p>
 * 
 * @author BREDEX GmbH
 * @created 17.03.2005
 */
public class RobotSwtImpl implements IRobot {
    /** the logger */
    private static AutServerLogger log = new AutServerLogger(RobotSwtImpl.class);

    /**
     * Base class for key typers.
     *
     * @author BREDEX GmbH
     * @created Nov 16, 2009
     */
    private abstract static class AbstractKeyTyper {

        /**
         * 
         * @return an event that the key for this key typer has been pressed.
         */
        public Event createKeyDownEvent() {
            return createKeyEvent(SWT.KeyDown);
        }

        /**
         * 
         * @return an event that the key for this key typer has been released.
         */
        public Event createKeyUpEvent() {
            return createKeyEvent(SWT.KeyUp);
        }

        /**
         * 
         * @param eventType The type of event to create.
         * @return an event of the given type with the key-specific information
         *         for this key typer.
         */
        private Event createKeyEvent(int eventType) {
            final Event ke = new Event();
            ke.type = eventType;
            ke.time = (int) System.currentTimeMillis();
            assignKey(ke);
            return ke;
        }

        /**
         * Sets values of parameter(s) of the given event such that the event
         * corresponds to the key represented by this key typer.
         * 
         * @param keyEvent The event to modify.
         */
        protected abstract void assignKey(Event keyEvent);
    }

    /**
     * Provides a keycode-based key event.
     *
     * @author BREDEX GmbH
     * @created Nov 19, 2009
     */
    private static class KeyCodeTyper extends AbstractKeyTyper {
        /** the keycode represented by this key typer */
        private int m_keyCode;

        /**
         * Constructor
         * 
         * @param keyCode The keycode for events generated by this key typer.
         */
        public KeyCodeTyper(int keyCode) {
            m_keyCode = keyCode;
        }

        /**
         * {@inheritDoc}
         */
        public void assignKey(Event keyEvent) {
            keyEvent.keyCode = m_keyCode;
        }
    }

    /**
     * Provides a character-based key event.
     *
     * @author BREDEX GmbH
     * @created Nov 19, 2009
     */
    private static class KeyCharTyper extends AbstractKeyTyper {
        /** the character represented by this key typer */
        private char m_keyChar;

        /**
         * Constructor
         * 
         * @param keyChar The character for events generated by this key typer.
         */
        public KeyCharTyper(char keyChar) {
            m_keyChar = keyChar;
        }

        /**
         * {@inheritDoc}
         */
        public void assignKey(Event keyEvent) {
            keyEvent.character = m_keyChar;
        }
    }

    /**
     * <code>m_autServer</code>
     */
    private SwtAUTServer m_autServer = null;

    /**
     * The AWT Robot instance.
     */
    private SwtRobot m_robot;

    /**
     * The KeyboardHelper.
     */
    private KeyboardHelper m_keyboardHelper = null;

    /**
     * The event interceptor.
     */
    private IRobotEventInterceptor m_interceptor;

    /** The event thread queuer. */
    private IEventThreadQueuer m_queuer;

    /**
     * Scrolls a component to visible. The Scroller asumes that the component is
     * embedded (directly or indirectly) into a <code>JScrollPane</code>(
     * <code>JViewPort</code> more precisely). Hierarchies of scrollpanes are
     * also supported. The default mechanismn of
     * {@link JComponent#scrollRectToVisible(java.awt.Rectangle) is not used
     * here because of limitations and bugs: <br>
     * 
     * <code>JViewPort</code> overrides <code>scrollRectToVisible()</code>,
     * but doesn't call <code>super.scrollRectToVisible()</code>, so that
     * hierarchies of scrollpanes don't work. <br>
     * 
     * <code>JTextField</code> interpretes <code>scrollRectToVisible()</code>
     * in a different way by scrolling the containing text, not the component
     * ifself. Again, <code>super.scrollRectToVisible()</code> is not called.
     */
    private class Scroller {

        /** The component to scroll to visible. */
        private Control m_component;

        /**
         * @param component The component to scroll to visible.
         */
        public Scroller(Control component) {
            m_component = component;
        }

        /**
         * Scrolls the component passed to the constructor to visible. Also 
         * scrolls the given rectangle (component-relative) to visible.
         * <em>NOTE</em>: This changes the given rectangle in order to reflect
         * its relative position within the component's client area.
         * 
         * @param aRect
         *            The bounds of the component, or a constraint within 
         *            a component.
         */
        public void scrollRectToVisible(final Rectangle aRect) {
            m_queuer.invokeAndWait("scrollToComponent", new IRunnable() { //$NON-NLS-1$

                public Object run() throws StepExecutionException {
                    Point currentPoint = new Point(aRect.x, aRect.y);
                    if (m_component instanceof Scrollable) {
                        final Scrollable scrollable = (Scrollable) m_component;
                        currentPoint.x = scroll(currentPoint.x, scrollable.getHorizontalBar());
                        currentPoint.y = scroll(currentPoint.y, scrollable.getVerticalBar());
                    }

                    for (Control current = m_component; current.getParent() != null; current = current
                            .getParent()) {

                        final Composite currentParent = current.getParent();
                        currentPoint.x = scroll(currentPoint.x, currentParent.getHorizontalBar());
                        currentPoint.y = scroll(currentPoint.y, currentParent.getVerticalBar());

                        currentPoint = current.getDisplay().map(current, currentParent, currentPoint);
                    }
                    return null;
                }

            });

        }

        /**
         * 
         * <b>Must be called from the GUI thread.</b>
         * 
         * @param scrollTarget The target value to make visible by scrolling.
         * @param scrollBar The scroll bar to use for performing the scrolling.
         *                  May be <code>null</code>. If this is 
         *                  <code>null</code>, or if the component cannot be 
         *                  scrolled, then no scrolling will occur.
         * @return the remaining distance to <code>scrollTarget</code>. This is
         *         equivalent to <code>scrollTarget - 
         *         &lt;amountScrolled&gt;</code>
         */
        private int scroll(int scrollTarget, ScrollBar scrollBar) {
            if (scrollBar != null && !scrollBar.isDisposed()) {
                int oldScrollLoc = scrollBar.getSelection();
                scrollBar.setSelection(scrollTarget);
                Event selectionEvent = new Event();
                selectionEvent.type = SWT.Selection;
                selectionEvent.widget = scrollBar;
                // SWT.None indicates the end of a mouse drag event
                selectionEvent.detail = SWT.None;
                selectionEvent.time = (int) System.currentTimeMillis();
                selectionEvent.display = scrollBar.getDisplay();
                scrollBar.notifyListeners(SWT.Selection, selectionEvent);
                return scrollTarget - (scrollBar.getSelection() - oldScrollLoc);
            }

            return scrollTarget;
        }

    }

    /**
     * Creates a new instance.
     * @param factory The Robot factory instance.
     * @throws RobotException If the SWT-Robot cannot be created.
     */
    public RobotSwtImpl(IRobotFactory factory) throws RobotException {
        try {
            m_autServer = (SwtAUTServer) AUTServer.getInstance();
            m_robot = new SwtRobot((m_autServer).getAutDisplay());
            m_robot.setAutoWaitForIdle(false);
            m_robot.setAutoDelay(0);
        } catch (SWTException swte) {
            log.error(swte);
            m_robot = null;
            throw new RobotException(swte);
        } catch (SecurityException se) {
            log.error(se);
            m_robot = null;
            throw new RobotException(se);
        }
        m_interceptor = factory.getRobotEventInterceptor();
        m_queuer = factory.getEventThreadQueuer();
    }

    /**
     * 
     * {@inheritDoc}
     */
    public void setKeyboardLayout(Properties layout) {
        m_keyboardHelper = new KeyboardHelper(layout);
    }

    /**
     * Implementation of the mouse click. The mouse is moved into the graphics
     * component by calling <code>moveImpl()</code> before performing the click.
     * @param graphicsComponent The graphics component to click on
     * @param constraints The constraints, may be <code>null</code>.
     * @param clickOptions The click options
     * @throws RobotException If the click delay is interupted or the event confirmation receives a timeout.
     */
    private void clickImpl(Object graphicsComponent, Object constraints, ClickOptions clickOptions)
            throws RobotException {

        clickImpl(graphicsComponent, constraints, clickOptions, 50, false, 50, false);
    }

    /**
     * Implementation of the mouse click. The mouse is moved into the graphics
     * component by calling <code>moveImpl()</code> before performing the click.
     * @param graphicsComponent The graphics component to click on
     * @param constraints The constraints, may be <code>null</code>.
     * @param clickOptions The click options
     * @param xPos xPos in component           
     * @param yPos yPos in component
     * @param yAbsolute true if y-position should be absolute
     * @param xAbsolute true if x-position should be absolute
     * @throws RobotException If the click delay is interupted or the event confirmation receives a timeout.
     */
    private void clickImpl(final Object graphicsComponent, final Object constraints,
            final ClickOptions clickOptions, final int xPos, final boolean xAbsolute, final int yPos,
            final boolean yAbsolute) throws RobotException {

        Widget component = (Widget) graphicsComponent;
        Rectangle constraintsRect = (Rectangle) constraints;

        moveImpl(component, constraintsRect, xPos, xAbsolute, yPos, yAbsolute, clickOptions);

        clickImpl(component, clickOptions);
    }

    /**
     * Logs information for a RobotException (usually a timeout).
     * 
     * @param graphicsComponent The component being tested.
     * @param re    The <code>RobotException</code> to log.
     * @param sb    <code>StringBuffer</code> containing any desired initial 
     *              content.
     */
    private void logRobotException(final Object graphicsComponent, RobotException re, final StringBuffer sb) {

        // Get mouse coordinates
        Object mouseCoords = m_queuer.invokeAndWait("get mouse coordinates", //$NON-NLS-1$
                new IRunnable() {

                    public Object run() throws StepExecutionException {
                        return m_autServer.getAutDisplay().getCursorLocation();
                    }

                });

        // Get component bounds
        Rectangle compBounds = null;
        if (graphicsComponent instanceof Widget) {

            compBounds = (Rectangle) m_queuer.invokeAndWait("getBounds", new IRunnable() { //$NON-NLS-1$
                public Object run() throws StepExecutionException {
                    return SwtUtils.getWidgetBounds((Widget) graphicsComponent);
                }

            });
        }

        sb.append("Component: "); //$NON-NLS-1$

        // Component value must be appended in the GUI thread. Otherwise
        // we receive a "*Wrong Thread*" message rather than actual
        // component information.
        m_queuer.invokeAndWait("getBounds", new IRunnable() { //$NON-NLS-1$
            public Object run() throws StepExecutionException {
                sb.append(graphicsComponent);

                // Return value not used
                return null;
            }
        });

        sb.append("\n"); //$NON-NLS-1$
        sb.append("Bounds: "); //$NON-NLS-1$
        sb.append(compBounds);
        sb.append("\n"); ////$NON-NLS-1$
        sb.append("Mouse position: "); //$NON-NLS-1$
        sb.append(mouseCoords);

        log.error(sb.toString(), re);
    }

    /**
     * Performs a mouse move only if the mouse cursor is not currently within 
     * given constraints. In this case, the mouse is moved into the middle of 
     * the given constraint.
     * 
     * @param constraint
     *            The rectangle to move to the center of, if necessary.
     */
    public void preMove(final Rectangle constraint) throws RobotException {

        boolean isAlreadyInConstraints = ((Boolean) m_queuer.invokeAndWait("isAlreadyInConstraints", //$NON-NLS-1$
                new IRunnable() {

                    public Object run() throws StepExecutionException {
                        return constraint.contains(m_autServer.getAutDisplay().getCursorLocation()) ? Boolean.TRUE
                                : Boolean.FALSE;
                    }

                })).booleanValue();
        if (!isAlreadyInConstraints) {
            Point p = new Point(constraint.x + (constraint.width / 2), constraint.y + (constraint.height / 2));
            m_robot.mouseMove(p.x - 1, p.y - 1);
            m_robot.mouseMove(p.x, p.y);
        }
    }

    /**
     * Checks if the mouse has to be moved on <code>p</code> or if the mouse
     * pointer already resides on this location.
     * Runs in the GUI-Thread
     * @param p The point to move to
     * @return <code>true</code> if the mouse pointer resides on a different point, otherwise <code>false</code>.
     */
    private boolean isMouseMoveRequired(Point p) {
        boolean result = false;
        Point point = (Point) m_queuer.invokeAndWait("isMouseMoveRequired", //$NON-NLS-1$
                new IRunnable() {
                    public Object run() throws StepExecutionException {
                        Display d = (m_autServer).getAutDisplay();
                        return d.getCursorLocation();
                    }
                });
        if (point != null) {
            result = !point.equals(p);
            result = true;
        }
        return result;
    }

    /**
     * The mouse is moved into the center of the graphics component.
     * @param graphicsComponent graphicsComponent The graphics component to move to.
     */
    private void moveToCenter(final Object graphicsComponent) {
        moveImpl((Widget) graphicsComponent, null, 50, false, 50, false, ClickOptions.create());
    }

    /**
     * Implementation of the mouse move. The mouse is moved into the graphics
     * component.
     * 
     * @param graphicsComponent The component into which the mouse will be 
     *                          moved.
     * @param constraints The rectangle to move to, relative to the location of
     *                    the given component. May be <code>null</code>.
     *                    If <code>null</code>, the mouse will be moved to the
     *                    center of the given component.
     * @param xPos xPos in component           
     * @param yPos yPos in component
     * @param xAbsolute true if x-position should be absolute  
     * @param yAbsolute true if y-position should be absolute  
     * @param clickOptions Contains mouse movement strategy information.
     * @throws StepExecutionException If the click delay is interupted or the event confirmation receives a timeout.
     */
    private void moveImpl(final Widget graphicsComponent, final Rectangle constraints, final int xPos,
            final boolean xAbsolute, final int yPos, final boolean yAbsolute, ClickOptions clickOptions)
            throws StepExecutionException {

        if (clickOptions.isScrollToVisible() && graphicsComponent instanceof Control) {
            ensureComponentVisible((Control) graphicsComponent, constraints);
        }

        Rectangle bounds = getBounds(graphicsComponent);

        if (constraints != null) {
            if (graphicsComponent instanceof Control) {
                Point convertedLocation = convertLocation(constraints, (Control) graphicsComponent);
                bounds.x = convertedLocation.x;
                bounds.y = convertedLocation.y;
            } else {
                bounds.x += constraints.x;
                bounds.y += constraints.y;
            }

            bounds.height = constraints.height;
            bounds.width = constraints.width;
        }

        Point pointToGo = SwtPointUtil.calculatePointToGo(xPos, xAbsolute, yPos, yAbsolute, bounds);

        if (isMouseMoveRequired(pointToGo)) {
            if (log.isDebugEnabled()) {
                log.debug("Moving mouse to: " + pointToGo); //$NON-NLS-1$
            }
            Point initialPoint = (Point) m_queuer.invokeAndWait("moveImpl", //$NON-NLS-1$
                    new IRunnable() {
                        public Object run() throws StepExecutionException {
                            Display d = m_autServer.getAutDisplay();
                            return d.getCursorLocation();
                        }
                    });

            if (pointToGo != null && (pointToGo.x < 0 || pointToGo.y < 0)) {
                throw new RobotException("Error occurred while attempting to move the mouse pointer.", //$NON-NLS-1$
                        EventFactory.createActionError(TestErrorEvent.CLICKPOINT_OFFSCREEN,
                                new String[] { String.valueOf(pointToGo.x), String.valueOf(pointToGo.y) }));
            }

            java.awt.Point[] path = MouseMovementStrategy.getMovementPath(
                    new java.awt.Point(initialPoint.x, initialPoint.y),
                    new java.awt.Point(pointToGo.x, pointToGo.y), clickOptions.getStepMovement(),
                    clickOptions.getFirstHorizontal());

            for (int i = 0; i < path.length; i++) {
                m_robot.mouseMove(path[i].x, path[i].y);
            }

            logAndCorrectMousePosition(pointToGo);
        }

    }

    /**
     * Use SWT's mapping function, if possible, as it is more
     * multi-platform than simply adding the x and y values.
     * @param constraints the constraints
     * @param graphicsComponent the graphics component
     * @return the converted location
     */
    private Point convertLocation(final Rectangle constraints, final Control graphicsComponent) {
        Point convertedLocation = (Point) m_queuer.invokeAndWait("toDisplay", new IRunnable() { //$NON-NLS-1$
            public Object run() throws StepExecutionException {
                return graphicsComponent.toDisplay(constraints.x, constraints.y);
            }

        });
        // adjust Y-location due to issue / bug 353907
        if (EnvironmentUtils.isMacOS() && graphicsComponent instanceof org.eclipse.swt.widgets.List) {
            final org.eclipse.swt.widgets.List list = (org.eclipse.swt.widgets.List) graphicsComponent;
            Integer correctedYPos = (Integer) m_queuer.invokeAndWait("correctedYPos", new IRunnable() { //$NON-NLS-1$
                public Object run() throws StepExecutionException {
                    return new Integer(list.getClientArea().y);
                }
            });
            convertedLocation.y += correctedYPos.intValue();
        }
        return convertedLocation;
    }

    /**
     * Checks whether the move was really successful. If not, a few workarounds
     * are attempted in order to correct the situation. The workarounds are 
     * logged at the warn-level as they are used. If, after all workarounds
     * have been applied, the mouse pointer is still not at the correct
     * position, then an error is logged.
     * 
     * @param pointToGo The point where the mouse pointer should currently be.
     */
    private void logAndCorrectMousePosition(Point pointToGo) {
        String runnableName = "moveImpl.getCursorPoint"; //$NON-NLS-1$
        Point curPoint = (Point) m_queuer.invokeAndWait(runnableName, new IRunnable() {
            public Object run() throws StepExecutionException {
                Display d = m_autServer.getAutDisplay();
                return d.getCursorLocation();
            }
        });

        if (!curPoint.equals(pointToGo)) {
            log.warn(
                    "Current and end points not equal after mouse move. Waiting 1 second to see if a delay fixes the problem."); //$NON-NLS-1$
            SwtRobot.delay(1000);
            m_robot.waitForIdle();
            curPoint = (Point) m_queuer.invokeAndWait(runnableName, new IRunnable() {
                public Object run() throws StepExecutionException {
                    Display d = m_autServer.getAutDisplay();
                    return d.getCursorLocation();
                }
            });
            if (!curPoint.equals(pointToGo)) {
                log.warn(
                        "Delay did not fix the problem. Trying to call mouse move one more time with an additional wait afterward."); //$NON-NLS-1$
                m_robot.mouseMove(pointToGo.x, pointToGo.y);
                SwtRobot.delay(1000);
                m_robot.waitForIdle();
                SwtRobot.delay(1000);
                curPoint = (Point) m_queuer.invokeAndWait(runnableName, new IRunnable() {
                    public Object run() throws StepExecutionException {
                        Display d = m_autServer.getAutDisplay();
                        return d.getCursorLocation();
                    }
                });
                if (!curPoint.equals(pointToGo)) {
                    log.error(
                            "Current and end points not equal after mouse move. The mouse pointer could not be correctly moved."); //$NON-NLS-1$
                }
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    public void click(Object graphicsComponent, Object constraints) throws RobotException {

        click(graphicsComponent, constraints, ClickOptions.create());
    }

    /**
     * {@inheritDoc}
     *      java.lang.Object, org.eclipse.jubula.rc.swt.robot.ClickOptions)
     */
    public void click(Object graphicsComponent, Object constraints, ClickOptions clickOptions)
            throws RobotException {

        clickImpl(graphicsComponent, constraints, clickOptions);
    }

    /**
     * {@inheritDoc}
     */
    public void doubleClick(Object graphicsComponent, Object constraints) throws RobotException {

        click(graphicsComponent, constraints, ClickOptions.create().setClickCount(2));
    }

    /**
     * {@inheritDoc}
     */
    public void move(final Object graphicsComponent, final Object constraints) throws RobotException {

        moveToCenter(graphicsComponent);
    }

    /**
     * {@inheritDoc}
     */
    public void type(final Object graphicsComponent, final char character) throws RobotException {
        Validate.notNull(graphicsComponent, "The graphic component must not be null"); //$NON-NLS-1$

        // Workaround for issue 342718
        if (EnvironmentUtils.isMacOS() && Character.toLowerCase(character) == WorkaroundUtil.CHAR_B) {
            SwtApplicationTester impClass = new SwtApplicationTester();
            impClass.rcNativeInputText(String.valueOf(character));
            return;
        }
        if (m_keyboardHelper == null) {
            throw new StepExecutionException("No keyboard layout available.", //$NON-NLS-1$ 
                    EventFactory.createActionError(TestErrorEvent.UNSUPPORTED_KEYBOARD_LAYOUT));
        }
        final KeyboardHelper.KeyStroke keyStroke = m_keyboardHelper.getKeyStroke(character);
        final Integer[] modifiers = keyStroke.getModifiers();
        final char key = keyStroke.getChar();
        final InterceptorOptions options = new InterceptorOptions(new long[] { SWT.KeyUp });
        // add confirmer
        final IEventMatcher matcher = new DefaultSwtEventMatcher(SWT.KeyUp);
        final IRobotEventConfirmer confirmer = m_interceptor.intercept(options);
        final Boolean succeeded = (Boolean) m_queuer.invokeAndWait(this.getClass().getName() + ".type", //$NON-NLS-1$
                new IRunnable() {
                    public Object run() {
                        Boolean success = Boolean.TRUE;
                        try {
                            // press the modifier keys
                            for (int i = 0; i < modifiers.length; i++) {
                                final int mod = modifiers[i].intValue();
                                if (!postKeyPress(graphicsComponent, mod, '\0')) {
                                    success = Boolean.FALSE;
                                    break;
                                }
                            }
                            if (success.booleanValue()) {
                                if (!postKeyPress(graphicsComponent, 0, key)) {
                                    success = Boolean.FALSE;
                                }
                            }
                        } finally {
                            if (!postKeyRelease(graphicsComponent, 0, key)) {
                                success = Boolean.FALSE;
                            }
                            // release the modifier keys
                            for (int i = 0; i < modifiers.length; i++) {
                                final int mod = modifiers[i].intValue();
                                if (!postKeyRelease(graphicsComponent, mod, '\0')) {
                                    success = Boolean.FALSE;
                                }
                            }
                        }
                        return success;
                    }
                });
        if (!succeeded.booleanValue()) {
            final String msg = "Failed to type character '" //$NON-NLS-1$
                    + String.valueOf(character) + "' into component '" //$NON-NLS-1$
                    + SwtUtils.toString((Widget) graphicsComponent) + "'"; //$NON-NLS-1$
            if (log.isWarnEnabled()) {
                log.warn(msg);
            }
            throw new RobotException(msg, EventFactory.createActionError(TestErrorEvent.INPUT_FAILED));
        }
        // Workaround for bug 342718
        if (!(key == WorkaroundUtil.CHAR_9 && EnvironmentUtils.isMacOS())) {
            confirmer.waitToConfirm(graphicsComponent, matcher);
        } else {
            TimeUtil.delay(50);
        }
    }

    /**
     * {@inheritDoc}
     */
    public void type(Object graphicsComponent, String text) throws RobotException {
        if (text != null) {
            for (int i = 0; i < text.length(); i++) {
                char ch = text.charAt(i);
                type(graphicsComponent, ch);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    public void keyType(Object graphicsComponent, int keycode) throws RobotException {
        try {
            InterceptorOptions options = new InterceptorOptions(new long[] { SWT.KeyUp, SWT.KeyDown });
            IRobotEventConfirmer confirmer = m_interceptor.intercept(options);
            try {
                m_robot.keyPress(keycode);
            } finally {
                m_robot.keyRelease(keycode);
            }
            confirmer.waitToConfirm(graphicsComponent, new KeySwtEventMatcher(SWT.KeyUp));
        } catch (IllegalArgumentException e) {
            throw new RobotException(e);
        }
    }

    /**
     * {@inheritDoc}
     */
    public void keyPress(Object graphicsComponent, int keycode) throws RobotException {

        // A direct "post" is necessary here (rather than using the AWT Robot)
        // because we do not receive event confirmation for the KeyDown event
        // on MacOS X Cocoa.
        if (!post(new KeyCodeTyper(keycode).createKeyDownEvent())) {
            final String msg = "Failed to post key event for keycode '" //$NON-NLS-1$
                    + keycode + "'"; //$NON-NLS-1$
            log.warn(msg);
            throw new RobotException(msg, EventFactory.createActionError(TestErrorEvent.INVALID_INPUT));
        }

    }

    /**
     * {@inheritDoc}
     */
    public void keyRelease(Object graphicsComponent, int keycode) throws RobotException {

        // A direct "post" is necessary here (rather than using the AWT Robot)
        // because we do not receive event confirmation for the KeyDown event
        // on MacOS X Cocoa.
        if (!post(new KeyCodeTyper(keycode).createKeyUpEvent())) {
            final String msg = "Failed to post key event for keycode '" //$NON-NLS-1$
                    + keycode + "'"; //$NON-NLS-1$
            log.warn(msg);
            throw new RobotException(msg, EventFactory.createActionError(TestErrorEvent.INVALID_INPUT));
        }
    }

    /**
     * 
     * @param keyStroke The key stroke. 
     * @return a list of key typers capable of generating the necessary
     *         events to simulate the modifiers of the given key stroke.
     */
    private List modifierKeyTypers(KeyStroke keyStroke) {
        List l = new LinkedList();
        int modifiers = keyStroke.getModifiers();
        // this is jdk 1.3 - code.
        // use ALT_DOWN_MASK instead etc. with jdk 1.4 !
        if ((modifiers & InputEvent.ALT_MASK) != 0) {
            l.add(new KeyCodeTyper(SWT.ALT));
        }
        if ((modifiers & InputEvent.CTRL_MASK) != 0) {
            l.add(new KeyCodeTyper(SWT.CTRL));
        }
        if ((modifiers & InputEvent.SHIFT_MASK) != 0) {
            l.add(new KeyCodeTyper(SWT.SHIFT));
        }
        if ((modifiers & InputEvent.META_MASK) != 0) {
            l.add(new KeyCodeTyper(SWT.COMMAND));
        }
        return l;
    }

    /**
     * 
     * @param keyStrokeSpec String representing the key for which a 
     *                      key typer should be returned. 
     * @return a key typer capable of generating the necessary
     *         event to simulate the base key of the given key stroke
     *         spec.
     */
    private static AbstractKeyTyper getBaseKeyTyper(String keyStrokeSpec) {
        String[] specElements = keyStrokeSpec.split(" "); //$NON-NLS-1$
        String baseKey = specElements[specElements.length - 1];
        int code = SwtKeyCodeConverter.getKeyCode(baseKey);

        if (code == -1) {
            return new KeyCharTyper(
                    getOSSspecificSpecBaseCharacter(SwtKeyCodeConverter.getKeyChar(baseKey).charValue()));
        }

        return new KeyCodeTyper(code);
    }

    /**
     * {@inheritDoc}
     */
    public void keyStroke(final String keyStrokeSpec) throws RobotException {
        final KeyStroke keyStroke = getKeyStroke(keyStrokeSpec);
        final List keyTyperList = modifierKeyTypers(keyStroke);
        keyTyperList.add(getBaseKeyTyper(keyStrokeSpec));

        m_robot.setAutoWaitForIdle(true);
        // first press all keys, then release all keys, but
        // avoid to press and release any key twice (even if perhaps alt
        // and meta should have the same keycode(??)
        final Set alreadyDown = new HashSet();
        final ListIterator i = keyTyperList.listIterator();
        final InterceptorOptions options = new InterceptorOptions(new long[] { SWT.KeyUp });
        try {
            m_queuer.invokeAndWait(this.getClass().getName() + ".type", //$NON-NLS-1$
                    new IRunnable() {
                        public Object run() { // SYNCH THREAD START
                            boolean success = true;
                            while (i.hasNext()) {
                                AbstractKeyTyper keyTyper = (AbstractKeyTyper) i.next();
                                if (log.isDebugEnabled()) {
                                    log.debug("trying to press: " + keyTyper); //$NON-NLS-1$
                                }
                                if (!alreadyDown.contains(keyTyper)) {
                                    if (log.isDebugEnabled()) {
                                        log.debug("pressing: " + keyTyper); //$NON-NLS-1$
                                    }

                                    // post the event!
                                    if (!post(keyTyper.createKeyDownEvent())) {
                                        success = false;
                                    }
                                    alreadyDown.add(keyTyper);
                                }
                            }
                            if (!success) {
                                final String msg = "Failed to post keystroke '" //$NON-NLS-1$
                                        + keyStrokeSpec + "'"; //$NON-NLS-1$
                                if (log.isWarnEnabled()) {
                                    log.warn(msg);
                                }
                                throw new RobotException(msg,
                                        EventFactory.createActionError(TestErrorEvent.INVALID_INPUT));
                            }
                            return Boolean.valueOf(success);
                        }
                    });
        } finally {
            releaseKeys(options, alreadyDown, i);
        }
    }

    /**
     * Creates a KeyStroke of the given keyStrokeSpec
     * 
     * @param keyStrokeSpec
     *            see {@link KeyStroke#getKeyStroke(String)} and
     *            {@link KeyStroke#getKeyStroke(Char)}
     * @return a KeyStroke.
     * @throws RobotException
     *             if no KeyStroke can be created.
     */
    private KeyStroke getKeyStroke(String keyStrokeSpec) throws RobotException {
        KeyStroke keyStroke;
        if (keyStrokeSpec.length() == 1) {
            char singeKeyStrokeSpecChar = keyStrokeSpec.charAt(0);
            singeKeyStrokeSpecChar = getOSSspecificSpecBaseCharacter(singeKeyStrokeSpecChar);
            keyStroke = KeyStroke.getKeyStroke(singeKeyStrokeSpecChar);
        } else {
            int keyStrokeSpecSize = keyStrokeSpec.length();
            char keySpec = keyStrokeSpec.charAt(keyStrokeSpecSize - 1);
            // ''.toUpperCase is "SS" we do not want that!
            if ('' != keySpec) {
                keySpec = Character.toUpperCase(keySpec);
            }
            String modifiedKeyStrokeSpec = keyStrokeSpec.substring(0, keyStrokeSpecSize - 1) + keySpec;
            keyStroke = KeyStroke.getKeyStroke(modifiedKeyStrokeSpec);
        }
        if (keyStroke == null) {
            final String msg = "Failed to post keystroke '" + keyStrokeSpec //$NON-NLS-1$
                    + "'"; //$NON-NLS-1$
            if (log.isWarnEnabled()) {
                log.warn(msg);
            }
            throw new RobotException(msg, EventFactory.createActionError(TestErrorEvent.INVALID_PARAM_VALUE));
        }
        return keyStroke;
    }

    /**
     * @param character
     *            the character
     * @return the "corrected" character, e.g. for Mac OS X it has to be in
     *         lower case
     */
    private static char getOSSspecificSpecBaseCharacter(char character) {
        if (EnvironmentUtils.isMacOS() && Character.isUpperCase(character)) {
            return Character.toLowerCase(character);
        }
        return character;
    }

    /**
     * Ensures that the passed component is visible
     * @param component The component.
     * @param bounds Optional bounds inside the component. If not <code>null</code>, the bounds are scrolled to visible.
     * @throws RobotException If the component's screen location cannot be calculated.
     */
    private void ensureComponentVisible(final Control component, final Rectangle bounds) throws RobotException {

        Rectangle rectangle = bounds;
        if (rectangle == null) {
            rectangle = (Rectangle) m_queuer.invokeAndWait("getRelativeWidgetBounds", new IRunnable() { //$NON-NLS-1$

                public Object run() throws StepExecutionException {
                    return SwtUtils.getRelativeWidgetBounds(component, component);
                }

            });
        }
        if (log.isDebugEnabled()) {
            log.debug("Scrolling rectangle to visible: " + rectangle); //$NON-NLS-1$
        }
        Scroller scroller = new Scroller(component);
        scroller.scrollRectToVisible(rectangle);
    }

    /**
     * {@inheritDoc}
     */
    public void scrollToVisible(Object graphicsComponent, Object constraints) throws RobotException {

        if (graphicsComponent instanceof Control) {
            ensureComponentVisible((Control) graphicsComponent, (Rectangle) constraints);
        }
    }

    /**
     * {@inheritDoc}
     */
    public void activateApplication(String method) throws RobotException {
        try {
            final Shell window = getActiveWindow();
            if (window == null) {
                throw new RobotException("No AUT window is available.", //$NON-NLS-1$
                        EventFactory.createImplClassErrorEvent());
            }
            WindowActivationMethod wam = WindowActivationMethod.createWindowActivationMethod(method, m_robot,
                    m_queuer);
            wam.activate(window);

            // Verify that window was successfully activated
            Shell activeWindow = (Shell) m_queuer.invokeAndWait("getActiveWindow", new IRunnable() { //$NON-NLS-1$

                public Object run() throws StepExecutionException {
                    return window.getDisplay().getActiveShell();
                }

            });
            if (activeWindow != window) {
                throw new StepExecutionException(I18n.getString(TestErrorEvent.WINDOW_ACTIVATION_FAILED, true),
                        EventFactory.createActionError(TestErrorEvent.WINDOW_ACTIVATION_FAILED));
            }

        } catch (Exception exc) {
            throw new RobotException(exc);
        }
    }

    /**
     * Guesses the active window. If windows exist but are not active, one of 
     * the existing windows will be returned. 
     * Returns <code>null</code> if no windows exist for the AUT.
     * @return the active window
     */
    public Shell getActiveWindow() {
        return (Shell) m_queuer.invokeAndWait("getActiveWindow", //$NON-NLS-1$
                new IRunnable() {
                    public Object run() throws StepExecutionException {
                        Display autDisplay = m_autServer.getAutDisplay();
                        Shell activeShell = autDisplay.getActiveShell();
                        if (activeShell == null) {
                            Shell[] existingShells = autDisplay.getShells();
                            for (int i = 0; i < existingShells.length; i++) {
                                Shell shell = existingShells[i];
                                if (shell.isVisible()) {
                                    activeShell = shell;
                                    break;
                                }
                            }
                            if (activeShell == null && existingShells.length > 0) {
                                activeShell = existingShells[0];
                            }
                        }
                        return activeShell;
                    }
                });
    }

    /**
     * {@inheritDoc}
     */
    public void keyToggle(Object obj, int key, boolean activated) throws OsNotSupportedException {

        if (activated && isActivated(key) || !activated && !isActivated(key)) {
            return;
        }
        List keyTyperList = new LinkedList();
        keyTyperList.add(new KeyCodeTyper(key));
        m_robot.setAutoWaitForIdle(true);
        final Set alreadyDown = new HashSet();
        final ListIterator i = keyTyperList.listIterator();
        final InterceptorOptions options = new InterceptorOptions(new long[] { SWT.KeyUp });
        try {
            while (i.hasNext()) {
                m_queuer.invokeAndWait(this.getClass().getName() + ".type", //$NON-NLS-1$
                        new IRunnable() {
                            public Object run() { // SYNCH THREAD START
                                AbstractKeyTyper keyTyper = (AbstractKeyTyper) i.next();
                                if (log.isDebugEnabled()) {
                                    log.debug("trying to press: " + keyTyper); //$NON-NLS-1$
                                }
                                if (!alreadyDown.contains(keyTyper)) {
                                    if (log.isDebugEnabled()) {
                                        log.debug("pressing: " + keyTyper); //$NON-NLS-1$
                                    }
                                    post(keyTyper.createKeyDownEvent());
                                    alreadyDown.add(keyTyper);
                                }
                                return null;
                            }
                        });
                TimeUtil.delay(20);
            }
        } catch (IllegalArgumentException e) {
            throw new RobotException(e);
        } finally {
            releaseKeys(options, alreadyDown, i);
        }
    }

    /**
     * @param key the key
     * @return true, if key is already activated
     */
    private boolean isActivated(int key) throws OsNotSupportedException {

        if (SWT.getPlatform().equals("win32")) { //$NON-NLS-1$
            try {
                return isActivatedWIN(key);
            } catch (Exception e) {
                log.error(e);
            }
        } else if (SWT.getPlatform().equals("motif")) { //$NON-NLS-1$
            return isActivatedMOTIF(key);
        } else if (SWT.getPlatform().equals("gtk")) { //$NON-NLS-1$
            return isActivatedGTK(key);
        }
        throw new OsNotSupportedException("Current os \"" //$NON-NLS-1$
                + SWT.getPlatform() + "\" is not supported.", //$NON-NLS-1$
                MessageIDs.E_UNSUPPORTED_OS);
    }

    /**
     * @param key the key to toggle
     * @return true, if <b>windows</b> key is already locked
     */
    private boolean isActivatedWIN(int key)
            throws ClassNotFoundException, IllegalArgumentException, SecurityException, IllegalAccessException,
            NoSuchFieldException, NoSuchMethodException, InvocationTargetException {

        // Use reflection so that this compiles on all platforms
        String className = "org.eclipse.swt.internal.win32.OS"; //$NON-NLS-1$
        Class clazz = Class.forName(className);
        int osSpecificKey = -1;
        switch (key) {
        case SWT.NUM_LOCK:
            osSpecificKey = clazz.getDeclaredField("VK_NUMLOCK").getInt(null); //$NON-NLS-1$
            break;
        case SWT.CAPS_LOCK:
            osSpecificKey = clazz.getDeclaredField("VK_CAPITAL").getInt(null); //$NON-NLS-1$
            break;
        case SWT.SCROLL_LOCK:
            osSpecificKey = clazz.getDeclaredField("VK_SCROLL").getInt(null); //$NON-NLS-1$
            break;
        default:
            break;
        }

        // Call org.eclipse.swt.internal.win32.OS.GetKeyState
        String methodName = "GetKeyState"; //$NON-NLS-1$
        Class[] params = new Class[] { Integer.TYPE, };
        Object[] args = new Object[] { new Integer(osSpecificKey), };
        Method method = clazz.getMethod(methodName, params);
        short keyState = ((Short) method.invoke(clazz, args)).shortValue();

        if (keyState == 1) {
            return true;
        }
        return false;
    }

    /**
     * @param key the key to toggle
     * @return true, if <b>gtk</b> key is already locked
     */
    private boolean isActivatedGTK(int key) {
        switch (key) {
        default:
        }
        throw new UnsupportedOperationException("OS.GetKeyState"); //$NON-NLS-1$
    }

    /**
     * @param key the key to toggle
     * @return true, if <b>motif</b> key is already locked
     */
    private boolean isActivatedMOTIF(int key) {
        switch (key) {
        default:
        }
        throw new UnsupportedOperationException("OS.GetKeyState"); //$NON-NLS-1$
    }

    /**
     * Gets the InputEvent-ButtonMask of the given mouse button number
     * @param button the button number
     * @return the InputEvent button mask
     */
    private int getButtonMask(int button) {
        switch (button) {
        case InputConstants.MOUSE_BUTTON_LEFT:
            return SWT.BUTTON1;
        case InputConstants.MOUSE_BUTTON_MIDDLE:
            return SWT.BUTTON2;
        case InputConstants.MOUSE_BUTTON_RIGHT:
            return SWT.BUTTON3;
        default:
            throw new RobotException("unsupported mouse button", null); //$NON-NLS-1$
        }
    }

    /**
     * {@inheritDoc}
     */
    public void clickAtCurrentPosition(Object graphicsComponent, int clickCount, int button) {

        clickImpl(graphicsComponent, ClickOptions.create().setClickCount(clickCount).setMouseButton(button));
    }

    /**
     * {@inheritDoc}
     */
    public java.awt.Point getCurrentMousePosition() {
        java.awt.Point currentPos = (java.awt.Point) m_queuer.invokeAndWait("getCursorPosition", new IRunnable() { //$NON-NLS-1$

            public Object run() throws StepExecutionException {
                Display d = (m_autServer).getAutDisplay();
                final Point cursorLocation = d.getCursorLocation();
                return new java.awt.Point(cursorLocation.x, cursorLocation.y);
            }
        });

        return currentPos;
    }

    /**
     * 
     * {@inheritDoc}
     */
    public boolean isMouseInComponent(Object graphicsComponent) {

        final Widget comp = (Widget) graphicsComponent;
        final java.awt.Point currentMousePosition = getCurrentMousePosition();
        final Point currMousePos = new Point(currentMousePosition.x, currentMousePosition.y);
        final Rectangle bounds = (Rectangle) m_queuer.invokeAndWait("getBounds", new IRunnable() { //$NON-NLS-1$
            public Object run() throws StepExecutionException {
                return SwtUtils.getWidgetBounds(comp);
            }

        });
        Point treeLocUpperLeft = new Point(bounds.x, bounds.y);
        final Point treeLocLowerRight = new Point(bounds.width + treeLocUpperLeft.x,
                bounds.height + treeLocUpperLeft.y);
        final boolean x1 = currMousePos.x >= treeLocUpperLeft.x;
        final boolean x2 = currMousePos.x < treeLocLowerRight.x;
        final boolean y1 = currMousePos.y >= treeLocUpperLeft.y;
        final boolean y2 = currMousePos.y < treeLocLowerRight.y;
        return x1 && x2 && y1 && y2;
    }

    /**
     * @param options options
     * @param alreadyDown set of pressed keys
     * @param i ListIterator
     */
    private void releaseKeys(final InterceptorOptions options, final Set alreadyDown, final ListIterator i) {

        // Release all keys in reverse order.
        final Set alreadyUp = new HashSet();
        try {
            while (i.hasPrevious()) {
                IRobotEventConfirmer confirmer = m_interceptor.intercept(options);
                m_queuer.invokeAndWait(this.getClass().getName() + ".type", //$NON-NLS-1$
                        new IRunnable() {
                            public Object run() { // SYNCH THREAD START
                                AbstractKeyTyper keyTyper = (AbstractKeyTyper) i.previous();
                                if (log.isDebugEnabled()) {
                                    log.debug("trying to release: " + keyTyper); //$NON-NLS-1$
                                }
                                if (alreadyDown.contains(keyTyper) && !alreadyUp.contains(keyTyper)) {

                                    if (log.isDebugEnabled()) {
                                        log.debug("releasing: " + keyTyper); //$NON-NLS-1$
                                    }

                                    post(keyTyper.createKeyUpEvent());
                                    alreadyUp.add(keyTyper);
                                }
                                return null;
                            }
                        });
                confirmer.waitToConfirm(null, new KeySwtEventMatcher(SWT.KeyUp));
            }
        } catch (RobotException e) {
            log.error("error releasing keys", e); //$NON-NLS-1$
            if (!i.hasPrevious()) {
                throw e;
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    public String getSystemModifierSpec() {
        String keyStrokeSpec = CompSystemConstants.MODIFIER_CONTROL;
        if (SWT.MOD1 == SWT.COMMAND) {
            keyStrokeSpec = CompSystemConstants.MODIFIER_META;
        } else if (SWT.MOD1 == SWT.ALT) {
            keyStrokeSpec = CompSystemConstants.MODIFIER_ALT;
        }
        return keyStrokeSpec;
    }

    /**
     * Gets the absolute bounds of the given Widget.
     * This method runs in the GUI-Thread.
     * @param component a Widget
     * @return the bounds
     * @see SwtUtils#getBounds(Control)  
     */
    private Rectangle getBounds(final Widget component) {
        return (Rectangle) m_queuer.invokeAndWait("getBounds", new IRunnable() { //$NON-NLS-1$
            public Object run() throws StepExecutionException {
                return SwtUtils.getWidgetBounds(component);
            }
        });
    }

    /**
     * Posts a Key-Press-event with the given modifier and the given character.
     * @param graphicsComponent a graphicsComponent.
     * @param modifier The modifier (e.g. SWT.SHIFT, SWT.CTRL, etc.)
     * @param character a character to press.
     * @return true if the event could be posted false otherwise.
     */
    private boolean postKeyPress(Object graphicsComponent, int modifier, char character) {

        final Event event = createUntypedEvent((Widget) graphicsComponent, modifier, character);
        event.type = SWT.KeyDown;
        return post(event);
    }

    /**
     * Posts a Key-Release-event with the given modifier and the given character.
     * @param graphicsComponent a graphicsComponent.
     * @param modifier The modifier (e.g. SWT.SHIFT, SWT.CTRL, etc.)
     * @param character a character to release.
     * @return true if the event could be posted false otherwise.
     */
    private boolean postKeyRelease(Object graphicsComponent, int modifier, char character) {

        final Event event = createUntypedEvent((Widget) graphicsComponent, modifier, character);
        event.type = SWT.KeyUp;
        return post(event);
    }

    /**
     * Creates an Event with the given parameters without an {@link Event#type}.
     * @param graphicsComponent a Widget
     * @param modifier a modifier, e.g. a SWT-constant (SWT.CTRL, etc.)
     * @param character a char value
     * @return an Event.
     */
    private Event createUntypedEvent(Widget graphicsComponent, int modifier, char character) {
        final Event event = new Event();
        event.keyCode = modifier;
        event.character = character;
        event.widget = graphicsComponent;
        return event;
    }

    /**
     * Posts an {@link Event} on the UI input queue.
     * 
     * @param event the {@link Event} to post
     * @return true if the event could be posted false otherwise.
     * @see Display#post(Event)
     */
    private boolean post(Event event) {
        try {
            final Display display = m_autServer.getAutDisplay();
            if (!display.post(event)) {
                final String msg = "posting event failed: " + event; //$NON-NLS-1$
                log.error(msg);
                return false;
            }
        } catch (SWTError swte) {
            log.error(swte);
            throw new RobotException(swte);
        }
        return true;
    }

    /**
     * {@inheritDoc}
     */
    public void mousePress(final Object graphicsComponent, Object constraints, final int button)
            throws RobotException {
        if (graphicsComponent != null) {
            move(graphicsComponent, constraints);
        }

        RobotTiming.sleepPreClickDelay();

        m_robot.mousePress(getButtonMask(button));
    }

    /**
     * {@inheritDoc}
     */
    public void mouseRelease(final Object graphicsComponent, Object constraints, final int button)
            throws RobotException {

        if (graphicsComponent != null) {
            move(graphicsComponent, constraints);
        }

        RobotTiming.sleepPreClickDelay();

        m_robot.mouseRelease(getButtonMask(button));
    }

    /**
     * Clicks at the current mouse position.
     * 
     * @param graphicsComponent The component used for confirming the click.
     * @param clickOptions Configuration for the click.
     */
    private void clickImpl(Object graphicsComponent, ClickOptions clickOptions) {

        int buttonMask = getButtonMask(clickOptions.getMouseButton());
        int clickCount = clickOptions.getClickCount();
        int[] modifierMask = getModifierMask(clickOptions.getClickModifier());
        if (clickCount > 0) {
            final IEventMatcher matcher = new ClickSwtEventMatcher(clickOptions);
            IRobotEventConfirmer confirmer = null;
            if (clickOptions.isConfirmClick()) {
                final InterceptorOptions options = new InterceptorOptions(
                        new long[] { SWT.MouseUp, SWT.MouseDown });
                confirmer = m_interceptor.intercept(options);
            }
            try {
                pressModifier(modifierMask);
                RobotTiming.sleepPreClickDelay();

                for (int i = 0; i < clickCount; i++) {
                    m_robot.mousePress(buttonMask);
                    RobotTiming.sleepPostMouseDownDelay();
                    m_robot.mouseRelease(buttonMask);
                    RobotTiming.sleepPostMouseUpDelay();
                }
                if (confirmer != null) {
                    try {
                        confirmer.waitToConfirm(graphicsComponent, matcher);
                    } catch (RobotException re) {
                        StringBuffer sb = new StringBuffer("Robot exception occurred while clicking...\n"); //$NON-NLS-1$
                        logRobotException(graphicsComponent, re, sb);
                        throw re;
                    }
                }
            } finally {
                releaseModifier(modifierMask);
            }
        }
    }

    /**
     * @param clickModifier
     *            the click modifier to use for this click
     * @return an array of modifiers to press before click and release after
     *         click
     */
    private int[] getModifierMask(ClickModifier clickModifier) {
        int[] modifier = new int[0];
        if (clickModifier.hasModifiers(ClickModifier.M1)) {
            modifier = ArrayUtils.add(modifier, SwtUtils.getSystemDefaultModifier());
        }
        if (clickModifier.hasModifiers(ClickModifier.M2)) {
            modifier = ArrayUtils.add(modifier, SwtUtils.getSystemModifier2());
        }
        if (clickModifier.hasModifiers(ClickModifier.M3)) {
            modifier = ArrayUtils.add(modifier, SwtUtils.getSystemModifier3());
        }
        if (clickModifier.hasModifiers(ClickModifier.M4)) {
            modifier = ArrayUtils.add(modifier, SwtUtils.getSystemModifier4());
        }
        return modifier;
    }

    /**
     * @param modifierMask
     *            array of modifiers to press before click
     */
    private void pressModifier(int[] modifierMask) {
        for (int i = 0; i < modifierMask.length; i++) {
            keyPress(null, modifierMask[i]);
        }
    }

    /**
     * @param modifierMask
     *            array of modifiers release after click
     */
    private void releaseModifier(int[] modifierMask) {
        for (int i = 0; i < modifierMask.length; i++) {
            keyRelease(null, modifierMask[i]);
        }
    }

    /**
     * {@inheritDoc}
     */
    public void click(Object graphicsComponent, Object constraints, ClickOptions clickOptions, int xPos,
            boolean xAbsolute, int yPos, boolean yAbsolute) throws RobotException {

        clickImpl(graphicsComponent, constraints, clickOptions, xPos, xAbsolute, yPos, yAbsolute);
    }

    /**
     * {@inheritDoc}
     */
    public String getPropertyValue(Object graphicsComponent, String propertyName) throws RobotException {
        String propertyValue = StringConstants.EMPTY;
        Validate.notNull(graphicsComponent, "Tested component must not be null"); //$NON-NLS-1$
        try {
            final Object prop = PropertyUtils.getProperty(graphicsComponent, propertyName);
            propertyValue = String.valueOf(prop);
        } catch (IllegalAccessException e) {
            throw new RobotException(e);
        } catch (InvocationTargetException e) {
            throw new RobotException(e);
        } catch (NoSuchMethodException e) {
            throw new RobotException(e);
        }

        return propertyValue;
    }

    /** {@inheritDoc} */
    public BufferedImage createFullScreenCapture() {
        return LocalScreenshotUtil.createFullScreenCapture();
    }
}