1 package com.atlassian.pageobjects.elements.query;
2
3 import com.atlassian.pageobjects.elements.timeout.TimeoutType;
4 import com.atlassian.pageobjects.elements.timeout.Timeouts;
5 import com.google.common.base.Supplier;
6 import com.google.common.collect.Iterables;
7 import org.apache.commons.lang.ArrayUtils;
8 import org.hamcrest.Matcher;
9 import org.hamcrest.StringDescription;
10 import org.slf4j.Logger;
11 import org.slf4j.LoggerFactory;
12
13 import javax.annotation.Nonnull;
14 import javax.annotation.Nullable;
15
16 import static com.atlassian.pageobjects.elements.util.StringConcat.asString;
17 import static com.google.common.base.Preconditions.checkNotNull;
18 import static org.hamcrest.Matchers.equalTo;
19
20
21
22
23
24 @SuppressWarnings("unchecked")
25 public final class Conditions
26 {
27 private static final Logger log = LoggerFactory.getLogger(Conditions.class);
28
29 private static final int DEFAULT_TIMEOUT = 100;
30
31 private Conditions()
32 {
33 throw new AssertionError(Conditions.class.getName() + " should not be instantiated");
34 }
35
36
37
38
39
40
41
42 @Nonnull
43 public static TimedQuery<Boolean> not(@Nonnull TimedQuery<Boolean> condition)
44 {
45 if (condition instanceof Not)
46 {
47 return asDecorator(condition).wrapped;
48 }
49 return new Not(condition);
50 }
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66 @Nonnull
67 public static CombinableCondition and(@Nonnull TimedQuery<Boolean>... conditions)
68 {
69 return new And(conditions);
70 }
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86 @Nonnull
87 public static CombinableCondition and(@Nonnull Iterable<TimedQuery<Boolean>> conditions)
88 {
89 return and(Iterables.toArray(conditions, TimedQuery.class));
90 }
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106 @Nonnull
107 public static CombinableCondition or(@Nonnull TimedQuery<Boolean>... conditions)
108 {
109 return new Or(conditions);
110 }
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126 @Nonnull
127 public static CombinableCondition or(@Nonnull Iterable<TimedQuery<Boolean>> conditions)
128 {
129 return or(Iterables.toArray(conditions, TimedQuery.class));
130 }
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151 @Nonnull
152 public static TimedCondition dependantCondition(@Nonnull TimedQuery<Boolean> original,
153 @Nonnull Supplier<TimedQuery<Boolean>> dependant)
154 {
155 return new DependantCondition(original, dependant);
156 }
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174 @Nonnull
175 public static <T> TimedCondition forMatcher(@Nonnull TimedQuery<T> query, @Nonnull Matcher<? super T> matcher)
176 {
177 return new MatchingCondition<T>(query, matcher);
178 }
179
180
181
182
183
184
185
186
187
188
189 @Nonnull
190 public static <T> TimedCondition isEqual(@Nullable T value, @Nonnull TimedQuery<T> query)
191 {
192 return forMatcher(query, equalTo(value));
193 }
194
195
196
197
198
199
200
201 @Nonnull
202 public static TimedCondition forSupplier(@Nonnull final Supplier<Boolean> supplier)
203 {
204 return forSupplier(DEFAULT_TIMEOUT, supplier);
205 }
206
207
208
209
210
211
212
213
214 @Nonnull
215 public static TimedCondition forSupplier(long defaultTimeout, @Nonnull final Supplier<Boolean> supplier)
216 {
217 return new AbstractTimedCondition(defaultTimeout, PollingQuery.DEFAULT_INTERVAL) {
218 @Override
219 protected Boolean currentValue() {
220 return supplier.get();
221 }
222 };
223 }
224
225
226
227
228
229
230
231
232
233 @Nonnull
234 public static TimedCondition forSupplier(@Nonnull Timeouts timeouts, @Nonnull final Supplier<Boolean> supplier)
235 {
236 return forSupplier(timeouts, supplier, TimeoutType.DEFAULT);
237 }
238
239
240
241
242
243
244
245
246
247
248 @Nonnull
249 public static TimedCondition forSupplier(@Nonnull Timeouts timeouts, @Nonnull final Supplier<Boolean> supplier,
250 @Nonnull TimeoutType timeoutType)
251 {
252 checkNotNull(timeouts, "timeouts");
253 checkNotNull(supplier, "supplier");
254 checkNotNull(timeoutType, "timeoutType");
255
256 return new AbstractTimedCondition(timeouts.timeoutFor(timeoutType),
257 timeouts.timeoutFor(TimeoutType.EVALUATION_INTERVAL)) {
258 @Override
259 protected Boolean currentValue() {
260 return supplier.get();
261 }
262 };
263 }
264
265
266
267
268
269
270 @Nonnull
271 public static TimedCondition alwaysTrue()
272 {
273 return new StaticCondition(true);
274 }
275
276
277
278
279
280
281 @Nonnull
282 public static TimedCondition alwaysFalse()
283 {
284 return new StaticCondition(false);
285 }
286
287 private static AbstractConditionDecorator asDecorator(TimedQuery<Boolean> condition)
288 {
289 return (AbstractConditionDecorator) condition;
290 }
291
292 private static class StaticCondition extends AbstractTimedCondition
293 {
294 private final Boolean value;
295
296 public StaticCondition(Boolean value)
297 {
298 super(DEFAULT_TIMEOUT, DEFAULT_INTERVAL);
299 this.value = checkNotNull(value);
300 }
301
302 @Override
303 protected Boolean currentValue()
304 {
305 return value;
306 }
307 }
308
309
310
311
312
313 public static interface CombinableCondition extends TimedCondition
314 {
315
316
317
318
319
320
321
322 CombinableCondition and(TimedCondition other);
323
324
325
326
327
328
329
330
331 CombinableCondition or(TimedCondition other);
332 }
333
334 private abstract static class AbstractConditionDecorator extends AbstractTimedCondition
335 {
336 protected final TimedQuery<Boolean> wrapped;
337
338 public AbstractConditionDecorator(TimedQuery<Boolean> wrapped)
339 {
340 super(wrapped);
341 this.wrapped = checkNotNull(wrapped, "wrapped");
342 }
343 }
344
345 private abstract static class AbstractConditionsDecorator extends AbstractTimedCondition implements CombinableCondition
346 {
347 protected final TimedQuery<Boolean>[] conditions;
348
349 public AbstractConditionsDecorator(TimedQuery<Boolean>... conditions)
350 {
351 super(conditions[0]);
352 this.conditions = conditions;
353 }
354
355 @Override
356 public String toString()
357 {
358 StringBuilder answer = new StringBuilder(conditions.length * 20).append(getClass().getName()).append(":\n");
359 for (TimedQuery<Boolean> condition : conditions)
360 {
361 answer.append(" -").append(condition.toString()).append('\n');
362 }
363 return answer.deleteCharAt(answer.length()-1).toString();
364 }
365 }
366
367 private static class Not extends AbstractConditionDecorator
368 {
369 public Not(TimedQuery<Boolean> other)
370 {
371 super(other);
372 }
373
374 public Boolean currentValue()
375 {
376 return !wrapped.now();
377 }
378
379 @Override
380 public String toString()
381 {
382 return asString("Negated: <", wrapped, ">");
383 }
384 }
385
386 private static class And extends AbstractConditionsDecorator
387 {
388 public And(TimedQuery<Boolean>... conditions)
389 {
390 super(conditions);
391 }
392
393 And(TimedQuery<Boolean>[] somes, TimedQuery<Boolean>[] more)
394 {
395 super((TimedCondition[]) ArrayUtils.addAll(somes, more));
396 }
397
398 And(TimedQuery<Boolean>[] somes, TimedQuery<Boolean> oneMore)
399 {
400 super((TimedCondition[]) ArrayUtils.add(somes, oneMore));
401 }
402
403 public Boolean currentValue()
404 {
405 boolean result = true;
406 for (TimedQuery<Boolean> condition : conditions)
407 {
408
409 result = condition.now() != null ? condition.now() : false;
410 if (!result)
411 {
412
413 log.debug(asString("[And] Condition <", condition, "> returned false"));
414 break;
415 }
416 }
417 return result;
418 }
419
420 public CombinableCondition and(TimedCondition other)
421 {
422 if (other.getClass().equals(And.class))
423 {
424 return new And(this.conditions, ((And) other).conditions);
425 }
426 return new And(this.conditions, other);
427 }
428
429 public CombinableCondition or(TimedCondition other)
430 {
431 if (other instanceof Or)
432 {
433 return ((Or)other).or(this);
434 }
435 return new Or(this, other);
436 }
437 }
438
439 private static class Or extends AbstractConditionsDecorator
440 {
441 public Or(TimedQuery<Boolean>... conditions)
442 {
443 super(conditions);
444 }
445
446 Or(TimedQuery<Boolean>[] somes, TimedQuery<Boolean>[] more)
447 {
448 super((TimedCondition[]) ArrayUtils.addAll(somes, more));
449 }
450
451 Or(TimedQuery<Boolean>[] somes, TimedQuery<Boolean> oneMore)
452 {
453 super((TimedCondition[]) ArrayUtils.add(somes, oneMore));
454 }
455
456 public Boolean currentValue()
457 {
458 boolean result = false;
459 for (TimedQuery<Boolean> condition : conditions)
460 {
461
462 result = condition.now() != null ? condition.now() : false;
463 if (result)
464 {
465
466 break;
467 }
468 log.debug(asString("[Or] Condition <", condition, "> returned false"));
469 }
470 return result;
471 }
472
473 public CombinableCondition and(TimedCondition other)
474 {
475 if (other instanceof And)
476 {
477 return ((And)other).and(this);
478 }
479 return new And(this, other);
480 }
481
482 public CombinableCondition or(TimedCondition other)
483 {
484 if (other.getClass().equals(Or.class))
485 {
486 return new Or(this.conditions, ((Or) other).conditions);
487 }
488 return new Or(this.conditions, other);
489 }
490 }
491
492 private static final class DependantCondition extends AbstractConditionDecorator
493 {
494 private final Supplier<TimedQuery<Boolean>> dependant;
495
496 DependantCondition(TimedQuery<Boolean> original, Supplier<TimedQuery<Boolean>> dependant)
497 {
498 super(original);
499 this.dependant = checkNotNull(dependant, "dependant");
500 }
501
502 @Override
503 public Boolean currentValue()
504 {
505 return wrapped.now() && dependant.get().now();
506 }
507
508 @Override
509 public String toString()
510 {
511 if (wrapped.now())
512 {
513 TimedQuery<Boolean> dep = dependant.get();
514 return asString("DependantCondition[original=",wrapped,",dependant=",dep,"]");
515 }
516 return asString("DependantCondition[original=",wrapped,"]");
517 }
518 }
519
520
521 static final class MatchingCondition<T> extends AbstractTimedCondition
522 {
523 final TimedQuery<T> query;
524 final Matcher<? super T> matcher;
525
526 T lastValue;
527
528 public MatchingCondition(final TimedQuery<T> query, final Matcher<? super T> matcher)
529 {
530 super(query);
531 this.query = checkNotNull(query);
532 this.matcher = checkNotNull(matcher);
533 }
534
535 @Override
536 protected Boolean currentValue()
537 {
538 try
539 {
540 lastValue = query.now();
541 return matcher.matches(lastValue);
542 }
543 catch (Exception e)
544 {
545 log.debug(String.format("TimedQuery.now() threw an exception. Not a match for %s", matcher), e);
546 return false;
547 }
548 }
549
550 @Override
551 public String toString()
552 {
553 return super.toString() + new StringDescription().appendText("[query=").appendValue(query)
554 .appendText("][matcher=").appendDescriptionOf(matcher).appendText("]");
555 }
556 }
557 }