package com.lambdaworks;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;
import java.util.function.Function;
import java.util.function.Predicate;
import com.google.code.tempusfugit.temporal.Duration;
import com.google.code.tempusfugit.temporal.Sleeper;
import com.google.code.tempusfugit.temporal.ThreadSleep;
import com.google.code.tempusfugit.temporal.Timeout;
/**
* Wait-Until helper.
*
* @author Mark Paluch
*/
public class Wait {
/**
* Initialize a {@link com.lambdaworks.Wait.WaitBuilder} to wait until the {@code supplier} supplies {@literal true}
*
* @param supplier
* @return
*/
public static WaitBuilder<Boolean> untilTrue(Supplier<Boolean> supplier) {
WaitBuilder<Boolean> wb = new WaitBuilder<>();
wb.supplier = supplier;
wb.check = o -> o;
return wb;
}
/**
* Initialize a {@link com.lambdaworks.Wait.WaitBuilder} to wait until the {@code condition} does not throw exceptions
*
* @param condition
* @return
*/
public static WaitBuilder<?> untilNoException(VoidWaitCondition condition) {
WaitBuilder<?> wb = new WaitBuilder<>();
wb.waitCondition = () -> {
try {
condition.test();
return true;
} catch (Exception e) {
return false;
}
};
wb.supplier = () -> {
condition.test();
return null;
};
return wb;
}
/**
* Initialize a {@link com.lambdaworks.Wait.WaitBuilder} to wait until the {@code actualSupplier} provides an object that is
* not equal to {@code expectation}
*
* @param expectation
* @param actualSupplier
* @param <T>
* @return
*/
public static <T> WaitBuilder<T> untilNotEquals(T expectation, Supplier<T> actualSupplier) {
WaitBuilder<T> wb = new WaitBuilder<>();
wb.supplier = actualSupplier;
wb.check = o -> {
if (o == expectation) {
return false;
}
if ((o == null && expectation != null) || (o != null && expectation == null)) {
return true;
}
if (o instanceof Number && expectation instanceof Number) {
Number actualNumber = (Number) o;
Number expectedNumber = (Number) o;
if (actualNumber.doubleValue() == expectedNumber.doubleValue()) {
return false;
}
if (actualNumber.longValue() == expectedNumber.longValue()) {
return false;
}
}
return !o.equals(expectation);
};
wb.messageFunction = o -> "Objects are equal: " + expectation + " and " + o;
return wb;
}
/**
* Initialize a {@link com.lambdaworks.Wait.WaitBuilder} to wait until the {@code actualSupplier} provides an object that is
* not equal to {@code expectation}
*
* @param expectation
* @param actualSupplier
* @param <T>
* @return
*/
public static <T> WaitBuilder<T> untilEquals(T expectation, Supplier<T> actualSupplier) {
WaitBuilder<T> wb = new WaitBuilder<>();
wb.supplier = actualSupplier;
wb.check = o -> {
if (o == expectation) {
return true;
}
if ((o == null && expectation != null) || (o != null && expectation == null)) {
return false;
}
if (o instanceof Number && expectation instanceof Number) {
Number actualNumber = (Number) o;
Number expectedNumber = (Number) expectation;
if (actualNumber.doubleValue() == expectedNumber.doubleValue()) {
return true;
}
if (actualNumber.longValue() == expectedNumber.longValue()) {
return true;
}
}
return o.equals(expectation);
};
wb.messageFunction = o -> "Objects are not equal: " + expectation + " and " + o;
return wb;
}
@FunctionalInterface
public interface WaitCondition {
boolean isSatisfied() throws Exception;
}
@FunctionalInterface
public interface VoidWaitCondition {
void test() throws Exception;
}
@FunctionalInterface
public interface Supplier<T> {
T get() throws Exception;
}
public static class WaitBuilder<T> {
private Duration duration = Duration.seconds(10);
private Sleeper sleeper = new ThreadSleep(Duration.millis(100));
private Function<T, String> messageFunction;
private Supplier<T> supplier;
private Predicate<T> check;
private WaitCondition waitCondition;
public WaitBuilder<T> during(Duration duration) {
this.duration = duration;
return this;
}
public WaitBuilder<T> message(String message) {
this.messageFunction = o -> message;
return this;
}
public void waitOrTimeout() {
Waiter waiter = new Waiter();
waiter.duration = duration;
waiter.sleeper = sleeper;
waiter.messageFunction = (Function<Object, String>) messageFunction;
if (waitCondition != null) {
waiter.waitOrTimeout(waitCondition, supplier);
} else {
waiter.waitOrTimeout(supplier, check);
}
}
}
private static class Waiter {
private Duration duration;
private Sleeper sleeper;
private Function<Object, String> messageFunction;
private <T> void waitOrTimeout(Supplier<T> supplier, Predicate<T> check) {
try {
if (!success(() -> check.test(supplier.get()), Timeout.timeout(duration))) {
if (messageFunction != null) {
throw new TimeoutException(messageFunction.apply(supplier.get()));
}
throw new TimeoutException("Condition not satisfied for: " + supplier.get());
}
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
private <T> void waitOrTimeout(WaitCondition waitCondition, Supplier<T> supplier) {
try {
if (!success(waitCondition, Timeout.timeout(duration))) {
try {
if (messageFunction != null) {
throw new TimeoutException(messageFunction.apply(supplier.get()));
}
throw new TimeoutException("Condition not satisfied for: " + supplier.get());
} catch (TimeoutException e) {
throw e;
} catch (Exception e) {
if (messageFunction != null) {
throw new ExecutionException(messageFunction.apply(null), e);
}
throw new ExecutionException("Condition not satisfied", e);
}
}
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
private boolean success(WaitCondition condition, Timeout timeout) throws Exception {
while (!timeout.hasExpired()) {
if (condition.isSatisfied()) {
return true;
}
sleeper.sleep();
}
return false;
}
}
}