/*
 * Copyright 2017 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.awaitility.proxy.internal;

import org.awaitility.core.CheckedExceptionRethrower;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.concurrent.Callable;

/**
 * The Class MethodCaller.
 *
 * @param <T> the generic type
 */
class MethodCaller<T> implements Callable<T> {

    /**
     * The target.
     */
    final Object target;

    /**
     * The method.
     */
    final Method method;

    /**
     * The args.
     */
    final Object[] args;

    /**
     * Instantiates a new method caller.
     *
     * @param target the target
     * @param method the method
     * @param args   the args
     */
    public MethodCaller(Object target, Method method, Object[] args) {
        this.target = target;
        this.method = method;
        this.args = args;
        method.setAccessible(true);
    }

    /*
       * (non-Javadoc)
       *
       * @see java.util.concurrent.Callable#call()
       */
    @SuppressWarnings("unchecked")
    public T call() throws InvocationTargetException, IllegalAccessException {
        try {
            return (T) method.invoke(target, args);
        } catch (IllegalAccessException e) {
            return CheckedExceptionRethrower.safeRethrow(e);
        } catch (InvocationTargetException e) {
            return CheckedExceptionRethrower.safeRethrow(e.getCause());
        }
    }
}


/*
 * Copyright 2010 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.awaitility.core;

import org.awaitility.Duration;

import java.lang.Thread.UncaughtExceptionHandler;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicReference;

import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.NANOSECONDS;
import static org.awaitility.classpath.ClassPathResolver.existInCP;

abstract class ConditionAwaiter implements UncaughtExceptionHandler {
    private final ExecutorService executor;
    private final ConditionEvaluator conditionEvaluator;
    private final AtomicReference<Throwable> uncaughtThrowable;
    private final ConditionSettings conditionSettings;

    /**
     *
Constructor for ConditionAwaiter.

     *
     * @param conditionEvaluator a {@link ConditionEvaluator} object.
     * @param conditionSettings  a {@link org.awaitility.core.ConditionSettings} object.
     */
    ConditionAwaiter(final ConditionEvaluator conditionEvaluator, final ConditionSettings conditionSettings) {
        if (conditionEvaluator == null) {
            throw new IllegalArgumentException("You must specify a condition (was null).");
        }
        if (conditionSettings == null) {
            throw new IllegalArgumentException("You must specify the condition settings (was null).");
        }
        if (conditionSettings.shouldCatchUncaughtExceptions()) {
            Thread.setDefaultUncaughtExceptionHandler(this);
        }
        this.conditionSettings = conditionSettings;
        this.conditionEvaluator = conditionEvaluator;
        this.executor = conditionSettings.getExecutorLifecycle().supplyExecutorService();
        this.uncaughtThrowable = new AtomicReference<Throwable>();
    }

    /**
     *
await.

     *
     * @param conditionEvaluationHandler The conditionEvaluationHandler
     */
    @SuppressWarnings("deprecation")
    public <T> void await(final ConditionEvaluationHandler<T> conditionEvaluationHandler) {
        final Duration pollDelay = conditionSettings.getPollDelay();
        final Duration maxWaitTime = conditionSettings.getMaxWaitTime();
        final Duration minWaitTime = conditionSettings.getMinWaitTime();

        final long maxTimeout = maxWaitTime.getValue();
        long pollingStartedNanos = System.nanoTime() - pollDelay.getValueInNS();

        int pollCount = 0;
        boolean succeededBeforeTimeout = false;
        ConditionEvaluationResult lastResult = null;
        Duration evaluationDuration = new Duration(0, MILLISECONDS);
        Future<ConditionEvaluationResult> currentConditionEvaluation = null;
        try {
            if (executor.isShutdown() || executor.isTerminated()) {
                throw new IllegalStateException("The executor service that Awaitility is instructed to use has been shutdown so condition evaluation cannot be performed. Is there something wrong the thread or executor configuration?");
            }

            conditionEvaluationHandler.start();
            if (!pollDelay.isZero()) {
                Thread.sleep(pollDelay.getValueInMS());
            }
            Duration pollInterval = pollDelay;
            while (maxWaitTime.compareTo(evaluationDuration) > 0) {
                pollCount = pollCount + 1;
                // Only wait for the next condition evaluation for at most what's remaining of
                Duration maxWaitTimeForThisCondition = maxWaitTime.minus(evaluationDuration);
                currentConditionEvaluation = executor.submit(new ConditionPoller(pollInterval));
                // Wait for condition evaluation to complete with "maxWaitTimeForThisCondition" or else throw TimeoutException
                lastResult = currentConditionEvaluation.get(maxWaitTimeForThisCondition.getValue(), maxWaitTimeForThisCondition.getTimeUnit());
                if (lastResult.isSuccessful() || lastResult.hasThrowable()) {
                    break;
                }
                pollInterval = conditionSettings.getPollInterval().next(pollCount, pollInterval);
                Thread.sleep(pollInterval.getValueInMS());
                evaluationDuration = calculateConditionEvaluationDuration(pollDelay, pollingStartedNanos);
            }
            evaluationDuration = calculateConditionEvaluationDuration(pollDelay, pollingStartedNanos);
            succeededBeforeTimeout = maxWaitTime.compareTo(evaluationDuration) > 0;
        } catch (TimeoutException e) {
            lastResult = new ConditionEvaluationResult(false, null, e);
        } catch (ExecutionException e) {
            lastResult = new ConditionEvaluationResult(false, e.getCause(), null);
        } catch (Throwable e) {
            lastResult = new ConditionEvaluationResult(false, e, null);
        } finally {
            if (currentConditionEvaluation != null) {
                // Cancelling future in order to avoid race-condition with last result for Hamcrest matchers
                // See https://github.com/awaitility/awaitility/issues/109
                currentConditionEvaluation.cancel(true);
            }
        }

        try {
            if (uncaughtThrowable.get() != null) {
                throw uncaughtThrowable.get();
            } else if (lastResult != null && lastResult.hasThrowable()) {
                throw lastResult.getThrowable();
            } else if (!succeededBeforeTimeout) {
                final String maxWaitTimeLowerCase = maxWaitTime.getTimeUnitAsString();
                final String message;
                String timeoutMessage = getTimeoutMessage();
                if (conditionSettings.hasAlias()) {
                    message = String.format("Condition with alias '%s' didn't complete within %s %s because %s.",
                            conditionSettings.getAlias(), maxTimeout, maxWaitTimeLowerCase, decapitalize(timeoutMessage));
                } else {
                    message = String.format("%s within %s %s.", timeoutMessage, maxTimeout, maxWaitTimeLowerCase);
                }

                Throwable cause = lastResult != null && lastResult.hasTrace() ? lastResult.getTrace() : null;
                // Not all systems support deadlock detection so ignore if ThreadMXBean & ManagementFactory is not in classpath
                if (existInCP("java.lang.management.ThreadMXBean") && existInCP("java.lang.management.ManagementFactory")) {
                    java.lang.management.ThreadMXBean bean = java.lang.management.ManagementFactory.getThreadMXBean();
                    try {
                        long[] threadIds = bean.findDeadlockedThreads();
                        if (threadIds != null) {
                            cause = new DeadlockException(threadIds);
                        }
                    } catch (UnsupportedOperationException ignored) {
                        // findDeadLockedThreads() not supported on this VM,
                        // don't init trace and move on.
                    }
                }
                throw new ConditionTimeoutException(message, cause);
            } else if (evaluationDuration.compareTo(minWaitTime) < 0) {
                String message = String.format("Condition was evaluated in %s %s which is earlier than expected " +
                                "minimum timeout %s %s", evaluationDuration.getValue(), evaluationDuration.getTimeUnit(),
                        minWaitTime.getValue(), minWaitTime.getTimeUnit());
                throw new ConditionTimeoutException(message);
            }
        } catch (Throwable e) {
            CheckedExceptionRethrower.safeRethrow(e);
        } finally {
            uncaughtThrowable.set(null);
            conditionSettings.getExecutorLifecycle().executeNormalCleanupBehavior(executor);
        }
    }

    private static String decapitalize(String str) {
        if (str == null) {
            return "";
        }
        String firstLetter = str.substring(0, 1).toLowerCase();
        String restLetters = str.substring(1);
        return firstLetter + restLetters;
    }

    /**
     *
getTimeoutMessage.

     *
     * @return a {@link java.lang.String} object.
     */
    protected abstract String getTimeoutMessage();

    /**
     * {@inheritDoc}
     */
    @Override
    public void uncaughtException(Thread thread, Throwable throwable) {
        if (!conditionSettings.shouldExceptionBeIgnored(throwable)) {
            uncaughtThrowable.set(throwable);
            // We shutdown the executor "now" in order to fail the test immediately
            conditionSettings.getExecutorLifecycle().executeUnexpectedCleanupBehavior(executor);
        }
    }

    private class ConditionPoller implements Callable<ConditionEvaluationResult> {
        private final Duration delayed;

        /**
         * @param delayed The duration of the poll interval
         */
        ConditionPoller(Duration delayed) {
            this.delayed = delayed;
        }

        @Override
        public ConditionEvaluationResult call() throws Exception {
            try {
                return conditionEvaluator.eval(delayed);
            } catch (Throwable e) {
                if (conditionSettings.shouldExceptionBeIgnored(e)) {
                    return new ConditionEvaluationResult(false, null, e);
                }
                return new ConditionEvaluationResult(false, e, null);
            }
        }
    }

    static Duration calculateConditionEvaluationDuration(Duration pollDelay, long pollingStarted) {
        return new Duration(System.nanoTime() - pollingStarted - pollDelay.getValueInNS(), NANOSECONDS);
    }
}


/*
 * Copyright 2015 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.awaitility.pollinterval;

import org.awaitility.Duration;
import org.awaitility.core.Function;

/**
 * A poll interval that is generated by a function and a start duration. The function is free to do anything with the duration.
 * For example:
 *

 * await().with().pollInterval(iterative(duration -> duration.multiply(2)), Duration.FIVE_HUNDRED_MILLISECONDS).until(..);
 *

 * This generates a poll interval sequence that looks like this (ms): 500, 1000, 2000, 4000, 8000, 16000, ...
 *

 * Note that if the user specifies a poll delay this delay will take place before the first call to {@link #next(int, Duration)}.
 */
public class IterativePollInterval implements PollInterval {

    private final Function<Duration, Duration> function;
    private final Duration startDuration;

    /**
     * Generate an iterative poll interval based on the supplied function.
     *
     * @param function The function to use.
     */
    public IterativePollInterval(Function<Duration, Duration> function) {
        this(function, null, false);
    }

    /**
     * Generate a iterative poll interval based on the supplied function and start duration.
     *
     * @param function      The function to use.
     * @param startDuration The start duration (initial function value)
     */
    public IterativePollInterval(Function<Duration, Duration> function, Duration startDuration) {
        this(function, startDuration, true);
    }

    /**
     * Generate a iterative poll interval based on the supplied function and start duration.
     *
     * @param function      The function to use.
     * @param startDuration The start duration (initial function value)
     */
    private IterativePollInterval(Function<Duration, Duration> function, Duration startDuration, boolean startDurationExplicitlyDefined) {
        if (function == null) {
            throw new IllegalArgumentException("Function<Duration, Duration> cannot be null");
        }
        if (startDurationExplicitlyDefined && startDuration == null) {
            throw new IllegalArgumentException("Start duration cannot be null");
        } else if (startDurationExplicitlyDefined && startDuration.isForever()) {
            throw new IllegalArgumentException("Cannot use a poll interval of length 'forever'");
        }
        this.function = function;
        this.startDuration = startDuration;
    }

    /**
     * Generate the next Duration based on the supplied function. If you've specified a start duration explicitly then
     * this start duration will override the value of previousDuration when pollCount is 1 (i.e. the poll delay).
     *
     * @param pollCount        The number of times the condition has been polled (evaluated). Always a positive integer.
     * @param previousDuration The duration of the previously returned poll interval.
     * @return The duration of the next poll interval
     */
    public Duration next(int pollCount, Duration previousDuration) {
        final Duration durationToUse;
        if (pollCount == 1 && startDuration != null) {
            durationToUse = startDuration;
        } else {
            durationToUse = previousDuration;
        }
        return function.apply(durationToUse);
    }

    /**
     * Syntactic sugar for creating a {@link IterativePollInterval}.
     *
     * @param function The function to use
     * @return A new instance of {@link IterativePollInterval}
     */
    public static IterativePollInterval iterative(Function<Duration, Duration> function) {
        return new IterativePollInterval(function);
    }

    /**
     * Syntactic sugar for creating a {@link IterativePollInterval}.
     *
     * @param function      The function to use
     * @param startDuration The start duration (initial function value)
     * @return A new instance of {@link IterativePollInterval}
     */
    public static IterativePollInterval iterative(Function<Duration, Duration> function, Duration startDuration) {
        return new IterativePollInterval(function, startDuration);
    }

    /**
     * Syntactic sugar
     *
     * @return The same of instance of {@link IterativePollInterval}
     */
    public IterativePollInterval with() {
        return this;
    }

    /**
     * Set the start duration of this poll interval
     *
     * @return A new of instance of {@link IterativePollInterval} with the given start duration
     */
    public IterativePollInterval startDuration(Duration duration) {
        return new IterativePollInterval(function, duration);
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof IterativePollInterval)) return false;

        IterativePollInterval that = (IterativePollInterval) o;

        return function.equals(that.function) && startDuration.equals(that.startDuration);

    }

    @Override
    public int hashCode() {
        int result = function.hashCode();
        result = 31 * result + startDuration.hashCode();
        return result;
    }

    @Override
    public String toString() {
        return "IterativePollInterval{" +
                "function=" + function +
                ", startDuration=" + startDuration +
                '}';
    }
}


/*
 * Copyright 2017 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.awaitility.proxy.internal;

import net.bytebuddy.implementation.bind.annotation.AllArguments;
import net.bytebuddy.implementation.bind.annotation.Origin;
import net.bytebuddy.implementation.bind.annotation.RuntimeType;

import java.lang.reflect.Method;

/**
 *
MethodCallRecorder class.

 */
public class MethodCallRecorder {

    private static final String NO_METHOD_CALL_RECORDED_MESSAGE = "No method call has been recorded. Perhaps the method was final?";
    private static final ThreadLocal<Object> lastTarget = new ThreadLocal<Object>();
    private static final ThreadLocal<Method> lastMethod = new ThreadLocal<Method>();
    private static final ThreadLocal<Object[]> lastArgs = new ThreadLocal<Object[]>();

    /**
     * Intercepts all methods executions in a proxied class feeding MethodCallRecorder.
     */
    public static class MethodCallInterceptor {
        @RuntimeType
        public static Object interceptExecutionDetails(@Origin Method method, @AllArguments Object[] args) {
            lastMethod.set(method);
            lastArgs.set(args);
            return TypeUtils.getDefaultValue(method.getReturnType());
        }
    }

    /**
     *
createProxy.

     *
     * @param target a {@link java.lang.Object} object.
     * @return a {@link java.lang.Object} object.
     */
    public static Object createProxy(Object target) {
        Object proxy = ByteBuddyProxyCreator.create(target.getClass(), MethodCallInterceptor.class);
        lastTarget.set(target);
        return proxy;
    }

    /**
     *
Getter for the field lastTarget.

     *
     * @return a {@link java.lang.Object} object.
     */
    static Object getLastTarget() {
        Object target = lastTarget.get();
        if (target == null) {
            throw new IllegalStateException(NO_METHOD_CALL_RECORDED_MESSAGE);
        }
        return target;
    }

    /**
     *
Getter for the field lastMethod.

     *
     * @return a {@link java.lang.reflect.Method} object.
     */
    static Method getLastMethod() {
        Method method = lastMethod.get();
        if (method == null) {
            throw new IllegalStateException(NO_METHOD_CALL_RECORDED_MESSAGE);
        }
        return method;
    }

    /**
     *
Getter for the field lastArgs.

     *
     * @return an array of {@link java.lang.Object} objects.
     */
    static Object[] getLastArgs() {
        Object target = lastTarget.get();
        if (target == null) {
            throw new IllegalStateException(NO_METHOD_CALL_RECORDED_MESSAGE);
        }
        return lastArgs.get();
    }

    /**
     *
reset.

     */
    static void reset() {
        lastTarget.remove();
        lastMethod.remove();
        lastArgs.remove();
    }
}


/*
 * Copyright 2009 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.awaitility.reflect;

import org.awaitility.reflect.exception.FieldNotFoundException;

import java.lang.reflect.Field;

class FieldNameAndTypeMatcherStrategy extends FieldMatcherStrategy {

    private final String fieldName;
    private final Class<?> fieldType;

    /**
     *
Constructor for FieldNameAndTypeMatcherStrategy.

     *
     * @param fieldName a {@link java.lang.String} object.
     * @param type a {@link java.lang.Class} object.
     */
    public FieldNameAndTypeMatcherStrategy(String fieldName, Class<?> type) {
        if (fieldName == null || fieldName.equals("") || fieldName.startsWith(" ")) {
            throw new IllegalArgumentException("field name cannot be null.");
        } else if (type == null) {
            throw new IllegalArgumentException("type cannot be null.");
        }
        this.fieldName = fieldName;
        this.fieldType = type;
    }

    /** {@inheritDoc} */
    @Override
    public boolean matches(Field field) {
        return fieldName.equals(field.getName()) && fieldType.equals(field.getType());
    }

    /** {@inheritDoc} */
    @Override
    public void notFound(Class<?> type, boolean isInstanceField) throws FieldNotFoundException {
        throw new FieldNotFoundException(String.format(
                "No %s field with name \"%s\" and type \"%s\" could be found in the class hierarchy of %s.",
                isInstanceField ? "instance" : "static", fieldName, fieldType.getName(), type.getName()));
    }

    /**
     *
toString.

     *
     * @return a {@link java.lang.String} object.
     */
    public String toString() {
        return "fieldName " + fieldName + ", fieldType = " + fieldType.getName();
    }
}


/*
 * Copyright 2017 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.awaitility.proxy;

import org.awaitility.proxy.internal.MethodCallRecorder;

public class AwaitilityClassProxy {

    /**
     * Await until a specific method invocation returns something. E.g.
     *

     *

     * await().until(callTo(service).getCount(), greaterThan(2));
     *

     *

     * Here we tell Awaitility to wait until the service.getCount()
     * method returns a value that is greater than 2.
     *
     * @param <S>    The type of the service.
     * @param object the object that contains the method of interest.
     * @return A proxy of the service
     */
    @SuppressWarnings("unchecked")
    public static <S> S to(S object) {
        return (S) MethodCallRecorder.createProxy(object);
    }
}


/*
 * Copyright 2010 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.awaitility.core;

import org.awaitility.reflect.WhiteboxImpl;
import org.awaitility.reflect.exception.FieldNotFoundException;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.concurrent.Callable;

/**
 * The field supplier builder allows you to create a supplier based a field.
 */
public class FieldSupplierBuilder {

    private final Object object;
    private String expectedFieldName;
    private Class<?> expectedFieldType;
    private Class<? extends Annotation> expectedAnnotation;

    /**
     *
Constructor for FieldSupplierBuilder.

     *
     * @param object a {@link java.lang.Object} object.
     */
    public FieldSupplierBuilder(Object object) {
        assertNotNullParameter(object, "Object passed to fieldIn");
        this.object = object;
    }

    /**
     * Find a field based on a type. E.g.
     *

     *
     * await().until(fieldIn(object).ofType(int.class), equalTo(2));
     *
     *

     * You can also specify the field more accurately by continuing the statement:
     * E.g.
     *
     * await().until(fieldIn(object).ofType(int.class).andWithName("fieldName"), equalTo(2));
     *
     *

     * or
     *

     *
     * await().until(fieldIn(object).ofType(int.class).andAnnotatedWith(MyAnnotation.class).andWithName("fieldName"), equalTo(2));
     *
     *
     * @param fieldType The type of the field.
     * @param <T>       The type of the field
     * @return The field supplier
     */
    public <T> NameAndAnnotationFieldSupplier<T> ofType(Class<T> fieldType) {
        this.expectedFieldType = fieldType;
        return new NameAndAnnotationFieldSupplier<T>();
    }

    public class NameFieldSupplier<T> implements Callable<T> {
        private final Field foundField;

        public NameFieldSupplier() {
            foundField = WhiteboxImpl.getFieldAnnotatedWith(object, expectedAnnotation);
            if (!foundField.getType().isAssignableFrom(expectedFieldType)) {
                throw new FieldNotFoundException(String.format(
                        "Couldn't find a field of type %s annotated with %s in %s.", expectedFieldType.getClass()
                                .getName(), expectedAnnotation.getClass().getName(), WhiteboxImpl.getType(object)
                                .getName()
                ));
            }
        }

        /**
         * Find a field based on the type and name. E.g.
         *

         *
         * await().until(fieldIn(object).ofType(int.class).andWithName("fieldName"), equalTo(2));
         *
         *
         * @param fieldName The name of the field
         * @return The supplier
         */
        public Callable<T> andWithName(final String fieldName) {
            assertNotNullParameter(fieldName, "fieldName");
            return new Callable<T>() {
                public T call() throws Exception {
                    return (T) WhiteboxImpl.getByNameAndType(object, fieldName, expectedFieldType);
                }
            };
        }

        public T call() throws Exception {
            return (T) (foundField == null ? WhiteboxImpl.getInternalState(object, expectedFieldType) : foundField
                    .get(object));
        }
    }

    public class NameAndAnnotationFieldSupplier<T> implements Callable<T> {
        /**
         * Find a field based on the type and name. E.g.
         *

         *
         * await().until(fieldIn(object).ofType(int.class).andWithName("fieldName"), equalTo(2));
         *
         *
         * @param fieldName The name of the field
         * @return The supplier
         */
        public AnnotationFieldSupplier<T> andWithName(final String fieldName) {
            assertNotNullParameter(fieldName, "fieldName");
            expectedFieldName = fieldName;
            return new AnnotationFieldSupplier<T>();
        }

        /**
         * Find a field based on the type and an annotation. E.g.
         *

         *
         * await().until(fieldIn(object).ofType(int.class).andAnnotatedWith(MyAnnotation.class), equalTo(2));
         *
         *
         * @param annotationType The name of the field
         * @return The supplier
         */
        public NameFieldSupplier<T> andAnnotatedWith(Class<? extends Annotation> annotationType) {
            assertNotNullParameter(annotationType, "annotationType");
            expectedAnnotation = annotationType;
            return new NameFieldSupplier<T>();
        }

        public T call() throws Exception {
            return (T) WhiteboxImpl.getInternalState(object, expectedFieldType);
        }
    }

    public class AnnotationFieldSupplier<T> implements Callable<T> {
        public AnnotationFieldSupplier() {
        }

        /**
         * Find a field based on a name, type and annotation. E.g.
         *

         *
         * await().until(fieldIn(object).ofType(int.class).andWithName("fieldName").andAnnotatedWith(MyAnnotation.class), equalTo(2));
         *
         *
         * @param annotationType The type of the annotation
         * @return The supplier
         */
        public Callable<T> andAnnotatedWith(final Class<? extends Annotation> annotationType) {
            assertNotNullParameter(annotationType, "annotationType");
            Field field = WhiteboxImpl.getFieldAnnotatedWith(object, annotationType);
            if (!field.getName().equals(expectedFieldName)) {
                throw new FieldNotFoundException(String.format(
                        "Couldn't find a field with name %s annotated with %s in %s.", expectedFieldName,
                        annotationType.getClass().getName(), WhiteboxImpl.getType(object).getName()));
            }
            expectedAnnotation = annotationType;
            return this;
        }

        public T call() throws Exception {
            return (T) WhiteboxImpl.getByNameAndType(object, expectedFieldName, expectedFieldType);
        }
    }

    public class NameAndTypeFieldSupplier<T> implements Callable<T> {
        /**
         * Find a field based on the annotation and field name. E.g.
         *

         *
         * await().until(fieldIn(object).annotatedWith(MyAnnotation.class).andWithName("fieldName"), equalTo(someObject));
         *
         *
         * @param fieldName The type of name of the field
         * @return The supplier
         */
        public AnnotationFieldSupplier<T> andWithName(String fieldName) {
            assertNotNullParameter(fieldName, "fieldName");
            FieldSupplierBuilder.this.expectedFieldName = fieldName;
            return new AnnotationFieldSupplier<T>();
        }

        /**
         * Find a field based on the annotation and field type. E.g.
         *

         *
         * await().until(fieldIn(object).annotatedWith(MyAnnotation.class).andOfType(int.class), equalTo(2));
         *
         *
         * @param type The class representing the type of the field
         * @param <S>  The type of the field
         * @return The supplier
         */
        public <S> NameAndAnnotationFieldSupplier<S> andOfType(Class<S> type) {
            assertNotNullParameter(type, "Expected field type");
            FieldSupplierBuilder.this.expectedFieldType = type;
            return new NameAndAnnotationFieldSupplier<S>();
        }

        @SuppressWarnings("rawtypes")
        public T call() throws Exception {
            return (T) WhiteboxImpl.getFieldAnnotatedWith(object, expectedAnnotation).get(object);
        }
    }

    private void assertNotNullParameter(Object parameterValue, String name) {
        if (parameterValue == null) {
            throw new IllegalArgumentException(name + " cannot be null");
        }
    }

    Object getObject() {
        return object;
    }

    String getExpectedFieldName() {
        return expectedFieldName;
    }

    Class<?> getExpectedFieldType() {
        return expectedFieldType;
    }

    Class<? extends Annotation> getExpectedAnnotation() {
        return expectedAnnotation;
    }
}


/*
 * Copyright 2010 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.awaitility.core;

import org.awaitility.Duration;
import org.awaitility.constraint.AtMostWaitConstraint;
import org.awaitility.constraint.WaitConstraint;
import org.awaitility.pollinterval.FixedPollInterval;
import org.awaitility.pollinterval.PollInterval;
import org.awaitility.spi.ProxyConditionFactory;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeMatcher;

import java.util.Iterator;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;

import static org.awaitility.classpath.ClassPathResolver.existInCP;
import static org.hamcrest.Matchers.anyOf;
import static org.hamcrest.Matchers.is;

/**
 * A factory for creating {@link org.awaitility.core.Condition} objects. It's not recommended to
 * instantiate this class directly.
 */
public class ConditionFactory {

    /**
     * Timing constraint.
     */
    private final WaitConstraint timeoutConstraint;

    /**
     * The poll interval.
     */
    private final PollInterval pollInterval;

    /**
     * The catch uncaught exceptions.
     */
    private final boolean catchUncaughtExceptions;

    /**
     * The ignore exceptions.
     */
    private final ExceptionIgnorer exceptionsIgnorer;

    /**
     * The alias.
     */
    private final String alias;

    /**
     * The poll delay.
     */
    private final Duration pollDelay;

    /**
     * The condition evaluation listener
     */
    private final ConditionEvaluationListener conditionEvaluationListener;

    /**
     * The executor lifecycle
     */
    private final ExecutorLifecycle executorLifecycle;

    /**
     * Instantiates a new condition factory.
     *
     * @param alias                         the alias
     * @param timeoutConstraint             the timeout constraint
     * @param pollInterval                  the poll interval
     * @param pollDelay                     The poll delay
     * @param catchUncaughtExceptions       the catch uncaught exceptions
     * @param exceptionsIgnorer             Determine which exceptions that should ignored
     * @param conditionEvaluationListener   Determine which exceptions that should ignored
     * @param executorLifecycle             The executor service and the lifecycle of the executor service that'll be used to evaluate the condition during polling
     */
    public ConditionFactory(final String alias, WaitConstraint timeoutConstraint, PollInterval pollInterval, Duration pollDelay,
                            boolean catchUncaughtExceptions, ExceptionIgnorer exceptionsIgnorer,
                            ConditionEvaluationListener conditionEvaluationListener, ExecutorLifecycle executorLifecycle) {
        if (pollInterval == null) {
            throw new IllegalArgumentException("pollInterval cannot be null");
        }
        if (timeoutConstraint == null) {
            throw new IllegalArgumentException("timeout cannot be null");
        }

        this.alias = alias;
        this.timeoutConstraint = timeoutConstraint;
        this.pollInterval = pollInterval;
        this.catchUncaughtExceptions = catchUncaughtExceptions;
        this.pollDelay = pollDelay;
        this.conditionEvaluationListener = conditionEvaluationListener;
        this.exceptionsIgnorer = exceptionsIgnorer;
        this.executorLifecycle = executorLifecycle;
    }

    /**
     * Handle condition evaluation results each time evaluation of a condition occurs. Works only with a Hamcrest matcher-based condition.
     *
     * @param conditionEvaluationListener the condition evaluation listener
     * @return the condition factory
     */
    public ConditionFactory conditionEvaluationListener(ConditionEvaluationListener conditionEvaluationListener) {
        return new ConditionFactory(alias, timeoutConstraint, pollInterval, pollDelay, catchUncaughtExceptions,
                exceptionsIgnorer, conditionEvaluationListener, executorLifecycle);
    }

    /**
     * Await at most timeout before throwing a timeout exception.
     *
     * @param timeout the timeout
     * @return the condition factory
     */
    public ConditionFactory timeout(Duration timeout) {
        return atMost(timeout);
    }

    /**
     * Await at most timeout before throwing a timeout exception.
     *
     * @param timeout the timeout
     * @return the condition factory
     */
    public ConditionFactory atMost(Duration timeout) {
        return new ConditionFactory(alias, timeoutConstraint.withMaxWaitTime(timeout), pollInterval, pollDelay,
                catchUncaughtExceptions, exceptionsIgnorer, conditionEvaluationListener, executorLifecycle);
    }

    /**
     * Set the alias
     *
     * @param alias alias
     * @return the condition factory
     * @see org.awaitility.Awaitility#await(String)
     */
    public ConditionFactory alias(String alias) {
        return new ConditionFactory(alias, timeoutConstraint, pollInterval, pollDelay,
                catchUncaughtExceptions, exceptionsIgnorer, conditionEvaluationListener, executorLifecycle);
    }

    /**
     * Condition has to be evaluated not earlier than timeout before throwing a timeout exception.
     *
     * @param timeout the timeout
     * @return the condition factory
     */
    public ConditionFactory atLeast(Duration timeout) {
        return new ConditionFactory(alias, timeoutConstraint.withMinWaitTime(timeout), pollInterval, pollDelay,
                catchUncaughtExceptions, exceptionsIgnorer, conditionEvaluationListener, executorLifecycle);
    }

    /**
     * Condition has to be evaluated not earlier than timeout before throwing a timeout exception.
     *
     * @param timeout the timeout
     * @param unit    the unit
     * @return the condition factory
     */
    public ConditionFactory atLeast(long timeout, TimeUnit unit) {
        return atLeast(new Duration(timeout, unit));
    }

    /**
     * Specifies the duration window which has to be satisfied during operation execution. In case operation is executed
     * before atLeast or after atMost timeout exception is thrown.
     *
     * @param atLeast lower part of execution window
     * @param atMost  upper part of execution window
     * @return the condition factory
     */
    public ConditionFactory between(Duration atLeast, Duration atMost) {
        return atLeast(atLeast).and().atMost(atMost);
    }

    /**
     * Specifies the duration window which has to be satisfied during operation execution. In case operation is executed
     * before atLeastDuration or after atMostDuration timeout exception is thrown.
     *
     * @param atLeastDuration lower part of execution window
     * @param atMostDuration  upper part of execution window
     * @return the condition factory
     */
    public ConditionFactory between(long atLeastDuration, TimeUnit atLeastTimeUnit, long atMostDuration, TimeUnit atMostTimeUnit) {
        return between(new Duration(atLeastDuration, atLeastTimeUnit), new Duration(atMostDuration, atMostTimeUnit));
    }

    /**
     * Await forever until the condition is satisfied. Caution: You can block
     * subsequent tests and the entire build can hang indefinitely, it's
     * recommended to always use a timeout.
     *
     * @return the condition factory
     */
    public ConditionFactory forever() {
        return new ConditionFactory(alias, AtMostWaitConstraint.FOREVER, pollInterval, pollDelay,
                catchUncaughtExceptions, exceptionsIgnorer, conditionEvaluationListener, executorLifecycle);
    }

    /**
     * Specify the polling interval Awaitility will use for this await
     * statement. This means the frequency in which the condition is checked for
     * completion.
     *

     * Note that the poll delay will be automatically set as to the same value
     * as the interval (if using a {@link FixedPollInterval}) unless it's specified explicitly using
     * {@link #pollDelay(Duration)}, {@link #pollDelay(long, TimeUnit)} or
     * {@link org.awaitility.core.ConditionFactory#pollDelay(org.awaitility.Duration)}.
     *

     *
     * @param pollInterval the poll interval
     * @return the condition factory
     */
    public ConditionFactory pollInterval(Duration pollInterval) {
        return new ConditionFactory(alias, timeoutConstraint, new FixedPollInterval(pollInterval), pollDelay, catchUncaughtExceptions,
                exceptionsIgnorer, conditionEvaluationListener, executorLifecycle);
    }

    /**
     * Await at most timeout before throwing a timeout exception.
     *
     * @param timeout the timeout
     * @param unit    the unit
     * @return the condition factory
     */
    public ConditionFactory timeout(long timeout, TimeUnit unit) {
        return atMost(timeout, unit);
    }

    /**
     * Specify the delay that will be used before Awaitility starts polling for
     * the result the first time. If you don't specify a poll delay explicitly
     * it'll be the same as the poll interval.
     *
     * @param delay the delay
     * @param unit  the unit
     * @return the condition factory
     */
    public ConditionFactory pollDelay(long delay, TimeUnit unit) {
        return new ConditionFactory(alias, timeoutConstraint, pollInterval, new Duration(delay, unit),
                catchUncaughtExceptions, exceptionsIgnorer, conditionEvaluationListener, executorLifecycle);
    }

    /**
     * Specify the delay that will be used before Awaitility starts polling for
     * the result the first time. If you don't specify a poll delay explicitly
     * it'll be the same as the poll interval.
     *
     * @param pollDelay the poll delay
     * @return the condition factory
     */
    public ConditionFactory pollDelay(Duration pollDelay) {
        if (pollDelay == null) {
            throw new IllegalArgumentException("pollDelay cannot be null");
        }
        return new ConditionFactory(alias, timeoutConstraint, pollInterval, pollDelay, catchUncaughtExceptions,
                exceptionsIgnorer, conditionEvaluationListener, executorLifecycle);
    }

    /**
     * Await at most timeout before throwing a timeout exception.
     *
     * @param timeout the timeout
     * @param unit    the unit
     * @return the condition factory
     */
    public ConditionFactory atMost(long timeout, TimeUnit unit) {
        return atMost(new Duration(timeout, unit));
    }

    /**
     * Specify the polling interval Awaitility will use for this await
     * statement. This means the frequency in which the condition is checked for
     * completion.
     *

     * Note that the poll delay will be automatically set as to the same value
     * as the interval unless it's specified explicitly using
     * {@link #pollDelay(Duration)}, {@link #pollDelay(long, TimeUnit)} or
     * {@link org.awaitility.core.ConditionFactory#pollDelay(org.awaitility.Duration)} , or
     * ConditionFactory#andWithPollDelay(long, TimeUnit)}. This is the same as creating a {@link FixedPollInterval}.
     *
     * @param pollInterval the poll interval
     * @param unit         the unit
     * @return the condition factory
     * @see FixedPollInterval
     */
    public ConditionFactory pollInterval(long pollInterval, TimeUnit unit) {
        PollInterval fixedPollInterval = new FixedPollInterval(new Duration(pollInterval, unit));
        return new ConditionFactory(alias, timeoutConstraint, fixedPollInterval, definePollDelay(pollDelay, fixedPollInterval),
                catchUncaughtExceptions, exceptionsIgnorer, conditionEvaluationListener, executorLifecycle);
    }

    public ConditionFactory pollInterval(PollInterval pollInterval) {
        return new ConditionFactory(alias, timeoutConstraint, pollInterval, definePollDelay(pollDelay, pollInterval), catchUncaughtExceptions,
                exceptionsIgnorer, conditionEvaluationListener, executorLifecycle);
    }

    /**
     * Instruct Awaitility to catch uncaught exceptions from other threads. This
     * is useful in multi-threaded systems when you want your test to fail
     * regardless of which thread throwing the exception. Default is
     * true.
     *
     * @return the condition factory
     */
    public ConditionFactory catchUncaughtExceptions() {
        return new ConditionFactory(alias, timeoutConstraint, pollInterval, pollDelay, true, exceptionsIgnorer,
                conditionEvaluationListener, executorLifecycle);
    }

    /**
     * Instruct Awaitility to ignore exceptions instance of the supplied exceptionType type.
     * Exceptions will be treated as evaluating to false.
     * This is useful in situations where the evaluated conditions may temporarily throw exceptions.
     *

     *
If you want to ignore a specific exceptionType then use {@link #ignoreException(Class)}

     *
     * @param exceptionType The exception type (hierarchy) to ignore
     * @return the condition factory
     */
    public ConditionFactory ignoreExceptionsInstanceOf(final Class<? extends Throwable> exceptionType) {
        if (exceptionType == null) {
            throw new IllegalArgumentException("exceptionType cannot be null");
        }
        return new ConditionFactory(alias, timeoutConstraint, pollInterval, pollDelay, catchUncaughtExceptions,
                new PredicateExceptionIgnorer(new Predicate<Throwable>() {
                    public boolean matches(Throwable e) {
                        return exceptionType.isAssignableFrom(e.getClass());
                    }
                }),
                conditionEvaluationListener, executorLifecycle);
    }

    /**
     * Instruct Awaitility to ignore a specific exception and no subclasses of this exception.
     * Exceptions will be treated as evaluating to false.
     * This is useful in situations where the evaluated conditions may temporarily throw exceptions.
     *
If you want to ignore a subtypes of this exception then use {@link #ignoreExceptionsInstanceOf(Class)}}

     *
     * @param exceptionType The exception type to ignore
     * @return the condition factory
     */
    public ConditionFactory ignoreException(final Class<? extends Throwable> exceptionType) {
        if (exceptionType == null) {
            throw new IllegalArgumentException("exception cannot be null");
        }
        return new ConditionFactory(alias, timeoutConstraint, pollInterval, pollDelay, catchUncaughtExceptions,
                new PredicateExceptionIgnorer(new Predicate<Throwable>() {
                    public boolean matches(Throwable e) {
                        return e.getClass().equals(exceptionType);
                    }
                }),
                conditionEvaluationListener, executorLifecycle);
    }

    /**
     * Instruct Awaitility to ignore all exceptions that occur during evaluation.
     * Exceptions will be treated as evaluating to
     * false. This is useful in situations where the evaluated
     * conditions may temporarily throw exceptions.
     *
     * @return the condition factory.
     */
    public ConditionFactory ignoreExceptions() {
        return ignoreExceptionsMatching(new Predicate<Throwable>() {
            public boolean matches(Throwable e) {
                return true;
            }
        });
    }

    /**
     * Instruct Awaitility to not ignore any exceptions that occur during evaluation.
     * This is only useful if Awaitility is configured to ignore exceptions by default but you want to
     * have a different behavior for a single test case.
     *
     * @return the condition factory.
     */
    public ConditionFactory ignoreNoExceptions() {
        return ignoreExceptionsMatching(new Predicate<Throwable>() {
            public boolean matches(Throwable e) {
                return false;
            }
        });
    }

    /**
     * Instruct Awaitility to ignore exceptions that occur during evaluation and matches the supplied Hamcrest matcher.
     * Exceptions will be treated as evaluating to
     * false. This is useful in situations where the evaluated conditions may temporarily throw exceptions.
     *
     * @return the condition factory.
     */
    public ConditionFactory ignoreExceptionsMatching(Matcher<? super Throwable> matcher) {
        return new ConditionFactory(alias, timeoutConstraint, pollInterval, pollDelay, catchUncaughtExceptions,
                new HamcrestExceptionIgnorer(matcher), conditionEvaluationListener, executorLifecycle);
    }

    /**
     * Instruct Awaitility to ignore exceptions that occur during evaluation and matches the supplied predicate.
     * Exceptions will be treated as evaluating to
     * false. This is useful in situations where the evaluated conditions may temporarily throw exceptions.
     *
     * @return the condition factory.
     */
    public ConditionFactory ignoreExceptionsMatching(Predicate<? super Throwable> predicate) {
        return new ConditionFactory(alias, timeoutConstraint, pollInterval, pollDelay, catchUncaughtExceptions,
                new PredicateExceptionIgnorer(predicate), conditionEvaluationListener, executorLifecycle);
    }

    /**
     * Await for an asynchronous operation. This method returns the same
     * {@link org.awaitility.core.ConditionFactory} instance and is used only to get a more
     * fluent-like syntax.
     *
     * @return the condition factory
     */
    public ConditionFactory await() {
        return this;
    }

    /**
     * Await for an asynchronous operation and give this await instance a
     * particular name. This is useful in cases when you have several await
     * statements in one test and you want to know which one that fails (the
     * alias will be shown if a timeout exception occurs).
     *
     * @param alias the alias
     * @return the condition factory
     */
    public ConditionFactory await(String alias) {
        return new ConditionFactory(alias, timeoutConstraint, pollInterval, pollDelay, catchUncaughtExceptions,
                exceptionsIgnorer, conditionEvaluationListener, executorLifecycle);
    }

    /**
     * A method to increase the readability of the Awaitility DSL. It simply
     * returns the same condition factory instance.
     *
     * @return the condition factory
     */
    public ConditionFactory and() {
        return this;
    }

    /**
     * A method to increase the readability of the Awaitility DSL. It simply
     * returns the same condition factory instance.
     *
     * @return the condition factory
     */
    public ConditionFactory with() {
        return this;
    }

    /**
     * A method to increase the readability of the Awaitility DSL. It simply
     * returns the same condition factory instance.
     *
     * @return the condition factory
     */
    public ConditionFactory then() {
        return this;
    }

    /**
     * A method to increase the readability of the Awaitility DSL. It simply
     * returns the same condition factory instance.
     *
     * @return the condition factory
     */
    public ConditionFactory given() {
        return this;
    }

    /**
     * Don't catch uncaught exceptions in other threads. This will not
     * make the await statement fail if exceptions occur in other threads.
     *
     * @return the condition factory
     */
    public ConditionFactory dontCatchUncaughtExceptions() {
        return new ConditionFactory(alias, timeoutConstraint, pollInterval, pollDelay, false,
                exceptionsIgnorer, conditionEvaluationListener, executorLifecycle);
    }

    /**
     * Specify the executor service whose threads will be used to evaluate the poll condition in Awaitility.
     * Note that the executor service must be shutdown manually!
     *
     * This is an advanced feature and it should only be used sparingly.
     *
     * @param executorService The executor service that Awaitility will use when polling condition evaluations
     * @return the condition factory
     */
    public ConditionFactory pollExecutorService(ExecutorService executorService) {
        if (executorService != null && executorService instanceof ScheduledExecutorService) {
            throw new IllegalArgumentException("Poll executor service cannot be an instance of " + ScheduledExecutorService.class.getName());
        }
        return new ConditionFactory(alias, timeoutConstraint, pollInterval, pollDelay, false,
                exceptionsIgnorer, conditionEvaluationListener, ExecutorLifecycle.withoutCleanup(executorService));
    }

    /**
     * Specify a thread supplier whose thread will be used to evaluate the poll condition in Awaitility.
     * The supplier will be called only once and the thread it returns will be reused during all condition evaluations.
     * This is an advanced feature and it should only be used sparingly.
     *
     * @param threadSupplier A supplier of the thread that Awaitility will use when polling
     * @return the condition factory
     */
    public ConditionFactory pollThread(final Function<Runnable, Thread> threadSupplier) {
        return new ConditionFactory(alias, timeoutConstraint, pollInterval, pollDelay, false,
                exceptionsIgnorer, conditionEvaluationListener, ExecutorLifecycle.withNormalCleanupBehavior(new Supplier<ExecutorService>() {
            @Override
            public ExecutorService get() {
                return InternalExecutorServiceFactory.create(threadSupplier);
            }
        }));
    }

    /**
     * Instructs Awaitility to execute the polling of the condition from the same as the test.
     * This is an advanced feature and you should be careful when combining this with conditions that
     * wait forever (or a long time) since Awaitility cannot interrupt the thread when it's using the same
     * thread as the test. For safety you should always combine tests using this feature with a test framework specific timeout,
     * for example in JUnit:
     *

     * @Test(timeout = 2000L)
     * public void myTest() {
     *     Awaitility.pollInSameThread();
     *     await().forever().until(...);
     * }
     *

     * @return the condition factory
     */
    public ConditionFactory pollInSameThread() {
        return new ConditionFactory(alias, timeoutConstraint, pollInterval, pollDelay, false,
                exceptionsIgnorer, conditionEvaluationListener, ExecutorLifecycle.withNormalCleanupBehavior(new Supplier<ExecutorService>() {
            @Override
            public ExecutorService get() {
                return InternalExecutorServiceFactory.sameThreadExecutorService();
            }
        }));
    }

    /**
     * Specify the condition that must be met when waiting for a method call.
     * E.g.
     *

     *

     * await().untilCall(to(orderService).size(), is(greaterThan(2)));
     *

     *
     * @param <T>     the generic type
     * @param proxyMethodReturnValue  the return value of the method call
     * @param matcher The condition that must be met when
     * @return a T object.
     * @throws org.awaitility.core.ConditionTimeoutException If condition was not fulfilled within the given time period.
     */
    public <T> T untilCall(T proxyMethodReturnValue, final Matcher<? super T> matcher) {
        if (!existInCP("java.util.ServiceLoader")) {
            throw new UnsupportedOperationException("java.util.ServiceLoader not found in classpath so cannot create condition");
        }
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        if (cl == null) {
            cl = ClassLoader.getSystemClassLoader();
        }
        Iterator<ProxyConditionFactory> iterator = java.util.ServiceLoader.load(ProxyConditionFactory.class, cl).iterator();
        if (!iterator.hasNext()) {
            throw new UnsupportedOperationException("There's currently no plugin installed that can handle proxy conditions, please consider adding 'awaitility-proxy' to the classpath. If using Maven you can do:" +
                    "<dependency>\n" +
                    "\t<groupId>org.awaitility</groupId>\n" +
                    "\t<artifactId>awaitility-proxy</artifactId>\n" +
                    "\t<version>${awaitility.version}</version>\n" +
                    "</dependency>\n");
        }
        @SuppressWarnings("unchecked") ProxyConditionFactory<T> factory = iterator.next();
        if (factory == null) {
            throw new IllegalArgumentException("Internal error: Proxy condition plugin initialization returned null, please report an issue.");
        }
        return until(factory.createProxyCondition(proxyMethodReturnValue, matcher, generateConditionSettings()));
    }

    /**
     * Await until a {@link java.util.concurrent.Callable} supplies a value matching the specified
     * {@link org.hamcrest.Matcher}. E.g.
     *

     *

     * await().until(numberOfPersons(), is(greaterThan(2)));
     *

     *

     * where "numberOfPersons()" returns a standard {@link java.util.concurrent.Callable}:
     *

     *

     * private Callable<Integer> numberOfPersons() {
     * 	return new Callable<Integer>() {
     * 		public Integer call() {
     * 			return personRepository.size();
     *        }
     *    };
     * }
     *

     *

     * Using a generic {@link java.util.concurrent.Callable} as done by using this version of "until"
     * allows you to reuse the "numberOfPersons()" definition in multiple await
     * statements. I.e. you can easily create another await statement (perhaps
     * in a different test case) using e.g.
     *

     *

     * await().until(numberOfPersons(), is(equalTo(6)));
     *

     *
     * @param <T>      the generic type
     * @param supplier the supplier that is responsible for getting the value that
     *                 should be matched.
     * @param matcher  the matcher The hamcrest matcher that checks whether the
     *                 condition is fulfilled.
     * @return a T object.
     * @throws org.awaitility.core.ConditionTimeoutException If condition was not fulfilled within the given time period.
     */
    public <T> T until(final Callable<T> supplier, final Matcher<? super T> matcher) {
        return until(new CallableHamcrestCondition<T>(supplier, matcher, generateConditionSettings()));
    }

    /**
     * Wait until the given supplier matches the supplied predicate. For example:
     *
     *

     * await().until(myRepository::count, cnt -> cnt == 2);
     *

     *
     * @param supplier The supplier that returns the object that will be evaluated by the predicate.
     * @param predicate The predicate that must match
     * @param <T> the generic type
     * @since 3.1.1
     * @return a T object.
     */
    public <T> T until(final Callable<T> supplier, final Predicate<? super T> predicate) {
        return until(supplier, new TypeSafeMatcher<T>() {
            @Override
            protected void describeMismatchSafely(T item, Description description) {
                description.appendText("it returned <false> for input of ").appendValue(item);
            }

            @Override
            public void describeTo(Description description) {
                description.appendText("the predicate to return <true>");
            }

            @Override
            protected boolean matchesSafely(T item) {
                return predicate.matches(item);
            }
        });
    }

    /**
     * Await until a {@link java.lang.Runnable} supplier execution passes (ends without throwing an exception). E.g. with Java 8:
     *

     *

     * await().untilAsserted(() -> Assertions.assertThat(personRepository.size()).isEqualTo(6));
     *

     * or
     *

     * await().untilAsserted(() -> assertEquals(6, personRepository.size()));
     *

     *

     * This method is intended to benefit from lambda expressions introduced in Java 8. It allows to use standard AssertJ/FEST Assert assertions
     * (by the way also standard JUnit/TestNG assertions) to test asynchronous calls and systems.
     *

     * {@link java.lang.AssertionError} instances thrown by the supplier are treated as an assertion failure and proper error message is propagated on timeout.
     * Other exceptions are rethrown immediately as an execution errors.
     *

     * Why technically it is completely valid to use plain Runnable class in Java 7 code, the resulting expression is very verbose and can decrease
     * the readability of the test case, e.g.
     *

     *

     * await().untilAsserted(new Runnable() {
     *     public void run() {
     *         Assertions.assertThat(personRepository.size()).isEqualTo(6);
     *     }
     * });
     *

     *

     * NOTE:

     * Be VERY careful so that you're not using this method incorrectly in languages (like Kotlin and Groovy) that doesn't
     * disambiguate between a {@link ThrowingRunnable} that doesn't return anything (void) and {@link Callable} that returns a value.
     * For example in Kotlin you can do like this:
     *

     *

     * await().untilAsserted { true == false }
     *

     * and the compiler won't complain with an error (as is the case in Java). If you were to execute this test in Kotlin it'll pass!
     *
     * @param assertion the supplier that is responsible for executing the assertion and throwing AssertionError on failure.
     * @throws org.awaitility.core.ConditionTimeoutException If condition was not fulfilled within the given time period.
     * @since 1.6.0
     */
    public void untilAsserted(final ThrowingRunnable assertion) {
        until(new AssertionCondition(assertion, generateConditionSettings()));
    }

    /**
     * Await until a Atomic variable has a value matching the specified
     * {@link org.hamcrest.Matcher}. E.g.
     *

     *

     * await().untilAtomic(myAtomic, is(greaterThan(2)));
     *

     *
     * @param atomic  the atomic variable
     * @param matcher the matcher The hamcrest matcher that checks whether the
     *                condition is fulfilled.
     * @return a {@link java.lang.Integer} object.
     * @throws org.awaitility.core.ConditionTimeoutException If condition was not fulfilled within the given time period.
     */
    public Integer untilAtomic(final AtomicInteger atomic, final Matcher<? super Integer> matcher) {
        return until(new CallableHamcrestCondition<Integer>(new Callable<Integer>() {
            public Integer call() {
                return atomic.get();
            }
        }, matcher, generateConditionSettings()));
    }

    /**
     * Await until a Atomic variable has a value matching the specified
     * {@link org.hamcrest.Matcher}. E.g.
     *

     *

     * await().untilAtomic(myAtomic, is(greaterThan(2)));
     *

     *
     * @param atomic  the atomic variable
     * @param matcher the matcher The hamcrest matcher that checks whether the
     *                condition is fulfilled.
     * @return a {@link java.lang.Long} object.
     * @throws org.awaitility.core.ConditionTimeoutException If condition was not fulfilled within the given time period.
     */
    public Long untilAtomic(final AtomicLong atomic, final Matcher<? super Long> matcher) {
        return until(new CallableHamcrestCondition<Long>(new Callable<Long>() {
            public Long call() {
                return atomic.get();
            }
        }, matcher, generateConditionSettings()));
    }

    /**
     * Await until a Atomic variable has a value matching the specified
     * {@link org.hamcrest.Matcher}. E.g.
     *

     *

     * await().untilAtomic(myAtomic, is(greaterThan(2)));
     *

     *
     * @param atomic  the atomic variable
     * @param matcher the matcher The hamcrest matcher that checks whether the
     *                condition is fulfilled.
     * @throws org.awaitility.core.ConditionTimeoutException If condition was not fulfilled within the given time period.
     */
    public void untilAtomic(final AtomicBoolean atomic, final Matcher<? super Boolean> matcher) {
        until(new CallableHamcrestCondition<Boolean>(new Callable<Boolean>() {
            public Boolean call() {
                return atomic.get();
            }
        }, matcher, generateConditionSettings()));
    }

    /**
     * Await until a Atomic boolean becomes true.
     *
     * @param atomic the atomic variable
     * @throws org.awaitility.core.ConditionTimeoutException If condition was not fulfilled within the given time period.
     */
    public void untilTrue(final AtomicBoolean atomic) {
        untilAtomic(atomic, anyOf(is(Boolean.TRUE), is(true)));
    }

    /**
     * Await until a Atomic boolean becomes false.
     *
     * @param atomic the atomic variable
     * @throws org.awaitility.core.ConditionTimeoutException If condition was not fulfilled within the given time period.
     */
    public void untilFalse(final AtomicBoolean atomic) {
        untilAtomic(atomic, anyOf(is(Boolean.FALSE), is(false)));
    }

    /**
     * Await until a Atomic variable has a value matching the specified
     * {@link org.hamcrest.Matcher}. E.g.
     *

     *

     * await().untilAtomic(myAtomic, is(greaterThan(2)));
     *

     *
     * @param atomic  the atomic variable
     * @param matcher the matcher The hamcrest matcher that checks whether the
     *                condition is fulfilled.
     * @param <V>     a V object.
     * @return a V object.
     * @throws org.awaitility.core.ConditionTimeoutException If condition was not fulfilled within the given time period.
     */
    public <V> V untilAtomic(final AtomicReference<V> atomic, final Matcher<? super V> matcher) {
        return until(new CallableHamcrestCondition<V>(new Callable<V>() {
            public V call() {
                return atomic.get();
            }
        }, matcher, generateConditionSettings()));
    }

    /**
     * Await until a {@link java.util.concurrent.Callable} returns true. This is method
     * is not as generic as the other variants of "until" but it allows for a
     * more precise and in some cases even more english-like syntax. E.g.
     *

     *

     * await().until(numberOfPersonsIsEqualToThree());
     *

     *

     * where "numberOfPersonsIsEqualToThree()" returns a standard
     * {@link java.util.concurrent.Callable} of type {@link java.lang.Boolean}:
     *

     *

     * private Callable<Boolean> numberOfPersons() {
     * 	return new Callable<Boolean>() {
     * 		public Boolean call() {
     * 			return personRepository.size() == 3;
     *        }
     *    };
     * }
     *

     *
     * @param conditionEvaluator the condition evaluator
     * @throws org.awaitility.core.ConditionTimeoutException If condition was not fulfilled within the given time period.
     */
    public void until(Callable<Boolean> conditionEvaluator) {
        until(new CallableCondition(conditionEvaluator, generateConditionSettings()));
    }

    private ConditionSettings generateConditionSettings() {
        Duration actualPollDelay = definePollDelay(pollDelay, pollInterval);

        if (actualPollDelay.isForever()) {
            throw new IllegalArgumentException("Cannot delay polling forever");
        }

        Duration timeout = timeoutConstraint.getMaxWaitTime();
        final long timeoutInMS = timeout.getValueInMS();
        if (!timeout.isForever() && timeoutInMS <= actualPollDelay.getValueInMS()) {
            throw new IllegalStateException(String.format("Timeout (%s %s) must be greater than the poll delay (%s %s).",
                    timeout.getValue(), timeout.getTimeUnitAsString(), actualPollDelay.getValue(), actualPollDelay.getTimeUnitAsString()));
        } else if ((!actualPollDelay.isForever() && !timeout.isForever()) && timeoutInMS <= actualPollDelay.getValueInMS()) {
            throw new IllegalStateException(String.format("Timeout (%s %s) must be greater than the poll delay (%s %s).",
                    timeout.getValue(), timeout.getTimeUnitAsString(), actualPollDelay.getValue(), actualPollDelay.getTimeUnitAsString()));
        }

        ExecutorLifecycle executorLifecycle;
        if (this.executorLifecycle == null) {
            executorLifecycle = ExecutorLifecycle.withNormalCleanupBehavior(new Supplier<ExecutorService>() {
                @Override
                public ExecutorService get() {
                    return InternalExecutorServiceFactory.create(new BiFunction<Runnable, String, Thread>() {
                        @Override
                        public Thread apply(Runnable r, String threadName) {
                            return new Thread(Thread.currentThread().getThreadGroup(), r, threadName);
                        }
                    }, alias);
                }
            });
        } else {
            executorLifecycle = this.executorLifecycle;
        }

        return new ConditionSettings(alias, catchUncaughtExceptions, timeoutConstraint, pollInterval, actualPollDelay,
                conditionEvaluationListener, exceptionsIgnorer, executorLifecycle);
    }

    private <T> T until(Condition<T> condition) {
        return condition.await();
    }

    /**
     * Ensures backward compatibility (especially that poll delay is the same as poll interval for fixed poll interval).
     * It also make sure that poll delay is {@link Duration#ZERO} for all other poll intervals if poll delay was not explicitly
     * defined. If poll delay was explicitly defined the it will just be returned.
     *
     * @param pollDelay    The poll delay
     * @param pollInterval The chosen (or default) poll interval
     * @return The poll delay to use
     */
    private Duration definePollDelay(Duration pollDelay, PollInterval pollInterval) {
        final Duration pollDelayToUse;
        // If a poll delay is null then a poll delay has not been explicitly defined by the user
        if (pollDelay == null) {
            if (pollInterval != null && pollInterval instanceof FixedPollInterval) {
                pollDelayToUse = pollInterval.next(1, Duration.ZERO); // Will return same poll delay as poll interval
            } else {
                pollDelayToUse = Duration.ZERO; // Default poll delay for non-fixed poll intervals
            }
        } else {
            // Poll delay was explicitly defined, use it!
            pollDelayToUse = pollDelay;
        }
        return pollDelayToUse;
    }
}


/*
 * Copyright 2016 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.awaitility;

import org.awaitility.constraint.AtMostWaitConstraint;
import org.awaitility.constraint.WaitConstraint;
import org.awaitility.core.*;
import org.awaitility.pollinterval.FixedPollInterval;
import org.awaitility.pollinterval.PollInterval;
import org.hamcrest.Matcher;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;

import static org.awaitility.Duration.ONE_HUNDRED_MILLISECONDS;

/**
 * Awaitility is a small Java DSL for synchronizing (waiting for) asynchronous
 * operations. It makes it easy to test asynchronous code. Examples:
 *

 * Wait at most 5 seconds until customer status has been updated:
 *

 *

 * await().atMost(5, SECONDS).until(customerStatusHasUpdated());
 *

 *

 * Wait forever until the call to orderService.orderCount() is
 * greater than 3.
 *

 *

 * await().forever().untilCall(to(orderService).orderCount(), greaterThan(3));
 *

 *

 * Wait 300 milliseconds until field in object myObject with name
 * fieldName and of type int.class is equal to 4.
 *

 *

 * await().atMost(300, MILLISECONDS).until(fieldIn(orderService).withName("fieldName").andOfType(int.class), equalTo(3));
 *

 *

 * Advanced usage: Use a poll interval of 100 milliseconds with an initial delay
 * of 20 milliseconds until customer status is equal to "REGISTERED". This
 * example also uses a named await by specifying an alias
 * ("customer registration"). This makes it easy to find out which await
 * statement that failed if you have multiple awaits in the same test.
 *

 *

 * with().pollInterval(ONE_HUNDERED_MILLISECONDS).and().with().pollDelay(20, MILLISECONDS).await("customer registration")
 *         .until(customerStatus(), equalTo(REGISTERED));
 *

 *

 * You can also specify a default timeout, poll interval and poll delay using:
 *

 *

 * Awaitility.setDefaultTimeout(..)
 * Awaitility.setDefaultPollInterval(..)
 * Awaitility.setDefaultPollDelay(..)
 *

 *

 * You can also reset to the default values using {@link org.awaitility.Awaitility#reset()}.
 * In order to use Awaitility effectively it's recommended to statically import
 * the following methods from the Awaitility framework:
 *

 *

 *
org.awaitility.Awaitlity.*

 *

 * It may also be useful to import these methods:
 *

 *
org.awaitility.Duration.*

 *
java.util.concurrent.TimeUnit.*

 *
org.hamcrest.Matchers.*

 *
org.junit.Assert.*

 *

 *

 * A word on poll interval and poll delay: Awaitility starts to check the
 * specified condition (the one you create using the Awaitility DSL) matches for
 * the first time after a "poll delay" (the initial delay before the polling
 * begins). By default Awaitility uses the same poll delay as poll interval
 * which means that it checks the condition periodically first after the given
 * poll delay, and subsequently with the given poll interval; that is conditions
 * are checked after pollDelay then pollDelay+pollInterval, then pollDelay + 2
 * pollInterval, and so on.

 * Note: If you change the poll interval the poll delay will also change
 * to match the specified poll interval unless you've specified a poll
 * delay explicitly.
 *

 * Note that since Awaitility uses polling to verify that a condition matches
 * it's not intended to use it for precise performance testing.
 *

 * IMPORTANT: Awaitility does nothing to ensure thread safety or thread
 * synchronization! This is your responsibility! Make sure your code is correctly
 * synchronized or that you are using thread safe data structures such as volatile
 * fields or classes such as AtomicInteger and ConcurrentHashMap.
 */
public class Awaitility {

    private static final Duration DEFAULT_POLL_DELAY = null;

    private static final PollInterval DEFAULT_POLL_INTERVAL = new FixedPollInterval(ONE_HUNDRED_MILLISECONDS);

    /**
     * The default poll interval (fixed 100 ms).
     */
    private static volatile PollInterval defaultPollInterval = DEFAULT_POLL_INTERVAL;

    /**
     * The default wait constraint (10 seconds).
     */
    private static volatile WaitConstraint defaultWaitConstraint = AtMostWaitConstraint.TEN_SECONDS;

    /**
     * The default poll delay
     */
    private static volatile Duration defaultPollDelay = DEFAULT_POLL_DELAY;

    /**
     * Catch all uncaught exceptions by default?
     */
    private static volatile boolean defaultCatchUncaughtExceptions = true;

    /**
     * Ignore caught exceptions by default?
     */
    private static volatile ExceptionIgnorer defaultExceptionIgnorer = new PredicateExceptionIgnorer(new Predicate<Throwable>() {
        public boolean matches(Throwable e) {
            return false;
        }
    });

    /**
     * Default listener of condition evaluation results.
     */
    private static volatile ConditionEvaluationListener defaultConditionEvaluationListener = null;

    /**
     * Default condition evaluation executor service.
     */
    private static volatile ExecutorLifecycle defaultExecutorLifecycle = null;

    /**
     * Instruct Awaitility to catch uncaught exceptions from other threads by
     * default. This is useful in multi-threaded systems when you want your test
     * to fail regardless of which thread throwing the exception. Default is
     * true.
     */
    public static void catchUncaughtExceptionsByDefault() {
        defaultCatchUncaughtExceptions = true;
    }

    /**
     * Instruct Awaitility not to catch uncaught exceptions from other threads.
     * Your test will not fail if another thread throws an exception.
     */
    public static void doNotCatchUncaughtExceptionsByDefault() {
        defaultCatchUncaughtExceptions = false;
    }

    /**
     * Instruct Awaitility to ignore caught or uncaught exceptions during condition evaluation.
     * Exceptions will be treated as evaluating to false. Your test will not fail
     * upon an exception, unless it times out.
     */
    public static void ignoreExceptionsByDefault() {
        defaultExceptionIgnorer = new PredicateExceptionIgnorer(new Predicate<Throwable>() {
            public boolean matches(Throwable e) {
                return true;
            }
        });
    }

    /**
     * Instruct Awaitility to ignore caught exception of the given type during condition evaluation.
     * Exceptions will be treated as evaluating to false. Your test will not fail
     * upon an exception matching the supplied exception type, unless it times out.
     */
    public static void ignoreExceptionByDefault(final Class<? extends Throwable> exceptionType) {
        defaultExceptionIgnorer = new PredicateExceptionIgnorer(new Predicate<Throwable>() {
            public boolean matches(Throwable e) {
                return e.getClass().equals(exceptionType);
            }
        });
    }

    /**
     * Instruct Awaitility to ignore caught exceptions matching the given predicate during condition evaluation.
     * Exceptions will be treated as evaluating to false. Your test will not fail
     * upon an exception matching the supplied predicate, unless it times out.
     */
    public static void ignoreExceptionsByDefaultMatching(Predicate<? super Throwable> predicate) {
        defaultExceptionIgnorer = new PredicateExceptionIgnorer(predicate);
    }

    /**
     * Instruct Awaitility to ignore caught exceptions matching the supplied matcher during condition evaluation.
     * Exceptions will be treated as evaluating to false. Your test will not fail
     * upon an exception matching the supplied exception type, unless it times out.
     */
    public static void ignoreExceptionsByDefaultMatching(Matcher<? super Throwable> matcher) {
        defaultExceptionIgnorer = new HamcrestExceptionIgnorer(matcher);
    }

    /**
     * Instructs Awaitility to execute the polling of the condition from the same as the test.
     * This is an advanced feature and you should be careful when combining this with conditions that
     * wait forever (or a long time) since Awaitility cannot interrupt the thread when using the same
     * thread as the test. For safety you should always combine tests using this feature with a test framework specific timeout,
     * for example in JUnit:
     *

     * @Test(timeout = 2000L)
     * public void myTest() {
     *     Awaitility.pollInSameThread();
     *     await().forever().until(...);
     * }
     *

     *
     * @since 3.0.0
     */
    public static void pollInSameThread() {
        defaultExecutorLifecycle = ExecutorLifecycle.withNormalCleanupBehavior(new Supplier<ExecutorService>() {
            @Override
            public ExecutorService get() {
                return InternalExecutorServiceFactory.sameThreadExecutorService();
            }
        });
    }

    /**
     * Specify the executor service whose threads will be used to evaluate the poll condition in Awaitility.
     * Note that the executor service must be shutdown manually!
     * This is an advanced feature and it should only be used sparingly.
     *
     * @param executorService The executor service that Awaitility will use when polling condition evaluations
     * @since 3.0.0
     */
    public static void pollExecutorService(final ExecutorService executorService) {
        defaultExecutorLifecycle = ExecutorLifecycle.withoutCleanup(executorService);
    }

    /**
     * Specify a thread supplier whose thread will be used to evaluate the poll condition in Awaitility.
     * The supplier will be called only once and the thread it returns will be reused during all condition evaluations.
     * This is an advanced feature and it should only be used sparingly.
     *
     * @param threadSupplier A supplier of the thread that Awaitility will use when polling
     * @since 3.0.0
     */
    public static void pollThread(final Function<Runnable, Thread> threadSupplier) {
        defaultExecutorLifecycle = ExecutorLifecycle.withNormalCleanupBehavior(new Supplier<ExecutorService>() {
            @Override
            public ExecutorService get() {
                return InternalExecutorServiceFactory.create(threadSupplier);
            }
        });
    }

    /**
     * Reset the timeout, poll interval, poll delay, uncaught exception handling
     * to their default values:
     *

     *

     *
timeout - 10 seconds

     *
poll interval - 100 milliseconds

     *
poll delay - 100 milliseconds

     *
Catch all uncaught exceptions - true

     *
Do not ignore caught exceptions

     *
Don't handle condition evaluation results

     *

     */
    public static void reset() {
        defaultPollInterval = DEFAULT_POLL_INTERVAL;
        defaultPollDelay = DEFAULT_POLL_DELAY;
        defaultWaitConstraint = AtMostWaitConstraint.TEN_SECONDS;
        defaultCatchUncaughtExceptions = true;
        defaultConditionEvaluationListener = null;
        defaultExecutorLifecycle = null;
        defaultExceptionIgnorer = new PredicateExceptionIgnorer(new Predicate<Throwable>() {
            public boolean matches(Throwable e) {
                return false;
            }
        });
        Thread.setDefaultUncaughtExceptionHandler(null);
    }

    /**
     * Start building an await statement.
     *
     * @return the condition factory
     */
    public static ConditionFactory await() {
        return await(null);
    }

    /**
     * Start building a named await statement. This is useful is cases when you
     * have several awaits in your test and you need to tell them apart. If a
     * named await timeout's the alias will be displayed indicating
     * which await statement that failed.
     *
     * @param alias the alias that will be shown if the await timeouts.
     * @return the condition factory
     */
    public static ConditionFactory await(String alias) {
        return new ConditionFactory(alias, defaultWaitConstraint, defaultPollInterval, defaultPollDelay,
                defaultCatchUncaughtExceptions, defaultExceptionIgnorer, defaultConditionEvaluationListener,
                defaultExecutorLifecycle);
    }

    /**
     * Catching uncaught exceptions in other threads. This will make the await
     * statement fail even if exceptions occur in other threads. This is the
     * default behavior.
     *
     * @return the condition factory
     */
    public static ConditionFactory catchUncaughtExceptions() {
        return new ConditionFactory(null, defaultWaitConstraint, defaultPollInterval, defaultPollDelay,
                defaultCatchUncaughtExceptions, defaultExceptionIgnorer, defaultConditionEvaluationListener,
                defaultExecutorLifecycle);
    }

    /**
     * Don't catch uncaught exceptions in other threads. This will not
     * make the await statement fail if exceptions occur in other threads.
     *
     * @return the condition factory
     */
    public static ConditionFactory dontCatchUncaughtExceptions() {
        return new ConditionFactory(null, defaultWaitConstraint, defaultPollInterval, defaultPollDelay,
                false, defaultExceptionIgnorer, defaultConditionEvaluationListener,
                defaultExecutorLifecycle);
    }

    /**
     * Start constructing an await statement with some settings. E.g.
     *

     *

     * with().pollInterval(20, MILLISECONDS).await().until(somethingHappens());
     *

     *
     * @return the condition factory
     */
    public static ConditionFactory with() {
        return new ConditionFactory(null, defaultWaitConstraint, defaultPollInterval, defaultPollDelay,
                defaultCatchUncaughtExceptions, defaultExceptionIgnorer, defaultConditionEvaluationListener,
                defaultExecutorLifecycle);
    }

    /**
     * Start constructing an await statement given some settings. E.g.
     *

     *

     * given().pollInterval(20, MILLISECONDS).then().await().until(somethingHappens());
     *

     *
     * @return the condition factory
     */
    public static ConditionFactory given() {
        return new ConditionFactory(null, defaultWaitConstraint, defaultPollInterval, defaultPollDelay,
                defaultCatchUncaughtExceptions, defaultExceptionIgnorer, defaultConditionEvaluationListener,
                defaultExecutorLifecycle);
    }

    /**
     * An alternative to using {@link #await()} if you want to specify a timeout
     * directly.
     *
     * @param timeout the timeout
     * @return the condition factory
     */
    public static ConditionFactory waitAtMost(Duration timeout) {
        return new ConditionFactory(null, defaultWaitConstraint.withMaxWaitTime(timeout), defaultPollInterval, defaultPollDelay,
                defaultCatchUncaughtExceptions, defaultExceptionIgnorer, defaultConditionEvaluationListener,
                defaultExecutorLifecycle);
    }

    /**
     * An alternative to using {@link #await()} if you want to specify a timeout
     * directly.
     *
     * @param value the value
     * @param unit  the unit
     * @return the condition factory
     */
    public static ConditionFactory waitAtMost(long value, TimeUnit unit) {
        return new ConditionFactory(null, defaultWaitConstraint.withMaxWaitTime(new Duration(value, unit)), defaultPollInterval, defaultPollDelay,
                defaultCatchUncaughtExceptions, defaultExceptionIgnorer, defaultConditionEvaluationListener,
                defaultExecutorLifecycle);
    }

    /**
     * Sets the default poll interval that all await statements will use.
     *
     * @param pollInterval the poll interval
     * @param unit         the unit
     */
    public static void setDefaultPollInterval(long pollInterval, TimeUnit unit) {
        defaultPollInterval = new FixedPollInterval(new Duration(pollInterval, unit));
    }

    /**
     * Sets the default poll delay all await statements will use.
     *
     * @param pollDelay the poll delay
     * @param unit      the unit
     */
    public static void setDefaultPollDelay(long pollDelay, TimeUnit unit) {
        defaultPollDelay = new Duration(pollDelay, unit);
    }

    /**
     * Sets the default timeout all await statements will use.
     *
     * @param timeout the timeout
     * @param unit    the unit
     */
    public static void setDefaultTimeout(long timeout, TimeUnit unit) {
        defaultWaitConstraint = defaultWaitConstraint.withMaxWaitTime(new Duration(timeout, unit));
    }

    /**
     * Sets the default poll interval that all await statements will use.
     *
     * @param pollInterval the new default poll interval
     */
    public static void setDefaultPollInterval(Duration pollInterval) {
        if (pollInterval == null) {
            throw new IllegalArgumentException("You must specify a poll interval (was null).");
        }
        defaultPollInterval = new FixedPollInterval(pollInterval);
    }

    /**
     * Sets the default poll interval that all await statements will use.
     *
     * @param pollInterval the new default poll interval
     */
    public static void setDefaultPollInterval(PollInterval pollInterval) {
        if (pollInterval == null) {
            throw new IllegalArgumentException("You must specify a poll interval (was null).");
        }
        defaultPollInterval = pollInterval;
    }

    /**
     * Sets the default poll delay that all await statements will use.
     *
     * @param pollDelay the new default poll delay
     */
    public static void setDefaultPollDelay(Duration pollDelay) {
        if (pollDelay == null) {
            throw new IllegalArgumentException("You must specify a poll delay (was null).");
        }
        defaultPollDelay = pollDelay;
    }

    /**
     * Sets the default timeout that all await statements will use.
     *
     * @param defaultTimeout the new default timeout
     */
    public static void setDefaultTimeout(Duration defaultTimeout) {
        if (defaultTimeout == null) {
            throw new IllegalArgumentException("You must specify a default timeout (was null).");
        }
        defaultWaitConstraint = defaultWaitConstraint.withMaxWaitTime(defaultTimeout);
    }

    /**
     * Sets the default condition evaluation listener that all await statements will use.
     *
     * @param defaultConditionEvaluationListener handles condition evaluation each time evaluation of a condition occurs. Works only with Hamcrest matcher-based conditions.
     */
    public static void setDefaultConditionEvaluationListener(ConditionEvaluationListener defaultConditionEvaluationListener) {
        Awaitility.defaultConditionEvaluationListener = defaultConditionEvaluationListener;
    }

    /**
     * Await until an instance field matches something. E.g.
     *

     *

     * await().until(fieldIn(service).ofType(int.class).andWithName("fieldName"), greaterThan(2));
     *

     *

     * Here Awaitility waits until a field with name fieldName and of the int.class
     * in object service is greater than 2.
     *

     * Note that the field must be thread-safe in order to guarantee correct behavior.
     *
     * @param object The object that contains the field.
     * @return A field supplier builder which lets you specify the parameters needed to find the field.
     */
    public static FieldSupplierBuilder fieldIn(Object object) {
        return new FieldSupplierBuilder(object);
    }

    /**
     * Await until a static field matches something. E.g.
     *

     *

     * await().until(fieldIn(Service.class).ofType(int.class).andWithName("fieldName"), greaterThan(2));
     *

     *

     * Here Awaitility waits until a static field with name fieldName and of the
     * int.class in object service is greater than 2.
     *

     * Note that the field must be thread-safe in order to guarantee correct behavior.
     *
     * @param clazz The class that contains the static field.
     * @return A field supplier builder which lets you specify the parameters needed to find the field.
     */
    public static FieldSupplierBuilder fieldIn(Class<?> clazz) {
        return new FieldSupplierBuilder(clazz);
    }
}


/**
 * Copyright (C) 2012-2019 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package ninja.migrations;

import ninja.lifecycle.Start;

import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import ninja.utils.NinjaConstant;
import ninja.utils.NinjaProperties;

/**
 * This class must be bound in a Guice module so that
 * @Start annotation works
 *
 * It starts migrations by the registered migration engine.
 *
 * @author ra
 *
 */
@Singleton
public class MigrationInitializer {

    private final Provider<MigrationEngine> migrationEngineProvider;
    private final NinjaProperties ninjaProperties;

    @Inject
    public MigrationInitializer(NinjaProperties ninjaProperties,
                                Provider<MigrationEngine> migrationEngineProvider) {
        this.ninjaProperties = ninjaProperties;
        this.migrationEngineProvider = migrationEngineProvider;
    }

    /**
     * We start it at order 9 which is below order 10 (where JPA is started)
     */
    @Start(order=9)
    public void start() {
        // Run migration only when activated in configuration.conf:
        Boolean runMigrations = ninjaProperties.getBoolean(NinjaConstant.NINJA_MIGRATION_RUN);

        if (runMigrations != null && runMigrations) {
            // provider used for late binding
            migrationEngineProvider.get().migrate();
        }
    }

}