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;
+ }
+}