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.Component;
26import java.awt.Frame;
27import java.awt.Window;
28import java.util.stream.Stream;
29
30/**
31 * A WindowWaiter is a utility class used to look or wait for Windows. It
32 * contains methods to search for a Window among the currently showing Windows
33 * as well as methods that wait for a Window to show within an allotted time
34 * period.
35 *
36 * Searches and waits always involve search criteria applied by a
37 * ComponentChooser instance. Searches and waits can both be restricted to
38 * windows owned by a given window.
39 *
40 * <BR>Timeouts used: <BR>
41 * WindowWaiter.WaitWindowTimeout - time to wait window displayed <BR>
42 * WindowWaiter.AfterWindowTimeout - time to sleep after window has been
43 * dispayed <BR>
44 *
45 * @see org.netbeans.jemmy.Timeouts
46 *
47 * @author Alexandre Iline (alexandre.iline@oracle.com)
48 */
49public class WindowWaiter extends Waiter<Window, Void> implements Timeoutable {
50
51    public static boolean FIND_INVISIBLE_WINDOWS = false;
52
53    private final static long WAIT_TIME = 60000;
54    private final static long AFTER_WAIT_TIME = 0;
55
56    private ComponentChooser chooser;
57    private Window owner = null;
58    private int index = 0;
59    private Timeouts timeouts;
60
61    /**
62     * Constructor.
63     */
64    public WindowWaiter() {
65        super();
66        setTimeouts(JemmyProperties.getProperties().getTimeouts());
67    }
68
69    /**
70     * Searches for a window. The search proceeds among the currently showing
71     * windows for the {@code index+1}'th window that is both owned by the
72     * {@code java.awt.Window} {@code owner} and that meets the
73     * criteria defined and applied by the {@code ComponentChooser}
74     * parameter.
75     *
76     * @param owner The owner window of all the windows to be searched.
77     * @param cc A component chooser used to define and apply the search
78     * criteria.
79     * @param index The ordinal index of the window in the set of currently
80     * displayed windows with the proper window ownership and a suitable title.
81     * The first index is 0.
82     * @return a reference to the {@code index+1}'th window that is
83     * showing, has the proper window ownership, and that meets the search
84     * criteria. If there are fewer than {@code index+1} windows, a
85     * {@code null} reference is returned.
86     */
87    public static Window getWindow(Window owner, ComponentChooser cc, int index) {
88        return getAWindow(owner, new IndexChooser(cc, index));
89    }
90
91    /**
92     * Searches for a window. Search among the currently showing windows for the
93     * first that is both owned by the {@code java.awt.Window}
94     * {@code owner} and that meets the search criteria applied by the
95     * {@code ComponentChooser} parameter.
96     *
97     * @param owner The owner window of the windows to be searched.
98     * @param cc A component chooser used to define and apply the search
99     * criteria.
100     * @return a reference to the first window that is showing, has a proper
101     * owner window, and that meets the search criteria. If no such window can
102     * be found, a {@code null} reference is returned.
103     */
104    public static Window getWindow(Window owner, ComponentChooser cc) {
105        return getWindow(owner, cc, 0);
106    }
107
108    /**
109     * Searches for a window. The search proceeds among the currently showing
110     * windows for the {@code index+1}'th window that meets the criteria
111     * defined and applied by the {@code ComonentChooser} parameter.
112     *
113     * @param cc A component chooser used to define and apply the search
114     * criteria.
115     * @param index The ordinal index of the window in the set of currently
116     * displayed windows. The first index is 0.
117     * @return a reference to the {@code index+1}'th window that is showing
118     * and that meets the search criteria. If there are fewer than
119     * {@code index+1} windows, a {@code null} reference is returned.
120     */
121    public static Window getWindow(ComponentChooser cc, int index) {
122        return getAWindow(new IndexChooser(cc, index));
123    }
124
125    /**
126     * Searches for a window. Search among the currently showing windows for one
127     * that meets the search criteria applied by the
128     * {@code ComponentChooser} parameter.
129     *
130     * @param cc A component chooser used to define and apply the search
131     * criteria.
132     * @return a reference to the first window that is showing and that meets
133     * the search criteria. If no such window can be found, a {@code null}
134     * reference is returned.
135     */
136    public static Window getWindow(ComponentChooser cc) {
137        return getWindow(cc, 0);
138    }
139
140    static {
141        Timeouts.initDefault("WindowWaiter.WaitWindowTimeout", WAIT_TIME);
142        Timeouts.initDefault("WindowWaiter.AfterWindowTimeout", AFTER_WAIT_TIME);
143    }
144
145    /**
146     * Defines current timeouts.
147     *
148     * @param timeouts A collection of timeout assignments.
149     * @see org.netbeans.jemmy.Timeoutable
150     * @see org.netbeans.jemmy.Timeouts
151     * @see #getTimeouts
152     */
153    @Override
154    public void setTimeouts(Timeouts timeouts) {
155        this.timeouts = timeouts;
156        Timeouts times = timeouts.cloneThis();
157        times.setTimeout("Waiter.WaitingTime",
158                timeouts.getTimeout("WindowWaiter.WaitWindowTimeout"));
159        times.setTimeout("Waiter.AfterWaitingTime",
160                timeouts.getTimeout("WindowWaiter.AfterWindowTimeout"));
161        setWaitingTimeOrigin("WindowWaiter.WaitWindowTimeout");
162        super.setTimeouts(times);
163    }
164
165    /**
166     * Return current timeouts.
167     *
168     * @return the collection of current timeout assignments.
169     * @see org.netbeans.jemmy.Timeoutable
170     * @see org.netbeans.jemmy.Timeouts
171     * @see #setTimeouts
172     */
173    @Override
174    public Timeouts getTimeouts() {
175        return timeouts;
176    }
177
178    /**
179     * Action producer--get a window. Get a window. The search uses constraints
180     * on window ownership, ordinal index, and search criteria defined by an
181     * instance of {@code org.netbeans.jemmy.ComponentChooser}.
182     *
183     * @param obj Not used.
184     * @return the window waited upon. If a window cannot be found then a
185     * {@code null} reference is returned.
186     * @see org.netbeans.jemmy.Action
187     */
188    @Override
189    public Window actionProduced(Void obj) {
190        return WindowWaiter.getWindow(owner, chooser, index);
191    }
192
193    /**
194     * Waits for a window to show. Wait for the {@code index+1}'th window
195     * that meets the criteria defined and applied by the
196     * {@code ComonentChooser} parameter to show up.
197     *
198     * @param ch A component chooser used to define and apply the search
199     * criteria.
200     * @param index The ordinal index of the window in the set of currently
201     * displayed windows. The first index is 0.
202     * @return a reference to the {@code index+1}'th window that shows and
203     * that meets the search criteria. If fewer than {@code index+1}
204     * windows show up in the allotted time period then a {@code null}
205     * reference is returned.
206     * @throws TimeoutExpiredException
207     * @see #actionProduced(Object)
208     * @exception InterruptedException
209     */
210    public Window waitWindow(ComponentChooser ch, int index)
211            throws InterruptedException {
212        chooser = ch;
213        owner = null;
214        this.index = index;
215        return waitWindow();
216    }
217
218    /**
219     * Waits for a window to show. Wait for a window that meets the search
220     * criteria applied by the {@code ComponentChooser} parameter to show
221     * up.
222     *
223     * @param ch A component chooser used to define and apply the search
224     * criteria.
225     * @return a reference to the first window that shows and that meets the
226     * search criteria. If no such window can be found within the time period
227     * allotted, a {@code null} reference is returned.
228     * @throws TimeoutExpiredException
229     * @see #actionProduced(Object)
230     * @exception InterruptedException
231     */
232    public Window waitWindow(ComponentChooser ch)
233            throws InterruptedException {
234        return waitWindow(ch, 0);
235    }
236
237    /**
238     * Waits for a window to show. Wait for the {@code index+1}'th window
239     * to show that is both owned by the {@code java.awt.Window}
240     * {@code o} and that meets the criteria defined and applied by the
241     * {@code ComponentChooser} parameter.
242     *
243     * @param o The owner window of all the windows to be searched.
244     * @param ch A component chooser used to define and apply the search
245     * criteria.
246     * @param index The ordinal index of the window in the set of currently
247     * displayed windows with the proper window ownership and a suitable title.
248     * The first index is 0.
249     * @return a reference to the {@code index+1}'th window to show that
250     * has the proper window ownership, and that meets the search criteria. If
251     * there are fewer than {@code index+1} windows, a {@code null}
252     * reference is returned.
253     * @throws TimeoutExpiredException
254     * @see #actionProduced(Object)
255     * @exception InterruptedException
256     */
257    public Window waitWindow(Window o, ComponentChooser ch, int index)
258            throws InterruptedException {
259        owner = o;
260        chooser = ch;
261        this.index = index;
262        return waitAction(null);
263    }
264
265    /**
266     * Waits for a window to show. Wait for the first window to show that is
267     * both owned by the {@code java.awt.Window} {@code o} and that
268     * meets the criteria defined and applied by the
269     * {@code ComponentChooser} parameter.
270     *
271     * @param o The owner window of all the windows to be searched.
272     * @param ch A component chooser used to define and apply the search
273     * criteria.
274     * @return a reference to the first window to show that has the proper
275     * window ownership, and that meets the search criteria. If there is no such
276     * window, a {@code null} reference is returned.
277     * @throws TimeoutExpiredException
278     * @see #actionProduced(Object)
279     * @exception InterruptedException
280     */
281    public Window waitWindow(Window o, ComponentChooser ch)
282            throws InterruptedException {
283        return waitWindow(o, ch, 0);
284    }
285
286    /**
287     * Wait till the count of windows which meet the the search criteria becomes
288     * equal to count.
289     *
290     * @param ch a component chooser used to define and apply the search
291     * criteria.
292     * @param count the number of expected windows meeting the search criteria.
293     * @throws InterruptedException
294     */
295    public static void waitWindowCount(ComponentChooser ch, int count)
296            throws InterruptedException {
297        waitWindowCount(null, ch, count);
298    }
299
300    /**
301     * Wait till the count of windows which meet the the search criteria becomes
302     * equal to count.
303     *
304     * @param owner The owner window of all the windows to be checked
305     * @param ch a component chooser used to define and apply the search
306     * criteria.
307     * @param count the number of expected windows meeting the search criteria.
308     * @throws InterruptedException
309     */
310    public static void waitWindowCount(Window owner, ComponentChooser ch, int count)
311            throws InterruptedException {
312        Waiter<String, Void> stateWaiter = new Waiter<>(new Waitable<String, Void>() {
313            @Override
314            public String actionProduced(Void obj) {
315                return countWindows(owner, ch) == count ? "" : null;
316            }
317
318            @Override
319            public String getDescription() {
320                return "Wait till the count of windows matching the criteria "
321                        + "specified by ComponentChooser reaches :" + count;
322            }
323
324            @Override
325            public String toString() {
326                return "Operator.waitState.Waitable{description = "
327                        + getDescription() + '}';
328            }
329        });
330        stateWaiter.waitAction(null);
331    }
332
333    /**
334     * Counts all the windows owned by the owner window which match the
335     * criterion specified by component chooser.
336     *
337     * @param owner The owner window of all the windows to be checked
338     * @param ch A component chooser used to define and apply the search
339     * criteria
340     * @return the number of matched windows
341     */
342    public static int countWindows(Window owner, ComponentChooser ch) {
343        return new QueueTool().invokeAndWait(new QueueTool.QueueAction<Integer>(null) {
344
345            @Override
346            public Integer launch() {
347                Window[] windows;
348                if (owner == null) {
349                    windows = Window.getWindows();
350                } else {
351                    windows = owner.getOwnedWindows();
352                }
353                return (int) Stream.of(windows)
354                        .filter(x -> ch.checkComponent(x)).count();
355            }
356        });
357    }
358
359    /**
360     * Counts all the windows which match the criterion specified by component
361     * chooser.
362     *
363     * @param ch A component chooser used to define and apply the search
364     * criteria
365     * @return the number of matched windows
366     */
367    public static int countWindows(ComponentChooser ch) {
368        return countWindows(null, ch);
369    }
370
371    @Override
372    public String getDescription() {
373        return chooser.getDescription();
374    }
375
376    @Override
377    public String toString() {
378        return "WindowWaiter{" + "chooser=" + chooser + ", owner=" + owner + ", index=" + index + '}';
379    }
380
381    /**
382     * Method can be used by a subclass to define chooser.
383     *
384     * @param ch a chooser specifying searching criteria.
385     * @see #getComponentChooser
386     */
387    protected void setComponentChooser(ComponentChooser ch) {
388        chooser = ch;
389    }
390
391    /**
392     * Method can be used by a subclass to define chooser.
393     *
394     * @return a chooser specifying searching criteria.
395     * @see #setComponentChooser
396     */
397    protected ComponentChooser getComponentChooser() {
398        return chooser;
399    }
400
401    /**
402     * Method can be used by a subclass to define window owner.
403     *
404     * @param owner Window-owner of the set of windows.
405     * @see #getOwner
406     */
407    protected void setOwner(Window owner) {
408        this.owner = owner;
409    }
410
411    /**
412     * Method can be used by a subclass to define window owner.
413     *
414     * @return Window-owner of the set of windows.
415     * @see #setOwner
416     */
417    protected Window getOwner() {
418        return owner;
419    }
420
421    /**
422     * @see org.netbeans.jemmy.Waiter#getWaitingStartedMessage()
423     */
424    @Override
425    protected String getWaitingStartedMessage() {
426        return "Start to wait window \"" + chooser.getDescription() + "\" opened";
427    }
428
429    /**
430     * Overrides Waiter.getTimeoutExpiredMessage.
431     *
432     * @see org.netbeans.jemmy.Waiter#getTimeoutExpiredMessage(long)
433     * @param timeSpent time from waiting start (milliseconds)
434     * @return a message.
435     */
436    @Override
437    protected String getTimeoutExpiredMessage(long timeSpent) {
438        return ("Window \"" + chooser.getDescription() + "\" has not been opened in "
439                + timeSpent + " milliseconds");
440    }
441
442    /**
443     * Overrides Waiter.getActionProducedMessage.
444     *
445     * @see org.netbeans.jemmy.Waiter#getActionProducedMessage(long, Object)
446     * @param timeSpent time from waiting start (milliseconds)
447     * @param result result of Waitable.actionproduced method.
448     * @return a message.
449     */
450    @Override
451    protected String getActionProducedMessage(long timeSpent, final Object result) {
452        String resultToString;
453        if (result instanceof Component) {
454            // run toString in dispatch thread
455            resultToString = new QueueTool().invokeSmoothly(
456                    new QueueTool.QueueAction<String>("result.toString()") {
457                @Override
458                public String launch() {
459                    return result.toString();
460                }
461            }
462            );
463        } else {
464            resultToString = result.toString();
465        }
466        return ("Window \"" + chooser.getDescription() + "\" has been opened in "
467                + timeSpent + " milliseconds"
468                + "\n    " + resultToString);
469    }
470
471    /**
472     * @return a message.
473     * @see org.netbeans.jemmy.Waiter#getGoldenWaitingStartedMessage()
474     */
475    @Override
476    protected String getGoldenWaitingStartedMessage() {
477        return "Start to wait window \"" + chooser.getDescription() + "\" opened";
478    }
479
480    /**
481     * @return a message.
482     * @see org.netbeans.jemmy.Waiter#getGoldenTimeoutExpiredMessage()
483     */
484    @Override
485    protected String getGoldenTimeoutExpiredMessage() {
486        return "Window \"" + chooser.getDescription() + "\" has not been opened";
487    }
488
489    /**
490     * @return a message.
491     * @see org.netbeans.jemmy.Waiter#getGoldenActionProducedMessage()
492     */
493    @Override
494    protected String getGoldenActionProducedMessage() {
495        return "Window \"" + chooser.getDescription() + "\" has been opened";
496    }
497
498    private static Window getAWindow(Window owner, ComponentChooser cc) {
499        if (owner == null) {
500            return WindowWaiter.getAWindow(cc);
501        } else {
502            Window result = null;
503            Window[] windows = owner.getOwnedWindows();
504            for (Window window : windows) {
505                if (cc.checkComponent(window)) {
506                    return window;
507                }
508                if ((result = WindowWaiter.getWindow(window, cc)) != null) {
509                    return result;
510                }
511            }
512            return null;
513        }
514    }
515
516    private static Window getAWindow(ComponentChooser cc) {
517        Window result = null;
518        Frame[] frames = Frame.getFrames();
519        for (Frame frame : frames) {
520            if (cc.checkComponent(frame)) {
521                return frame;
522            }
523            if ((result = WindowWaiter.getWindow(frame, cc)) != null) {
524                return result;
525            }
526        }
527        return null;
528    }
529
530    private Window waitWindow()
531            throws InterruptedException {
532        return waitAction(null);
533    }
534
535    private static class IndexChooser implements ComponentChooser {
536
537        private int curIndex = 0;
538        private int index;
539        private ComponentChooser chooser;
540
541        public IndexChooser(ComponentChooser ch, int i) {
542            index = i;
543            chooser = ch;
544            curIndex = 0;
545        }
546
547        @Override
548        public boolean checkComponent(Component comp) {
549            if ((FIND_INVISIBLE_WINDOWS || (comp.isShowing() && comp.isVisible()))
550                    && chooser.checkComponent(comp)) {
551                if (curIndex == index) {
552                    return true;
553                }
554                curIndex++;
555            }
556            return false;
557        }
558
559        @Override
560        public String getDescription() {
561            return chooser.getDescription();
562        }
563
564        @Override
565        public String toString() {
566            return "IndexChooser{" + "curIndex=" + curIndex + ", index=" + index + ", chooser=" + chooser + '}';
567        }
568    }
569}
570