1/*
2 * Copyright (c) 1997, 2016, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23package org.netbeans.jemmy;
24
25import java.awt.AWTEvent;
26import java.awt.Component;
27import java.awt.Toolkit;
28import java.awt.Window;
29import java.awt.event.AWTEventListener;
30import java.awt.event.InputEvent;
31import java.awt.event.KeyEvent;
32import java.awt.event.MouseEvent;
33import java.awt.event.WindowEvent;
34import java.lang.reflect.Field;
35import java.lang.reflect.InvocationTargetException;
36
37/**
38 * Provides low level functions for reproducing user actions. One dispatch model
39 * uses the managed component's event queue to dispatch events. The other
40 * dispatch model uses {@code java.awt.Robot} to generate native events. It
41 * is an option in the Robot dispatch model to wait for the managed component's
42 * event queue to empty before dispatching events.
43 *
44 * Timeouts used: <BR>
45 * EventDispatcher.WaitQueueEmptyTimeout - to wait event queue empty. <BR>
46 * EventDispatcher.RobotAutoDelay - param for java.awt.Robot.setAutoDelay
47 * method. <BR>
48 * EventDispatcher.WaitComponentUnderMouseTimeout - time to wait component under
49 * mouse. <BR>
50 *
51 * @see org.netbeans.jemmy.Timeouts
52 *
53 * @author Alexandre Iline (alexandre.iline@oracle.com)
54 *
55 */
56public class EventDispatcher implements Outputable, Timeoutable {
57
58    private static final long WAIT_QUEUE_EMPTY_TIMEOUT = 180000;
59    private static final long ROBOT_AUTO_DELAY = 10;
60    private static final long WAIT_COMPONENT_UNDER_MOUSE_TIMEOUT = 60000;
61
62    private static Field[] keyFields;
63    private static volatile MotionListener motionListener = null;
64
65    /**
66     * Component to dispatch events to.
67     */
68    protected Component component;
69    private TestOut output;
70    private Timeouts timeouts;
71    private final ClassReference reference;
72    private int model;
73    private ClassReference robotReference = null;
74    private boolean outsider = false;
75    private final QueueTool queueTool;
76
77    /**
78     * Constructor.
79     *
80     * @param comp Component to operate with.
81     */
82    public EventDispatcher(Component comp) {
83        super();
84        component = comp;
85        reference = new ClassReference(comp);
86        queueTool = new QueueTool();
87        setOutput(JemmyProperties.getProperties().getOutput());
88        setTimeouts(JemmyProperties.getProperties().getTimeouts());
89        setDispatchingModel(JemmyProperties.getProperties().getDispatchingModel());
90    }
91
92    /**
93     * Waits for the managed component's {@code java.awt.EventQueue} to
94     * empty. The timeout for this wait is
95     * EventDispatcher.WaitQueueEmptyTimeout.
96     *
97     * @param output Output to print exception into.
98     * @param timeouts A collection of timeout assignments.
99     * @throws TimeoutExpiredException
100     * @see org.netbeans.jemmy.QueueTool
101     */
102    public static void waitQueueEmpty(TestOut output, Timeouts timeouts) {
103        QueueTool qt = new QueueTool();
104        qt.setTimeouts(timeouts.cloneThis());
105        qt.getTimeouts().
106                setTimeout("QueueTool.WaitQueueEmptyTimeout",
107                        JemmyProperties.
108                        getCurrentTimeout("EventDispatcher.WaitQueueEmptyTimeout"));
109        qt.setOutput(output);
110        qt.waitEmpty();
111    }
112
113    /**
114     * Waits for the managed component's {@code java.awt.EventQueue} to
115     * empty. Uses default output and timeouts. The timeout for this wait is
116     * EventDispatcher.WaitQueueEmptyTimeout.
117     *
118     * @see QueueTool
119     * @throws TimeoutExpiredException
120     */
121    public static void waitQueueEmpty() {
122        waitQueueEmpty(JemmyProperties.getCurrentOutput(),
123                JemmyProperties.getCurrentTimeouts());
124    }
125
126    /**
127     * Waits for the managed component's {@code java.awt.EventQueue} to
128     * stay empty. The timeout for this wait is
129     * EventDispatcher.WaitQueueEmptyTimeout.
130     *
131     * @param emptyTime The time that the event queue has to stay empty to avoid
132     * a TimeoutExpiredException.
133     * @param output Output to print exception into
134     * @param timeouts A collection of timeout assignments.
135     * @throws TimeoutExpiredException
136     * @see org.netbeans.jemmy.QueueTool
137     */
138    public static void waitQueueEmpty(long emptyTime, TestOut output, Timeouts timeouts) {
139        QueueTool qt = new QueueTool();
140        qt.setTimeouts(timeouts.cloneThis());
141        qt.getTimeouts().
142                setTimeout("QueueTool.WaitQueueEmptyTimeout",
143                        JemmyProperties.
144                        getCurrentTimeout("EventDispatcher.WaitQueueEmptyTimeout"));
145        qt.setOutput(output);
146        qt.waitEmpty(emptyTime);
147    }
148
149    /**
150     * Waits for the managed component's {@code java.awt.EventQueue} to
151     * stay empty. Uses default output and timeouts. The timeout for this wait
152     * is EventDispatcher.WaitQueueEmptyTimeout.
153     *
154     * @param emptyTime The time that the event queue has to stay empty to avoid
155     * a TimeoutExpiredException.
156     * @throws TimeoutExpiredException
157     * @see org.netbeans.jemmy.QueueTool
158     */
159    public static void waitQueueEmpty(long emptyTime) {
160        waitQueueEmpty(emptyTime,
161                JemmyProperties.getCurrentOutput(),
162                JemmyProperties.getCurrentTimeouts());
163    }
164
165    /**
166     * Get a string representation for key modifiers. Used to print trace.
167     *
168     * @param modifiers Bit mask of keyboard event modifiers.
169     * @return a string representation for the keyboard event modifiers.
170     */
171    public static String getModifiersString(int modifiers) {
172        String result = "";
173        if ((modifiers & InputEvent.CTRL_MASK) != 0) {
174            result = result + "CTRL_MASK | ";
175        }
176        if ((modifiers & InputEvent.META_MASK) != 0) {
177            result = result + "META_MASK | ";
178        }
179        if ((modifiers & InputEvent.ALT_MASK) != 0) {
180            result = result + "ALT_MASK | ";
181        }
182        if ((modifiers & InputEvent.ALT_GRAPH_MASK) != 0) {
183            result = result + "ALT_GRAPH_MASK | ";
184        }
185        if ((modifiers & InputEvent.SHIFT_MASK) != 0) {
186            result = result + "SHIFT_MASK | ";
187        }
188        if (result.length() > 0) {
189            return result.substring(0, result.length() - 3);
190        }
191        return result;
192    }
193
194    /**
195     * Returns a string representation for a keyboard event. Used to print
196     * trace.
197     *
198     * @param keyCode Key code ({@code KeyEvent.VK_*} value)
199     * @return the KeyEvent field name.
200     */
201    public static String getKeyDescription(int keyCode) {
202        for (Field keyField : keyFields) {
203            try {
204                if (keyField.getName().startsWith("VK_")
205                        && keyField.getInt(null) == keyCode) {
206                    return keyField.getName();
207                }
208            } catch (IllegalAccessException e) {
209                JemmyProperties.getCurrentOutput().printStackTrace(e);
210            }
211        }
212        return "VK_UNKNOWN";
213    }
214
215    /**
216     * Returns a mouse button string representation. Used to print trace.
217     *
218     * @param button Mouse button ({@code InputEvent.BUTTON1/2/3_MASK}
219     * value).
220     * @return InputEvent field name.
221     */
222    public static String getMouseButtonDescription(int button) {
223        String result;
224        if ((button & InputEvent.BUTTON1_MASK) != 0) {
225            result = "BUTTON1";
226        } else if ((button & InputEvent.BUTTON2_MASK) != 0) {
227            result = "BUTTON2";
228        } else if ((button & InputEvent.BUTTON3_MASK) != 0) {
229            result = "BUTTON3";
230        } else {
231            result = "UNKNOWN_BUTTON";
232        }
233        return result;
234    }
235
236    public static void performInit() {
237        Timeouts.initDefault("EventDispatcher.WaitQueueEmptyTimeout", WAIT_QUEUE_EMPTY_TIMEOUT);
238        Timeouts.initDefault("EventDispatcher.RobotAutoDelay", ROBOT_AUTO_DELAY);
239        Timeouts.initDefault("EventDispatcher.WaitComponentUnderMouseTimeout", WAIT_COMPONENT_UNDER_MOUSE_TIMEOUT);
240        try {
241            keyFields = Class.forName("java.awt.event.KeyEvent").getFields();
242        } catch (ClassNotFoundException e) {
243            JemmyProperties.getCurrentOutput().printStackTrace(e);
244        }
245    }
246
247    static {
248        performInit();
249    }
250
251    /**
252     * Wait (or not) for the mouse to move over a Java component before
253     * pressing. This option is relevant when using {@code java.awt.Robot}
254     * to generate mouse events. If a mouse press occurs at a position not
255     * occupied by a known Java component then a
256     * {@code NoComponentUnderMouseException} will be thrown.
257     *
258     * @param yesOrNo if {@code true} then the test system will wait for
259     * the mouse to move over a Java component before pressing. therwise, mouse
260     * presses can take place anywhere on the screen.
261     */
262    public void checkComponentUnderMouse(boolean yesOrNo) {
263        outsider = !yesOrNo;
264    }
265
266    /**
267     * Defines print output streams or writers.
268     *
269     * @param out Identify the streams or writers used for print output.
270     * @see org.netbeans.jemmy.Outputable
271     * @see org.netbeans.jemmy.TestOut
272     * @see #getOutput
273     */
274    @Override
275    public void setOutput(TestOut out) {
276        output = out;
277        queueTool.setOutput(out);
278    }
279
280    /**
281     * Returns print output streams or writers.
282     *
283     * @return an object that contains references to objects for printing to
284     * output and err streams.
285     * @see org.netbeans.jemmy.Outputable
286     * @see org.netbeans.jemmy.TestOut
287     * @see #setOutput
288     */
289    @Override
290    public TestOut getOutput() {
291        return output;
292    }
293
294    /**
295     * Defines current timeouts.
296     *
297     * @param timeouts A collection of timeout assignments.
298     * @see org.netbeans.jemmy.Timeoutable
299     * @see org.netbeans.jemmy.Timeouts
300     * @see #getTimeouts
301     */
302    @Override
303    public void setTimeouts(Timeouts timeouts) {
304        this.timeouts = timeouts;
305        queueTool.setTimeouts(timeouts);
306        queueTool.getTimeouts().
307                setTimeout("QueueTool.WaitQueueEmptyTimeout",
308                        timeouts.
309                        getTimeout("EventDispatcher.WaitQueueEmptyTimeout"));
310        if (robotReference != null) {
311            try {
312                Object[] params = {(int) timeouts.getTimeout("EventDispatcher.RobotAutoDelay")};
313                Class<?>[] paramClasses = {Integer.TYPE};
314                robotReference.invokeMethod("setAutoDelay", params, paramClasses);
315            } catch (InvocationTargetException
316                    | IllegalStateException
317                    | NoSuchMethodException
318                    | IllegalAccessException e) {
319                output.printStackTrace(e);
320            }
321        }
322    }
323
324    /**
325     * Return current timeouts.
326     *
327     * @return the collection of current timeout assignments.
328     * @see org.netbeans.jemmy.Timeoutable
329     * @see org.netbeans.jemmy.Timeouts
330     * @see #setTimeouts
331     */
332    @Override
333    public Timeouts getTimeouts() {
334        return timeouts;
335    }
336
337    /**
338     * Defines dispatching model.
339     *
340     * @param m New model value.
341     * @see #getDispatchingModel()
342     * @see org.netbeans.jemmy.JemmyProperties#QUEUE_MODEL_MASK
343     * @see org.netbeans.jemmy.JemmyProperties#ROBOT_MODEL_MASK
344     * @see org.netbeans.jemmy.JemmyProperties#getCurrentDispatchingModel()
345     * @see org.netbeans.jemmy.JemmyProperties#setCurrentDispatchingModel(int)
346     * @see org.netbeans.jemmy.JemmyProperties#initDispatchingModel(boolean,
347     * boolean)
348     * @see org.netbeans.jemmy.JemmyProperties#initDispatchingModel()
349     */
350    public void setDispatchingModel(int m) {
351        model = m;
352        if ((model & JemmyProperties.ROBOT_MODEL_MASK) != 0) {
353            createRobot();
354            try {
355                Object[] params = {(model & JemmyProperties.QUEUE_MODEL_MASK) != 0 ? Boolean.TRUE : Boolean.FALSE};
356                Class<?>[] paramClasses = {Boolean.TYPE};
357                robotReference.invokeMethod("setAutoWaitForIdle", params, paramClasses);
358            } catch (InvocationTargetException
359                    | IllegalStateException
360                    | NoSuchMethodException
361                    | IllegalAccessException e) {
362                output.printStackTrace(e);
363            }
364        }
365    }
366
367    /**
368     * Gets the dispatching model value.
369     *
370     * @return the model value.
371     * @see #setDispatchingModel(int)
372     * @see org.netbeans.jemmy.JemmyProperties#QUEUE_MODEL_MASK
373     * @see org.netbeans.jemmy.JemmyProperties#ROBOT_MODEL_MASK
374     * @see org.netbeans.jemmy.JemmyProperties#getCurrentDispatchingModel()
375     * @see org.netbeans.jemmy.JemmyProperties#setCurrentDispatchingModel(int)
376     * @see org.netbeans.jemmy.JemmyProperties#initDispatchingModel(boolean,
377     * boolean)
378     * @see org.netbeans.jemmy.JemmyProperties#initDispatchingModel()
379     */
380    public int getDispatchingModel() {
381        return model;
382    }
383
384    /**
385     * Dispatches {@code AWTEvent} to component passed in constructor. If
386     * {@code (getDispatchingModel & JemmyProperties.QUEUE_MODEL_MASK) == 0}
387     * dispatched event directly, otherwise uses
388     * {@code javax.swing.SwingUtilities.invokeAndWait(Runnable)}<BR>
389     *
390     * @param event AWTEvent instance to be dispatched.
391     * @throws ComponentIsNotVisibleException
392     * @throws ComponentIsNotFocusedException
393     */
394    public void dispatchEvent(final AWTEvent event) {
395        // run in dispatch thread
396        String eventToString = queueTool.invokeSmoothly(
397                new QueueTool.QueueAction<String>("event.toString()") {
398            @Override
399            public String launch() {
400                return event.toString();
401            }
402        }
403        );
404        output.printLine("Dispatch event " + eventToString);
405        output.printGolden("Dispatch event " + event.getClass().toString());
406        Dispatcher<Void> disp = new Dispatcher<>(event);
407        queueTool.invokeAndWait(disp);
408    }
409
410    /**
411     * Dispatches a MouseEvent.
412     *
413     * @see #dispatchEvent(AWTEvent)
414     * @param id {@code MouseEvent.MOUSE_*} value
415     * @param mods {@code InputEvent.MOUSE1/2/3_BUTTON} | (modifiers value)
416     * @param clickCount Click count
417     * @param x Horizontal click point coordinate.
418     * @param y vertical click point coordinate.
419     * @param popup Defines if mouse event is a popup event.
420     */
421    public void dispatchMouseEvent(int id, int mods, int clickCount, int x, int y,
422            boolean popup) {
423        MouseEvent event = new MouseEvent(component, id, System.currentTimeMillis(),
424                mods, x, y, clickCount, popup);
425        dispatchEvent(event);
426    }
427
428    /**
429     * Dispatches MouseEvent at the center of component.
430     *
431     * @see #dispatchEvent(AWTEvent)
432     * @param id {@code MouseEvent.MOUSE_*} value
433     * @param mods {@code InputEvent.MOUSE1/2/3_BUTTON} | (modiviers value)
434     * @param clickCount Click count
435     * @param popup Difines if mouse event is popup event.
436     */
437    public void dispatchMouseEvent(int id, int mods, int clickCount,
438            boolean popup) {
439        int x = component.getWidth() / 2;
440        int y = component.getHeight() / 2;
441        dispatchMouseEvent(id, mods, clickCount, x, y, popup);
442    }
443
444    /**
445     * Dispatches WindowEvent.
446     *
447     * @see #dispatchEvent(AWTEvent)
448     * @param id {@code WindowEvent.WINDOW_*} value
449     */
450    public void dispatchWindowEvent(int id) {
451        WindowEvent event = new WindowEvent((Window) component, id);
452        dispatchEvent(event);
453    }
454
455    /**
456     * Dispatches KeyEvent.
457     *
458     * @see #dispatchEvent(AWTEvent)
459     * @param id {@code KeyEvent.KEY_PRESSED} or
460     * {@code KeyEvent.KEY_RELEASED} value.
461     * @param mods Modifiers.
462     * @param keyCode Key code,
463     */
464    @Deprecated
465    public void dispatchKeyEvent(int id, int mods, int keyCode) {
466        KeyEvent event = new KeyEvent(component, id, System.currentTimeMillis(), mods, keyCode);
467        dispatchEvent(event);
468    }
469
470    /**
471     * Dispatches KeyEvent.
472     *
473     * @see #dispatchEvent(AWTEvent)
474     * @param id {@code KeyEvent.KEY_TYPED} value.
475     * @param mods Modifiers.
476     * @param keyCode Key code,
477     * @param keyChar Char to be tiped
478     */
479    public void dispatchKeyEvent(int id, int mods, int keyCode, char keyChar) {
480        KeyEvent event = new KeyEvent(component, id, System.currentTimeMillis(),
481                mods, keyCode, keyChar);
482        dispatchEvent(event);
483    }
484
485    /**
486     * Waits until all events currently on the event queue have been processed.
487     */
488    public void waitForIdle() {
489        makeRobotOperation("waitForIdle", null, null);
490    }
491
492    /**
493     * Bind horizontal relative cursor coordinate to screen coordinate.
494     *
495     * @param x Relative coordinate
496     * @return Absolute coordinate
497     */
498    protected int getAbsoluteX(int x) {
499        return (int) component.getLocationOnScreen().getX() + x;
500    }
501
502    /**
503     * Bind vertical relative cursor coordinate to screen coordinate.
504     *
505     * @param y Relative coordinate
506     * @return Absolute coordinate
507     */
508    protected int getAbsoluteY(int y) {
509        return (int) component.getLocationOnScreen().getY() + y;
510    }
511
512    /**
513     * Delays robot.
514     *
515     * @param time Time to dalay robot for.
516     */
517    public void delayRobot(long time) {
518        Object[] params = {(int) time};
519        Class<?>[] paramClasses = {Integer.TYPE};
520        makeRobotOperation("delay", params, paramClasses);
521    }
522
523    /**
524     * Moves mouse by robot.
525     *
526     * @param x Component relative horizontal coordinate.
527     * @param y Component relative vertical coordinate.
528     * @throws ComponentIsNotVisibleException
529     */
530    public void robotMoveMouse(int x, int y) {
531        if (motionListener == null) {
532            initMotionListener();
533        }
534        output.printLine("Move mouse to (" + Integer.toString(x) + ","
535                + Integer.toString(y) + ")");
536        Object[] params = {getAbsoluteX(x), getAbsoluteY(y)};
537        Class<?>[] paramClasses = {Integer.TYPE, Integer.TYPE};
538        makeRobotOperation("mouseMove", params, paramClasses);
539    }
540
541    /**
542     * Press mouse button by robot.
543     *
544     * @param button Mouse button (InputEvent.MOUSE1/2/3_BUTTON value)
545     * @param modifiers Modifiers
546     * @throws ComponentIsNotVisibleException
547     */
548    public void robotPressMouse(int button, int modifiers) {
549        if (!outsider) {
550            waitMouseOver();
551        }
552        robotPressModifiers(modifiers);
553        output.printLine("Press " + getMouseButtonDescription(button) + " mouse button");
554        Object[] params = {button};
555        Class<?>[] paramClasses = {Integer.TYPE};
556        makeRobotOperation("mousePress", params, paramClasses);
557    }
558
559    /**
560     * Press mouse button with 0 modifiers.
561     *
562     * @param button Mouse button ({@code InputEvent.MOUSE1/2/3_BUTTON}
563     * value)
564     * @see #robotPressMouse(int, int)
565     */
566    public void robotPressMouse(int button) {
567        robotPressMouse(button, 0);
568    }
569
570    /**
571     * Releases mouse button by robot.
572     *
573     * @param button Mouse button ({@code InputEvent.MOUSE1/2/3_BUTTON}
574     * value)
575     * @param modifiers Modifiers
576     * @throws ComponentIsNotVisibleException
577     */
578    public void robotReleaseMouse(int button, int modifiers) {
579        output.printLine("Release " + getMouseButtonDescription(button) + " mouse button");
580        Object[] params = {button};
581        Class<?>[] paramClasses = {Integer.TYPE};
582        makeRobotOperation("mouseRelease", params, paramClasses);
583        robotReleaseModifiers(modifiers);
584    }
585
586    /**
587     * Releases mouse button with 0 modifiers.
588     *
589     * @param button Mouse button ({@code InputEvent.MOUSE1/2/3_BUTTON}
590     * value)
591     * @see #robotReleaseMouse(int, int)
592     */
593    public void robotReleaseMouse(int button) {
594        robotReleaseMouse(button, 0);
595    }
596
597    /**
598     * Press a key using {@code java.awt.Robot}.
599     *
600     * @param keyCode Key ({@code KeyEvent.VK_*} value)
601     * @param modifiers Mask of KeyEvent modifiers.
602     * @throws ComponentIsNotVisibleException
603     * @throws ComponentIsNotFocusedException
604     */
605    public void robotPressKey(int keyCode, int modifiers) {
606        robotPressModifiers(modifiers);
607        output.printLine("Press " + getKeyDescription(keyCode) + " key");
608        Object[] params = {keyCode};
609        Class<?>[] paramClasses = {Integer.TYPE};
610        makeRobotOperation("keyPress", params, paramClasses);
611    }
612
613    /**
614     * Press key with no modifiers using {@code java.awt.Robot}.
615     *
616     * @param keyCode Key ({@code KeyEvent.VK_*} value)
617     * @see #robotPressKey(int, int)
618     */
619    public void robotPressKey(int keyCode) {
620        robotPressKey(keyCode, 0);
621    }
622
623    /**
624     * Releases key by robot.
625     *
626     * @param keyCode Key ({@code KeyEvent.VK_*} value)
627     * @param modifiers Mask of KeyEvent modifiers.
628     * @throws ComponentIsNotVisibleException
629     * @throws ComponentIsNotFocusedException
630     */
631    public void robotReleaseKey(int keyCode, int modifiers) {
632        output.printLine("Release " + getKeyDescription(keyCode) + " key");
633        Object[] params = {keyCode};
634        Class<?>[] paramClasses = {Integer.TYPE};
635        makeRobotOperation("keyRelease", params, paramClasses);
636        robotReleaseModifiers(modifiers);
637    }
638
639    /**
640     * Releases key with 0 modifiers.
641     *
642     * @param keyCode Key ({@code KeyEvent.VK_*} value)
643     * @see #robotPressKey(int, int)
644     */
645    public void robotReleaseKey(int keyCode) {
646        robotReleaseKey(keyCode, 0);
647    }
648
649    /**
650     * Invokes component method through
651     * {@code SwingUtilities.invokeAndWait(Runnable)}.
652     *
653     * @param method_name Name of a method to be invoked
654     * @param params Method params
655     * @param params_classes Method params' classes
656     * @return an Object - methods result.
657     * @see org.netbeans.jemmy.ClassReference
658     * @exception IllegalAccessException
659     * @exception NoSuchMethodException
660     * @exception IllegalStateException
661     * @exception InvocationTargetException
662     */
663    public Object invokeMethod(String method_name, Object[] params, Class<?>[] params_classes)
664            throws InvocationTargetException, IllegalStateException, NoSuchMethodException, IllegalAccessException {
665        Invoker invk = new Invoker(method_name, params, params_classes);
666        try {
667            return queueTool.invokeAndWait(invk);
668        } catch (JemmyException e) {
669            Exception ex = invk.getException();
670            if (ex != null) {
671                if (ex instanceof InvocationTargetException) {
672                    InvocationTargetException ite = (InvocationTargetException) ex;
673                    ite.addSuppressed(e);
674                    throw ite;
675                } else if (ex instanceof IllegalStateException) {
676                    IllegalStateException ise = (IllegalStateException) ex;
677                    ise.addSuppressed(e);
678                    throw ise;
679                } else if (ex instanceof NoSuchMethodException) {
680                    NoSuchMethodException nsme = (NoSuchMethodException) ex;
681                    nsme.addSuppressed(e);
682                    throw nsme;
683                } else if (ex instanceof IllegalAccessException) {
684                    IllegalAccessException iae = (IllegalAccessException) ex;
685                    iae.addSuppressed(e);
686                    throw iae;
687                } else {
688                    e.addSuppressed(ex);
689                }
690            }
691            throw (e);
692        }
693    }
694
695    /**
696     * Gets component field value through
697     * {@code SwingUtilities.invokeAndWait(Runnable)}.
698     *
699     * @param field_name Name of a field
700     * @see #setField(String, Object)
701     * @see org.netbeans.jemmy.ClassReference
702     * @return an Object - field value
703     * @exception IllegalAccessException
704     * @exception IllegalStateException
705     * @exception InvocationTargetException
706     * @exception NoSuchFieldException
707     */
708    public Object getField(String field_name)
709            throws InvocationTargetException, IllegalStateException, NoSuchFieldException, IllegalAccessException {
710        Getter gtr = new Getter(field_name);
711        try {
712            return queueTool.invokeAndWait(gtr);
713        } catch (JemmyException e) {
714            Exception ex = gtr.getException();
715            if (ex != null) {
716                if (ex instanceof InvocationTargetException) {
717                    InvocationTargetException ite = (InvocationTargetException) ex;
718                    ite.addSuppressed(e);
719                    throw ite;
720                } else if (ex instanceof IllegalStateException) {
721                    IllegalStateException ise = (IllegalStateException) ex;
722                    ise.addSuppressed(e);
723                    throw ise;
724                } else if (ex instanceof NoSuchFieldException) {
725                    NoSuchFieldException nsfe = (NoSuchFieldException) ex;
726                    nsfe.addSuppressed(e);
727                    throw nsfe;
728                } else if (ex instanceof IllegalAccessException) {
729                    IllegalAccessException iae = (IllegalAccessException) ex;
730                    iae.addSuppressed(e);
731                    throw iae;
732                } else {
733                    e.addSuppressed(ex);
734                }
735            }
736            throw (e);
737        }
738    }
739
740    /**
741     * Sets component field value through
742     * {@code SwingUtilities.invokeAndWait(Runnable)}.
743     *
744     * @param field_name Name of a field
745     * @param newValue New field value
746     * @see #getField(String)
747     * @see org.netbeans.jemmy.ClassReference
748     * @exception IllegalAccessException
749     * @exception IllegalStateException
750     * @exception InvocationTargetException
751     * @exception NoSuchFieldException
752     */
753    public void setField(String field_name, Object newValue)
754            throws InvocationTargetException, IllegalStateException, NoSuchFieldException, IllegalAccessException {
755        Setter str = new Setter(field_name, newValue);
756        try {
757            queueTool.invokeAndWait(str);
758        } catch (JemmyException e) {
759            Exception ex = str.getException();
760            if (ex != null) {
761                if (ex instanceof InvocationTargetException) {
762                    InvocationTargetException ite = (InvocationTargetException) ex;
763                    ite.addSuppressed(e);
764                    throw ite;
765                } else if (ex instanceof IllegalStateException) {
766                    IllegalStateException ise = (IllegalStateException) ex;
767                    ise.addSuppressed(e);
768                    throw ise;
769                } else if (ex instanceof NoSuchFieldException) {
770                    NoSuchFieldException nsfe = (NoSuchFieldException) ex;
771                    nsfe.addSuppressed(e);
772                    throw nsfe;
773                } else if (ex instanceof IllegalAccessException) {
774                    IllegalAccessException iae = (IllegalAccessException) ex;
775                    iae.addSuppressed(e);
776                    throw iae;
777                } else {
778                    e.addSuppressed(ex);
779                }
780            }
781            throw (e);
782        }
783    }
784
785    /**
786     * Invokes component method through
787     * {@code SwingUtilities.invokeAndWait(Runnable)}. and catch all
788     * exceptions.
789     *
790     * @param method_name Name of a method to be invoked
791     * @param params Method params
792     * @param params_classes Method params' classes
793     * @param out TestOut instance to print exceptions stack trace to.
794     * @return an Object - method result
795     * @see #invokeMethod(String, Object[], Class[])
796     * @see org.netbeans.jemmy.ClassReference
797     */
798    public Object invokeExistingMethod(String method_name, Object[] params, Class<?>[] params_classes,
799            TestOut out) {
800        try {
801            return invokeMethod(method_name, params, params_classes);
802        } catch (InvocationTargetException
803                | IllegalStateException
804                | NoSuchMethodException
805                | IllegalAccessException e) {
806            out.printStackTrace(e);
807        }
808        return null;
809    }
810
811    /**
812     * Gets component field value through
813     * {@code SwingUtilities.invokeAndWait(Runnable)}. and catch all
814     * exceptions.
815     *
816     * @param field_name Name of a field
817     * @param out TestOut instance to print exceptions stack trace to.
818     * @return an Object - fields value
819     * @see #getField(String)
820     * @see #setExistingField(String, Object, TestOut)
821     * @see org.netbeans.jemmy.ClassReference
822     */
823    public Object getExistingField(String field_name,
824            TestOut out) {
825        try {
826            return getField(field_name);
827        } catch (InvocationTargetException
828                | IllegalStateException
829                | NoSuchFieldException
830                | IllegalAccessException e) {
831            out.printStackTrace(e);
832        }
833        return null;
834    }
835
836    /**
837     * Sets component field value through
838     * {@code SwingUtilities.invokeAndWait(Runnable)}. and catch all
839     * exceptions.
840     *
841     * @param field_name Name of a field
842     * @param newValue New field value
843     * @param out TestOut instance to print exceptions stack trace to.
844     * @see #setField(String, Object)
845     * @see #getExistingField(String, TestOut)
846     * @see org.netbeans.jemmy.ClassReference
847     */
848    public void setExistingField(String field_name, Object newValue,
849            TestOut out) {
850        try {
851            setField(field_name, newValue);
852        } catch (InvocationTargetException
853                | IllegalStateException
854                | NoSuchFieldException
855                | IllegalAccessException e) {
856            out.printStackTrace(e);
857        }
858    }
859
860    /**
861     * Invokes component method through
862     * {@code SwingUtilities.invokeAndWait(Runnable)}. and catch all
863     * exceptions. Exceptions are printed into TestOut object defined by
864     * setOutput(TestOut) method.
865     *
866     * @param method_name Name of a method to be invoked
867     * @param params Method params
868     * @param params_classes Method params' classes
869     * @return an Object - method result
870     * @see #invokeExistingMethod(String, Object[], Class[], TestOut)
871     * @see org.netbeans.jemmy.ClassReference
872     */
873    public Object invokeExistingMethod(String method_name, Object[] params, Class<?>[] params_classes) {
874        return invokeExistingMethod(method_name, params, params_classes, output);
875    }
876
877    /**
878     * Gets component field value through
879     * {@code SwingUtilities.invokeAndWait(Runnable)}. and catch all
880     * exceptions. Exceptions are printed into TestOut object defined by
881     * setOutput(TestOut) method.
882     *
883     * @param field_name Name of a field
884     * @return an Object - fields value
885     * @see #getExistingField(String, TestOut)
886     * @see #setExistingField(String, Object)
887     * @see org.netbeans.jemmy.ClassReference
888     */
889    public Object getExistingField(String field_name) {
890        return getExistingField(field_name, output);
891    }
892
893    /**
894     * Sets component field value through
895     * {@code SwingUtilities.invokeAndWait(Runnable)}. and catch all
896     * exceptions. Exceptions are printed into TestOut object defined by
897     * setOutput(TestOut) method.
898     *
899     * @param field_name Name of a field
900     * @param newValue New field value
901     * @see #setExistingField(String, Object, TestOut)
902     * @see #getExistingField(String)
903     * @see org.netbeans.jemmy.ClassReference
904     */
905    public void setExistingField(String field_name, Object newValue) {
906        setExistingField(field_name, newValue, output);
907    }
908
909    //recursivelly releases all modifiers keys
910    private void robotReleaseModifiers(int modifiers) {
911        if ((modifiers & InputEvent.SHIFT_MASK) != 0) {
912            robotReleaseKey(KeyEvent.VK_SHIFT, modifiers - (InputEvent.SHIFT_MASK & modifiers));
913        } else if ((modifiers & InputEvent.ALT_GRAPH_MASK) != 0) {
914            robotReleaseKey(KeyEvent.VK_ALT_GRAPH, modifiers - (InputEvent.ALT_GRAPH_MASK & modifiers));
915        } else if ((modifiers & InputEvent.ALT_MASK) != 0) {
916            robotReleaseKey(KeyEvent.VK_ALT, modifiers - (InputEvent.ALT_MASK & modifiers));
917        } else if ((modifiers & InputEvent.META_MASK) != 0) {
918            robotReleaseKey(KeyEvent.VK_META, modifiers - (InputEvent.META_MASK & modifiers));
919        } else if ((modifiers & InputEvent.CTRL_MASK) != 0) {
920            robotReleaseKey(KeyEvent.VK_CONTROL, modifiers - (InputEvent.CTRL_MASK & modifiers));
921        }
922    }
923
924    //throws ComponentIsNotVisibleException if component is not visible
925    private void checkVisibility() {
926        if (!component.isVisible()) {
927            throw (new ComponentIsNotVisibleException(component));
928        }
929    }
930
931    //throws ComponentIsNotFocusedException if component has not focus
932    private void checkFocus() {
933        if (!component.hasFocus()) {
934            throw (new ComponentIsNotFocusedException(component));
935        }
936    }
937
938    //creates java.awt.Robot instance
939    private void createRobot() {
940        try {
941            ClassReference robotClassReverence = new ClassReference("java.awt.Robot");
942            robotReference = new ClassReference(robotClassReverence.newInstance(null, null));
943        } catch (ClassNotFoundException
944                | InstantiationException
945                | InvocationTargetException
946                | IllegalStateException
947                | NoSuchMethodException
948                | IllegalAccessException e) {
949            output.printStackTrace(e);
950        }
951    }
952
953    private void waitMouseOver() {
954        try {
955            Waiter<String, Component> wt = new Waiter<>(new Waitable<String, Component>() {
956                @Override
957                public String actionProduced(Component obj) {
958                    if (motionListener.getComponent() != null) {
959                        return "";
960                    } else {
961                        return null;
962                    }
963                }
964
965                @Override
966                public String getDescription() {
967                    return "Mouse over component";
968                }
969
970                @Override
971                public String toString() {
972                    return "waitMouseOver.Waiter{" + getDescription() + '}';
973                }
974            });
975            wt.setTimeoutsToCloneOf(timeouts, "EventDispatcher.WaitComponentUnderMouseTimeout");
976            wt.setOutput(output.createErrorOutput());
977            wt.waitAction(component);
978        } catch (InterruptedException e) {
979            output.printStackTrace(e);
980        } catch (TimeoutExpiredException e) {
981            throw (new NoComponentUnderMouseException());
982        }
983    }
984
985    //produce a robot operations through reflection
986    private void makeRobotOperation(String method, Object[] params, Class<?>[] paramClasses) {
987        try {
988            robotReference.invokeMethod(method, params, paramClasses);
989        } catch (InvocationTargetException
990                | IllegalStateException
991                | NoSuchMethodException
992                | IllegalAccessException e) {
993            output.printStackTrace(e);
994        }
995        if ((model & JemmyProperties.QUEUE_MODEL_MASK) != 0) {
996            try {
997                waitQueueEmpty(output.createErrorOutput(), timeouts);
998            } catch (TimeoutExpiredException e) {
999                output.printStackTrace(e);
1000            }
1001        }
1002    }
1003
1004    //recursivelly presses all modifiers keys
1005    private void robotPressModifiers(int modifiers) {
1006        if ((modifiers & InputEvent.SHIFT_MASK) != 0) {
1007            robotPressKey(KeyEvent.VK_SHIFT, modifiers & ~InputEvent.SHIFT_MASK);
1008        } else if ((modifiers & InputEvent.ALT_GRAPH_MASK) != 0) {
1009            robotPressKey(KeyEvent.VK_ALT_GRAPH, modifiers & ~InputEvent.ALT_GRAPH_MASK);
1010        } else if ((modifiers & InputEvent.ALT_MASK) != 0) {
1011            robotPressKey(KeyEvent.VK_ALT, modifiers & ~InputEvent.ALT_MASK);
1012        } else if ((modifiers & InputEvent.META_MASK) != 0) {
1013            robotPressKey(KeyEvent.VK_META, modifiers & ~InputEvent.META_MASK);
1014        } else if ((modifiers & InputEvent.CTRL_MASK) != 0) {
1015            robotPressKey(KeyEvent.VK_CONTROL, modifiers & ~InputEvent.CTRL_MASK);
1016        }
1017    }
1018
1019    private void initMotionListener() {
1020        synchronized(EventDispatcher.class) {
1021            if (motionListener == null) {
1022                motionListener = new MotionListener();
1023                Toolkit.getDefaultToolkit().addAWTEventListener(motionListener, AWTEvent.MOUSE_EVENT_MASK);
1024                Object[] params = new Object[2];
1025                Class<?>[] paramClasses = {Integer.TYPE, Integer.TYPE};
1026                params[0] = getAbsoluteX(-1);
1027                params[1] = getAbsoluteX(-1);
1028                makeRobotOperation("mouseMove", params, paramClasses);
1029                params[0] = getAbsoluteX(0);
1030                params[1] = getAbsoluteX(0);
1031                makeRobotOperation("mouseMove", params, paramClasses);
1032            }
1033        }
1034    }
1035
1036    private class Dispatcher<R> extends QueueTool.QueueAction<R> {
1037
1038        AWTEvent event;
1039
1040        public Dispatcher(AWTEvent e) {
1041            super(e.getClass().getName() + " event dispatching");
1042            event = e;
1043        }
1044
1045        @Override
1046        public R launch() {
1047            if (event instanceof MouseEvent || event instanceof KeyEvent) {
1048                checkVisibility();
1049            }
1050            component.dispatchEvent(event);
1051            return null;
1052        }
1053    }
1054
1055    private class Invoker extends QueueTool.QueueAction<Object> {
1056
1057        protected String methodName;
1058        protected Object[] params;
1059        protected Class<?>[] paramClasses;
1060
1061        public Invoker(String mn, Object[] p, Class<?>[] pc) {
1062            super(mn + " method invocation");
1063            methodName = mn;
1064            params = p;
1065            paramClasses = pc;
1066        }
1067
1068        @Override
1069        public Object launch()
1070                throws InvocationTargetException, NoSuchMethodException, IllegalAccessException {
1071            checkVisibility();
1072            if (methodName.equals("keyPress") || methodName.equals("keyRelease")) {
1073                checkFocus();
1074            }
1075            return reference.invokeMethod(methodName, params, paramClasses);
1076        }
1077    }
1078
1079    private class Getter extends QueueTool.QueueAction<Object> {
1080
1081        String fieldName;
1082
1083        public Getter(String fn) {
1084            super(fn + " field receiving");
1085            fieldName = fn;
1086        }
1087
1088        @Override
1089        public Object launch()
1090                throws InvocationTargetException, NoSuchFieldException, IllegalAccessException {
1091            return reference.getField(fieldName);
1092        }
1093    }
1094
1095    private class Setter extends QueueTool.QueueAction<Object> {
1096
1097        String fieldName;
1098        Object newValue;
1099
1100        public Setter(String fn, Object nv) {
1101            super(fn + " field changing");
1102            fieldName = fn;
1103            newValue = nv;
1104        }
1105
1106        @Override
1107        public Object launch()
1108                throws InvocationTargetException, NoSuchFieldException, IllegalAccessException {
1109            reference.setField(fieldName, newValue);
1110            return null;
1111        }
1112    }
1113
1114    private static class MotionListener implements AWTEventListener {
1115
1116        private volatile Component mouseComponent;
1117
1118        @Override
1119        public void eventDispatched(AWTEvent event) {
1120            if (event instanceof MouseEvent) {
1121                MouseEvent e = (MouseEvent) event;
1122                if (e.getID() == MouseEvent.MOUSE_ENTERED) {
1123                    mouseComponent = e.getComponent();
1124                } else if (e.getID() == MouseEvent.MOUSE_EXITED) {
1125                    mouseComponent = null;
1126                }
1127            }
1128        }
1129
1130        public Component getComponent() {
1131            return mouseComponent;
1132        }
1133    }
1134}
1135