package org.fluentlenium.core.conditions.wait; import org.fluentlenium.core.FluentControl; import org.fluentlenium.core.conditions.Conditions; import org.fluentlenium.core.conditions.ConditionsObject; import org.fluentlenium.core.conditions.Negation; import org.fluentlenium.core.conditions.message.MessageContext; import org.fluentlenium.core.conditions.message.MessageProxy; import org.fluentlenium.core.wait.FluentWait; import org.openqa.selenium.internal.WrapsElement; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.function.Function; import java.util.function.Predicate; import java.util.function.Supplier; /** * Invocation handler used to wait for a particular conditions call. * * @param <C> type of conditions */ public class WaitConditionInvocationHandler<C extends Conditions<?>> implements InvocationHandler { private final Class<C> conditionClass; private final Supplier<C> conditionSupplier; private final FluentWait wait; private String context; private boolean negation; /** * Creates a new wait condition invocation handler. * * @param conditionClass condition class * @param wait fluent wait * @param context base context of generated message if condition is not verified * @param conditionSupplier supplier of conditions */ public WaitConditionInvocationHandler(Class<C> conditionClass, FluentWait wait, String context, Supplier<C> conditionSupplier) { this.conditionClass = conditionClass; this.wait = wait; this.context = context; this.conditionSupplier = conditionSupplier; } /** * Get the underlying conditions of wait matcher. * * @return underlying conditions. */ protected C conditions() { return conditions(false); } /** * Get the underlying conditions of wait matcher. * * @param ignoreNot true if the negation should be ignored. * @return underlying conditions. */ protected C conditions(boolean ignoreNot) { C conditions = conditionSupplier.get(); return applyNegation(conditions, ignoreNot); } /** * Apply the current negation to the given condition * * @param conditions conditions. * @param ignoreNegation true if the negation should be ignored. * @return conditions with the negation applied. */ protected C applyNegation(C conditions, boolean ignoreNegation) { if (!ignoreNegation && negation) { return (C) conditions.not(); } return conditions; } /** * Builds a message builder proxy. * * @return message builder proxy */ protected C messageBuilder() { return messageBuilder(false); } /** * Builds a message builder proxy. * * @param ignoreNegation true if the negation should be ignored. * @return message builder proxy */ protected C messageBuilder(boolean ignoreNegation) { C conditions = MessageProxy.builder(conditionClass, context); conditions = applyNegation(conditions, ignoreNegation); return conditions; } /** * Build the final message from default message. * * @return final message */ protected Function<String, String> messageCustomizer() { return Function.identity(); } /** * Perform the wait. * * @param present predicate to wait for. * @param message message to use. */ protected void until(Predicate<FluentControl> present, String message) { if (wait.hasMessageDefined()) { wait.untilPredicate(present); } else { message = messageCustomizer().apply(message); wait.withMessage(message).untilPredicate(present); } } /** * Perform the wait. * * @param present predicate to wait for. * @param messageSupplier default message to use. */ protected void until(Predicate<FluentControl> present, Supplier<String> messageSupplier) { if (wait.hasMessageDefined()) { wait.untilPredicate(present); } else { Supplier<String> customMessageSupplier = new Supplier<String>() { @Override public String get() { return messageCustomizer().apply(messageSupplier.get()); } }; wait.withMessage(customMessageSupplier).untilPredicate(present); } } /** * Perform the wait. * * @param condition condition object to wait for * @param messageBuilder message builder matching the condition object * @param conditionFunction condition fonction */ protected void until(C condition, C messageBuilder, Function<C, Boolean> conditionFunction) { Predicate<FluentControl> predicate = input -> conditionFunction.apply(condition); Supplier<String> messageSupplier = () -> { conditionFunction.apply(messageBuilder); StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append(MessageProxy.message(messageBuilder)); if (condition instanceof ConditionsObject) { Object actualObject = ((ConditionsObject) condition).getActualObject(); if (!(actualObject instanceof WrapsElement)) { stringBuilder.append(" (actual: "); stringBuilder.append(actualObject); stringBuilder.append(')'); } } return stringBuilder.toString(); }; until(predicate, messageSupplier); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (method.isAnnotationPresent(Negation.class)) { return buildNegationProxy(); } if (method.isAnnotationPresent(MessageContext.class)) { context = context + " " + method.getAnnotation(MessageContext.class).value(); } Class<?> returnType = method.getReturnType(); if (boolean.class.equals(returnType) || Boolean.class.equals(returnType)) { return waitForCondition(method, args); } else if (Conditions.class.isAssignableFrom(returnType)) { return buildChildProxy(method, args); } else { throw new IllegalStateException("An internal error has occured."); } } private Object buildChildProxy(Method method, Object[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { Method conditionGetter = conditions().getClass().getMethod(method.getName(), method.getParameterTypes()); Conditions<?> childConditions = (Conditions<?>) conditionGetter.invoke(conditions(true), args); Conditions<?> childProxy = WaitConditionProxy .custom((Class<Conditions<?>>) method.getReturnType(), wait, context, () -> childConditions); WaitConditionInvocationHandler childHandler = (WaitConditionInvocationHandler) Proxy.getInvocationHandler(childProxy); childHandler.negation = negation; return childProxy; } private boolean waitForCondition(Method method, Object[] args) { C messageBuilder = messageBuilder(); until(conditions(), messageBuilder, new Function<C, Boolean>() { @Override public Boolean apply(C input) { try { return (Boolean) method.invoke(input, args); } catch (IllegalAccessException e) { throw new IllegalStateException("An internal error has occured while waiting", e); } catch (InvocationTargetException e) { Throwable targetException = e.getTargetException(); if (targetException instanceof RuntimeException) { throw (RuntimeException) targetException; } throw new IllegalStateException("An internal error has occured while waiting", e); } } }); return true; } private Conditions<?> buildNegationProxy() { Conditions<?> negationProxy = WaitConditionProxy.custom(conditionClass, wait, context, conditionSupplier); WaitConditionInvocationHandler negationHandler = (WaitConditionInvocationHandler) Proxy .getInvocationHandler(negationProxy); negationHandler.negation = !negation; return negationProxy; } }