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;
26
27/**
28 *
29 * Waits for something defined by Waitable interface to be happened.
30 *
31 * <BR><BR>Timeouts used: <BR>
32 * Waiter.TimeDelta - time delta to check actionProduced result.<BR>
33 * Waiter.WaitingTime - maximal waiting time<BR>
34 * Waiter.AfterWaitingTime - time to sleep after waiting has been finished.<BR>
35 *
36 * @see Timeouts
37 * @see Waitable
38 *
39 * @author Alexandre Iline (alexandre.iline@oracle.com)
40 */
41public class Waiter<R, P> implements Waitable<R, P>, Timeoutable, Outputable {
42
43    private final static long TIME_DELTA = 10;
44    private final static long WAIT_TIME = 60000;
45    private final static long AFTER_WAIT_TIME = 0;
46
47    private final Waitable<R, P> waitable;
48    private long startTime = 0;
49    private long endTime = -1;
50    private R result;
51    private Timeouts timeouts;
52    private String waitingTimeOrigin;
53    private TestOut out;
54
55    /**
56     * Replace the fine-grained timeouts with a global flag which can be set,
57     * for instance, by a separate thread when a global timeout runs out.
58     */
59    public static volatile boolean USE_GLOBAL_TIMEOUT = false;
60    public static volatile boolean globalTimeoutExpired = false;
61
62    /**
63     * Constructor.
64     *
65     * @param w Waitable object defining waiting criteria.
66     */
67    public Waiter(Waitable<R, P> w) {
68        super();
69        if (w == null) {
70            throw new NullPointerException("Waitable cannot be null");
71        }
72        setTimeouts(JemmyProperties.getProperties().getTimeouts());
73        setOutput(JemmyProperties.getProperties().getOutput());
74        waitable = w;
75    }
76
77    /**
78     * Can be used from subclass. actionProduced() method must be overriden in
79     * a class that uses this super constructor. actionProduced() method in
80     * Waiter will throw UnsupportedOperationException whenever invoked.
81     */
82    protected Waiter() {
83        super();
84        setTimeouts(JemmyProperties.getProperties().getTimeouts());
85        setOutput(JemmyProperties.getProperties().getOutput());
86        waitable = null;
87    }
88
89    static {
90        Timeouts.initDefault("Waiter.TimeDelta", TIME_DELTA);
91        Timeouts.initDefault("Waiter.WaitingTime", WAIT_TIME);
92        Timeouts.initDefault("Waiter.AfterWaitingTime", AFTER_WAIT_TIME);
93    }
94
95    /**
96     * Defines current timeouts.
97     *
98     * @param timeouts A collection of timeout assignments.
99     * @see org.netbeans.jemmy.Timeoutable
100     * @see org.netbeans.jemmy.Timeouts
101     * @see #getTimeouts
102     */
103    @Override
104    public void setTimeouts(Timeouts timeouts) {
105        this.timeouts = timeouts;
106    }
107
108    /**
109     * Like {@link #setTimeouts(Timeouts)}, but clones the timeouts first, then
110     * sets "Waiter.WaitingTime" to the timeout whose name is passed in. This
111     * name is remembered for display in timeout error messages so people know
112     * what to adjust.
113     *
114     * @param timeouts to be cloned and in which to look up "useAsWaitingTime".
115     * @param useAsWaitingTime the name of the timeout to apply to
116     * "Waiter.WaitingTime".
117     * @param waitingTimeOrigin overrides {@code useAsWaitingTime} in timeout
118     * reporting if non-null.
119     * @return the cloned timeouts.
120     */
121    public Timeouts setTimeoutsToCloneOf(Timeouts timeouts,
122            String useAsWaitingTime, String waitingTimeOrigin) {
123        Timeouts t = timeouts.cloneThis();
124        t.setTimeout("Waiter.WaitingTime", t.getTimeout(useAsWaitingTime));
125        setTimeouts(t);
126        setWaitingTimeOrigin((null != waitingTimeOrigin) ? waitingTimeOrigin : useAsWaitingTime);
127        return t;
128    }
129
130    /**
131     * @see #setTimeoutsToCloneOf(Timeouts, String, String)
132     */
133    public Timeouts setTimeoutsToCloneOf(Timeouts timeouts,
134            String useAsWaitingTime) {
135        return setTimeoutsToCloneOf(timeouts, useAsWaitingTime, null);
136    }
137
138    /**
139     * Sets the origin of the current "Waiter.WaitingTime" to be shown in
140     * timeout error messages
141     *
142     * @param origin is the name of the origin.
143     */
144    public void setWaitingTimeOrigin(String origin) {
145        waitingTimeOrigin = origin;
146    }
147
148    /**
149     * Return current timeouts.
150     *
151     * @return the collection of current timeout assignments.
152     * @see org.netbeans.jemmy.Timeoutable
153     * @see org.netbeans.jemmy.Timeouts
154     * @see #setTimeouts
155     */
156    @Override
157    public Timeouts getTimeouts() {
158        return timeouts;
159    }
160
161    /**
162     * Defines print output streams or writers.
163     *
164     * @param out Identify the streams or writers used for print output.
165     * @see org.netbeans.jemmy.Outputable
166     * @see org.netbeans.jemmy.TestOut
167     * @see #getOutput
168     */
169    @Override
170    public void setOutput(TestOut out) {
171        this.out = out;
172    }
173
174    /**
175     * Returns print output streams or writers.
176     *
177     * @return an object that contains references to objects for printing to
178     * output and err streams.
179     * @see org.netbeans.jemmy.Outputable
180     * @see org.netbeans.jemmy.TestOut
181     * @see #setOutput
182     */
183    @Override
184    public TestOut getOutput() {
185        return out;
186    }
187
188    /**
189     * Waits for not null result of actionProduced method of Waitable
190     * implementation passed into constructor.
191     *
192     * @param waitableObject Object to be passed into actionProduced method.
193     * @return non null result of action.
194     * @throws TimeoutExpiredException
195     * @exception InterruptedException
196     */
197    public R waitAction(P waitableObject)
198            throws InterruptedException {
199        startTime = System.currentTimeMillis();
200        out.printTrace(getWaitingStartedMessage());
201        out.printGolden(getGoldenWaitingStartedMessage());
202        long timeDelta = timeouts.getTimeout("Waiter.TimeDelta");
203        while ((result = actionProduced(waitableObject)) == null) {
204            Thread.sleep(timeDelta);
205            if (timeoutExpired()) {
206                out.printError(getTimeoutExpiredMessage(timeFromStart()));
207                out.printGolden(getGoldenTimeoutExpiredMessage());
208                throw (new TimeoutExpiredException(getActualDescription()));
209            }
210        }
211        endTime = System.currentTimeMillis();
212        out.printTrace(getActionProducedMessage(endTime - startTime, result));
213        out.printGolden(getGoldenActionProducedMessage());
214        Thread.sleep(timeouts.getTimeout("Waiter.AfterWaitingTime"));
215        return result;
216    }
217
218    /**
219     * This method delegates call to the waitable passed in constructor. If a
220     * subclass was created using protected no-parameters constructor, it should
221     * implement its own actionProduced method() as this one will throw an
222     * UnsupportedOperationException.
223     * @see Waitable
224     * @param obj
225     */
226    @Override
227    public R actionProduced(P obj) {
228        if (waitable != null) {
229            return waitable.actionProduced(obj);
230        } else {
231            throw new UnsupportedOperationException("actionProduced() return "
232                    + "value is not defined. It used to return Boolean.TRUE "
233                    + "in previous versions of Jemmy.");
234        }
235    }
236
237    /**
238     * @see Waitable
239     */
240    @Override
241    public String getDescription() {
242        return "Unknown waiting";
243    }
244
245    @Override
246    public String toString() {
247        return "Waiter{" + "description = " + getDescription() + ", waitable=" + waitable + ", startTime=" + startTime + ", endTime=" + endTime + ", result=" + result + ", waitingTimeOrigin=" + waitingTimeOrigin + '}';
248    }
249
250    /**
251     * Returns message to be printed before waiting start.
252     *
253     * @return a message.
254     */
255    protected String getWaitingStartedMessage() {
256        return "Start to wait action \"" + getActualDescription() + "\"";
257    }
258
259    /**
260     * Returns message to be printed when waiting timeout has been expired.
261     *
262     * @param timeSpent time from waiting start (milliseconds)
263     * @return a message.
264     */
265    protected String getTimeoutExpiredMessage(long timeSpent) {
266        return ("\"" + getActualDescription() + "\" action has not been produced in "
267                + timeSpent + " milliseconds");
268    }
269
270    /**
271     * Returns message to be printed when waiting has been successfully
272     * finished.
273     *
274     * @param timeSpent time from waiting start (milliseconds)
275     * @param result result of Waitable.actionproduced method.
276     * @return a message.
277     */
278    protected String getActionProducedMessage(long timeSpent, final Object result) {
279        String resultToString;
280        if (result instanceof Component) {
281            // run toString in dispatch thread
282            resultToString = new QueueTool().invokeSmoothly(
283                    new QueueTool.QueueAction<String>("result.toString()") {
284                @Override
285                public String launch() {
286                    return result.toString();
287                }
288            }
289            );
290        } else {
291            resultToString = result.toString();
292        }
293        return ("\"" + getActualDescription() + "\" action has been produced in "
294                + timeSpent + " milliseconds with result "
295                + "\n    : " + resultToString);
296    }
297
298    /**
299     * Returns message to be printed int golden output before waiting start.
300     *
301     * @return a message.
302     */
303    protected String getGoldenWaitingStartedMessage() {
304        return "Start to wait action \"" + getActualDescription() + "\"";
305    }
306
307    /**
308     * Returns message to be printed int golden output when waiting timeout has
309     * been expired.
310     *
311     * @return a message.
312     */
313    protected String getGoldenTimeoutExpiredMessage() {
314        return "\"" + getActualDescription() + "\" action has not been produced";
315    }
316
317    /**
318     * Returns message to be printed int golden output when waiting has been
319     * successfully finished.
320     *
321     * @return a message.
322     */
323    protected String getGoldenActionProducedMessage() {
324        return "\"" + getActualDescription() + "\" action has been produced";
325    }
326
327    /**
328     * Returns time from waiting start.
329     *
330     * @return Time spent for waiting already.
331     */
332    protected long timeFromStart() {
333        return System.currentTimeMillis() - startTime;
334    }
335
336    private String getActualDescription() {
337        final String suffix = (null == waitingTimeOrigin) ? "" : " (" + waitingTimeOrigin + ")";
338        if (waitable != null) {
339            return waitable.getDescription() + suffix;
340        } else {
341            return getDescription() + suffix;
342        }
343    }
344
345    private boolean timeoutExpired() {
346        if (USE_GLOBAL_TIMEOUT) {
347            return globalTimeoutExpired;
348        }
349        return timeFromStart() > timeouts.getTimeout("Waiter.WaitingTime");
350    }
351
352}
353