You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by ha...@apache.org on 2015/08/18 17:03:26 UTC

[17/64] [abbrv] incubator-brooklyn git commit: BROOKLYN-162 - apply org.apache package prefix to utils-common

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/cf2f7a93/utils/common/src/main/java/org/apache/brooklyn/util/collections/MutableList.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/collections/MutableList.java b/utils/common/src/main/java/org/apache/brooklyn/util/collections/MutableList.java
new file mode 100644
index 0000000..4f8206a
--- /dev/null
+++ b/utils/common/src/main/java/org/apache/brooklyn/util/collections/MutableList.java
@@ -0,0 +1,251 @@
+/*
+ * 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.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.annotation.Nullable;
+
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.collect.ImmutableList;
+
+public class MutableList<V> extends ArrayList<V> {
+    private static final long serialVersionUID = -5533940507175152491L;
+
+    private static final Logger log = LoggerFactory.getLogger(MutableList.class);
+    
+    public static <V> MutableList<V> of() {
+        return new MutableList<V>();
+    }
+    
+    public static <V> MutableList<V> of(V v1) {
+        MutableList<V> result = new MutableList<V>();
+        result.add(v1);
+        return result;
+    }
+    
+    public static <V> MutableList<V> of(V v1, V v2, V ...vv) {
+        MutableList<V> result = new MutableList<V>();
+        result.add(v1);
+        result.add(v2);
+        for (V v: vv) result.add(v);
+        return result;
+    }
+
+    public static <V> MutableList<V> copyOf(@Nullable Iterable<? extends V> orig) {
+        return (orig instanceof Collection)
+                ? new MutableList<V>((Collection<? extends V>)orig)
+                : orig!=null ? new MutableList<V>(orig) : new MutableList<V>();
+    }
+
+    public static <E> MutableList<E> copyOf(Iterator<? extends E> elements) {
+        if (!elements.hasNext()) {
+            return of();
+        }
+        return new MutableList.Builder<E>().addAll(elements).build();
+    }
+
+    public MutableList() {
+    }
+
+    public MutableList(Collection<? extends V> source) {
+        super(source);
+    }
+
+    public MutableList(Iterable<? extends V> source) {
+        for (V s : source) {
+            add(s);
+        }
+    }
+    
+    /** @deprecated since 0.7.0, use {@link #asImmutableCopy()}, or {@link #asUnmodifiable()} / {@link #asUnmodifiableCopy()} */ @Deprecated
+    public ImmutableList<V> toImmutable() {
+        return ImmutableList.copyOf(this);
+    }
+    /** creates an {@link ImmutableList} which is a copy of this list.  note that the list should not contain nulls.  */
+    public List<V> asImmutableCopy() {
+        try {
+            return ImmutableList.copyOf(this);
+        } catch (Exception e) {
+            Exceptions.propagateIfFatal(e);
+            log.warn("Error converting list to Immutable, using unmodifiable instead: "+e, e);
+            return asUnmodifiableCopy();
+        }
+    }
+    /** creates a {@link Collections#unmodifiableList(List)} wrapper around this list. the method is efficient,
+     * as there is no copying, but the returned view might change if the list here is changed.  */
+    public List<V> asUnmodifiable() {
+        return Collections.unmodifiableList(this);
+    }
+    /** creates a {@link Collections#unmodifiableList(List)} of a copy of this list.
+     * the returned item is immutable, but unlike {@link #asImmutableCopy()} nulls are permitted. */
+    public List<V> asUnmodifiableCopy() {
+        return Collections.unmodifiableList(MutableList.copyOf(this));
+    }
+
+    public static <V> Builder<V> builder() {
+        return new Builder<V>();
+    }
+
+    /**
+     * @see guava's ImMutableList.Builder
+     */
+    public static class Builder<V> {
+        final MutableList<V> result = new MutableList<V>();
+
+        public Builder() {}
+
+        public Builder<V> add(V value) {
+            result.add(value);
+            return this;
+        }
+
+        public Builder<V> add(V value1, V value2, V ...values) {
+            result.add(value1);
+            result.add(value2);
+            for (V v: values) result.add(v);
+            return this;
+        }
+
+        public Builder<V> remove(V val) {
+            result.remove(val);
+            return this;
+        }
+        
+        public Builder<V> addAll(Iterable<? extends V> iterable) {
+            if (iterable instanceof Collection) {
+                result.addAll((Collection<? extends V>) iterable);
+            } else {
+                for (V v : iterable) {
+                    result.add(v);
+                }
+            }
+            return this;
+        }
+
+        public Builder<V> addAll(Iterator<? extends V> iter) {
+            while (iter.hasNext()) {
+                add(iter.next());
+            }
+            return this;
+        }
+
+        public Builder<V> addAll(V[] vals) {
+            for (V v : vals) {
+                result.add(v);
+            }
+            return this;
+        }
+
+        public Builder<V> removeAll(Iterable<? extends V> iterable) {
+            if (iterable instanceof Collection) {
+                result.removeAll((Collection<? extends V>) iterable);
+            } else {
+                for (V v : iterable) {
+                    result.remove(v);
+                }
+            }
+            return this;
+        }
+
+        public MutableList<V> build() {
+          return new MutableList<V>(result);
+        }
+        
+        public ImmutableList<V> buildImmutable() {
+            return ImmutableList.copyOf(result);
+        }
+
+        public Builder<V> addLists(Iterable<? extends V> ...items) {
+            for (Iterable<? extends V> item: items) {
+                addAll(item);
+            }
+            return this;
+        }
+    }
+    
+    /** as {@link List#add(Object)} but fluent style */
+    public MutableList<V> append(V item) {
+        add(item);
+        return this;
+    }
+
+    /** as {@link List#add(Object)} but excluding nulls, and fluent style */
+    public MutableList<V> appendIfNotNull(V item) {
+        if (item!=null) add(item);
+        return this;
+    }
+
+    /** as {@link List#add(Object)} but accepting multiple, and fluent style */
+    public MutableList<V> append(V item1, V item2, V ...items) {
+        add(item1);
+        add(item2);
+        for (V item: items) add(item);
+        return this;
+    }
+
+    /** as {@link List#add(Object)} but excluding nulls, accepting multiple, and fluent style */
+    public MutableList<V> appendIfNotNull(V item1, V item2, V ...items) {
+        if (item1!=null) add(item1);
+        if (item2!=null) add(item2);
+        for (V item: items) 
+            if (item!=null) add(item);
+        return this;
+    }
+
+    /** as {@link List#addAll(Collection)} but fluent style */
+    public MutableList<V> appendAll(Iterable<? extends V> items) {
+        if (items!=null)
+            for (V item: items) add(item);
+        return this;
+    }
+    /** as {@link List#addAll(Collection)} but fluent style */
+    public MutableList<V> appendAll(Iterator<? extends V> items) {
+        addAll(items);
+        return this;
+    }
+
+    public boolean addAll(Iterable<? extends V> setToAdd) {
+        // copy of parent, but accepting Iterable and null
+        if (setToAdd==null) return false;
+        return addAll(setToAdd.iterator());
+    }
+    public boolean addAll(Iterator<? extends V> setToAdd) {
+        if (setToAdd==null) return false;
+        boolean modified = false;
+        while (setToAdd.hasNext()) {
+            if (add(setToAdd.next()))
+                modified = true;
+        }
+        return modified;
+    }
+
+    public boolean removeIfNotNull(V item) {
+        if (item==null) return false;
+        return remove(item);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/cf2f7a93/utils/common/src/main/java/org/apache/brooklyn/util/collections/MutableMap.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/collections/MutableMap.java b/utils/common/src/main/java/org/apache/brooklyn/util/collections/MutableMap.java
new file mode 100644
index 0000000..8ca2652
--- /dev/null
+++ b/utils/common/src/main/java/org/apache/brooklyn/util/collections/MutableMap.java
@@ -0,0 +1,253 @@
+/*
+ * 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.Collections;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import javax.annotation.Nullable;
+
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.apache.brooklyn.util.guava.Maybe;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableMap;
+
+/** Map impl, exposing simple builder operations (add) in a fluent-style API,
+ * where the final map is mutable.  You can also toImmutable. */
+public class MutableMap<K,V> extends LinkedHashMap<K,V> {
+    
+    private static final long serialVersionUID = -2463168443382874384L;
+    private static final Logger log = LoggerFactory.getLogger(MutableMap.class);
+
+    public static <K,V> MutableMap<K,V> of() {
+        return new MutableMap<K,V>();
+    }
+    
+    public static <K,V> MutableMap<K,V> of(K k1, V v1) {
+        MutableMap<K,V> result = new MutableMap<K,V>();
+        result.put(k1, v1);
+        return result;
+    }
+    
+    public static <K,V> MutableMap<K,V> of(K k1, V v1, K k2, V v2) {
+        MutableMap<K,V> result = new MutableMap<K,V>();
+        result.put(k1, v1);
+        result.put(k2, v2);
+        return result;
+    }
+    
+    public static <K,V> MutableMap<K,V> of(K k1, V v1, K k2, V v2, K k3, V v3) {
+        MutableMap<K,V> result = new MutableMap<K,V>();
+        result.put(k1, v1);
+        result.put(k2, v2);
+        result.put(k3, v3);
+        return result;
+    }
+
+    public static <K,V> MutableMap<K,V> of(K k1, V v1, K k2, V v2, K k3, V v3,K k4, V v4) {
+        MutableMap<K,V> result = new MutableMap<K,V>();
+        result.put(k1, v1);
+        result.put(k2, v2);
+        result.put(k3, v3);
+        result.put(k4, v4);
+        return result;
+    }
+
+    public static <K,V> MutableMap<K,V> of(K k1, V v1, K k2, V v2, K k3, V v3,K k4, V v4,K k5, V v5) {
+        MutableMap<K,V> result = new MutableMap<K,V>();
+        result.put(k1, v1);
+        result.put(k2, v2);
+        result.put(k3, v3);
+        result.put(k4, v4);
+        result.put(k5, v5);
+        return result;
+    }
+
+    public static <K,V> MutableMap<K,V> of(K k1, V v1, K k2, V v2, K k3, V v3,K k4, V v4,K k5, V v5,K k6,V v6) {
+        MutableMap<K,V> result = new MutableMap<K,V>();
+        result.put(k1, v1);
+        result.put(k2, v2);
+        result.put(k3, v3);
+        result.put(k4, v4);
+        result.put(k5, v5);
+        result.put(k6, v6);
+        return result;
+    }
+
+    public static <K,V> MutableMap<K,V> of(K k1, V v1, K k2, V v2, K k3, V v3,K k4, V v4,K k5, V v5,K k6,V v6,K k7,V v7) {
+        MutableMap<K,V> result = new MutableMap<K,V>();
+        result.put(k1, v1);
+        result.put(k2, v2);
+        result.put(k3, v3);
+        result.put(k4, v4);
+        result.put(k5, v5);
+        result.put(k6, v6);
+        result.put(k7, v7);
+        return result;
+     }
+
+    public static <K,V> MutableMap<K,V> copyOf(@Nullable Map<? extends K, ? extends V> orig) {
+        MutableMap<K,V> result = new MutableMap<K,V>();
+        if (orig!=null)
+            result.putAll(orig);
+        return result;
+    }
+    
+    public MutableMap() {}
+    @SuppressWarnings("unchecked")
+    public MutableMap(@SuppressWarnings("rawtypes") Map source) { super(source); }
+
+    /** as {@link #put(Object, Object)} but fluent style */
+    public MutableMap<K,V> add(K key, V value) {
+        put(key, value);
+        return this;
+    }
+
+    /** as {@link #putAll(Map)} but fluent style (and accepting null, ignoring it) */
+    public MutableMap<K,V> add(@Nullable Map<? extends K,? extends V> m) {
+        if (m!=null) putAll(m);
+        return this;
+    }
+
+    /** as {@link #put(Object, Object)} but excluding null values, and fluent style */
+    public MutableMap<K,V> addIfNotNull(K key, V value) {
+        if (value!=null) add(key, value);
+        return this;
+    }
+
+    public Maybe<V> getMaybe(K key) {
+        if (containsKey(key)) return Maybe.of(get(key));
+        return Maybe.absent("No entry for key '"+key+"' in this map");
+    }
+    
+    /** @deprecated since 0.7.0, use {@link #asImmutableCopy()}, or {@link #asUnmodifiable()} / {@link #asUnmodifiableCopy()} */ @Deprecated
+    public ImmutableMap<K,V> toImmutable() {
+        return ImmutableMap.copyOf(this);
+    }
+    /** as {@link MutableList#asImmutableCopy()} */
+    public Map<K,V> asImmutableCopy() {
+        try {
+            return ImmutableMap.copyOf(this);
+        } catch (Exception e) {
+            Exceptions.propagateIfFatal(e);
+            log.warn("Error converting list to Immutable, using unmodifiable instead: "+e, e);
+            return asUnmodifiableCopy();
+        }
+    }
+    /** as {@link MutableList#asUnmodifiable()} */
+    public Map<K,V> asUnmodifiable() {
+        return Collections.unmodifiableMap(this);
+    }
+    /** as {@link MutableList#asUnmodifiableCopy()} */
+    public Map<K,V> asUnmodifiableCopy() {
+        return Collections.unmodifiableMap(MutableMap.copyOf(this));
+    }
+    
+    public static <K, V> Builder<K, V> builder() {
+        return new Builder<K,V>();
+    }
+
+    /**
+     * @see guava's ImmutableMap.Builder
+     */
+    public static class Builder<K, V> {
+        final MutableMap<K,V> result = new MutableMap<K,V>();
+
+        public Builder() {}
+
+        public Builder<K, V> put(K key, V value) {
+            result.put(key, value);
+            return this;
+        }
+
+        public Builder<K, V> putIfNotNull(K key, V value) {
+            if (value!=null) result.put(key, value);
+            return this;
+        }
+
+        public Builder<K, V> putIfAbsent(K key, V value) {
+            if (!result.containsKey(key)) result.put(key, value);
+            return this;
+        }
+
+        public Builder<K, V> put(Entry<? extends K, ? extends V> entry) {
+            result.put(entry.getKey(), entry.getValue());
+            return this;
+        }
+
+        public Builder<K, V> putAll(Map<? extends K, ? extends V> map) {
+            result.add(map);
+            return this;
+        }
+
+        public Builder<K, V> remove(K key) {
+            result.remove(key);
+            return this;
+        }
+        
+        public Builder<K, V> removeAll(K... keys) {
+            for (K key : keys) {
+                result.remove(key);
+            }
+            return this;
+        }
+
+        public Builder<K, V> removeAll(Iterable<? extends K> keys) {
+            for (K key : keys) {
+                result.remove(key);
+            }
+            return this;
+        }
+
+        /** moves the value stored under oldKey to newKey, if there was such a value */
+        public Builder<K, V> renameKey(K oldKey, K newKey) {
+            if (result.containsKey(oldKey)) {
+                V oldValue = result.remove(oldKey);
+                result.put(newKey, oldValue);
+            }
+            return this;
+        }
+        
+        public MutableMap<K, V> build() {
+          return new MutableMap<K,V>(result);
+        }
+        
+        public Builder<K, V> filterValues(Predicate<? super V> filter) {
+            for (Iterator<V> iter = result.values().iterator(); iter.hasNext();) {
+                V val = iter.next();
+                if (!filter.apply(val)) iter.remove();
+            }
+            return this;
+        }
+        
+        public Builder<K, V> filterKeys(Predicate<? super K> filter) {
+            for (Iterator<K> iter = result.keySet().iterator(); iter.hasNext();) {
+                K key = iter.next();
+                if (!filter.apply(key)) iter.remove();
+            }
+            return this;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/cf2f7a93/utils/common/src/main/java/org/apache/brooklyn/util/collections/MutableSet.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/collections/MutableSet.java b/utils/common/src/main/java/org/apache/brooklyn/util/collections/MutableSet.java
new file mode 100644
index 0000000..f851305
--- /dev/null
+++ b/utils/common/src/main/java/org/apache/brooklyn/util/collections/MutableSet.java
@@ -0,0 +1,207 @@
+/*
+ * 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.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+import javax.annotation.Nullable;
+
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+
+public class MutableSet<V> extends LinkedHashSet<V> {
+    
+    private static final long serialVersionUID = 2330133488446834595L;
+    private static final Logger log = LoggerFactory.getLogger(MutableSet.class);
+
+    public static <V> MutableSet<V> of() {
+        return new MutableSet<V>();
+    }
+    
+    public static <V> MutableSet<V> of(V v1) {
+        MutableSet<V> result = new MutableSet<V>();
+        result.add(v1);
+        return result;
+    }
+    
+    public static <V> MutableSet<V> of(V v1, V v2) {
+        MutableSet<V> result = new MutableSet<V>();
+        result.add(v1);
+        result.add(v2);
+        return result;
+    }
+    
+    public static <V> MutableSet<V> of(V v1, V v2, V v3, V ...vMore) {
+        MutableSet<V> result = new MutableSet<V>();
+        result.add(v1);
+        result.add(v2);
+        result.add(v3);
+        for (V vi: vMore) result.add(vi);
+        return result;
+    }
+
+    public static <V> MutableSet<V> copyOf(@Nullable Iterable<? extends V> orig) {
+        return orig==null ? new MutableSet<V>() : new MutableSet<V>(orig);
+    }
+    
+    public static <V> MutableSet<V> copyOf(@Nullable Iterator<? extends V> elements) {
+        if (elements == null || !elements.hasNext()) {
+            return of();
+        }
+        return new MutableSet.Builder<V>().addAll(elements).build();
+    }
+    
+    public MutableSet() {
+    }
+    
+    public MutableSet(Iterable<? extends V> source) {
+        super((source instanceof Collection) ? (Collection<? extends V>)source : Sets.newLinkedHashSet(source));
+    }
+    
+    /** @deprecated since 0.7.0, use {@link #asImmutableCopy()}, or {@link #asUnmodifiable()} / {@link #asUnmodifiableCopy()} */ @Deprecated
+    public Set<V> toImmutable() {
+        // Don't use ImmutableSet as that does not accept nulls
+        return Collections.unmodifiableSet(Sets.newLinkedHashSet(this));
+    }
+    /** as {@link MutableList#asImmutableCopy()()} */
+    public Set<V> asImmutableCopy() {
+        try {
+            return ImmutableSet.copyOf(this);
+        } catch (Exception e) {
+            Exceptions.propagateIfFatal(e);
+            log.warn("Error converting list to Immutable, using unmodifiable instead: "+e, e);
+            return asUnmodifiableCopy();
+        }
+    }
+    /** as {@link MutableList#asUnmodifiable()} */
+    public Set<V> asUnmodifiable() {
+        return Collections.unmodifiableSet(this);
+    }
+    /** as {@link MutableList#asUnmodifiableCopy()} */
+    public Set<V> asUnmodifiableCopy() {
+        return Collections.unmodifiableSet(MutableSet.copyOf(this));
+    }
+    
+    public static <V> Builder<V> builder() {
+        return new Builder<V>();
+    }
+
+    /**
+     * @see guava's ImmutableSet.Builder
+     */
+    public static class Builder<V> {
+        final MutableSet<V> result = new MutableSet<V>();
+
+        public Builder() {}
+
+        public Builder<V> add(V value) {
+            result.add(value);
+            return this;
+        }
+
+        public Builder<V> add(V v1, V v2, V ...values) {
+            result.add(v1);
+            result.add(v2);
+            for (V value: values) result.add(value);
+            return this;
+        }
+
+        public Builder<V> remove(V val) {
+            result.remove(val);
+            return this;
+        }
+        
+        public Builder<V> addAll(V[] values) {
+            for (V v : values) {
+                result.add(v);
+            }
+            return this;
+        }
+        public Builder<V> addAll(Iterable<? extends V> iterable) {
+            if (iterable instanceof Collection) {
+                result.addAll((Collection<? extends V>) iterable);
+            } else {
+                for (V v : iterable) {
+                    result.add(v);
+                }
+            }
+            return this;
+        }
+
+        public Builder<V> addAll(Iterator<? extends V> iter) {
+            while (iter.hasNext()) {
+                add(iter.next());
+            }
+            return this;
+        }
+
+        public Builder<V> removeAll(Iterable<? extends V> iterable) {
+            if (iterable instanceof Collection) {
+                result.removeAll((Collection<? extends V>) iterable);
+            } else {
+                for (V v : iterable) {
+                    result.remove(v);
+                }
+            }
+            return this;
+        }
+        
+        public MutableSet<V> build() {
+          return new MutableSet<V>(result);
+        }
+        
+    }
+    
+    public boolean addIfNotNull(V e) {
+        if (e!=null) return add(e);
+        return false;
+    }
+
+    public boolean addAll(Iterable<? extends V> setToAdd) {
+        // copy of parent, but accepting Iterable and null
+        if (setToAdd==null) return false;
+        boolean modified = false;
+        Iterator<? extends V> e = setToAdd.iterator();
+        while (e.hasNext()) {
+            if (add(e.next()))
+                modified = true;
+        }
+        return modified;
+    }
+    
+    /** as {@link #addAll(Collection)} but fluent style and permitting null */
+    public MutableSet<V> putAll(Iterable<? extends V> setToAdd) {
+        if (setToAdd!=null) addAll(setToAdd);
+        return this;
+    }
+    
+    public boolean removeIfNotNull(V item) {
+        if (item==null) return false;
+        return remove(item);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/cf2f7a93/utils/common/src/main/java/org/apache/brooklyn/util/collections/QuorumCheck.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/collections/QuorumCheck.java b/utils/common/src/main/java/org/apache/brooklyn/util/collections/QuorumCheck.java
new file mode 100644
index 0000000..44b5fa2
--- /dev/null
+++ b/utils/common/src/main/java/org/apache/brooklyn/util/collections/QuorumCheck.java
@@ -0,0 +1,236 @@
+/*
+ * 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.io.Serializable;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.brooklyn.util.yaml.Yamls;
+
+import com.google.common.base.Objects;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Iterables;
+
+/**
+ * For checking if a group/cluster is quorate. That is, whether the group has sufficient
+ * healthy members.
+ */
+public interface QuorumCheck {
+
+    /**
+     * @param sizeHealthy Number of healthy members
+     * @param totalSize   Total number of members one would expect to be healthy (i.e. ignoring stopped members)
+     * @return            Whether this group is healthy
+     */
+    public boolean isQuorate(int sizeHealthy, int totalSize);
+
+    public static class QuorumChecks {
+        /**
+         * Checks that all members that should be up are up (i.e. ignores stopped nodes).
+         */
+        public static QuorumCheck all() {
+            return new NumericQuorumCheck(0, 1.0, false, "all");
+        }
+        /**
+         * Checks all members that should be up are up, and that there is at least one such member.
+         */
+        public static QuorumCheck allAndAtLeastOne() {
+            return new NumericQuorumCheck(1, 1.0, false, "allAndAtLeastOne");
+        }
+        /**
+         * Requires at least one member that should be up is up.
+         */
+        public static QuorumCheck atLeastOne() {
+            return new NumericQuorumCheck(1, 0.0, false, "atLeastOne");
+        }
+        /**
+         * Requires at least one member to be up if the total size is non-zero.
+         * i.e. okay if empty, or if non-empty and something is healthy, but not okay if not-empty and nothing is healthy.
+         * "Empty" means that no members are supposed to be up  (e.g. there may be stopped members).
+         */
+        public static QuorumCheck atLeastOneUnlessEmpty() {
+            return new NumericQuorumCheck(1, 0.0, true, "atLeastOneUnlessEmpty");
+        }
+        /**
+         * Always "healthy"
+         */
+        public static QuorumCheck alwaysTrue() {
+            return new NumericQuorumCheck(0, 0.0, true, "alwaysHealthy");
+        }
+        
+        public static QuorumCheck newInstance(int minRequiredSize, double minRequiredRatio, boolean allowEmpty) {
+            return new NumericQuorumCheck(minRequiredSize, minRequiredRatio, allowEmpty);
+        }
+        
+        /** See {@link QuorumChecks#newLinearRange(String,String)} */
+        public static QuorumCheck newLinearRange(String range) {
+            return newLinearRange(range, null);
+        }
+        
+        /** Given a JSON representation of a list of points (where a point is a list of 2 numbers),
+         * with the points in increasing x-coordinate value,
+         * this constructs a quorum check which does linear interpolation on those coordinates,
+         * with extensions to either side.
+         * The x-coordinate is taken as the total size, and the y-coordinate as the minimum required size.
+         * <p>
+         * It sounds complicated but it gives a very easy and powerful way to define quorum checks.
+         * For instance:
+         * <p>
+         * <code>[[0,0],[1,1]]</code> says that if 0 items are expected, at least 0 is required; 
+         *   if 1 is expected, 1 is required; and by extension if 10 are expected, 10 are required.
+         *   In other words, this is the same as {@link #all()}.
+         * <p>
+         * <code>[[0,1],[1,1],[2,2]]</code> is the same as the previous for x (number expected) greater-than or equal to 1;
+         * but if 0 is expected, 1 is required, and so it fails when 0 are present.
+         * In other words, {@link #allAndAtLeastOne()}.
+         * <p>
+         * <code>[[5,5],[10,10],[100,70],[200,140]]</code> has {@link #all()} behavior up to 10 expected 
+         * (line extended to the left, for less than 5); but then gently tapers off to requiring only 70% at 100
+         * (with 30 of 40 = 75% required at that intermediate point along the line [[10,10],[100,70]]);
+         * and then from 100 onwards it is a straight 70%.
+         * <p>
+         * The type of linear regression described in the last example is quite useful in practise, 
+         * to be stricter for smaller clusters (or possibly more lax for small values in some cases,
+         * such as when tolerating dangling references during rebind). 
+         */
+        @SuppressWarnings({ "rawtypes", "unchecked" })
+        public static QuorumCheck newLinearRange(String range, String name) {
+            return LinearRangeQuorumCheck.of(name, (Iterable)Iterables.getOnlyElement( Yamls.parseAll(range) ));
+        }
+        
+        private static final List<QuorumCheck> NAMED_CHECKS = MutableList
+                .of(all(), allAndAtLeastOne(), atLeastOne(), atLeastOneUnlessEmpty(), alwaysTrue());
+        
+        public static QuorumCheck of(String nameOrRange) {
+            if (nameOrRange==null) return null;
+            for (QuorumCheck qc: NAMED_CHECKS) {
+                if (qc instanceof NumericQuorumCheck) {
+                    if (Objects.equal(nameOrRange, ((NumericQuorumCheck)qc).getName()))
+                        return qc;
+                }
+            }
+            return newLinearRange(nameOrRange);
+        }
+    }
+    
+    public static class NumericQuorumCheck implements QuorumCheck, Serializable {
+        private static final long serialVersionUID = -5090669237460159621L;
+        
+        protected final int minRequiredSize;
+        protected final double minRequiredRatio;
+        protected final boolean allowEmpty;
+        protected final String name;
+
+        public NumericQuorumCheck(int minRequiredSize, double minRequiredRatio, boolean allowEmpty) {
+            this(minRequiredSize, minRequiredRatio, allowEmpty, null);
+        }
+        public NumericQuorumCheck(int minRequiredSize, double minRequiredRatio, boolean allowEmpty, String name) {
+            this.minRequiredSize = minRequiredSize;
+            this.minRequiredRatio = minRequiredRatio;
+            this.allowEmpty = allowEmpty;
+            this.name = name;
+        }
+        
+        @Override
+        public boolean isQuorate(int sizeHealthy, int totalSize) {
+            if (allowEmpty && totalSize==0) return true;
+            if (sizeHealthy < minRequiredSize) return false;
+            if (sizeHealthy < totalSize*minRequiredRatio-0.000000001) return false;
+            return true;
+        }
+
+        public String getName() {
+            return name;
+        }
+        
+        @Override
+        public String toString() {
+            return "QuorumCheck["+(name!=null?name+";":"")+"require="+minRequiredSize+","+((int)100*minRequiredRatio)+"%"+(allowEmpty ? "|0" : "")+"]";
+        }
+    }
+
+    /** See {@link QuorumChecks#newLinearRange(String,String)} */
+    public static class LinearRangeQuorumCheck implements QuorumCheck, Serializable {
+
+        private static final long serialVersionUID = -6425548115925898645L;
+
+        private static class Point {
+            final double size, minRequiredAtSize;
+            public Point(double size, double minRequiredAtSize) { this.size = size; this.minRequiredAtSize = minRequiredAtSize; }
+            public static Point ofIntegerCoords(Iterable<Integer> coords) {
+                Preconditions.checkNotNull(coords==null, "coords");
+                Preconditions.checkArgument(Iterables.size(coords)==2, "A point must consist of two coordinates; invalid data: "+coords);
+                Iterator<Integer> ci = coords.iterator();
+                return new Point(ci.next(), ci.next());
+            }
+            public static List<Point> listOfIntegerCoords(Iterable<? extends Iterable<Integer>> points) {
+                MutableList<Point> result = MutableList.of();
+                for (Iterable<Integer> point: points) result.add(ofIntegerCoords(point));
+                return result.asUnmodifiable();
+            }
+            @Override
+            public String toString() {
+                return "("+size+","+minRequiredAtSize+")";
+            }
+        }
+        
+        protected final String name;
+        protected final List<Point> points;
+
+        public static LinearRangeQuorumCheck of(String name, Iterable<? extends Iterable<Integer>> points) {
+            return new LinearRangeQuorumCheck(name, Point.listOfIntegerCoords(points));
+        }
+        public static LinearRangeQuorumCheck of(Iterable<? extends Iterable<Integer>> points) {
+            return new LinearRangeQuorumCheck(null, Point.listOfIntegerCoords(points));
+        }
+        
+        protected LinearRangeQuorumCheck(String name, Iterable<Point> points) {
+            Preconditions.checkArgument(Iterables.size(points)>=2, "At least two points must be supplied for "+name+": "+points);
+            this.name = name;
+            this.points = MutableList.copyOf(points).asUnmodifiable();
+            // check valid
+            Point last = null;
+            for (Point p: points) {
+                if (last!=null) {
+                    if (p.size <= last.size) throw new IllegalStateException("Points must be supplied in order of increasing totalSize (x coordinate); instead have "+last+" and "+p);
+                }
+            }
+        }
+
+        @Override
+        public boolean isQuorate(int sizeHealthy, int totalSize) {
+            Point next = points.get(0);
+            Point prev = null;
+            for (int i=1; i<points.size(); i++) {
+                prev = next;
+                next = points.get(i);
+                if (next.size>totalSize) break;
+            }
+            double minRequiredAtSize = (totalSize-prev.size)/(next.size-prev.size) * (next.minRequiredAtSize-prev.minRequiredAtSize) + prev.minRequiredAtSize;
+            return (sizeHealthy > minRequiredAtSize-0.000000001);
+        }
+        
+        @Override
+        public String toString() {
+            return "LinearRangeQuorumCheck["+(name!=null ? name+":" : "")+points+"]";
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/cf2f7a93/utils/common/src/main/java/org/apache/brooklyn/util/collections/SetFromLiveMap.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/collections/SetFromLiveMap.java b/utils/common/src/main/java/org/apache/brooklyn/util/collections/SetFromLiveMap.java
new file mode 100644
index 0000000..085e31d
--- /dev/null
+++ b/utils/common/src/main/java/org/apache/brooklyn/util/collections/SetFromLiveMap.java
@@ -0,0 +1,141 @@
+/*
+ * 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.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.Serializable;
+import java.util.AbstractSet;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+import javax.annotation.Nullable;
+
+import com.google.common.annotations.GwtIncompatible;
+import com.google.common.collect.Sets;
+
+/**
+ * Creates a Set backed by a given map (using the map's {@link Map#keySet()} for the contents of the set).
+ * <p>
+ * As {@link Collections#newSetFromMap(Map)} and guava's {@link Sets#newSetFromMap(Map)}, but accepts
+ * a non-empty map. Also supports others modifying the backing map simultaneously, if the backing map
+ * is a ConcurrentMap.
+ */
+public class SetFromLiveMap<E> extends AbstractSet<E> implements Set<E>, Serializable {
+
+    public static <E> Set<E> create(Map<E, Boolean> map) {
+        return new SetFromLiveMap<E>(map);
+    }
+
+    private final Map<E, Boolean> m; // The backing map
+    private transient Set<E> s; // Its keySet
+
+    SetFromLiveMap(Map<E, Boolean> map) {
+        m = map;
+        s = map.keySet();
+    }
+
+    @Override
+    public void clear() {
+        m.clear();
+    }
+
+    @Override
+    public int size() {
+        return m.size();
+    }
+
+    @Override
+    public boolean isEmpty() {
+        return m.isEmpty();
+    }
+
+    @Override
+    public boolean contains(Object o) {
+        return m.containsKey(o);
+    }
+
+    @Override
+    public boolean remove(Object o) {
+        return m.remove(o) != null;
+    }
+
+    @Override
+    public boolean add(E e) {
+        return m.put(e, Boolean.TRUE) == null;
+    }
+
+    @Override
+    public Iterator<E> iterator() {
+        return s.iterator();
+    }
+
+    @Override
+    public Object[] toArray() {
+        return s.toArray();
+    }
+
+    @Override
+    public <T> T[] toArray(T[] a) {
+        return s.toArray(a);
+    }
+
+    @Override
+    public String toString() {
+        return s.toString();
+    }
+
+    @Override
+    public int hashCode() {
+        return s.hashCode();
+    }
+
+    @Override
+    public boolean equals(@Nullable Object object) {
+        return this == object || this.s.equals(object);
+    }
+
+    @Override
+    public boolean containsAll(Collection<?> c) {
+        return s.containsAll(c);
+    }
+
+    @Override
+    public boolean removeAll(Collection<?> c) {
+        return s.removeAll(c);
+    }
+
+    @Override
+    public boolean retainAll(Collection<?> c) {
+        return s.retainAll(c);
+    }
+
+    // addAll is the only inherited implementation
+    @GwtIncompatible("not needed in emulated source")
+    private static final long serialVersionUID = 0;
+
+    @GwtIncompatible("java.io.ObjectInputStream")
+    private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
+        stream.defaultReadObject();
+        s = m.keySet();
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/cf2f7a93/utils/common/src/main/java/org/apache/brooklyn/util/collections/TimeWindowedList.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/collections/TimeWindowedList.java b/utils/common/src/main/java/org/apache/brooklyn/util/collections/TimeWindowedList.java
new file mode 100644
index 0000000..570c2ed
--- /dev/null
+++ b/utils/common/src/main/java/org/apache/brooklyn/util/collections/TimeWindowedList.java
@@ -0,0 +1,147 @@
+/*
+ * 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.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.brooklyn.util.time.Duration;
+
+import com.google.common.collect.ImmutableList;
+
+/**
+ * Keeps a list of timestamped values that are in the given time-period (millis).
+ * It also guarantees to keep the given minimum number of values in the list (even if old),
+ * and to keep the given number of out-of-date values.
+ * 
+ * For example, this is useful if we want to determine if a metric has been consistently high.
+ * 
+ * @author aled
+ */
+public class TimeWindowedList<T> {
+    private final LinkedList<TimestampedValue<T>> values = new LinkedList<TimestampedValue<T>>();
+    private volatile Duration timePeriod;
+    private final int minVals;
+    private final int minExpiredVals;
+
+    public TimeWindowedList(Duration timePeriod) {
+        this.timePeriod = timePeriod;
+        minVals = 0;
+        minExpiredVals = 0;
+    }
+    
+    /**
+     * @deprecated since 0.7.0; use {@link #TimeWindowedList(Duration)}
+     */
+    public TimeWindowedList(long timePeriod) {
+        this(Duration.millis(timePeriod));
+    }
+
+    public TimeWindowedList(Map<String,?> flags) {
+        if (!flags.containsKey("timePeriod")) throw new IllegalArgumentException("Must define timePeriod");
+        timePeriod = Duration.of(flags.get("timePeriod"));
+        
+        if (flags.containsKey("minVals")) {
+            minVals = ((Number)flags.get("minVals")).intValue();
+        } else {
+            minVals = 0;
+        }
+        if (flags.containsKey("minExpiredVals")) {
+            minExpiredVals = ((Number)flags.get("minExpiredVals")).intValue();
+        } else {
+            minExpiredVals = 0;
+        }
+    }
+    
+    public void setTimePeriod(Duration newTimePeriod) {
+        timePeriod = newTimePeriod;
+    }
+    
+    public synchronized T getLatestValue() {
+        return (values.isEmpty()) ? null : values.get(values.size()-1).getValue();
+    }
+    
+    public List<TimestampedValue<T>> getValues() {
+        return getValues(System.currentTimeMillis());
+    }
+    
+    public synchronized List<TimestampedValue<T>> getValues(long now) {
+        pruneValues(now);
+        return ImmutableList.copyOf(values);
+    }
+    
+    public synchronized List<TimestampedValue<T>> getValuesInWindow(long now, Duration subTimePeriod) {
+        long startTime = now - subTimePeriod.toMilliseconds();
+        List<TimestampedValue<T>> result = new LinkedList<TimestampedValue<T>>();
+        TimestampedValue<T> mostRecentExpired = null;
+        for (TimestampedValue<T> val : values) {
+            if (val.getTimestamp() < startTime) {
+                // discard; but remember most recent too-old value so we include that as the "initial"
+                mostRecentExpired = val;
+            } else {
+                result.add(val);
+            }
+        }
+        if (minExpiredVals > 0 && mostRecentExpired != null) {
+            result.add(0, mostRecentExpired);
+        }
+        
+        if (result.size() < minVals) {
+            int minIndex = Math.max(0, values.size()-minVals);
+            return ImmutableList.copyOf(values.subList(minIndex, values.size()));
+        } else {
+            return result;
+        }
+    }
+    
+    public void add(T val) {
+        add(val, System.currentTimeMillis());
+    }
+    
+    public synchronized void add(T val, long timestamp) {
+        values.add(values.size(), new TimestampedValue<T>(val, timestamp));
+        pruneValues(timestamp);
+    }
+    
+    public synchronized void pruneValues(long now) {
+        long startTime = now - timePeriod.toMilliseconds();
+        int expiredValsCount = 0;
+        if (timePeriod.equals(Duration.ZERO)) {
+            expiredValsCount = values.size();
+        } else {
+            for (TimestampedValue<T> val : values) {
+                if (val.getTimestamp() < startTime) {
+                    expiredValsCount++;
+                } else {
+                    break;
+                }
+            }
+        }
+        int numToPrune = Math.min(expiredValsCount - minExpiredVals, values.size()-minVals);
+        for (int i = 0; i < numToPrune; i++) {
+            values.removeFirst();
+        }
+    }
+    
+    @Override
+    public String toString() {
+        return "timePeriod="+timePeriod+", vals="+values;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/cf2f7a93/utils/common/src/main/java/org/apache/brooklyn/util/collections/TimestampedValue.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/collections/TimestampedValue.java b/utils/common/src/main/java/org/apache/brooklyn/util/collections/TimestampedValue.java
new file mode 100644
index 0000000..0015748
--- /dev/null
+++ b/utils/common/src/main/java/org/apache/brooklyn/util/collections/TimestampedValue.java
@@ -0,0 +1,59 @@
+/*
+ * 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 com.google.common.base.Objects;
+
+public class TimestampedValue<T> {
+
+    private final T value;
+    private final long timestamp;
+    
+    public TimestampedValue(T value, long timestamp) {
+        this.value = value;
+        this.timestamp = timestamp;
+    }
+    
+    public T getValue() {
+        return value;
+    }
+    
+    public long getTimestamp() {
+        return timestamp;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(value, timestamp);
+    }
+    
+    @Override
+    public boolean equals(Object other) {
+        if (!(other instanceof TimestampedValue)) {
+            return false;
+        }
+        TimestampedValue<?> o = (TimestampedValue<?>) other;
+        return o.getTimestamp() == timestamp && Objects.equal(o.getValue(), value);
+    }
+    
+    @Override
+    public String toString() {
+        return "val="+value+"; timestamp="+timestamp;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/cf2f7a93/utils/common/src/main/java/org/apache/brooklyn/util/concurrent/CallableFromRunnable.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/concurrent/CallableFromRunnable.java b/utils/common/src/main/java/org/apache/brooklyn/util/concurrent/CallableFromRunnable.java
new file mode 100644
index 0000000..72510f6
--- /dev/null
+++ b/utils/common/src/main/java/org/apache/brooklyn/util/concurrent/CallableFromRunnable.java
@@ -0,0 +1,54 @@
+/*
+ * 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.concurrent;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.Executors;
+
+import com.google.common.annotations.Beta;
+
+/** Wraps a Runnable as a Callable. Like {@link Executors#callable(Runnable, Object)} but including the underlying toString. */
+@Beta
+public class CallableFromRunnable<T> implements Callable<T> {
+    
+    public static <T> CallableFromRunnable<T> newInstance(Runnable task, T result) {
+        return new CallableFromRunnable<T>(task, result);
+    }
+    
+    private final Runnable task;
+    private final T result;
+    
+    protected CallableFromRunnable(Runnable task, T result) {
+        this.task = task;
+        this.result = result;
+    }
+    
+    public T call() {
+        task.run();
+        return result;
+    }
+    
+    @Override
+    public String toString() {
+        if (result!=null)
+            return "CallableFromRunnable["+task+(result!=null ? "->"+result : "")+"]";
+        else
+            return ""+task;
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/cf2f7a93/utils/common/src/main/java/org/apache/brooklyn/util/crypto/AuthorizedKeysParser.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/crypto/AuthorizedKeysParser.java b/utils/common/src/main/java/org/apache/brooklyn/util/crypto/AuthorizedKeysParser.java
new file mode 100644
index 0000000..14edabe
--- /dev/null
+++ b/utils/common/src/main/java/org/apache/brooklyn/util/crypto/AuthorizedKeysParser.java
@@ -0,0 +1,134 @@
+/*
+ * 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.crypto;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.math.BigInteger;
+import java.security.KeyFactory;
+import java.security.PublicKey;
+import java.security.interfaces.DSAPublicKey;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.DSAPublicKeySpec;
+import java.security.spec.RSAPublicKeySpec;
+
+import org.apache.brooklyn.util.exceptions.Exceptions;
+
+import com.google.common.io.BaseEncoding;
+
+public class AuthorizedKeysParser {
+
+    public static PublicKey decodePublicKey(String keyLine) {
+        try {
+            ByteArrayInputStream stream = null;
+
+            // look for the Base64 encoded part of the line to decode
+            // both ssh-rsa and ssh-dss begin with "AAAA" due to the length bytes
+            for (String part : keyLine.split(" ")) {
+                if (part.startsWith("AAAA")) {
+                    stream = new ByteArrayInputStream(BaseEncoding.base64().decode(part));
+                    break;
+                }
+            }
+            if (stream == null)
+                throw new IllegalArgumentException("Encoded public key should include phrase beginning AAAA.");
+
+            String type = readType(stream);
+            if (type.equals("ssh-rsa")) {
+                BigInteger e = readBigInt(stream, 1);
+                BigInteger m = readBigInt(stream, 1);
+                RSAPublicKeySpec spec = new RSAPublicKeySpec(m, e);
+                return KeyFactory.getInstance("RSA").generatePublic(spec);
+            } else if (type.equals("ssh-dss")) {
+                BigInteger p = readBigInt(stream, 1);
+                BigInteger q = readBigInt(stream, 1);
+                BigInteger g = readBigInt(stream, 1);
+                BigInteger y = readBigInt(stream, 1);
+                DSAPublicKeySpec spec = new DSAPublicKeySpec(y, p, q, g);
+                return KeyFactory.getInstance("DSA").generatePublic(spec);
+            } else {
+                throw new IllegalArgumentException("Unknown public key type " + type);
+            }
+            
+        } catch (Exception e) {
+            Exceptions.propagateIfFatal(e);
+            throw new IllegalArgumentException("Error parsing authorized_keys/SSH2 format public key: "+e);
+        }
+    }
+
+    private static int readInt(InputStream stream) throws IOException {
+        return ((stream.read() & 0xFF) << 24) | ((stream.read() & 0xFF) << 16)
+            | ((stream.read() & 0xFF) << 8) | (stream.read() & 0xFF);
+    }
+    
+    private static byte[] readBytesWithLength(InputStream stream, int minLen) throws IOException {
+        int len = readInt(stream);
+        if (len<minLen || len>100000)
+            throw new IllegalStateException("Invalid stream header: length "+len);
+        byte[] result = new byte[len];
+        stream.read(result);
+        return result;
+    }
+    
+    private static void writeInt(OutputStream stream, int v) throws IOException {
+        for (int shift = 24; shift >= 0; shift -= 8)
+            stream.write((v >>> shift) & 0xFF);
+    }
+    private static void writeBytesWithLength(OutputStream stream, byte[] buf) throws IOException {
+        writeInt(stream, buf.length);
+        stream.write(buf);
+    }
+    
+    private static String readType(InputStream stream) throws IOException { return new String(readBytesWithLength(stream, 0)); }
+    private static BigInteger readBigInt(InputStream stream, int minLen) throws IOException { return new BigInteger(readBytesWithLength(stream, minLen)); }
+
+    public static String encodePublicKey(PublicKey key) {
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        try {
+            String type = null;
+            if (key==null) { 
+                return null;
+            } else if (key instanceof RSAPublicKey) {
+                type = "ssh-rsa";
+                writeBytesWithLength(out, type.getBytes()); 
+                writeBytesWithLength(out, ((RSAPublicKey)key).getPublicExponent().toByteArray());
+                writeBytesWithLength(out, ((RSAPublicKey)key).getModulus().toByteArray()); 
+            } else if (key instanceof DSAPublicKey) {
+                type = "ssh-dss";
+                writeBytesWithLength(out, type.getBytes());
+                writeBytesWithLength(out, ((DSAPublicKey)key).getParams().getP().toByteArray()); 
+                writeBytesWithLength(out, ((DSAPublicKey)key).getParams().getQ().toByteArray()); 
+                writeBytesWithLength(out, ((DSAPublicKey)key).getParams().getG().toByteArray()); 
+                writeBytesWithLength(out, ((DSAPublicKey)key).getY().toByteArray()); 
+            } else {
+                throw new IllegalStateException("Unsupported public key type for encoding: "+key);
+            }
+            out.close();
+            
+            return type+" "+BaseEncoding.base64().encode(out.toByteArray());
+        } catch (Exception e) {
+            // shouldn't happen, as it's a byte stream...
+            throw Exceptions.propagate(e);
+        }
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/cf2f7a93/utils/common/src/main/java/org/apache/brooklyn/util/crypto/SecureKeysWithoutBouncyCastle.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/crypto/SecureKeysWithoutBouncyCastle.java b/utils/common/src/main/java/org/apache/brooklyn/util/crypto/SecureKeysWithoutBouncyCastle.java
new file mode 100644
index 0000000..b801fb8
--- /dev/null
+++ b/utils/common/src/main/java/org/apache/brooklyn/util/crypto/SecureKeysWithoutBouncyCastle.java
@@ -0,0 +1,161 @@
+/*
+ * 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.crypto;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.NoSuchElementException;
+
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+import javax.net.ssl.X509TrustManager;
+import javax.security.auth.x500.X500Principal;
+
+import org.apache.brooklyn.util.exceptions.Exceptions;
+
+/**
+ * Utility methods for generating and working with keys, with no BC dependencies
+ */
+public class SecureKeysWithoutBouncyCastle {
+
+    private static KeyPairGenerator defaultKeyPairGenerator = newKeyPairGenerator("RSA", 1024);  
+
+    protected SecureKeysWithoutBouncyCastle() {}
+
+    public static KeyPairGenerator newKeyPairGenerator(String algorithm, int bits) {
+        KeyPairGenerator keyPairGenerator;
+        try {
+            keyPairGenerator = KeyPairGenerator.getInstance(algorithm);
+        } catch (NoSuchAlgorithmException e) {
+            throw Exceptions.propagate(e);
+        }
+        keyPairGenerator.initialize(bits);
+        return keyPairGenerator;
+    }
+    
+    public static KeyPair newKeyPair() {
+        return defaultKeyPairGenerator.generateKeyPair();
+    }
+
+    public static KeyPair newKeyPair(String algorithm, int bits) {
+        return newKeyPairGenerator(algorithm, bits).generateKeyPair();
+    }
+
+    /** returns a new keystore, of the default type, and initialized to be empty.
+     * (everyone always forgets to load(null,null).) */
+    public static KeyStore newKeyStore() {
+        try {
+            KeyStore store = KeyStore.getInstance(KeyStore.getDefaultType());
+            store.load(null, null);
+            return store;
+        } catch (Exception e) {
+            throw Exceptions.propagate(e);
+        }
+    }
+
+    /** returns keystore of default type read from given source */
+    public static KeyStore newKeyStore(InputStream source, String passphrase) {
+        try {
+            KeyStore store = KeyStore.getInstance(KeyStore.getDefaultType());
+            store.load(source, passphrase!=null ? passphrase.toCharArray() : new char[0]);
+            return store;
+        } catch (Exception e) {
+            throw Exceptions.propagate(e);
+        }
+    }
+
+    /** see {@link #getTrustManager(KeyStore, Class)}, matching any type */
+    public static TrustManager getTrustManager(KeyStore trustStore) {
+        return getTrustManager(trustStore, null);
+    }
+    /** returns the trust manager inferred from trustStore, matching the type (if not null);
+     * throws exception if there are none, or if there are multiple */
+    @SuppressWarnings("unchecked")
+    public static <T extends TrustManager> T getTrustManager(KeyStore trustStore, Class<T> type) {
+        try {
+            TrustManagerFactory tmf = TrustManagerFactory.getInstance(
+                    TrustManagerFactory.getDefaultAlgorithm());
+            tmf.init(trustStore);
+            T result = null;
+            for (TrustManager tm: tmf.getTrustManagers()) {
+                if (type==null || type.isInstance(tm)) {
+                    if (result!=null)
+                        throw new IllegalStateException("Multiple trust managers matching "+type+" inferred from "+trustStore);
+                    result = (T)tm;
+                }
+            }
+            if (result!=null)
+                return result;
+            throw new NoSuchElementException("No trust manager matching "+type+" can be inferred from "+trustStore);
+        } catch (Exception e) {
+            throw Exceptions.propagate(e);
+        }
+    }
+    
+    public static X509TrustManager getTrustManager(X509Certificate certificate) {
+        try {
+            KeyStore ks = newKeyStore();
+            ks.setCertificateEntry("", certificate);
+            return getTrustManager(ks, X509TrustManager.class);
+        } catch (KeyStoreException e) {
+            throw Exceptions.propagate(e);
+        }
+    }
+
+    /** converts a certificate to the canonical implementation, commonly sun.security.x509.X509CertImpl,
+     * which is required in some places -- the Bouncy Castle X509 impl is not accepted 
+     * (e.g. where certs are chained, passed to trust manager) */
+    public static X509Certificate getCanonicalImpl(X509Certificate inCert) {
+        try {
+            KeyStore store = newKeyStore();
+            store.setCertificateEntry("to-canonical", inCert);
+            ByteArrayOutputStream out = new ByteArrayOutputStream();
+            store.store(out, "".toCharArray());
+
+            KeyStore store2 = KeyStore.getInstance(KeyStore.getDefaultType());
+            store2.load(new ByteArrayInputStream(out.toByteArray()), "".toCharArray());
+            return (X509Certificate) store2.getCertificate("to-canonical");
+        } catch (Exception e) {
+            throw Exceptions.propagate(e);
+        }
+    }
+
+    public static boolean isCertificateAuthorizedBy(X509Certificate candidate, X509Certificate authority) {
+        try {
+            candidate = getCanonicalImpl(candidate);
+            getTrustManager(authority).checkClientTrusted(new X509Certificate[] { candidate }, "RSA");
+            return true;
+        } catch (CertificateException e) {
+            return false;
+        }
+    }
+    
+    public static X500Principal getX500PrincipalWithCommonName(String commonName) {
+        return new X500Principal("" + "C=None," + "L=None," + "O=None," + "OU=None," + "CN=" + commonName);
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/cf2f7a93/utils/common/src/main/java/org/apache/brooklyn/util/crypto/SslTrustUtils.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/crypto/SslTrustUtils.java b/utils/common/src/main/java/org/apache/brooklyn/util/crypto/SslTrustUtils.java
new file mode 100644
index 0000000..3dd394d
--- /dev/null
+++ b/utils/common/src/main/java/org/apache/brooklyn/util/crypto/SslTrustUtils.java
@@ -0,0 +1,100 @@
+/*
+ * 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.crypto;
+
+import java.net.URLConnection;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+
+public class SslTrustUtils {
+
+    /** configures a connection to accept all certificates, if it is for https */
+    public static <T extends URLConnection> T trustAll(T connection) {
+        if (connection instanceof HttpsURLConnection) {
+            ((HttpsURLConnection)connection).setSSLSocketFactory(TrustingSslSocketFactory.getInstance());
+            ((HttpsURLConnection)connection).setHostnameVerifier(ALL_HOSTS_VALID);
+        }
+        return connection;
+    }
+    
+    /** trusts all SSL certificates */
+    public static final TrustManager TRUST_ALL = new X509TrustManager() {
+        public X509Certificate[] getAcceptedIssuers() {
+            return new X509Certificate[0];
+        }
+        @Override
+        public void checkClientTrusted(X509Certificate[] chain, String authType)
+                throws java.security.cert.CertificateException {
+            
+        }
+        @Override
+        public void checkServerTrusted(X509Certificate[] chain, String authType)
+                throws java.security.cert.CertificateException {
+        }
+    };
+    
+    /** trusts no SSL certificates */
+    public static final TrustManager TRUST_NONE = new X509TrustManager() {
+        public X509Certificate[] getAcceptedIssuers() {
+            return new X509Certificate[0];
+        }
+        @Override
+        public void checkClientTrusted(X509Certificate[] chain, String authType)
+                throws java.security.cert.CertificateException {
+            throw new java.security.cert.CertificateException("No clients allowed.");
+        }
+        @Override
+        public void checkServerTrusted(X509Certificate[] chain, String authType)
+                throws java.security.cert.CertificateException {
+            throw new java.security.cert.CertificateException("No servers allowed.");
+        }
+    };
+
+    public static class DelegatingTrustManager implements X509TrustManager {
+        private final X509TrustManager delegate;
+        public DelegatingTrustManager(X509TrustManager delegate) {
+            this.delegate = delegate;
+        }
+        @Override
+        public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
+            delegate.checkClientTrusted(chain, authType);
+        }
+        @Override
+        public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
+            delegate.checkServerTrusted(chain, authType);
+        }
+        @Override
+        public X509Certificate[] getAcceptedIssuers() {
+            return delegate.getAcceptedIssuers();
+        }
+    }
+    
+    public static final HostnameVerifier ALL_HOSTS_VALID = new HostnameVerifier() {
+        public boolean verify(String hostname, SSLSession session) {
+            return true;
+        }
+    };
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/cf2f7a93/utils/common/src/main/java/org/apache/brooklyn/util/crypto/TrustingSslSocketFactory.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/crypto/TrustingSslSocketFactory.java b/utils/common/src/main/java/org/apache/brooklyn/util/crypto/TrustingSslSocketFactory.java
new file mode 100644
index 0000000..2597035
--- /dev/null
+++ b/utils/common/src/main/java/org/apache/brooklyn/util/crypto/TrustingSslSocketFactory.java
@@ -0,0 +1,105 @@
+/*
+ * 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.crypto;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.net.UnknownHostException;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.TrustManager;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Throwables;
+
+/** An SSLSocketFactory which trusts all endpoints (ie encryption but no authentication) */
+public class TrustingSslSocketFactory extends SSLSocketFactory {
+
+    private static final Logger logger = LoggerFactory.getLogger(TrustingSslSocketFactory.class);
+    
+    private static TrustingSslSocketFactory INSTANCE;
+    public synchronized static TrustingSslSocketFactory getInstance() {
+        if (INSTANCE==null) INSTANCE = new TrustingSslSocketFactory();
+        return INSTANCE;
+    }
+    
+    private static SSLContext sslContext; 
+    static {
+        try {
+            sslContext = SSLContext.getInstance("TLS");
+        } catch (Exception e) {
+            logger.error("Unable to set up SSLContext with TLS. Https activity will likely fail.", e);
+        }
+    }
+
+    // no reason this can't be public, but no reason it should be necessary;
+    // just use getInstance to get the shared INSTANCE
+    protected TrustingSslSocketFactory() {
+        super();
+        try {
+            sslContext.init(null, new TrustManager[] { SslTrustUtils.TRUST_ALL }, null);
+        } catch (Exception e) {
+            throw Throwables.propagate(e);
+        }
+    }
+
+    @Override
+    public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException, UnknownHostException {
+        return sslContext.getSocketFactory().createSocket(socket, host, port, autoClose);
+    }
+
+    @Override
+    public Socket createSocket() throws IOException {
+        return sslContext.getSocketFactory().createSocket();
+    }
+
+    @Override
+    public String[] getDefaultCipherSuites() {
+        return sslContext.getSocketFactory().getDefaultCipherSuites();
+    }
+
+    @Override
+    public String[] getSupportedCipherSuites() {
+        return sslContext.getSocketFactory().getSupportedCipherSuites();
+    }
+
+    @Override
+    public Socket createSocket(String arg0, int arg1) throws IOException, UnknownHostException {
+        return sslContext.getSocketFactory().createSocket(arg0, arg1);
+    }
+
+    @Override
+    public Socket createSocket(InetAddress arg0, int arg1) throws IOException {
+        return sslContext.getSocketFactory().createSocket(arg0, arg1);
+    }
+
+    @Override
+    public Socket createSocket(String arg0, int arg1, InetAddress arg2, int arg3) throws IOException, UnknownHostException {
+        return sslContext.getSocketFactory().createSocket(arg0, arg1, arg2, arg3);
+    }
+
+    @Override
+    public Socket createSocket(InetAddress arg0, int arg1, InetAddress arg2, int arg3) throws IOException {
+        return sslContext.getSocketFactory().createSocket(arg0, arg1, arg2, arg3);
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/cf2f7a93/utils/common/src/main/java/org/apache/brooklyn/util/exceptions/CompoundRuntimeException.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/exceptions/CompoundRuntimeException.java b/utils/common/src/main/java/org/apache/brooklyn/util/exceptions/CompoundRuntimeException.java
new file mode 100644
index 0000000..bd2ac6c
--- /dev/null
+++ b/utils/common/src/main/java/org/apache/brooklyn/util/exceptions/CompoundRuntimeException.java
@@ -0,0 +1,59 @@
+/*
+ * 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.exceptions;
+
+import java.util.Collections;
+import java.util.List;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+
+public class CompoundRuntimeException extends RuntimeException {
+
+    private static final long serialVersionUID = 6110995537064639587L;
+
+    private final List<Throwable> causes;
+
+    public CompoundRuntimeException(String message) {
+        super(message);
+        this.causes = Collections.emptyList();
+    }
+
+    public CompoundRuntimeException(String message, Throwable cause) {
+        super(message, cause);
+        this.causes = (cause == null) ? Collections.<Throwable>emptyList() : Collections.singletonList(cause);
+    }
+
+    public CompoundRuntimeException(Throwable cause) {
+        super(cause);
+        this.causes = (cause == null) ? Collections.<Throwable>emptyList() : Collections.singletonList(cause);
+    }
+
+    public CompoundRuntimeException(String message, Iterable<? extends Throwable> causes) {
+        this(message, (Iterables.isEmpty(causes) ? null : Iterables.get(causes, 0)), causes);
+    }
+    public CompoundRuntimeException(String message, Throwable primaryCauseToReport, Iterable<? extends Throwable> allCauses) {
+        super(message, primaryCauseToReport);
+        this.causes = ImmutableList.copyOf(allCauses);
+    }
+
+    public List<Throwable> getAllCauses() {
+        return causes;
+    }
+}