1 package com.atlassian.pageobjects.elements.query;
2
3 import com.atlassian.pageobjects.elements.query.util.Clock;
4 import com.atlassian.pageobjects.elements.query.util.ClockAware;
5 import com.atlassian.pageobjects.elements.query.util.SystemClock;
6 import com.atlassian.pageobjects.elements.util.Timeout;
7
8 import java.util.concurrent.TimeUnit;
9 import javax.annotation.concurrent.NotThreadSafe;
10
11 import static com.atlassian.pageobjects.elements.query.util.Clocks.getClock;
12 import static com.atlassian.pageobjects.elements.util.StringConcat.asString;
13 import static com.google.common.base.Preconditions.checkArgument;
14 import static com.google.common.base.Preconditions.checkNotNull;
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32 @NotThreadSafe
33 public abstract class AbstractTimedQuery<T> extends AbstractPollingQuery implements TimedQuery<T>, ClockAware
34 {
35 private final Clock clock;
36 private final ExpirationHandler expirationHandler;
37
38 private boolean lastRun = false;
39
40 protected AbstractTimedQuery(Clock clock, long defTimeout, long interval, ExpirationHandler expirationHandler)
41 {
42 super(interval, defTimeout);
43 this.clock = checkNotNull(clock, "clock");
44 this.expirationHandler = checkNotNull(expirationHandler, "expirationHandler");
45 }
46
47
48 protected AbstractTimedQuery(long defTimeout, long interval, ExpirationHandler expirationHandler)
49 {
50 this(SystemClock.INSTANCE, defTimeout, interval, expirationHandler);
51 }
52
53 protected AbstractTimedQuery(PollingQuery other, ExpirationHandler expirationHandler)
54 {
55 this(getClock(other), other.defaultTimeout(), checkNotNull(other, "other").interval(), expirationHandler);
56 }
57
58 public final T by(long timeout)
59 {
60 checkArgument(timeout > 0);
61 resetLastRun();
62 final long start = clock.currentTime();
63 final long deadline = start + timeout;
64 while (withinTimeout(deadline))
65 {
66 T current = currentValue();
67 if (shouldReturn(current))
68 {
69 return current;
70 }
71 else if (isLastRun())
72 {
73 return expired(current, timeout);
74 }
75 else
76 {
77 Timeout.waitFor(sleepTime(deadline)).milliseconds();
78 }
79 }
80 return expired(currentValue(), timeout);
81 }
82
83 public final T by(long timeout, TimeUnit unit)
84 {
85 return by(TimeUnit.MILLISECONDS.convert(timeout, unit));
86 }
87
88 public T byDefaultTimeout()
89 {
90 return by(defaultTimeout);
91 }
92
93 public final T now()
94 {
95 T val = currentValue();
96 if (shouldReturn(val))
97 {
98 return val;
99 }
100 else
101 {
102 return expired(val, 0);
103 }
104 }
105
106
107
108
109
110
111 public final ExpirationHandler expirationHandler()
112 {
113 return expirationHandler;
114 }
115
116
117
118
119
120
121
122
123 protected abstract boolean shouldReturn(T currentEval);
124
125
126
127
128
129
130 protected abstract T currentValue();
131
132
133
134
135
136
137
138
139 private T expired(T currentVal, long timeout)
140 {
141 return expirationHandler.expired(this, currentVal, timeout);
142 }
143
144 public Clock clock()
145 {
146 return clock;
147 }
148
149 private boolean withinTimeout(final long deadline)
150 {
151 return clock.currentTime() <= deadline;
152 }
153
154 private long sleepTime(final long deadline)
155 {
156 final long now = clock.currentTime();
157 if (now + interval < deadline)
158 {
159 return interval;
160 }
161 else
162 {
163
164
165 long toEvalOneMoreTime = deadline - now - 2;
166 setLastRun();
167 return toEvalOneMoreTime > 0 ? toEvalOneMoreTime : 1;
168 }
169 }
170
171 private void setLastRun()
172 {
173 lastRun = true;
174 }
175
176 protected void resetLastRun()
177 {
178 lastRun = false;
179 }
180
181 private boolean isLastRun()
182 {
183 return lastRun;
184 }
185
186 @Override
187 public String toString()
188 {
189 return asString(getClass().getName(), "[interval=", interval, ",defaultTimeout=", defaultTimeout, "]");
190 }
191 }