View Javadoc

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   * <p>
19   * Abstract query that implements {@link #byDefaultTimeout()} in terms of {@link #by(long)}, and {@link #by(long)} as a
20   * template method calling the following hooks (to be implemented by subclasses):
21   * <ul>
22   * <li>{@link #currentValue()} - to determine current evaluation of the query
23   * <li>{@link #shouldReturn(Object)} - which indicates, if current value of the query should be returned
24   * </ul>
25   *
26   * <p>
27   * In addition, an {@link ExpirationHandler} must be provided to handle the case of expired query.
28   *
29   * @see ExpirationHandler
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      * Expiration handler of this query
108      *
109      * @return expiration handler of this query
110      */
111     public final ExpirationHandler expirationHandler()
112     {
113         return expirationHandler;
114     }
115 
116     /**
117      * If the current evaluated query value should be returned.
118      *
119      * @param currentEval current query evaluation
120      * expires
121      * @return <code>true</code>, if the current query evaluation should be returned as a result of this timed query
122      */
123     protected abstract boolean shouldReturn(T currentEval);
124 
125     /**
126      * Current evaluation of the query.
127      *
128      * @return current evaluation of the query
129      */
130     protected abstract T currentValue();
131 
132     /**
133      * Value to returned on the timeout expiry (last query evaluation).
134      *
135      * @param currentVal current query value
136      * @param timeout timeout
137      * @return query evaluation on the timeout expiration
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             // yes this is quite arbitrary, the goal is to run the last evaluation as close to the deadline as possible,
164             // but not let it pass
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 }