1   package com.atlassian.pageobjects.elements.query;
2   
3   import org.apache.commons.lang.StringUtils;
4   import org.hamcrest.Description;
5   import org.hamcrest.Matcher;
6   import org.hamcrest.StringDescription;
7   
8   import java.util.concurrent.TimeUnit;
9   
10  import static com.google.common.base.Preconditions.checkNotNull;
11  import static org.hamcrest.Matchers.equalTo;
12  import static org.hamcrest.Matchers.is;
13  
14  /**
15   * Utility class to poll and wait for a particular states of timeout-based queries inheriting from {@link PollingQuery}.
16   *
17   * @see PollingQuery
18   * @see TimedQuery
19   * @see TimedCondition
20   */
21  public final class Poller
22  {
23      private Poller() {
24          throw new AssertionError("Don't instantiate me");
25      }
26  
27      /**
28       * Wait until given <tt>condition</tt> is <code>true</code> by default timeout
29       *
30       * @param condition condition to exercise
31       */
32      public static void waitUntilTrue(TimedQuery<Boolean> condition)
33      {
34          waitUntil(condition, is(true));
35      }
36  
37      /**
38       * Wait until given <tt>condition</tt> is <code>true</code> by default timeout, with custom error message.
39       *
40       * @param message error message
41       * @param condition condition to exercise
42       */
43      public static void waitUntilTrue(String message, TimedQuery<Boolean> condition)
44      {
45          waitUntil(message, condition, is(true));
46      }
47  
48      /**
49       * Wait until given <tt>condition</tt> is <code>false</code> by default timeout
50       *
51       * @param condition condition to exercise
52       */
53      public static void waitUntilFalse(TimedQuery<Boolean> condition)
54      {
55          waitUntil(condition, is(false));
56      }
57  
58      /**
59       * Wait until given <tt>condition</tt> is <code>false</code> by default timeout, with custom error message.
60       *
61       * @param message error message
62       * @param condition condition to exercise
63       */
64      public static void waitUntilFalse(String message, TimedQuery<Boolean> condition)
65      {
66          waitUntil(message, condition, is(false));
67      }
68  
69      /**
70       * Wait until given <tt>query</tt> evaluates to actual value that is equal to <tt>expectedValue</tt>
71       * by default timeout.
72       *
73       * @param expectedValue expected value
74       * @param query query to evaluate
75       * @return last value from the query, satisfying the expected value
76       */
77      public static <T> T waitUntilEquals(T expectedValue, TimedQuery<T> query)
78      {
79          return waitUntil(query, equalTo(expectedValue));
80      }
81  
82      /**
83       * Wait until given <tt>query</tt> evaluates to actual value that is equal to <tt>expectedValue</tt> by default timeout,
84       * with custom error message.
85       *
86       * @param message error message
87       * @param expectedValue expected value
88       * @param query query to evaluate
89       * @return last value from the query, satisfying the expected value
90       */
91      public static <T> T waitUntilEquals(String message, T expectedValue, TimedQuery<T> query)
92      {
93          return waitUntil(message, query, equalTo(expectedValue));
94      }
95  
96      /**
97       * <p>
98       * Wait until given <tt>query</tt> fulfils certain condition specified by the <tt>matcher</tt>, by
99       * default timeout of the <tt>query</tt>.
100      *
101      * <p>
102      * Use any matcher from the available libraries (e.g. Hamcrest, JUnit etc.), or a custom one.
103      *
104      * @param query timed query to evaluate
105      * @param matcher a matcher representing the assertion condition
106      * @see Matcher
107      * @see org.hamcrest.Matchers
108      * @return last value from the query, satisfying the matcher
109      */
110     public static <T> T waitUntil(TimedQuery<T> query, Matcher<T> matcher)
111     {
112         return waitUntil(null, query, matcher, byDefaultTimeout());
113     }
114 
115     /**
116      * <p>
117      * Wait until given <tt>query</tt> fulfils certain condition specified by the <tt>matcher</tt>, by
118      * default timeout of the <tt>query</tt>.
119      *
120      * <p>
121      * Use any matcher from the available libraries (e.g. Hamcrest, JUnit etc.), or a custom one.
122      *
123      * @param message message displayed in case of failure
124      * @param query timed query to evaluate
125      * @param matcher a matcher representing the assertion condition
126      * @see Matcher
127      * @see org.hamcrest.Matchers
128      * @return last value from the query, satisfying the matcher
129      */
130     public static <T> T waitUntil(String message, TimedQuery<T> query, Matcher<T> matcher)
131     {
132         return waitUntil(message, query, matcher, byDefaultTimeout());
133     }
134 
135     /**
136      * <p>
137      * Wait until given <tt>query</tt> fulfils certain condition specified by given the <tt>matcher</tt>, by
138      * given <tt>timeout</tt>.
139      *
140      * <p>
141      * Use any matcher from the available libraries (e.g. Hamcrest, JUnit etc.), or a custom one.
142      *
143      * <p>
144      * To specify desired timeout, use one of four provided timeouts: {@link #now()}, {@link #byDefaultTimeout()},
145      * #{@link #by(long)}, {@link #by(long, java.util.concurrent.TimeUnit)}.
146      *
147      * @param query timed query to evaluate
148      * @param matcher a matcher representing the assertion condition
149      * @param timeout time limit for this wait
150      * @see Matcher
151      * @see org.hamcrest.Matchers
152      * @see #now()
153      * @see #by(long)
154      * @see #by(long, java.util.concurrent.TimeUnit)
155      * @see #byDefaultTimeout()
156      * @return last value from the query, satisfying the matcher
157      */
158     public static <T> T waitUntil(TimedQuery<T> query, Matcher<T> matcher, WaitTimeout timeout)
159     {
160         return waitUntil(null, query, matcher, timeout);
161     }
162 
163 
164     /**
165      * <p>
166      * Wait until given <tt>query</tt> fulfils certain condition specified by given the <tt>matcher</tt>, by
167      * given <tt>timeout</tt>.
168      *
169      * <p>
170      * Use any matcher from the libraries available (e.g. Hamcrest, JUnit etc.), or a custom one.
171      *
172      * <p>
173      * To specify desired timeout, use one of four provided timeouts: {@link #now()}, {@link #byDefaultTimeout()},
174      * #{@link #by(long)}, {@link #by(long, java.util.concurrent.TimeUnit)}.
175      *
176      * @param message message displayed for failed assertion
177      * @param query timed query to verify
178      * @param matcher a matcher representing the assertion condition
179      * @param timeout timeout of the assertion
180      * @see Matcher
181      * @see org.hamcrest.Matchers
182      * @see #now()
183      * @see #by(long)
184      * @see #by(long, java.util.concurrent.TimeUnit)
185      * @see #byDefaultTimeout()
186      * @return last value from the query, satisfying the matcher
187      */
188     public static <T> T waitUntil(String message, TimedQuery<T> query, Matcher<T> matcher, WaitTimeout timeout)
189     {
190         checkNotNull(timeout);
191         final Conditions.MatchingCondition<T> assertion = new Conditions.MatchingCondition<T>(query, matcher);
192         if (!timeout.evaluate(assertion))
193         {
194             throw new AssertionError(buildMessage(message, assertion, matcher, timeout));
195         }
196         return assertion.lastValue;
197     }
198 
199     private static <T> String buildMessage(String message, Conditions.MatchingCondition<T> assertion,
200                                            Matcher<T> matcher, WaitTimeout timeout)
201     {
202         final Description answer = new StringDescription();
203         if (StringUtils.isNotEmpty(message))
204         {
205             answer.appendText(message).appendText(":\n");
206         }
207         return answer.appendText("Query ").appendValue(assertion.query).appendText("\nExpected: ")
208                 .appendDescriptionOf(matcher).appendText(timeout.msgTimeoutSuffix(assertion))
209                 .appendText("\nGot (last value): ").appendValue(assertion.lastValue).toString();
210     }
211 
212 
213 
214 
215     public static abstract class WaitTimeout
216     {
217         // we don't want this to be instantiated, or extended by anybody
218         private WaitTimeout() {}
219 
220         abstract boolean evaluate(TimedCondition condition);
221 
222         abstract String msgTimeoutSuffix(TimedCondition condition);
223     }
224 
225 
226     /**
227      * Timeout indicating that the assertion method will evaluate the assertion condition immediately, without waiting.
228      *
229      * @return new immediate assertion timeout
230      */
231     public static WaitTimeout now()
232     {
233         return new WaitTimeout()
234         {
235             @Override
236             boolean evaluate(final TimedCondition condition)
237             {
238                 return condition.now();
239             }
240             @Override
241             String msgTimeoutSuffix(final TimedCondition condition)
242             {
243                 return " immediately";
244             }
245         };
246     }
247 
248     /**
249      * Timeout indicating that the assertion method will wait for the default timeout of exercised timed query for
250      * the condition to become <code>true</code>.
251      *
252      * @return new default assertion timeout
253      */
254     public static WaitTimeout byDefaultTimeout()
255     {
256         return new WaitTimeout()
257         {
258             @Override
259             boolean evaluate(final TimedCondition condition)
260             {
261                 return condition.byDefaultTimeout();
262             }
263             @Override
264             String msgTimeoutSuffix(final TimedCondition condition)
265             {
266                 return " by " + condition.defaultTimeout() + "ms (default timeout)";
267             }
268         };
269     }
270 
271     /**
272      * Custom assertion timeout expressed in milliseconds. E.g. <code>TimedAssertions.by(500L);</code>
273      * passed to an assertion method will make it wait 500 milliseconds for given condition to become <code>true</code>.
274      *
275      * @param timeoutInMillis number of milliseconds to wait for the assertion condition
276      * @return new custom timeout assertion
277      */
278     public static WaitTimeout by(final long timeoutInMillis)
279     {
280         return new WaitTimeout()
281         {
282             @Override
283             boolean evaluate(final TimedCondition condition)
284             {
285                 return condition.by(timeoutInMillis);
286             }
287             @Override
288             String msgTimeoutSuffix(final TimedCondition condition)
289             {
290                 return " by " + timeoutInMillis + "ms";
291             }
292         };
293     }
294 
295     /**
296      * Custom assertion timeout expressed in a number of time units. E.g. <code>TimedAssertions.by(5, TimeUnit.SECONDS);</code>
297      * passed to an assertion method will make it wait 5 seconds for given condition to become <code>true</code>.
298      *
299      * @param timeout timeout count
300      * @param unit unit of the timeout
301      * @return new custom timeout assertion
302      */
303     public static WaitTimeout by(final long timeout, final TimeUnit unit)
304     {
305         return new WaitTimeout()
306         {
307             @Override
308             boolean evaluate(final TimedCondition condition)
309             {
310                 return condition.by(timeout, unit);
311             }
312             @Override
313             String msgTimeoutSuffix(final TimedCondition condition)
314             {
315                 return " by " + unit.toMillis(timeout) + "ms";
316             }
317         };
318     }
319 
320 }