You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by he...@apache.org on 2015/12/23 12:06:25 UTC

[02/71] [abbrv] incubator-brooklyn git commit: Merge commit 'e430723' into reorg2

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/018a0e15/brooklyn-server/utils/common/src/main/java/org/apache/brooklyn/test/Asserts.java
----------------------------------------------------------------------
diff --cc brooklyn-server/utils/common/src/main/java/org/apache/brooklyn/test/Asserts.java
index 0000000,668362a..2bd7b5c
mode 000000,100644..100644
--- a/brooklyn-server/utils/common/src/main/java/org/apache/brooklyn/test/Asserts.java
+++ b/brooklyn-server/utils/common/src/main/java/org/apache/brooklyn/test/Asserts.java
@@@ -1,0 -1,1238 +1,1236 @@@
+ /*
+  * 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 groovy.lang.Closure;
+ 
+ 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.javalang.JavaClassNames;
+ import org.apache.brooklyn.util.text.StringPredicates;
+ 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.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;
+ 
+ /**
+  * 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 {
+ 
+     /**
+      * The default timeout for assertions - 30s.
+      * Alter in individual tests by giving a "timeout" entry in method flags.
+      */
+     public static final Duration DEFAULT_TIMEOUT = Duration.THIRTY_SECONDS;
+ 
+     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(null);
+     }
+ 
+     /**
+      * 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);
+     }
+ 
+     /**
+      * 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 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
+     
+     public static <T> void eventually(Supplier<? extends T> supplier, Predicate<T> predicate) {
+         eventually(ImmutableMap.<String,Object>of(), supplier, predicate);
+     }
+     
+     public static <T> void eventually(Map<String,?> flags, Supplier<? extends T> supplier, Predicate<T> predicate) {
+         eventually(flags, supplier, predicate, (String)null);
+     }
+     
+     public static <T> void eventually(Map<String,?> flags, Supplier<? extends T> supplier, Predicate<T> predicate, String errMsg) {
+         Duration timeout = toDuration(flags.get("timeout"), Duration.ONE_SECOND);
+         Duration period = toDuration(flags.get("period"), Duration.millis(10));
+         long periodMs = period.toMilliseconds();
+         long startTime = System.currentTimeMillis();
+         long expireTime = startTime+timeout.toMilliseconds();
+         
+         boolean first = true;
+         T supplied = supplier.get();
+         while (first || System.currentTimeMillis() <= expireTime) {
+             supplied = supplier.get();
+             if (predicate.apply(supplied)) {
+                 return;
+             }
+             first = false;
+             if (periodMs > 0) sleep(periodMs);
+         }
+         fail("supplied="+supplied+"; predicate="+predicate+(errMsg!=null?"; "+errMsg:""));
+     }
+     
+     // TODO improve here -- these methods aren't very useful without timeouts
+     public static <T> void continually(Supplier<? extends T> supplier, Predicate<T> predicate) {
+         continually(ImmutableMap.<String,Object>of(), supplier, predicate);
+     }
+ 
+     public static <T> void continually(Map<String,?> flags, Supplier<? extends T> supplier, Predicate<? super T> predicate) {
+         continually(flags, supplier, predicate, (String)null);
+     }
+ 
+     public static <T> void continually(Map<String,?> flags, Supplier<? extends T> supplier, Predicate<T> predicate, String errMsg) {
+         Duration duration = toDuration(flags.get("timeout"), Duration.ONE_SECOND);
+         Duration period = toDuration(flags.get("period"), Duration.millis(10));
+         long periodMs = period.toMilliseconds();
+         long startTime = System.currentTimeMillis();
+         long expireTime = startTime+duration.toMilliseconds();
+         
+         boolean first = true;
+         while (first || System.currentTimeMillis() <= expireTime) {
+             assertTrue(predicate.apply(supplier.get()), "supplied="+supplier.get()+"; predicate="+predicate+(errMsg!=null?"; "+errMsg:""));
+             if (periodMs > 0) sleep(periodMs);
+             first = false;
+         }
+     }
+ 
+     
+     /**
+      * @see #succeedsContinually(Map, Callable)
+      */
+     public static void succeedsEventually(Runnable r) {
+         succeedsEventually(ImmutableMap.<String,Object>of(), r);
+     }
+ 
+     /**
+      * @see #succeedsContinually(Map, Callable)
+      */
+     public static void succeedsEventually(Map<String,?> flags, Runnable r) {
+         succeedsEventually(flags, toCallable(r));
+     }
+     
+     /**
+      * @see #succeedsContinually(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;
+         }
+         public String toString() {
+             return message;
+         }
+     }
+ 
+     /**
+      * 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_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_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) {
+                     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(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 result: "+result);
+         } catch (Throwable t) {
+             if (logException) log.info("failed succeeds-eventually, "+attempt+" attempts, "+
+                     (System.currentTimeMillis()-startTime)+"ms elapsed "+
+                     "(rethrowing): "+t);
+             throw propagate(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);
+     }
+     
+     public static <T> T succeedsContinually(Map<?,?> flags, Callable<T> job) {
+         Duration duration = toDuration(flags.get("timeout"), Duration.ONE_SECOND);
+         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());
+     }
+     
+     public static void assertFailsWith(Callable<?> c, final Closure<Boolean> exceptionChecker) {
+         assertFailsWith(c, new Predicate<Throwable>() {
+             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>() {
+             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) {
+         if (condition.apply(object)) return;
+         fail("Failed "+condition+": "+object);
+     }
+ 
+     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 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. */
+     public static void 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 one of the given types. 
+      * 
+      * @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. */
+     public static void expectedFailureOfType(Throwable e, Class<?> ...permittedSupertypes) {
+         if (e instanceof ShouldHaveFailedPreviouslyAssertionError) throw (Error)e;
+         for (Class<?> t: permittedSupertypes) {
+             if (t.isInstance(e)) return;
+         }
+         rethrowPreferredException(e, 
+             new AssertionError("Error "+JavaClassNames.simpleClassName(e)+" is not any of the expected types: " + Arrays.asList(permittedSupertypes)));
+     }
+     
+     /** 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(e.toString(), 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(e.toString(), phrase1ToContain, optionalOtherPhrasesToContain);
+         } catch (AssertionError ee) {
+             rethrowPreferredException(e, ee);
+         }
+     }
+ 
+     /** Implements the return beahvior for {@link #expectedFailureOfType(Throwable, Class...)} and others. */
+     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;
+         }
+         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) {
+         if (t instanceof InterruptedException) {
+             Thread.currentThread().interrupt();
+         }
+         if (t instanceof RuntimeException) throw (RuntimeException)t;
+         if (t instanceof Error) throw (Error)t;
+         throw new RuntimeException(t);
+     }
+ 
+ }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/018a0e15/brooklyn-server/utils/common/src/main/java/org/apache/brooklyn/util/collections/CollectionFunctionals.java
----------------------------------------------------------------------
diff --cc brooklyn-server/utils/common/src/main/java/org/apache/brooklyn/util/collections/CollectionFunctionals.java
index 0000000,aa6831b..91f53f9
mode 000000,100644..100644
--- a/brooklyn-server/utils/common/src/main/java/org/apache/brooklyn/util/collections/CollectionFunctionals.java
+++ b/brooklyn-server/utils/common/src/main/java/org/apache/brooklyn/util/collections/CollectionFunctionals.java
@@@ -1,0 -1,242 +1,263 @@@
+ /*
+  * 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.util.collections;
+ 
+ import java.util.Arrays;
+ import java.util.Collection;
+ import java.util.List;
+ import java.util.Map;
+ import java.util.Set;
+ 
+ import javax.annotation.Nullable;
+ 
+ import org.apache.brooklyn.util.collections.QuorumCheck.QuorumChecks;
+ 
+ import com.google.common.base.Function;
+ import com.google.common.base.Functions;
+ import com.google.common.base.Preconditions;
+ import com.google.common.base.Predicate;
+ import com.google.common.base.Predicates;
+ import com.google.common.base.Supplier;
+ import com.google.common.collect.Iterables;
+ import com.google.common.collect.Sets;
+ 
+ /** things which it seems should be in guava, but i can't find 
+  * @author alex */
+ public class CollectionFunctionals {
+ 
+     private static final class EqualsSetPredicate implements Predicate<Iterable<?>> {
+         private final Iterable<?> target;
+ 
+         private EqualsSetPredicate(Iterable<?> target) {
+             this.target = target;
+         }
+ 
+         @Override
+         public boolean apply(@Nullable Iterable<?> input) {
+             if (input==null) return false;
+             return Sets.newHashSet(target).equals(Sets.newHashSet(input));
+         }
+     }
+ 
+     private static final class KeysOfMapFunction<K> implements Function<Map<K, ?>, Set<K>> {
+         @Override
+         public Set<K> apply(Map<K, ?> input) {
+             if (input==null) return null;
+             return input.keySet();
+         }
+ 
+         @Override public String toString() { return "keys"; }
+     }
+ 
+     private static final class SizeSupplier implements Supplier<Integer> {
+         private final Iterable<?> collection;
+ 
+         private SizeSupplier(Iterable<?> collection) {
+             this.collection = collection;
+         }
+ 
+         @Override
+         public Integer get() {
+             return Iterables.size(collection);
+         }
+ 
+         @Override public String toString() { return "sizeSupplier("+collection+")"; }
+     }
+ 
+     public static final class SizeFunction implements Function<Iterable<?>, Integer> {
+         private final Integer valueIfInputNull;
+ 
+         private SizeFunction(Integer valueIfInputNull) {
+             this.valueIfInputNull = valueIfInputNull;
+         }
+ 
+         @Override
+         public Integer apply(Iterable<?> input) {
+             if (input==null) return valueIfInputNull;
+             return Iterables.size(input);
+         }
+ 
+         @Override public String toString() { return "sizeFunction"; }
+     }
+ 
+     public static Supplier<Integer> sizeSupplier(final Iterable<?> collection) {
+         return new SizeSupplier(collection);
+     }
+     
+     public static Function<Iterable<?>, Integer> sizeFunction() { return sizeFunction(null); }
+     
+     public static Function<Iterable<?>, Integer> sizeFunction(final Integer valueIfInputNull) {
+         return new SizeFunction(valueIfInputNull);
+     }
+ 
+     public static final class FirstElementFunction<T> implements Function<Iterable<? extends T>, T> {
+         public FirstElementFunction() {
+         }
+ 
+         @Override
+         public T apply(Iterable<? extends T> input) {
+             if (input==null || Iterables.isEmpty(input)) return null;
+             return Iterables.get(input, 0);
+         }
+ 
+         @Override public String toString() { return "firstElementFunction"; }
+     }
+ 
+     public static <T> Function<Iterable<? extends T>, T> firstElement() {
+         return new FirstElementFunction<T>();
+     }
+     
+     public static <K> Function<Map<K,?>,Set<K>> keys() {
+         return new KeysOfMapFunction<K>();
+     }
+ 
+     public static <K> Function<Map<K, ?>, Integer> mapSize() {
+         return mapSize(null);
+     }
+     
+     public static <K> Function<Map<K, ?>, Integer> mapSize(Integer valueIfNull) {
+         return Functions.compose(CollectionFunctionals.sizeFunction(valueIfNull), CollectionFunctionals.<K>keys());
+     }
+ 
+     /** default guava Equals predicate will reflect order of target, and will fail when matching against a list;
+      * this treats them both as sets */
+     public static Predicate<Iterable<?>> equalsSetOf(Object... target) {
+         return equalsSet(Arrays.asList(target));
+     }
+     public static Predicate<Iterable<?>> equalsSet(final Iterable<?> target) {
+         return new EqualsSetPredicate(target);
+     }
+ 
+     public static Predicate<Iterable<?>> sizeEquals(int targetSize) {
+         return Predicates.compose(Predicates.equalTo(targetSize), CollectionFunctionals.sizeFunction());
+     }
+ 
+     public static Predicate<Iterable<?>> empty() {
+         return sizeEquals(0);
+     }
+ 
+     public static Predicate<Iterable<?>> notEmpty() {
+         return Predicates.not(empty());
+     }
+ 
+     public static <K> Predicate<Map<K,?>> mapSizeEquals(int targetSize) {
+         return Predicates.compose(Predicates.equalTo(targetSize), CollectionFunctionals.<K>mapSize());
+     }
+ 
+     public static <T,I extends Iterable<T>> Function<I, List<T>> limit(final int max) {
+         return new LimitFunction<T,I>(max);
+     }
+ 
+     private static final class LimitFunction<T, I extends Iterable<T>> implements Function<I, List<T>> {
+         private final int max;
+         private LimitFunction(int max) {
+             this.max = max;
+         }
+         @Override
+         public List<T> apply(I input) {
+             if (input==null) return null;
+             MutableList<T> result = MutableList.of();
+             for (T i: input) {
+                 result.add(i);
+                 if (result.size()>=max)
+                     return result;
+             }
+             return result;
+         }
+     }
+ 
+     // ---------
+     public static <I,T extends Collection<I>> Predicate<T> contains(I item) {
+         return new CollectionContains<I,T>(item);
+     }
+     
+     private static final class CollectionContains<I,T extends Collection<I>> implements Predicate<T> {
+         private final I item;
+         private CollectionContains(I item) {
+             this.item = item;
+         }
+         @Override
+         public boolean apply(T input) {
+             if (input==null) return false;
+             return input.contains(item);
+         }
+         @Override
+         public String toString() {
+             return "contains("+item+")";
+         }
+     }
+ 
+     // ---------
+     
++    /** 
++     * Returns a predicate for a collection which is true if 
++     * all elements in the collection given to the predicate
++     * which satisfies the predicate given here.
++     * <p>
++     * This will return true for the empty set.
++     * To require additionally that there is at least one
++     * use {@link #quorum(QuorumCheck, Predicate)} with
++     * {@link QuorumChecks#allAndAtLeastOne()}. */
+     public static <T,TT extends Iterable<T>> Predicate<TT> all(Predicate<T> attributeSatisfies) {
+         return quorum(QuorumChecks.all(), attributeSatisfies);
+     }
+ 
++    /** Returns a predicate for a collection which is true if 
++     * there is at least one element in the collection given to the predicate
++     * which satisfies the predicate given here. 
++     */
++    public static <T,TT extends Iterable<T>> Predicate<TT> any(Predicate<T> attributeSatisfies) {
++        // implementation could be more efficient -- ie succeed fast
++        return quorum(QuorumChecks.atLeastOne(), attributeSatisfies);
++    }
++
++    /** Returns a predicate for a collection which is true if 
++     * the number of elements in the collection satisfying the predicate given here
++     * passes the {@link QuorumCheck} given here.
++     */
+     public static <T,TT extends Iterable<T>> Predicate<TT> quorum(QuorumCheck quorumCheck, Predicate<T> attributeSatisfies) {
+         return new QuorumSatisfies<T, TT>(quorumCheck, attributeSatisfies);
+     }
+ 
 -
+     private static final class QuorumSatisfies<I,T extends Iterable<I>> implements Predicate<T> {
+         private final Predicate<I> itemCheck;
+         private final QuorumCheck quorumCheck;
+         private QuorumSatisfies(QuorumCheck quorumCheck, Predicate<I> itemCheck) {
+             this.itemCheck = Preconditions.checkNotNull(itemCheck, "itemCheck");
+             this.quorumCheck = Preconditions.checkNotNull(quorumCheck, "quorumCheck");
+         }
+         @Override
+         public boolean apply(T input) {
+             if (input==null) return false;
+             int sizeHealthy = 0, totalSize = 0;
+             for (I item: input) {
+                 totalSize++;
+                 if (itemCheck.apply(item)) sizeHealthy++;
+             }
+             return quorumCheck.isQuorate(sizeHealthy, totalSize);
+         }
+         @Override
+         public String toString() {
+             return quorumCheck.toString()+"("+itemCheck+")";
+         }
+     }
+ 
+ 
+ 
+ }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/018a0e15/brooklyn-server/utils/common/src/main/java/org/apache/brooklyn/util/guava/Maybe.java
----------------------------------------------------------------------
diff --cc brooklyn-server/utils/common/src/main/java/org/apache/brooklyn/util/guava/Maybe.java
index 0000000,2264362..6b3df49
mode 000000,100644..100644
--- a/brooklyn-server/utils/common/src/main/java/org/apache/brooklyn/util/guava/Maybe.java
+++ b/brooklyn-server/utils/common/src/main/java/org/apache/brooklyn/util/guava/Maybe.java
@@@ -1,0 -1,296 +1,376 @@@
+ /*
+  * 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.util.guava;
+ 
+ import static com.google.common.base.Preconditions.checkNotNull;
+ 
+ import java.io.Serializable;
+ import java.lang.ref.SoftReference;
+ import java.util.Collections;
+ import java.util.Iterator;
+ import java.util.Set;
+ 
+ import javax.annotation.Nonnull;
+ import javax.annotation.Nullable;
+ 
+ import org.apache.brooklyn.util.javalang.JavaClassNames;
+ 
+ import com.google.common.annotations.Beta;
+ import com.google.common.base.Function;
+ import com.google.common.base.Objects;
+ import com.google.common.base.Optional;
+ import com.google.common.base.Preconditions;
+ import com.google.common.base.Supplier;
+ import com.google.common.collect.AbstractIterator;
+ import com.google.common.collect.ImmutableSet;
+ 
+ /** Like Guava Optional but permitting null and permitting errors to be thrown. */
+ public abstract class Maybe<T> implements Serializable, Supplier<T> {
+ 
+     private static final long serialVersionUID = -6372099069863179019L;
+ 
+     public static <T> Maybe<T> absent() {
+         return new Absent<T>();
+     }
+ 
+     /** Creates an absent whose get throws an {@link IllegalStateException} with the indicated message.
+      * Both stack traces (the cause and the callers) are provided, which can be quite handy. */
+     public static <T> Maybe<T> absent(final String message) {
+         return absent(new IllegalStateExceptionSupplier(message));
+     }
+ 
+     /** Creates an absent whose get throws an {@link IllegalStateException} with the indicated cause.
+      * Both stack traces (the cause and the callers) are provided, which can be quite handy. */
+     public static <T> Maybe<T> absent(final Throwable cause) {
+         return absent(new IllegalStateExceptionSupplier(cause));
+     }
+     
+     /** Creates an absent whose get throws an {@link IllegalStateException} with the indicated message and underlying cause.
+      * Both stack traces (the cause and the callers) are provided, which can be quite handy. */
+     public static <T> Maybe<T> absent(final String message, final Throwable cause) {
+         return absent(new IllegalStateExceptionSupplier(message, cause));
+     }
+     
+     /** Creates an absent whose get throws an {@link RuntimeException} generated on demand from the given supplier */
+     public static <T> Maybe<T> absent(final Supplier<? extends RuntimeException> exceptionSupplier) {
+         return new Absent<T>(Preconditions.checkNotNull(exceptionSupplier));
+     }
++
++    /** as {@link #absentNull(String)} but with a generic message */
++    public static <T> Maybe<T> absentNull() {
++        return absentNull("value is null");
++    }
+     
 -    public static <T> Maybe<T> of(@Nullable T value) {
++    /** like {@link #absent(String)} but {@link #isNull()} will return true on the result. */
++    public static <T> Maybe<T> absentNull(String message) {
++        return new AbsentNull<T>(message);
++    }
++    
++    /** Creates a new Maybe object which is present. 
++     * The argument may be null and the object still present, 
++     * which may be confusing in some contexts
++     * (traditional {@link Optional} usages) but
++     * may be natural in others (where null is a valid value, distinguished from no value set). 
++     * See also {@link #ofDisallowingNull(Object)}. */
++    public static <T> Maybe<T> ofAllowingNull(@Nullable T value) {
+         return new Present<T>(value);
+     }
+ 
 -    /** creates an instance wrapping a {@link SoftReference}, so it might go absent later on */
 -    public static <T> Maybe<T> soft(T value) {
++    /** Creates a new Maybe object which is present if and only if the argument is not null.
++     * If the argument is null, then an {@link #absentNull()} is returned,
++     * on which {@link #isNull()} will be true. */
++    public static <T> Maybe<T> ofDisallowingNull(@Nullable T value) {
++        if (value==null) return absentNull();
++        return new Present<T>(value);
++    }
++
++    /** Creates a new Maybe object.
++     * Currently this uses {@link #ofAllowingNull(Object)} semantics,
++     * but it is recommended to use that method for clarity 
++     * if the argument might be null. */
++    // note: Optional throws if null is supplied; we might want to do the same here
++    public static <T> Maybe<T> of(@Nullable T value) {
++        return ofAllowingNull(value);
++    }
++
++    /** Creates a new Maybe object using {@link #ofDisallowingNull(Object)} semantics. 
++     * It is recommended to use that method for clarity. 
++     * This method is provided for consistency with {@link Optional#fromNullable(Object)}. */
++    public static <T> Maybe<T> fromNullable(@Nullable T value) {
++        return ofDisallowingNull(value);
++    }
++    
++    /** creates an instance wrapping a {@link SoftReference}, so it might go absent later on.
++     * if null is supplied the result is a present null. */
++    public static <T> Maybe<T> soft(@Nonnull T value) {
+         return softThen(value, null);
+     }
 -    /** creates an instance wrapping a {@link SoftReference}, using the second item given if lost */
++    /** creates an instance wrapping a {@link SoftReference}, using the second item given 
++     * if the first argument is dereferenced.
++     * however if the first argument is null, this is a permanent present null,
++     * as {@link #of(Object)} with null. */
+     public static <T> Maybe<T> softThen(T value, Maybe<T> ifEmpty) {
+         if (value==null) return of((T)null);
+         return new SoftlyPresent<T>(value).usingAfterExpiry(ifEmpty);
+     }
+ 
 -    /** like {@link Optional#fromNullable(Object)}, returns absent if the argument is null */
 -    public static <T> Maybe<T> fromNullable(@Nullable T value) {
 -        if (value==null) return absent();
 -        return new Present<T>(value);
 -    }
 -
+     public static <T> Maybe<T> of(final Optional<T> value) {
+         if (value.isPresent()) return new AbstractPresent<T>() {
+             private static final long serialVersionUID = -5735268814211401356L;
+             @Override
+             public T get() {
+                 return value.get();
+             }
++            @Override
++            public boolean isNull() {
++                // should always be false as per Optional contract
++                return get()==null;
++            }
+         };
+         return absent();
+     }
+     
+     public static <T> Maybe<T> of(final Supplier<T> value) {
+         return new AbstractPresent<T>() {
+             private static final long serialVersionUID = -5735268814211401356L;
+             @Override
+             public T get() {
+                 return value.get();
+             }
++            @Override
++            public boolean isNull() {
++                return get()==null;
++            }
+         };
+     }
+     
+     /** returns a Maybe containing the next element in the iterator, or absent if none */ 
+     public static <T> Maybe<T> next(Iterator<T> iterator) {
+         return iterator.hasNext() ? Maybe.of(iterator.next()) : Maybe.<T>absent();
+     }
+ 
+     public abstract boolean isPresent();
+     public abstract T get();
+     
+     public boolean isAbsent() {
+         return !isPresent(); 
+     }
+     public boolean isAbsentOrNull() {
 -        return !isPresentAndNonNull();
++        return isAbsent() || isNull();
+     }
+     public boolean isPresentAndNonNull() {
 -        return isPresent() && get()!=null;
++        return isPresent() && !isNull();
+     }
++    /** Whether the value is null, if present, or
++     * if it was specified as absent because it was null,
++     * e.g. using {@link #fromNullable(Object)}.
++     */
++    public abstract boolean isNull();
+     
+     public T or(T nextValue) {
+         if (isPresent()) return get();
+         return nextValue;
+     }
+ 
+     public Maybe<T> or(Maybe<T> nextValue) {
+         if (isPresent()) return this;
+         return nextValue;
+     }
+ 
+     public T or(Supplier<T> nextValue) {
+         if (isPresent()) return get();
+         return nextValue.get();
+     }
+ 
+     public T orNull() {
+         if (isPresent()) return get();
+         return null;
+     }
+     
+     public Set<T> asSet() {
+         if (isPresent()) return ImmutableSet.of(get());
+         return Collections.emptySet();
+     }
+     
+     public <V> Maybe<V> transform(final Function<? super T, V> f) {
+         if (isPresent()) return new AbstractPresent<V>() {
+             private static final long serialVersionUID = 325089324325L;
+             public V get() {
+                 return f.apply(Maybe.this.get());
+             }
++            @Override
++            public boolean isNull() {
++                return get()==null;
++            }
+         };
+         return absent();
+     }
+ 
+     /**
+      * Returns the value of each present instance from the supplied {@code maybes}, in order,
+      * skipping over occurrences of {@link Maybe#absent()}. Iterators are unmodifiable and are
+      * evaluated lazily.
+      *
+      * @see Optional#presentInstances(Iterable)
+      */
+     @Beta
+     public static <T> Iterable<T> presentInstances(final Iterable<? extends Maybe<? extends T>> maybes) {
+         checkNotNull(maybes);
+         return new Iterable<T>() {
+             @Override
+             public Iterator<T> iterator() {
+                 return new AbstractIterator<T>() {
+                     private final Iterator<? extends Maybe<? extends T>> iterator = checkNotNull(maybes.iterator());
+ 
+                     @Override
+                     protected T computeNext() {
+                         while (iterator.hasNext()) {
+                             Maybe<? extends T> maybe = iterator.next();
+                             if (maybe.isPresent()) { return maybe.get(); }
+                         }
+                         return endOfData();
+                     }
+                 };
+             }
+         };
+     }
+     
+     public static class Absent<T> extends Maybe<T> {
+         private static final long serialVersionUID = -757170462010887057L;
+         private final Supplier<? extends RuntimeException> exception;
+         public Absent() {
+             this(IllegalStateExceptionSupplier.EMPTY_EXCEPTION);
+         }
+         public Absent(Supplier<? extends RuntimeException> exception) {
+             this.exception = exception;
+         }
+         @Override
+         public boolean isPresent() {
+             return false;
+         }
+         @Override
++        public boolean isNull() {
++            return false;
++        }
++        @Override
+         public T get() {
+             throw getException();
+         }
+         public RuntimeException getException() {
+             return exception.get();
+         }
+     }
+ 
++    public static class AbsentNull<T> extends Absent<T> {
++        private static final long serialVersionUID = 2422627709567857268L;
++        public AbsentNull(String message) {
++            super(new IllegalStateExceptionSupplier(message));
++        }
++        @Override
++        public boolean isNull() {
++            return true;
++        }
++    }
++    
+     public static abstract class AbstractPresent<T> extends Maybe<T> {
+         private static final long serialVersionUID = -2266743425340870492L;
+         protected AbstractPresent() {
+         }
+         @Override
+         public boolean isPresent() {
+             return true;
+         }
+     }
+ 
+     public static class Present<T> extends AbstractPresent<T> {
+         private static final long serialVersionUID = 436799990500336015L;
+         private final T value;
+         protected Present(T value) {
+             this.value = value;
+         }
+         @Override
+         public T get() {
+             return value;
+         }
++        @Override
++        public boolean isNull() {
++            return value==null;
++        }
+     }
+ 
+     public static class SoftlyPresent<T> extends Maybe<T> {
+         private static final long serialVersionUID = 436799990500336015L;
+         private final SoftReference<T> value;
+         private Maybe<T> defaultValue;
+         protected SoftlyPresent(@Nonnull T value) {
+             this.value = new SoftReference<T>(value);
+         }
+         @Override
+         public T get() {
+             T result = value.get();
+             if (result!=null) return result;
+             if (defaultValue==null) throw new IllegalStateException("Softly present item has been GC'd");
+             return defaultValue.get();
+         }
+         @Override
+         public T orNull() {
+             T result = value.get();
+             if (result!=null) return result;
+             if (defaultValue==null) return null;
+             return defaultValue.orNull();
+         }
+         @Override
+         public boolean isPresent() {
+             return value.get()!=null || (defaultValue!=null && defaultValue.isPresent()); 
+         }
++        @Override
++        public boolean isNull() {
++            // null not allowed here
++            return false;
++        }
+         public Maybe<T> solidify() {
+             return Maybe.fromNullable(value.get());
+         }
+         SoftlyPresent<T> usingAfterExpiry(Maybe<T> defaultValue) {
+             this.defaultValue = defaultValue;
+             return this;
+         }
+     }
+ 
+     @Override
+     public String toString() {
+         return JavaClassNames.simpleClassName(this)+"["+(isPresent()?"value="+get():"")+"]";
+     }
+ 
+     @Override
+     public int hashCode() {
+         if (!isPresent()) return Objects.hashCode(31, isPresent());
+         return Objects.hashCode(31, get());
+     }
+     
+     @Override
+     public boolean equals(Object obj) {
+         if (!(obj instanceof Maybe)) return false;
+         Maybe<?> other = (Maybe<?>)obj;
+         if (!isPresent()) 
+             return !other.isPresent();
+         return Objects.equal(get(), other.get());
+     }
+     
+ }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/018a0e15/brooklyn-server/utils/common/src/main/java/org/apache/brooklyn/util/text/ComparableVersion.java
----------------------------------------------------------------------
diff --cc brooklyn-server/utils/common/src/main/java/org/apache/brooklyn/util/text/ComparableVersion.java
index 0000000,9065603..339f1ca
mode 000000,100644..100644
--- a/brooklyn-server/utils/common/src/main/java/org/apache/brooklyn/util/text/ComparableVersion.java
+++ b/brooklyn-server/utils/common/src/main/java/org/apache/brooklyn/util/text/ComparableVersion.java
@@@ -1,0 -1,89 +1,90 @@@
+ /*
+  * 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.util.text;
+ 
+ 
 -/** takes a version string, and compares to other versions, using {@link NaturalOrderComparator} */
++/** takes a version string, and compares to other versions, 
++ * using {@link VersionComparator} */
+ public class ComparableVersion implements Comparable<String> {
+ 
+     public final String version;
+     
+     public ComparableVersion(String version) {
+         this.version = version;
+     }
+ 
+     public int compareTo(String target) {
 -        return new NaturalOrderComparator().compare(version, target);
++        return VersionComparator.INSTANCE.compare(version, target);
+     }
+     
+     public boolean isGreaterThanOrEqualTo(String target) {
+         return compareTo(target) >= 0;
+     }
+     public boolean isGreaterThanAndNotEqualTo(String target) {
+         return compareTo(target) > 0;
+     }
+     public boolean isLessThanOrEqualTo(String target) {
+         return compareTo(target) <= 0;
+     }
+     public boolean isLessThanAndNotEqualTo(String target) {
+         return compareTo(target) < 0;
+     }
+ 
+     /** inclusive at endpoints */
+     public boolean isInRange(String lowerBound, String upperBound) {
+         return isGreaterThanAndNotEqualTo(lowerBound) && isLessThanAndNotEqualTo(upperBound);
+     }
+ 
+     /** parses a string expressed with common mathematical sematics,
+      * as either square brackets (inclusive), round brackets (exclusive), or one of each,
+      * surrounding a pair of version strings separated by a comma, where a version string 
+      * consists of any non-whitespace non-bracket characters 
+      * (ie numbers, letters, dots, hyphens, underscores) or is empty (to indicate no bound); 
+      * e.g. "[10.6,10.7)" to mean >= 10.6 and < 10.7;
+      * "[10.6,)" to mean >= 10.6.
+      */
+     public boolean isInRange(String range) {
+         String r = range.trim();
+         boolean strictLeft, strictRight;
+         
+         if (r.startsWith("(")) strictLeft = true;
+         else if (r.startsWith("[")) strictLeft = false;
+         else throw new IllegalArgumentException("Range must start with ( or [");
+         if (r.endsWith(")")) strictRight = true;
+         else if (r.endsWith("]")) strictRight = false;
+         else throw new IllegalArgumentException("Range must end with ) or ]");
+         
+         int i = r.indexOf(",");
+         if (i==-1) throw new IllegalArgumentException("Range must contain , following the open bracket and version");
+         String left = r.substring(1, i).trim();
+         String right = r.substring(i+1, r.length()-1).trim();
+         
+         if (left.length()>0) {
+             if (strictLeft && compareTo(left)<=0) return false; 
+             if (!strictLeft && compareTo(left)<0) return false; 
+         }
+         if (right.length()>0) {
+             if (strictRight && compareTo(right)>=0) return false; 
+             if (!strictRight && compareTo(right)>0) return false; 
+         }
+ 
+         return true;
+     }
+ 
+ }