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.Toolkit;
27import java.awt.event.AWTEventListener;
28import java.lang.ref.Reference;
29import java.lang.ref.WeakReference;
30import java.lang.reflect.Field;
31import java.lang.reflect.Modifier;
32import java.util.Vector;
33
34/**
35 *
36 * Provides methods to check last dispatched events, to wait for events of
37 * specific types, or to guarantee that events of specific types are not
38 * dispatched during some time frame.
39 * <BR><BR>
40 * All possible listeners are added during this class initialization in case if
41 * "jemmy.event_listening" system property is not equal to "no", so, by default,
42 * all events are listened.
43 *
44 * Uses timeouts:<BR>
45 * EventTool.WaitEventTimeout - time to wait for AWT events.<BR>
46 * EventTool.WaitNoEventTimeout - when checking for the absence of incoming AWT
47 * events.<BR>
48 * EventTool.EventCheckingDelta - time delta between checks for AWT events.
49 *
50 * @author Alexandre Iline (alexandre.iline@oracle.com)
51 */
52public class EventTool implements Timeoutable, Outputable {
53
54    private static final long WAIT_EVENT_TIMEOUT = 60000;
55    private static final long WAIT_NO_EVENT_TIMEOUT = 180000;
56    private static final long EVENT_CHECKING_DELTA = 10;
57
58    private static ListenerSet listenerSet;
59    private static long currentEventMask = 0;
60
61    private TestOut output;
62    private Timeouts timeouts;
63
64    /**
65     * Constructor.
66     */
67    public EventTool() {
68        setOutput(JemmyProperties.getProperties().getOutput());
69        setTimeouts(JemmyProperties.getProperties().getTimeouts());
70    }
71
72    /**
73     * Returns time of the last dispatched event under mask.
74     *
75     * @param eventMask Events types to be searched.
76     * {@code AWTEvent.*_EVENT_MASK} fields combination.
77     * @return time in milliseconds
78     * @see #addListeners(long)
79     */
80    public static long getLastEventTime(long eventMask) {
81        return listenerSet.getLastEventTime(eventMask);
82    }
83
84    /**
85     * Returns last dispatched event under mask.
86     *
87     * @param eventMask Events types to be searched.
88     * {@code AWTEvent.*_EVENT_MASK} fields combination.
89     * @return AWTEvent
90     * @see #addListeners(long)
91     */
92    public static AWTEvent getLastEvent(long eventMask) {
93        return listenerSet.getLastEvent(eventMask);
94    }
95
96    /**
97     * Returns time of the last dispatched event.
98     *
99     * @return time in milliseconds
100     * @see #addListeners(long)
101     */
102    public static long getLastEventTime() {
103        return getLastEventTime(listenerSet.getTheWholeMask());
104    }
105
106    /**
107     * Returns last dispatched event.
108     *
109     * @return AWTEvent
110     * @see #addListeners(long)
111     */
112    public static AWTEvent getLastEvent() {
113        return getLastEvent(listenerSet.getTheWholeMask());
114    }
115
116    /**
117     * Adds listeners to listen events under mask. Invokes
118     * {@code removeListeners()} first, so any event history is lost.
119     *
120     * @param eventMask Mask to listen events under.
121     * {@code AWTEvent.*_EVENT_MASK} fields combination.
122     * @see #addListeners()
123     * @see #removeListeners()
124     */
125    public static void addListeners(long eventMask) {
126        removeListeners();
127        listenerSet.addListeners(eventMask);
128        currentEventMask = eventMask;
129    }
130
131    /**
132     * Adds listeners to listen all types of events. Invokes
133     * {@code removeListeners()} first, so any event history is lost. This
134     * method is invoked during static section of this class.
135     *
136     * @see #addListeners(long)
137     * @see #removeListeners()
138     * @see #getTheWholeEventMask()
139     */
140    public static void addListeners() {
141        addListeners(listenerSet.getTheWholeMask());
142    }
143
144    /**
145     * Removes all listeners.
146     *
147     * @see #addListeners(long)
148     * @see #addListeners()
149     */
150    public static void removeListeners() {
151        listenerSet.removeListeners();
152    }
153
154    /**
155     * Returns event mask last time used by {@code addListeners(long)}
156     * method. In case if {@code addListeners()} method was used last,
157     * {@code getTheWholeEventMask() } result is returned.
158     *
159     * @return a long representing the current event mask value
160     * @see #getTheWholeEventMask()
161     */
162    public static long getCurrentEventMask() {
163        return currentEventMask;
164    }
165
166    /**
167     * Returns a combination of all {@code AWTEvent.*_EVENT_MASK} fields..
168     *
169     * @return a combination of all {@code AWTEvent.*_EVENT_MASK} fields.
170     */
171    public static long getTheWholeEventMask() {
172        return listenerSet.getTheWholeMask();
173    }
174
175    static {
176        Timeouts.initDefault("EventTool.WaitEventTimeout", WAIT_EVENT_TIMEOUT);
177        Timeouts.initDefault("EventTool.WaitNoEventTimeout", WAIT_NO_EVENT_TIMEOUT);
178        Timeouts.initDefault("EventTool.EventCheckingDelta", EVENT_CHECKING_DELTA);
179        listenerSet = new ListenerSet();
180        if (System.getProperty("jemmy.event_listening") == null
181                || !System.getProperty("jemmy.event_listening").equals("no")) {
182            listenerSet.addListeners();
183        }
184    }
185
186    /**
187     * Defines current timeouts.
188     *
189     * @param ts ?t? A collection of timeout assignments.
190     * @see org.netbeans.jemmy.Timeouts
191     * @see org.netbeans.jemmy.Timeoutable
192     * @see #getTimeouts
193     */
194    @Override
195    public void setTimeouts(Timeouts ts) {
196        timeouts = ts;
197    }
198
199    /**
200     * Return current timeouts.
201     *
202     * @return the collection of current timeout assignments.
203     * @see org.netbeans.jemmy.Timeouts
204     * @see org.netbeans.jemmy.Timeoutable
205     * @see #setTimeouts
206     */
207    @Override
208    public Timeouts getTimeouts() {
209        return timeouts;
210    }
211
212    /**
213     * Defines print output streams or writers.
214     *
215     * @param out Identify the streams or writers used for print output.
216     * @see org.netbeans.jemmy.Outputable
217     * @see org.netbeans.jemmy.TestOut
218     * @see #getOutput
219     */
220    @Override
221    public void setOutput(TestOut out) {
222        output = out;
223    }
224
225    /**
226     * Returns print output streams or writers.
227     *
228     * @return an object that contains references to objects for printing to
229     * output and err streams.
230     * @see org.netbeans.jemmy.Outputable
231     * @see org.netbeans.jemmy.TestOut
232     * @see #setOutput
233     */
234    @Override
235    public TestOut getOutput() {
236        return output;
237    }
238
239    /**
240     * Waits for the first event under mask. Waits during
241     * {@code EventTool.WaitEventTimeout} milliseconds.
242     *
243     * @param eventMask Mask to wait events under.
244     * {@code AWTEvent.*_EVENT_MASK} fields combination.
245     * @return an AWTEvent object
246     * @see #waitEvent()
247     * @throws TimeoutExpiredException
248     */
249    public AWTEvent waitEvent(long eventMask) {
250        return (waitEvent(eventMask,
251                timeouts.getTimeout("EventTool.WaitEventTimeout"),
252                output.createErrorOutput()));
253    }
254
255    /**
256     * Waits for the first event. Waits during
257     * {@code EventTool.WaitEventTimeout} milliseconds.
258     *
259     * @return an AWTEvent object
260     * @see #waitEvent(long)
261     * @see #getTheWholeEventMask()
262     * @throws TimeoutExpiredException
263     */
264    public AWTEvent waitEvent() {
265        return waitEvent(listenerSet.getTheWholeMask());
266    }
267
268    /**
269     * Check that no event under mask will be dispatched during time specified.
270     *
271     * @param eventMask Mask to wait events under.
272     * {@code AWTEvent.*_EVENT_MASK} fields combination.
273     * @param waitTime Quiet time (millisecons).
274     * @return true if no event ahs found.
275     * @see #checkNoEvent(long)
276     */
277    public boolean checkNoEvent(long eventMask, long waitTime) {
278        return checkNoEvent(eventMask, waitTime, output);
279    }
280
281    /**
282     * Check that no event will be dispatched during time specified.
283     *
284     * @param waitTime Quiet time (millisecons).
285     * @return true if no event ahs found.
286     * @see #checkNoEvent(long, long)
287     * @see #getTheWholeEventMask()
288     */
289    public boolean checkNoEvent(long waitTime) {
290        return checkNoEvent(listenerSet.getTheWholeMask(), waitTime);
291    }
292
293    /**
294     * During {@code EventTool.WaitNoEventTimeout} time waits for true
295     * result of checkNoEvent(long, long) method.
296     *
297     * @param eventMask Mask to wait events under.
298     * {@code AWTEvent.*_EVENT_MASK} fields combination.
299     * @param waitTime Quiet time (millisecons).
300     * @see #checkNoEvent(long, long)
301     * @see #waitNoEvent(long)
302     * @throws TimeoutExpiredException
303     */
304    public void waitNoEvent(long eventMask, long waitTime) {
305        NoEventWaiter waiter = new NoEventWaiter(eventMask, waitTime);
306        waiter.setTimeouts(timeouts.cloneThis());
307        waiter.getTimeouts().
308                setTimeout("Waiter.WaitingTime",
309                        timeouts.getTimeout("EventTool.WaitNoEventTimeout"));
310        waiter.getTimeouts().
311                setTimeout("Waiter.TimeDelta",
312                        timeouts.getTimeout("EventTool.EventCheckingDelta"));
313        try {
314            waiter.waitAction(null);
315        } catch (InterruptedException e) {
316            output.printStackTrace(e);
317        }
318    }
319
320    /**
321     * During {@code EventTool.WaitNoEventTimeout} time waits for true
322     * result of {@code checkNoEvent(long)} method.
323     *
324     * @param waitTime Quiet time (millisecons).
325     * @see #checkNoEvent(long)
326     * @see #waitNoEvent(long, long)
327     * @throws TimeoutExpiredException
328     */
329    public void waitNoEvent(long waitTime) {
330        ListenerSet ls = listenerSet;
331        if (ls != null) {
332            // surprisingly this field can be null in case of massive
333            // garbage collecting efforts like in NbTestCase.assertGC
334            waitNoEvent(ls.getTheWholeMask(), waitTime);
335        }
336    }
337
338    private AWTEvent waitEvent(long eventMask, long waitTime, TestOut waiterOutput) {
339        EventWaiter waiter = new EventWaiter(eventMask);
340        waiter.setTimeouts(timeouts.cloneThis());
341        waiter.setOutput(waiterOutput);
342        waiter.getTimeouts().
343                setTimeout("Waiter.WaitingTime",
344                        waitTime);
345        waiter.getTimeouts().
346                setTimeout("Waiter.TimeDelta",
347                        timeouts.getTimeout("EventTool.EventCheckingDelta"));
348        try {
349            return waiter.waitAction(null);
350        } catch (InterruptedException e) {
351            output.printStackTrace(e);
352            return null;
353        }
354    }
355
356    private boolean checkNoEvent(long eventMask, long waitTime, TestOut waiterOutput) {
357        try {
358            AWTEvent event = waitEvent(eventMask, waitTime, TestOut.getNullOutput());
359            waiterOutput.printLine("AWT event was produced during waiting: ");
360            // used instead of event.toString() because it is not thread safe
361            waiterOutput.printLine(event.getClass().getName());
362            return false;
363        } catch (TimeoutExpiredException e) {
364            return true;
365        }
366    }
367
368    private static class EventType implements AWTEventListener {
369
370        long eventMask;
371        long eventTime;
372        private Reference<AWTEvent> eventRef;
373
374        public EventType(long eventMask) {
375            this.eventMask = eventMask;
376            eventRef = new WeakReference<>(null);
377            eventTime = -1;
378        }
379
380        @Override
381        public void eventDispatched(AWTEvent event) {
382            eventRef = new WeakReference<>(event);
383            eventTime = System.currentTimeMillis();
384        }
385
386        public AWTEvent getEvent() {
387            return eventRef.get();
388        }
389
390        public long getTime() {
391            return eventTime;
392        }
393
394        public long getEventMask() {
395            return eventMask;
396        }
397    }
398
399    private static class ListenerSet {
400
401        private Vector<EventType> eventTypes;
402        private long theWholeMask;
403
404        public ListenerSet() {
405            eventTypes = new Vector<>();
406            try {
407                Class<?> eventClass = Class.forName("java.awt.AWTEvent");
408                Field[] fields = eventClass.getFields();
409                theWholeMask = 0;
410                long eventMask;
411                for (Field field : fields) {
412                    if ((field.getModifiers()
413                            & (Modifier.PUBLIC | Modifier.STATIC)) != 0
414                            && field.getType().equals(Long.TYPE)
415                            && field.getName().endsWith("_EVENT_MASK")) {
416                        eventMask = (Long) field.get(null);
417                        eventTypes.add(new EventType(eventMask));
418                        theWholeMask = theWholeMask | eventMask;
419                    }
420                }
421            } catch (ClassNotFoundException | IllegalAccessException e) {
422                JemmyProperties.getCurrentOutput().printStackTrace(e);
423            }
424        }
425
426        public void addListeners(long eventMask) {
427            Toolkit dtk = Toolkit.getDefaultToolkit();
428            for (EventType et : eventTypes) {
429                if ((et.getEventMask() & eventMask) != 0) {
430                    dtk.addAWTEventListener(et, et.getEventMask());
431                }
432            }
433        }
434
435        public void addListeners() {
436            addListeners(getTheWholeMask());
437        }
438
439        public void removeListeners() {
440            Toolkit dtk = Toolkit.getDefaultToolkit();
441            for (EventType eventType : eventTypes) {
442                dtk.removeAWTEventListener(eventType);
443            }
444        }
445
446        public long getTheWholeMask() {
447            return theWholeMask;
448        }
449
450        public long getLastEventTime(long eventMask) {
451            EventType et = getLastEventType(eventMask);
452            return (et == null) ? -1 : et.getTime();
453        }
454
455        public AWTEvent getLastEvent(long eventMask) {
456            EventType et = getLastEventType(eventMask);
457            return (et == null) ? null : et.getEvent();
458        }
459
460        private EventType getLastEventType(long eventMask) {
461            long maxTime = -1;
462            EventType maxType = null;
463            for (EventType et : eventTypes) {
464                if ((eventMask & et.getEventMask()) != 0
465                        && et.getTime() > maxTime) {
466                    maxType = et;
467                    maxTime = maxType.getTime();
468                }
469            }
470            return maxType;
471        }
472    }
473
474    private static class EventWaiter extends Waiter<AWTEvent, Void> {
475
476        long eventMask;
477        long startTime;
478
479        public EventWaiter(long eventMask) {
480            this.eventMask = eventMask;
481            startTime = getLastEventTime(eventMask);
482        }
483
484        @Override
485        public AWTEvent actionProduced(Void obj) {
486            EventType et = listenerSet.getLastEventType(eventMask);
487            if (et != null
488                    && et.getTime() > startTime) {
489                return et.getEvent();
490            } else {
491                return null;
492            }
493        }
494
495        @Override
496        public String getDescription() {
497            return ("Last event under "
498                    + Long.toString(eventMask, 2) + " event mask");
499        }
500
501        @Override
502        public String toString() {
503            return "EventWaiter{" + "eventMask=" + Long.toString(eventMask, 2) + ", startTime=" + startTime + '}';
504        }
505    }
506
507    private class NoEventWaiter extends Waiter<String, Void> {
508
509        long eventMask;
510        long waitTime;
511
512        public NoEventWaiter(long eventMask, long waitTime) {
513            this.eventMask = eventMask;
514            this.waitTime = waitTime;
515        }
516
517        @Override
518        public String actionProduced(Void obj) {
519            return (checkNoEvent(eventMask, waitTime, TestOut.getNullOutput())
520                    ? "Reached!"
521                    : null);
522        }
523
524        @Override
525        public String getDescription() {
526            return ("No event under "
527                    + Long.toString(eventMask, 2)
528                    + " event mask during "
529                    + Long.toString(waitTime)
530                    + " milliseconds");
531        }
532
533        @Override
534        public String toString() {
535            return "NoEventWaiter{" + "eventMask=" + Long.toString(eventMask, 2) + ", waitTime=" + waitTime + '}';
536        }
537    }
538}
539