/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.apache.brooklyn.test;

import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicReference;

import org.apache.brooklyn.util.collections.MutableList;
import org.apache.brooklyn.util.collections.MutableSet;
import org.apache.brooklyn.util.exceptions.Exceptions;
import org.apache.brooklyn.util.guava.Maybe;
import org.apache.brooklyn.util.javalang.JavaClassNames;
import org.apache.brooklyn.util.repeat.Repeater;
import org.apache.brooklyn.util.text.StringPredicates;
import org.apache.brooklyn.util.text.Strings;
import org.apache.brooklyn.util.time.CountdownTimer;
import org.apache.brooklyn.util.time.Duration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.annotations.Beta;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;

import groovy.lang.Closure;

/**
 * TODO should move this to new package brooklyn.util.assertions
 * and TODO should add a repeating() method which returns an AssertingRepeater extending Repeater
 * and:
 * <ul>
 * <li> adds support for requireAllIterationsTrue
 * <li> convenience run methods equivalent to succeedsEventually and succeedsContinually
 * </ul>
 * <p>
 * NOTE Selected routines in this class are originally copied from <a href="http://testng.org">TestNG</a>, to allow us to make assertions without having to
 * introduce a runtime dependency on TestNG.
 * </p>
 */
@Beta
public class Asserts {

    // Used in annotations so needs to be a constant - can't be initialized similarly to DEFAULT_LONG_TIMEOUT
    // TODO Can we force this by default on all unit tests, beforeMethod, afterMethod methods?
    public static final long THIRTY_SECONDS_TIMEOUT_MS = 30000;

    /** 
     * Timeout for use when something should happen. This is the *default timeout* that should
     * be used by tests (unless that test is asserting performance).
     * 
     * We willingly accept the hit of slow failing tests, in exchange for removing the 
     * false-negative failures that have historically littered our jenkins builds 
     * (which are presumably sometimes run on over-contended or low-spec machines).
     * 
     * If the long timeout is irritates during dev (e.g. when doing TDD, where failing tests are 
     * an important step), then one can change this value locally or set using something like
     * {@code -Dbrooklyn.test.defaultTimeout=1s}.
     */
    public static final Duration DEFAULT_LONG_TIMEOUT;

    static {
        String defaultTimeout = System.getProperty("brooklyn.test.defaultTimeout");
        if (defaultTimeout == null){
            DEFAULT_LONG_TIMEOUT = Duration.millis(THIRTY_SECONDS_TIMEOUT_MS);
        } else {
            DEFAULT_LONG_TIMEOUT = Duration.of(defaultTimeout);
        }
    }

    /** 
     * Timeout for use when waiting for other threads to (normally) finish.
     * <p>
     * Long enough for parallel execution to (normally!) catch up, even on overloaded mediocre 
     * test boxes most of the time, but short enough not to irritate too much when waiting
     * this long.
     * 
     * Never use this for asserting that a condition is satisfied within a given length of time.
     * Instead use {@link #DEFAULT_LONG_TIMEOUT} to avoid the false-negatives that have 
     * historically littered our jenkins builds.
     * 
     * Use this constant for asserting that something does *not* happen. If it was going to happen,
     * then it's reasonable to expect it would normally have happened within this time. For 
     * example, if we are asserting that no more events are received after unsubscribing, then
     * this would be an appropriate timeout to use (perhaps by using 
     * {@link #succeedsContinually(Runnable)}, which defaults to this timeout).
     */
    public static final Duration DEFAULT_SHORT_TIMEOUT = Duration.ONE_SECOND;
    
    /** @deprecated since 0.9.0 use {@link #DEFAULT_LONG_TIMEOUT} */ @Deprecated
    public static final Duration DEFAULT_TIMEOUT = DEFAULT_LONG_TIMEOUT;
    
    private static final Duration DEFAULT_SHORT_PERIOD = Repeater.DEFAULT_REAL_QUICK_PERIOD;

    private static final Logger log = LoggerFactory.getLogger(Asserts.class);

    private Asserts() {}

    private static final Character OPENING_CHARACTER = '[';
    private static final Character CLOSING_CHARACTER = ']';

    private static final String ASSERT_LEFT = "expected " + OPENING_CHARACTER;
    private static final String ASSERT_MIDDLE = CLOSING_CHARACTER + " but found " + OPENING_CHARACTER;
    private static final String ASSERT_RIGHT = Character.toString(CLOSING_CHARACTER);

    static String format(Object actual, Object expected, String message) {
        String formatted = "";
        if (null != message) {
            formatted = message + " ";
        }

        return formatted + ASSERT_LEFT + expected + ASSERT_MIDDLE + actual + ASSERT_RIGHT;
    }

    static private void failNotEquals(Object actual , Object expected, String message ) {
        fail(format(actual, expected, message));
    }

    /**
     * Assert that an object reference is null.
     *
     * @param object The object reference.
     *
     * @throws AssertionError if the assertion fails.
     */
    static public void assertNull(final Object object) {
        assertNull(object, null);
    }

    /**
     * Assert that an object reference is not null.
     *
     * @param object The object reference.
     *
     * @throws AssertionError if the assertion fails.
     */
    static public void assertNotNull(final Object object) {
        assertNotNull(object, null);
    }

    /**
     * Assert that an object reference is null.
     *
     * @param object The object reference.
     * @param message The assertion error message.
     *
     * @throws AssertionError if the assertion fails.
     */
    static public void assertNull(final Object object, final String message) {
        if (null != object) {
            throw new AssertionError(message == null ? "object reference is not null" : message);
        }
    }

    /**
     * Assert that an object reference is not null.
     *
     * @param object The object reference.
     * @param message The assertion error message.
     *
     * @throws AssertionError if the assertion fails.
     */
    static public void assertNotNull(final Object object, final String message) {
        if (null == object) {
            throw new AssertionError(message == null ? "object reference is null" : message);
        }
    }

    /**
     * Asserts that two collections contain the same elements in the same order. If they do not,
     * an AssertionError is thrown.
     *
     * @param actual the actual value
     * @param expected the expected value
     */
    static public void assertEquals(Collection<?> actual, Collection<?> expected) {
        assertEquals(actual, expected, null);
    }

    /**
     * Asserts that two collections contain the same elements in the same order. If they do not,
     * an AssertionError, with the given message, is thrown.
     * @param actual the actual value
     * @param expected the expected value
     * @param message the assertion error message
     */
    static public void assertEquals(Collection<?> actual, Collection<?> expected, String message) {
        if(actual == expected) {
            return;
        }

        if (actual == null || expected == null) {
            if (message != null) {
                fail(message);
            } else {
                fail("Collections not equal: expected: " + expected + " and actual: " + actual);
            }
        }

        assertEquals(actual.size(), expected.size(), message + ": lists don't have the same size");

        Iterator<?> actIt = actual.iterator();
        Iterator<?> expIt = expected.iterator();
        int i = -1;
        while(actIt.hasNext() && expIt.hasNext()) {
            i++;
            Object e = expIt.next();
            Object a = actIt.next();
            String explanation = "Lists differ at element [" + i + "]: " + e + " != " + a;
            String errorMessage = message == null ? explanation : message + ": " + explanation;

            assertEquals(a, e, errorMessage);
        }
    }

    /** Asserts that two iterators return the same elements in the same order. If they do not,
     * an AssertionError is thrown.
     * Please note that this assert iterates over the elements and modifies the state of the iterators.
     * @param actual the actual value
     * @param expected the expected value
     */
    static public void assertEquals(Iterator<?> actual, Iterator<?> expected) {
        assertEquals(actual, expected, null);
    }

    /** Asserts that two iterators return the same elements in the same order. If they do not,
     * an AssertionError, with the given message, is thrown.
     * Please note that this assert iterates over the elements and modifies the state of the iterators.
     * @param actual the actual value
     * @param expected the expected value
     * @param message the assertion error message
     */
    static public void assertEquals(Iterator<?> actual, Iterator<?> expected, String message) {
        if(actual == expected) {
            return;
        }

        if(actual == null || expected == null) {
            if(message != null) {
                fail(message);
            } else {
                fail("Iterators not equal: expected: " + expected + " and actual: " + actual);
            }
        }

        int i = -1;
        while(actual.hasNext() && expected.hasNext()) {

            i++;
            Object e = expected.next();
            Object a = actual.next();
            String explanation = "Iterators differ at element [" + i + "]: " + e + " != " + a;
            String errorMessage = message == null ? explanation : message + ": " + explanation;

            assertEquals(a, e, errorMessage);

        }

        if(actual.hasNext()) {

            String explanation = "Actual iterator returned more elements than the expected iterator.";
            String errorMessage = message == null ? explanation : message + ": " + explanation;
            fail(errorMessage);

        } else if(expected.hasNext()) {

            String explanation = "Expected iterator returned more elements than the actual iterator.";
            String errorMessage = message == null ? explanation : message + ": " + explanation;
            fail(errorMessage);

        }

    }

    /** Asserts that two iterables return iterators with the same elements in the same order. If they do not,
     * an AssertionError is thrown.
     * @param actual the actual value
     * @param expected the expected value
     */
    static public void assertEquals(Iterable<?> actual, Iterable<?> expected) {
        assertEquals(actual, expected, null);
    }

    /** Asserts that two iterables return iterators with the same elements in the same order. If they do not,
     * an AssertionError, with the given message, is thrown.
     * @param actual the actual value
     * @param expected the expected value
     * @param message the assertion error message
     */
    static public void assertEquals(Iterable<?> actual, Iterable<?> expected, String message) {
        if(actual == expected) {
            return;
        }

        if(actual == null || expected == null) {
            if(message != null) {
                fail(message);
            } else {
                fail("Iterables not equal: expected: " + expected + " and actual: " + actual);
            }
        }

        Iterator<?> actIt = actual.iterator();
        Iterator<?> expIt = expected.iterator();

        assertEquals(actIt, expIt, message);
    }



    /**
     * Asserts that two sets are equal.
     */
    static public void assertEquals(Set<?> actual, Set<?> expected) {
        assertEquals(actual, expected, null);
    }

    /**
     * Assert set equals
     */
    static public void assertEquals(Set<?> actual, Set<?> expected, String message) {
        if (actual == expected) {
            return;
        }

        if (actual == null || expected == null) {
            // Keep the back compatible
            if (message == null) {
                fail("Sets not equal: expected: " + expected + " and actual: " + actual);
            } else {
                failNotEquals(actual, expected, message);
            }
        }

        if (!actual.equals(expected)) {
            if (message == null) {
                fail("Sets differ: expected " + expected + " but got " + actual);
            } else {
                failNotEquals(actual, expected, message);
            }
        }
    }

    /**
     * Asserts that two maps are equal.
     */
    static public void assertEquals(Map<?, ?> actual, Map<?, ?> expected) {
        if (actual == expected) {
            return;
        }

        if (actual == null || expected == null) {
            fail("Maps not equal: expected: " + expected + " and actual: " + actual);
        }

        if (actual.size() != expected.size()) {
            fail("Maps do not have the same size:" + actual.size() + " != " + expected.size());
        }

        Set<?> entrySet = actual.entrySet();
        for (Iterator<?> iterator = entrySet.iterator(); iterator.hasNext();) {
            Map.Entry<?, ?> entry = (Map.Entry<?, ?>) iterator.next();
            Object key = entry.getKey();
            Object value = entry.getValue();
            Object expectedValue = expected.get(key);
            assertEquals(value, expectedValue, "Maps do not match for key:" + key + " actual:" + value
                    + " expected:" + expectedValue);
        }

    }


    /**
     * Asserts that two arrays contain the same elements in the same order. If they do not,
     * an AssertionError, with the given message, is thrown.
     * @param actual the actual value
     * @param expected the expected value
     * @param message the assertion error message
     */
    static public void assertEquals(Object[] actual, Object[] expected, String message) {
        if(actual == expected) {
            return;
        }

        if ((actual == null && expected != null) || (actual != null && expected == null)) {
            if (message != null) {
                fail(message);
            } else {
                fail("Arrays not equal: " + Arrays.toString(expected) + " and " + Arrays.toString(actual));
            }
        }
        assertEquals(Arrays.asList(actual), Arrays.asList(expected), message);
    }

    /**
     * Asserts that two arrays contain the same elements in the same order. If they do not,
     * an AssertionError is thrown.
     *
     * @param actual the actual value
     * @param expected the expected value
     */
    static public void assertEquals(Object[] actual, Object[] expected) {
        assertEquals(actual, expected, null);
    }

    /**
     * Asserts that two objects are equal. 
     * @param actual the actual value
     * @param expected the expected value
     *                 
     * @throws AssertionError if the values are not equal.
     */
    static public void assertEquals(Object actual, Object expected) {
        assertEquals(actual, expected, null);
    }
    
    /**
     * Asserts that two objects are equal. 
     * 
     * @param actual the actual value
     * @param expected the expected value
     * @param message the assertion error message
     *
     * @throws AssertionError if the values are not equal.
     */
    static public void assertEquals(Object actual, Object expected, String message) {
        if((expected == null) && (actual == null)) {
            return;
        }
        if(expected != null) {
            if (expected.getClass().isArray()) {
                assertArrayEquals(actual, expected, message);
                return;
            } else if (expected.equals(actual)) {
                return;
            }
        }
        failNotEquals(actual, expected, message);
    }


    /**
     * Asserts that two objects are equal. It they are not, an AssertionError,
     * with given message, is thrown.
     * @param actual the actual value
     * @param expected the expected value (should be an non-null array value)
     * @param message the assertion error message
     */
    private static void assertArrayEquals(Object actual, Object expected, String message) {
        //is called only when expected is an array
        if (actual.getClass().isArray()) {
            int expectedLength = Array.getLength(expected);
            if (expectedLength == Array.getLength(actual)) {
                for (int i = 0 ; i < expectedLength ; i++) {
                    Object _actual = Array.get(actual, i);
                    Object _expected = Array.get(expected, i);
                    try {
                        assertEquals(_actual, _expected);
                    } catch (AssertionError ae) {
                        failNotEquals(actual, expected, message == null ? "" : message
                                + " (values at index " + i + " are not the same)");
                    }
                }
                //array values matched
                return;
            } else {
                failNotEquals(Array.getLength(actual), expectedLength, message == null ? "" : message
                        + " (Array lengths are not the same)");
            }
        }
        failNotEquals(actual, expected, message);
    }


    /**
     * Asserts that two Strings are equal. If they are not,
     * an AssertionError, with the given message, is thrown.
     * @param actual the actual value
     * @param expected the expected value
     * @param message the assertion error message
     */
    static public void assertEquals(String actual, String expected, String message) {
        assertEquals((Object) actual, (Object) expected, message);
    }

    /**
     * Asserts that two Strings are equal. If they are not,
     * an AssertionError is thrown.
     * @param actual the actual value
     * @param expected the expected value
     */
    static public void assertEquals(String actual, String expected) {
        assertEquals(actual, expected, null);
    }

    /**
     * Asserts that two doubles are equal within a delta.  If they are not,
     * an AssertionError, with the given message, is thrown.  If the expected
     * value is infinity then the delta value is ignored.
     * @param actual the actual value
     * @param expected the expected value
     * @param delta the absolute tolerable difference between the actual and expected values
     * @param message the assertion error message
     */
    static public void assertEquals(double actual, double expected, double delta, String message) {
        // handle infinity specially since subtracting to infinite values gives NaN and the
        // the following test fails
        if(Double.isInfinite(expected)) {
            if(!(expected == actual)) {
                failNotEquals(new Double(actual), new Double(expected), message);
            }
        }
        else if(!(Math.abs(expected - actual) <= delta)) { // Because comparison with NaN always returns false
            failNotEquals(new Double(actual), new Double(expected), message);
        }
    }

    /**
     * Asserts that two doubles are equal within a delta. If they are not,
     * an AssertionError is thrown. If the expected value is infinity then the
     * delta value is ignored.
     * @param actual the actual value
     * @param expected the expected value
     * @param delta the absolute tolerable difference between the actual and expected values
     */
    static public void assertEquals(double actual, double expected, double delta) {
        assertEquals(actual, expected, delta, null);
    }

    /**
     * Asserts that two floats are equal within a delta. If they are not,
     * an AssertionError, with the given message, is thrown.  If the expected
     * value is infinity then the delta value is ignored.
     * @param actual the actual value
     * @param expected the expected value
     * @param delta the absolute tolerable difference between the actual and expected values
     * @param message the assertion error message
     */
    static public void assertEquals(float actual, float expected, float delta, String message) {
        // handle infinity specially since subtracting to infinite values gives NaN and the
        // the following test fails
        if(Float.isInfinite(expected)) {
            if(!(expected == actual)) {
                failNotEquals(new Float(actual), new Float(expected), message);
            }
        }
        else if(!(Math.abs(expected - actual) <= delta)) {
            failNotEquals(new Float(actual), new Float(expected), message);
        }
    }

    /**
     * Asserts that two floats are equal within a delta. If they are not,
     * an AssertionError is thrown. If the expected
     * value is infinity then the delta value is ignored.
     * @param actual the actual value
     * @param expected the expected value
     * @param delta the absolute tolerable difference between the actual and expected values
     */
    static public void assertEquals(float actual, float expected, float delta) {
        assertEquals(actual, expected, delta, null);
    }

    /**
     * Asserts that two longs are equal. If they are not,
     * an AssertionError, with the given message, is thrown.
     * @param actual the actual value
     * @param expected the expected value
     * @param message the assertion error message
     */
    static public void assertEquals(long actual, long expected, String message) {
        assertEquals(Long.valueOf(actual), Long.valueOf(expected), message);
    }

    /**
     * Asserts that two longs are equal. If they are not,
     * an AssertionError is thrown.
     * @param actual the actual value
     * @param expected the expected value
     */
    static public void assertEquals(long actual, long expected) {
        assertEquals(actual, expected, null);
    }

    /**
     * Asserts that two booleans are equal. If they are not,
     * an AssertionError, with the given message, is thrown.
     * @param actual the actual value
     * @param expected the expected value
     * @param message the assertion error message
     */
    static public void assertEquals(boolean actual, boolean expected, String message) {
        assertEquals( Boolean.valueOf(actual), Boolean.valueOf(expected), message);
    }

    /**
     * Asserts that two booleans are equal. If they are not,
     * an AssertionError is thrown.
     * @param actual the actual value
     * @param expected the expected value
     */
    static public void assertEquals(boolean actual, boolean expected) {
        assertEquals(actual, expected, null);
    }

    /**
     * Asserts that two bytes are equal. If they are not,
     * an AssertionError, with the given message, is thrown.
     * @param actual the actual value
     * @param expected the expected value
     * @param message the assertion error message
     */
    static public void assertEquals(byte actual, byte expected, String message) {
        assertEquals(Byte.valueOf(actual), Byte.valueOf(expected), message);
    }

    /**
     * Asserts that two bytes are equal. If they are not,
     * an AssertionError is thrown.
     * @param actual the actual value
     * @param expected the expected value
     */
    static public void assertEquals(byte actual, byte expected) {
        assertEquals(actual, expected, null);
    }

    /**
     * Asserts that two chars are equal. If they are not,
     * an AssertionFailedError, with the given message, is thrown.
     * @param actual the actual value
     * @param expected the expected value
     * @param message the assertion error message
     */
    static public void assertEquals(char actual, char expected, String message) {
        assertEquals(Character.valueOf(actual), Character.valueOf(expected), message);
    }

    /**
     * Asserts that two chars are equal. If they are not,
     * an AssertionError is thrown.
     * @param actual the actual value
     * @param expected the expected value
     */
    static public void assertEquals(char actual, char expected) {
        assertEquals(actual, expected, null);
    }

    /**
     * Asserts that two shorts are equal. If they are not,
     * an AssertionFailedError, with the given message, is thrown.
     * @param actual the actual value
     * @param expected the expected value
     * @param message the assertion error message
     */
    static public void assertEquals(short actual, short expected, String message) {
        assertEquals(Short.valueOf(actual), Short.valueOf(expected), message);
    }

    /**
     * Asserts that two shorts are equal. If they are not,
     * an AssertionError is thrown.
     * @param actual the actual value
     * @param expected the expected value
     */
    static public void assertEquals(short actual, short expected) {
        assertEquals(actual, expected, null);
    }

    /**
     * Asserts that two ints are equal. If they are not,
     * an AssertionFailedError, with the given message, is thrown.
     * @param actual the actual value
     * @param expected the expected value
     * @param message the assertion error message
     */
    static public void assertEquals(int actual,  int expected, String message) {
        assertEquals(Integer.valueOf(actual), Integer.valueOf(expected), message);
    }

    /**
     * Asserts that two ints are equal. If they are not,
     * an AssertionError is thrown.
     * @param actual the actual value
     * @param expected the expected value
     */
    static public void assertEquals(int actual, int expected) {
        assertEquals(actual, expected, null);
    }

    /**
     * Asserts that a condition is true. If it isn't, an AssertionError is thrown.
     * @param condition the condition to evaluate
     */
    public static void assertTrue(boolean condition) {
        if (!condition) fail();
    }

    /**
     * Asserts that a condition is true. If it isn't,
     * an AssertionError, with the given message, is thrown.
     * @param condition the condition to evaluate
     * @param message the assertion error message
     */
    public static void assertTrue(boolean condition, String message) {
        if (!condition) fail(message);
    }

    /**
     * Asserts that a condition is false. If it isn't,
     * an AssertionError, with the given message, is thrown.
     * @param condition the condition to evaluate
     * @param message the assertion error message
     */
    public static void assertFalse(boolean condition, String message) {
        if (condition) fail(message);
    }
    public static void assertFalse(boolean condition) {
        if (condition) fail();
    }

    /**
     * Fails a test with the given message.
     * @param message the assertion error message
     */
    public static AssertionError fail(String message) {
        throw new AssertionError(message);
    }
    public static AssertionError fail(Throwable error) {
        throw new AssertionError(error);
    }
    public static AssertionError fail() { throw new AssertionError(); }

    public static void assertEqualsIgnoringOrder(Iterable<?> actual, Iterable<?> expected) {
        assertEqualsIgnoringOrder(actual, expected, false, null);
    }

    public static void assertEqualsIgnoringOrder(Iterable<?> actual, Iterable<?> expected, boolean logDuplicates, String errmsg) {
        Set<?> actualSet = Sets.newLinkedHashSet(actual);
        Set<?> expectedSet = Sets.newLinkedHashSet(expected);
        Set<?> extras = Sets.difference(actualSet, expectedSet);
        Set<?> missing = Sets.difference(expectedSet, actualSet);
        List<Object> duplicates = Lists.newArrayList(actual);
        for (Object a : actualSet) {
            duplicates.remove(a);
        }
        String fullErrmsg = "extras="+extras+"; missing="+missing
                + (logDuplicates ? "; duplicates="+MutableSet.copyOf(duplicates) : "")
                +"; actualSize="+Iterables.size(actual)+"; expectedSize="+Iterables.size(expected)
                +"; actual="+actual+"; expected="+expected+"; "+errmsg;
        assertTrue(extras.isEmpty(), fullErrmsg);
        assertTrue(missing.isEmpty(), fullErrmsg);
        assertTrue(Iterables.size(actual) == Iterables.size(expected), fullErrmsg);
        assertTrue(actualSet.equals(expectedSet), fullErrmsg); // should be covered by extras/missing/size test
    }

    // --- new routines
    
    /**  As {@link #eventually(Supplier, Predicate, Duration, Duration, String)} with defaults. */
    public static <T> void eventually(Supplier<? extends T> supplier, Predicate<T> predicate) {
        eventually(supplier, predicate, null, null, null);
    }
    
    /** @deprecated since 0.9.0 use {@link #eventually(Supplier, Predicate, Duration, Duration, String)} */ @Deprecated
    public static <T> void eventually(Map<String,?> flags, Supplier<? extends T> supplier, Predicate<T> predicate) {
        eventually(flags, supplier, predicate, (String)null);
    }
    /** @deprecated since 0.9.0 use {@link #eventually(Supplier, Predicate, Duration, Duration, String)} */ @Deprecated
    public static <T> void eventually(Map<String,?> flags, Supplier<? extends T> supplier, Predicate<T> predicate, String errMsg) {
        eventually(supplier, predicate, toDuration(flags.get("timeout"), null), toDuration(flags.get("period"), null), errMsg);
    }
    
    /**  As {@link #eventually(Supplier, Predicate, Duration, Duration, String)} with default. */
    public static <T> void eventually(Supplier<? extends T> supplier, Predicate<T> predicate, Duration timeout) {
        eventually(supplier, predicate, timeout, null, null);
    }
    
    /** Asserts that eventually the supplier gives a value accepted by the predicate. 
     * Tests periodically and succeeds as soon as the supplier gives an allowed value.
     * Other arguments can be null.
     * 
     * @param supplier supplies the value to test, such as {@link Suppliers#ofInstance(Object)} for a constant 
     * @param predicate the {@link Predicate} to apply to each value given by the supplier
     * @param timeout how long to wait, default {@link #DEFAULT_LONG_TIMEOUT}
     * @param period how often to check, default quite often so you won't notice but letting the CPU do work
     * @param errMsg an error message to display if not satisfied, in addition to the last-tested supplied value and the predicate
     */
    public static <T> void eventually(Supplier<? extends T> supplier, Predicate<T> predicate, Duration timeout, Duration period, String errMsg) {
        if (timeout==null) timeout = DEFAULT_LONG_TIMEOUT;
        if (period==null) period = DEFAULT_SHORT_PERIOD;
        CountdownTimer timeleft = timeout.countdownTimer();
        
        T supplied;
        int count = 0;
        do {
            if (count++ > 0) Duration.sleep(period);
            supplied = supplier.get();
            if (predicate.apply(supplied)) return;
        } while (timeleft.isNotExpired());

        fail("Expected: eventually "+predicate+"; got most recently: "+supplied
            +" (waited "+timeleft.getDurationElapsed()+", checked "+count+")"
            +(errMsg!=null?"; "+errMsg:""));
    }
    
    /**  As {@link #continually(Supplier, Predicate, Duration, Duration, String)} with defaults. */
    public static <T> void continually(Supplier<? extends T> supplier, Predicate<T> predicate) {
        continually(ImmutableMap.<String,Object>of(), supplier, predicate);
    }

    /** @deprecated since 0.9.0 use {@link #continually(Supplier, Predicate, Duration, Duration, String)} */ @Deprecated
    public static <T> void continually(Map<String,?> flags, Supplier<? extends T> supplier, Predicate<? super T> predicate) {
        continually(flags, supplier, predicate, null);
    }

    /** @deprecated since 0.9.0 use {@link #continually(Supplier, Predicate, Duration, Duration, String)} */ @Deprecated
    public static <T> void continually(Map<String,?> flags, Supplier<? extends T> supplier, Predicate<T> predicate, String errMsg) {
        continually(supplier, predicate, toDuration(flags.get("timeout"), toDuration(flags.get("duration"), null)), 
            toDuration(flags.get("period"), null), null);
    }
    /** 
     * Asserts that continually the supplier gives a value accepted by the predicate. 
     * Tests periodically and fails if the supplier gives a disallowed value.
     * Other arguments can be null.
     * 
     * @param supplier supplies the value to test, such as {@link Suppliers#ofInstance(Object)} for a constant 
     * @param predicate the {@link Predicate} to apply to each value given by the supplier
     * @param duration how long to test for, default {@link #DEFAULT_SHORT_TIMEOUT}
     * @param period how often to check, default quite often to minimise chance of missing a flashing violation but letting the CPU do work
     * @param errMsg an error message to display if not satisfied, in addition to the last-tested supplied value and the predicate
     */
    public static <T> void continually(Supplier<? extends T> supplier, Predicate<T> predicate, Duration duration, Duration period, String errMsg) {
        if (duration==null) duration = DEFAULT_SHORT_TIMEOUT;
        if (period==null) period = DEFAULT_SHORT_PERIOD;

        CountdownTimer timeleft = duration.countdownTimer();
        
        T supplied;
        int count = 0;
        do {
            if (count > 0) Duration.sleep(period);
            supplied = supplier.get();
            if (!predicate.apply(supplied)) {
                fail("Expected: continually "+predicate+"; got violation: "+supplied
                    // tell timing if it worked the first time and then failed
                    +(count > 0 ? " (after "+timeleft.getDurationElapsed()+", successfully checked "+count+")" : "")
                    +(errMsg!=null?"; "+errMsg:""));
            }
            count++;
        } while (timeleft.isNotExpired());
    }
    
    /**
     * @see #succeedsEventually(Map, Callable)
     */
    public static void succeedsEventually(Runnable r) {
        succeedsEventually(ImmutableMap.<String,Object>of(), r);
    }

    /**
     * @see #succeedsEventually(Map, Callable)
     */
    public static void succeedsEventually(Map<String,?> flags, Runnable r) {
        succeedsEventually(flags, toCallable(r));
    }
    
    /**
     * @see #succeedsEventually(Map, Callable)
     */
    public static <T> T succeedsEventually(Callable<T> c) {
        return succeedsEventually(ImmutableMap.<String,Object>of(), c);
    }
    
    // FIXME duplication with TestUtils.BooleanWithMessage
    public static class BooleanWithMessage {
        boolean value; String message;
        public BooleanWithMessage(boolean value, String message) {
            this.value = value; this.message = message;
        }
        public boolean asBoolean() {
            return value;
        }
        @Override
        public String toString() {
            return message;
        }
    }

    // TODO flags are ugly; remove this in favour of something strongly typed,
    // e.g. extending Repeater and taking the extra semantics.
    // TODO remove the #succeedsEventually in favour of #eventually (and same for continually)
    //      Aled says why? I've found succeedsEventually to be a concise & clear way to write tests.
    /**
     * Convenience method for cases where we need to test until something is true.
     *
     * The Callable will be invoked periodically until it succesfully concludes.
     * <p>
     * The following flags are supported:
     * <ul>
     * <li>abortOnError (boolean, default true)
     * <li>abortOnException - (boolean, default false)
     * <li>timeout - (a Duration or an integer in millis, defaults to {@link Asserts#DEFAULT_LONG_TIMEOUT})
     * <li>period - (a Duration or an integer in millis, for fixed retry time; if not set, defaults to exponentially increasing from 1 to 500ms)
     * <li>minPeriod - (a Duration or an integer in millis; only used if period not explicitly set; the minimum period when exponentially increasing; defaults to 1ms)
     * <li>maxPeriod - (a Duration or an integer in millis; only used if period not explicitly set; the maximum period when exponentially increasing; defaults to 500ms)
     * <li>maxAttempts - (integer, Integer.MAX_VALUE)
     * </ul>
     * 
     * The following flags are deprecated:
     * <ul>
     * <li>useGroovyTruth - (defaults to false; any result code apart from 'false' will be treated as success including null; ignored for Runnables which aren't Callables)
     * </ul>
     * 
     * @param flags, accepts the flags listed above
     * @param c The callable to invoke
     */
    public static <T> T succeedsEventually(Map<String,?> flags, Callable<T> c) {
        boolean abortOnException = get(flags, "abortOnException", false);
        boolean abortOnError = get(flags, "abortOnError", false);
        boolean useGroovyTruth = get(flags, "useGroovyTruth", false);
        boolean logException = get(flags, "logException", true);

        // To speed up tests, default is for the period to start small and increase...
        Duration duration = toDuration(flags.get("timeout"), DEFAULT_LONG_TIMEOUT);
        Duration fixedPeriod = toDuration(flags.get("period"), null);
        Duration minPeriod = (fixedPeriod != null) ? fixedPeriod : toDuration(flags.get("minPeriod"), Duration.millis(1));
        Duration maxPeriod = (fixedPeriod != null) ? fixedPeriod : toDuration(flags.get("maxPeriod"), Duration.millis(500));
        int maxAttempts = get(flags, "maxAttempts", Integer.MAX_VALUE);
        int attempt = 0;
        long startTime = System.currentTimeMillis();
        try {
            Throwable lastException = null;
            T result = null;
            long lastAttemptTime = 0;
            long expireTime = startTime+duration.toMilliseconds();
            long sleepTimeBetweenAttempts = minPeriod.toMilliseconds();
            
            while (attempt < maxAttempts && lastAttemptTime < expireTime) {
                try {
                    attempt++;
                    lastAttemptTime = System.currentTimeMillis();
                    result = c.call();
                    if (log.isTraceEnabled()) log.trace("Attempt {} after {} ms: {}", new Object[] {attempt, System.currentTimeMillis() - startTime, result});
                    if (useGroovyTruth) {
                        if (groovyTruth(result)) return result;
                    } else if (Boolean.FALSE.equals(result)) {
                        if (result instanceof BooleanWithMessage) 
                            log.warn("Test returned an instance of BooleanWithMessage but useGroovyTruth is not set! " +
                                     "The result of this probably isn't what you intended.");
                        // FIXME surprising behaviour, "false" result here is acceptable
                        return result;
                    } else {
                        return result;
                    }
                    lastException = null;
                } catch(Throwable e) {
                    Exceptions.propagateIfInterrupt(e);
                    lastException = e;
                    if (log.isTraceEnabled()) log.trace("Attempt {} after {} ms: {}", new Object[] {attempt, System.currentTimeMillis() - startTime, e.getMessage()});
                    if (abortOnException) throw e;
                    if (abortOnError && e instanceof Error) throw e;
                }
                long sleepTime = Math.min(sleepTimeBetweenAttempts, expireTime-System.currentTimeMillis());
                if (sleepTime > 0) Thread.sleep(sleepTime);
                sleepTimeBetweenAttempts = Math.min(
                    // grow by 1.5x; doubling causes longer extra waits than we like in tests
                    sleepTimeBetweenAttempts + Math.max(1, sleepTimeBetweenAttempts/2), 
                    maxPeriod.toMilliseconds());
            }
            
            log.info("succeedsEventually exceeded max attempts or timeout - {} attempts lasting {} ms, for {}", new Object[] {attempt, System.currentTimeMillis()-startTime, c});
            if (lastException != null)
                throw lastException;
            throw fail("invalid results; last was: "+result);
        } catch (Throwable t) {
            Exceptions.propagateIfInterrupt(t);
            if (logException) log.info("failed succeeds-eventually, "+attempt+" attempts, "+
                    (System.currentTimeMillis()-startTime)+"ms elapsed "+
                    "(rethrowing): "+t);
            throw new AssertionError("failed succeeds-eventually, "+attempt+" attempts, "+
                (System.currentTimeMillis()-startTime)+"ms elapsed: "+Exceptions.collapseText(t), t);
        }
    }

    public static <T> void succeedsContinually(Runnable r) {
        succeedsContinually(ImmutableMap.<String,Object>of(), r);
    }
    
    public static <T> void succeedsContinually(Map<?,?> flags, Runnable r) {
        succeedsContinually(flags, toCallable(r));
    }

    public static <T> T succeedsContinually(Callable<T> c) {
        return succeedsContinually(ImmutableMap.<String,Object>of(), c);
    }

    // TODO unify with "continually"; see also eventually, some of those options might be useful
    public static <T> T succeedsContinually(Map<?,?> flags, Callable<T> job) {
        Duration duration = toDuration(flags.get("timeout"), DEFAULT_SHORT_TIMEOUT);
        Duration period = toDuration(flags.get("period"), Duration.millis(10));
        long periodMs = period.toMilliseconds();
        long startTime = System.currentTimeMillis();
        long expireTime = startTime+duration.toMilliseconds();
        int attempt = 0;
        
        boolean first = true;
        T result = null;
        while (first || System.currentTimeMillis() <= expireTime) {
            attempt++;
            try {
                result = job.call();
            } catch (Exception e) {
                log.info("succeedsContinually failed - {} attempts lasting {} ms, for {} (rethrowing)", new Object[] {attempt, System.currentTimeMillis()-startTime, job});
                throw propagate(e);
            }
            if (periodMs > 0) sleep(periodMs);
            first = false;
        }
        return result;
    }
    
    private static Duration toDuration(Object duration, Duration defaultVal) {
        if (duration == null)
            return defaultVal;
        else 
            return Duration.of(duration);
    }
    
    public static void assertFails(Runnable r) {
        assertFailsWith(toCallable(r), Predicates.alwaysTrue());
    }
    
    public static void assertFails(Callable<?> c) {
        assertFailsWith(c, Predicates.alwaysTrue());
    }
    
    /**
     * @deprecated since 0.11.0; explicit groovy utilities/support will be deleted.
     */
    @Deprecated
    public static void assertFailsWith(Callable<?> c, final Closure<Boolean> exceptionChecker) {
        assertFailsWith(c, new Predicate<Throwable>() {
            @Override
            public boolean apply(Throwable input) {
                return exceptionChecker.call(input);
            }
        });
    }
    
    @SafeVarargs
    public static void assertFailsWith(Runnable c, final Class<? extends Throwable> validException, final Class<? extends Throwable> ...otherValidExceptions) {
        final List<Class<?>> validExceptions = ImmutableList.<Class<?>>builder()
                .add(validException)
                .addAll(ImmutableList.copyOf(otherValidExceptions))
                .build();
        
        assertFailsWith(c, new Predicate<Throwable>() {
            @Override
            public boolean apply(Throwable e) {
                for (Class<?> validException: validExceptions) {
                    if (validException.isInstance(e)) return true;
                }
                fail("Test threw exception of unexpected type "+e.getClass()+"; expecting "+validExceptions);
                return false;
            }
        });
    }

    public static void assertFailsWith(Runnable r, Predicate<? super Throwable> exceptionChecker) {
        assertFailsWith(toCallable(r), exceptionChecker);
    }
    
    public static void assertFailsWith(Callable<?> c, Predicate<? super Throwable> exceptionChecker) {
        boolean failed = false;
        try {
            c.call();
        } catch (Throwable e) {
            failed = true;
            if (!exceptionChecker.apply(e)) {
                log.debug("Test threw invalid exception (failing)", e);
                fail("Test threw invalid exception: "+e);
            }
            log.debug("Test for exception successful ("+e+")");
        }
        if (!failed) fail("Test code should have thrown exception but did not");
    }

    public static void assertReturnsEventually(final Runnable r, Duration timeout) throws InterruptedException, ExecutionException, TimeoutException {
        final AtomicReference<Throwable> throwable = new AtomicReference<Throwable>();
        Runnable wrappedR = new Runnable() {
            @Override public void run() {
                try {
                    r.run();
                } catch (Throwable t) {
                    throwable.set(t);
                    throw Exceptions.propagate(t);
                }
            }
        };
        Thread thread = new Thread(wrappedR, "assertReturnsEventually("+r+")");
        try {
            thread.start();
            thread.join(timeout.toMilliseconds());
            if (thread.isAlive()) {
                throw new TimeoutException("Still running: r="+r+"; thread="+Arrays.toString(thread.getStackTrace()));
            }
        } catch (InterruptedException e) {
            throw Exceptions.propagate(e);
        } finally {
            thread.interrupt();
        }
        
        if (throwable.get() !=  null) {
            throw new ExecutionException(throwable.get());
        }
    }

    public static <T> void assertThat(T object, Predicate<T> condition) {
        assertThat(object, condition, null);
    }
    public static <T> void assertThat(T object, Predicate<T> condition, String message) {
        if (condition.apply(object)) return;
        fail(Strings.isBlank(message) ? "Failed "+condition+": "+object : message);
    }

    public static void assertStringContains(String input, String phrase1ToContain, String ...optionalOtherPhrasesToContain) {
        if (input==null) fail("Input is null.");
        if (phrase1ToContain!=null) {
            assertThat(input, StringPredicates.containsLiteral(phrase1ToContain));
        }
        for (String otherPhrase: optionalOtherPhrasesToContain) {
            if (otherPhrase!=null) {
                assertThat(input, StringPredicates.containsLiteral(otherPhrase));
            }
        }
    }
    
    public static void assertStringDoesNotContain(String input, String phrase1ToNotContain, String ...optionalOtherPhrasesToNotContain) {
        if (input==null) fail("Input is null.");
        if (phrase1ToNotContain!=null) {
            assertThat(input, Predicates.not(StringPredicates.containsLiteral(phrase1ToNotContain)));
        }
        for (String otherPhrase: optionalOtherPhrasesToNotContain) {
            if (otherPhrase!=null) {
                assertThat(input, Predicates.not(StringPredicates.containsLiteral(otherPhrase)));
            }
        }
    }
    
    public static void assertStringContainsAtLeastOne(String input, String possiblePhrase1ToContain, String ...optionalOtherPossiblePhrasesToContain) {
        if (input==null) fail("Input is null.");
        List<String> missing = MutableList.of();
        if (possiblePhrase1ToContain!=null) {
            if (input.contains(possiblePhrase1ToContain)) return;
            missing.add(possiblePhrase1ToContain);
        }
        for (String otherPhrase: optionalOtherPossiblePhrasesToContain) {
            if (otherPhrase!=null) {
                if (input.contains(otherPhrase)) return;
                missing.add(otherPhrase);
            }
        }
        fail("Input did not contain any of the expected phrases "+missing+": "+input);
    }
    
    public static void assertStringContainsIgnoreCase(String input, String phrase1ToContain, String ...optionalOtherPhrasesToContain) {
        if (input==null) fail("Input is null.");
        if (phrase1ToContain!=null) {
            assertThat(input, StringPredicates.containsLiteralIgnoreCase(phrase1ToContain));
        }
        for (String otherPhrase: optionalOtherPhrasesToContain) {
            if (otherPhrase!=null) {
                assertThat(input, StringPredicates.containsLiteralIgnoreCase(otherPhrase));
            }
        }
    }
    
    public static void assertStringMatchesRegex(String input, String regex1ToMatch, String ...optionalOtherRegexesToMatch) {
        if (input==null) fail("Input is null.");
        if (regex1ToMatch!=null) {
            assertThat(input, StringPredicates.matchesRegex(regex1ToMatch));
        }
        for (String otherRegex: optionalOtherRegexesToMatch) {
            if (otherRegex!=null) {
                assertThat(input, StringPredicates.matchesRegex(otherRegex));
            }
        }
    }

    /** Subclass of {@link AssertionError} which indicates that code which should have thrown did not, 
     * so that callers can disambiguate expected errors from this one.
     * See {@link #shouldHaveFailedPreviously()} */
    public static class ShouldHaveFailedPreviouslyAssertionError extends AssertionError {
        private static final long serialVersionUID = 4359541529633617518L;
        public ShouldHaveFailedPreviouslyAssertionError() { this("Should have failed previously."); }
        public ShouldHaveFailedPreviouslyAssertionError(String message) { super(message); }
    }
    
    /** Throws a {@link ShouldHaveFailedPreviouslyAssertionError} exception, 
     * to more easily distinguish this failure from other fails.
     * In particular, use one of the <code>expectedFailure</code> methods
     * in the surrounding <code>catch</code> block and this error will pass through it.
     * <p>
     * This method throws, never returning normally, but declares a return type 
     * so you can pretend to throw the result,
     * for instance if your calling code otherwise warns about needing to return something. */
    public static RuntimeException shouldHaveFailedPreviously() {
        throw new ShouldHaveFailedPreviouslyAssertionError();
    }
    /** As {@link #shouldHaveFailedPreviously()} but allowing detail,
     * for example a value which was received when it shouldn't have been. */
    public static void shouldHaveFailedPreviously(String message) {
        throw new ShouldHaveFailedPreviouslyAssertionError(message);
    }

    /** Tests that an exception is not {@link ShouldHaveFailedPreviouslyAssertionError}.
     * See {@link #shouldHaveFailedPreviously()} */
    public static void expectedFailure(Throwable e) {
        if (e instanceof ShouldHaveFailedPreviouslyAssertionError) throw (Error)e;
    }
    
    /**
     * Tests that an exception is not {@link ShouldHaveFailedPreviouslyAssertionError}
     * and is either one of the given types, or has a caused-by of one of the given types.
     * 
     * If you want *just* the instanceof (without checking the caused-by), then just catch
     * those exception types, treat that as success, and let any other exception be propagated.
     * 
     * @return If the test is satisfied, this method returns normally. 
     * The caller can decide whether anything more should be done with the exception.
     * If the test fails, then either it is propagated, 
     * if the {@link Throwable} is a fatal ({@link Exceptions#propagateIfFatal(Throwable)}) other than an {@link AssertionError}, 
     * or more usually the test failure of this method is thrown, 
     * with detail of the original {@link Throwable} logged and included in the caused-by.
     */
    @SuppressWarnings("unchecked")
    public static void expectedFailureOfType(Throwable e, Class<?> permittedSupertype, Class<?> ...permittedSupertypes) {
        if (e instanceof ShouldHaveFailedPreviouslyAssertionError) throw (Error) e;
        Throwable match = Exceptions.getFirstThrowableOfType(e, (Class<? extends Throwable>) permittedSupertype);
        if (match != null) return;
        for (Class<?> clazz: permittedSupertypes) {
            match = Exceptions.getFirstThrowableOfType(e, (Class<? extends Throwable>)clazz);
            if (match != null) return;
        }
        rethrowPreferredException(e, 
            new AssertionError("Error "+JavaClassNames.simpleClassName(e)+" is not any of the expected types: " + Arrays.asList(permittedSupertypes), e));
    }
    
    /** Tests {@link #expectedFailure(Throwable)} and that the <code>toString</code>
     * satisfies {@link #assertStringContains(String, String, String...)}.
     * @return as per {@link #expectedFailureOfType(Throwable, Class...)} */
    public static void expectedFailureContains(Throwable e, String phrase1ToContain, String ...optionalOtherPhrasesToContain) {
        if (e instanceof ShouldHaveFailedPreviouslyAssertionError) throw (Error)e;
        try {
            assertStringContains(Exceptions.collapseText(e), phrase1ToContain, optionalOtherPhrasesToContain);
        } catch (AssertionError ee) {
            rethrowPreferredException(e, ee);
        }
    }

    /** As {@link #expectedFailureContains(Throwable, String, String...)} but case insensitive */
    public static void expectedFailureContainsIgnoreCase(Throwable e, String phrase1ToContain, String ...optionalOtherPhrasesToContain) {
        if (e instanceof ShouldHaveFailedPreviouslyAssertionError) throw (Error)e;
        try {
            assertStringContainsIgnoreCase(Exceptions.collapseText(e), phrase1ToContain, optionalOtherPhrasesToContain);
        } catch (AssertionError ee) {
            rethrowPreferredException(e, ee);
        }
    }

    /** Tests {@link #expectedFailure(Throwable)} and that the <code>toString</code>
     * satisfies {@link #assertStringContains(String, String, String...)}.
     * @return as per {@link #expectedFailureOfType(Throwable, Class...)} */
    public static void expectedFailureDoesNotContain(Throwable e, String phrase1ToNotContain, String ...optionalOtherPhrasesToNotContain) {
        if (e instanceof ShouldHaveFailedPreviouslyAssertionError) throw (Error)e;
        try {
            assertStringDoesNotContain(Exceptions.collapseText(e), phrase1ToNotContain, optionalOtherPhrasesToNotContain);
        } catch (AssertionError ee) {
            rethrowPreferredException(e, ee);
        }
    }
    
    /** Implements the return behavior for {@link #expectedFailureOfType(Throwable, Class...)} and others,
     * to log interesting earlier errors but to suppress those which are internal or redundant. */
    private static void rethrowPreferredException(Throwable earlierPreferredIfFatalElseLogged, Throwable laterPreferredOtherwise) throws AssertionError {
        if (!(earlierPreferredIfFatalElseLogged instanceof AssertionError)) {
            Exceptions.propagateIfFatal(earlierPreferredIfFatalElseLogged);
        }
        log.warn("Detail of unexpected error: "+earlierPreferredIfFatalElseLogged, earlierPreferredIfFatalElseLogged);
        throw Exceptions.propagate(laterPreferredOtherwise);
    }

    @SuppressWarnings("rawtypes")
    private static boolean groovyTruth(Object o) {
        // TODO Doesn't handle matchers (see http://docs.codehaus.org/display/GROOVY/Groovy+Truth)
        if (o == null) {
            return false;
        } else if (o instanceof Boolean) {
            return (Boolean)o;
        } else if (o instanceof String) {
            return !((String)o).isEmpty();
        } else if (o instanceof Collection) {
            return !((Collection)o).isEmpty();
        } else if (o instanceof Map) {
            return !((Map)o).isEmpty();
        } else if (o instanceof Iterator) {
            return ((Iterator)o).hasNext();
        } else if (o instanceof Enumeration) {
            return ((Enumeration)o).hasMoreElements();
        } else {
            return true;
        }
    }
    
    @SuppressWarnings("unchecked")
    private static <T> T get(Map<String,?> map, String key, T defaultVal) {
        Object val = map.get(key);
        return (T) ((val == null) ? defaultVal : val);
    }
    
    private static Callable<?> toCallable(Runnable r) {
        return (r instanceof Callable) ? (Callable<?>)r : new RunnableAdapter<Void>(r, null);
    }
    
    /** Same as {@link java.util.concurrent.Executors#callable(Runnable)}, except includes toString() */
    static final class RunnableAdapter<T> implements Callable<T> {
        final Runnable task;
        final T result;
        RunnableAdapter(Runnable task, T result) {
            this.task = task;
            this.result = result;
        }
        @Override
        public T call() {
            task.run();
            return result;
        }
        @Override
        public String toString() {
            return "RunnableAdapter("+task+")";
        }
    }
    
    private static void sleep(long periodMs) {
        if (periodMs > 0) {
            try {
                Thread.sleep(periodMs);
            } catch (InterruptedException e) {
                throw propagate(e);
            }
        }
    }
    
    private static RuntimeException propagate(Throwable t) {
        throw Exceptions.propagate(t);
    }

    /** As {@link #eventuallyOnNotify(Object, Supplier, Predicate, Duration, boolean)} with default timeout. */
    public static <T> void eventuallyOnNotify(Object notifyTarget, Supplier<T> supplier, Predicate<T> predicate) {
        eventuallyOnNotify(notifyTarget, supplier, predicate, null);
    }
    
    /** as {@link #eventually(Supplier, Predicate)} for cases where an object is notified;
     * more efficient as it waits on the notify target object. 
     * See also the simpler {@link #eventuallyOnNotify(Object, Predicate)} when looking at a collection which is getting notified.
     * Timeout defaults to {@link #DEFAULT_SHORT_TIMEOUT}. 
     * <p>
     * This synchronizes on the notify target for the duration of the wait, 
     * including while getting and checking the value, so as not to miss any notification. */
    public static <T> void eventuallyOnNotify(Object notifyTarget, Supplier<T> supplier, Predicate<T> predicate, Duration timeout) {
        T supplied = null;
        if (timeout==null) timeout = DEFAULT_SHORT_TIMEOUT;
        CountdownTimer remaining = timeout.countdownTimer();
        int checks = 0;
        synchronized (notifyTarget) {
            do {
                if (checks>0) {
                    remaining.waitOnForExpiryUnchecked(notifyTarget);
                }
                supplied = supplier.get();
                if (predicate.apply(supplied)) return;
                checks++;
            } while (remaining.isNotExpired());
        }
        
        // should get 2 checks, 1 before and 1 after, if no notifications; if more, tell the user
        fail("Expected: eventually "+predicate+"; got most recently: "+supplied+
            " (waited "+remaining.getDurationElapsed()+
                (checks>2 ? "; notification count "+(checks-2) : "")+
            ")");
    }

    /** Convenience for {@link #eventuallyOnNotify(Object, Supplier, Predicate, Duration, boolean)} 
     * when the notify target and the value under test are the same. */
    public static <T> void eventuallyOnNotify(T object, Predicate<T> predicate, Duration timeout) {
        eventuallyOnNotify(object, Suppliers.ofInstance(object), predicate, timeout);
    }

    /** As {@link #eventuallyOnNotify(Object, Predicate, Duration)} with the default duration of {@link #eventuallyOnNotify(Object, Supplier, Predicate)}. */
    public static <T> void eventuallyOnNotify(T object, Predicate<T> predicate) {
        eventuallyOnNotify(object, Suppliers.ofInstance(object), predicate, null);
    }

    public static void assertSize(Iterable<?> list, int expectedSize) {
        if (list==null) fail("List is null");
        if (Iterables.size(list)!=expectedSize) fail("List has wrong size "+Iterables.size(list)+" (expected "+expectedSize+"): "+list);
    }

    public static void assertInstanceOf(Object obj, Class<?> type) {
        assertThat(obj, Predicates.instanceOf(type), "Expected "+type+" but found "+(obj==null ? "null" : obj+" ("+obj.getClass()+")"));
    }

    public static <T> void assertPresent(Maybe<T> candidate) {
        if (candidate.isPresent()) return;
        fail( Maybe.getException(candidate) );
    }

    public static <T> void assertNotPresent(Maybe<T> candidate) {
        if (candidate.isAbsent()) return;
        fail("Expected absent value; instead got: "+candidate.get());
    }

}
