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:27 UTC
[18/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/config/ConfigKey.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/org/apache/brooklyn/config/ConfigKey.java b/utils/common/src/main/java/org/apache/brooklyn/config/ConfigKey.java
new file mode 100644
index 0000000..f2f31fe
--- /dev/null
+++ b/utils/common/src/main/java/org/apache/brooklyn/config/ConfigKey.java
@@ -0,0 +1,94 @@
+/*
+ * 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.config;
+
+import java.util.Collection;
+
+import javax.annotation.Nullable;
+
+import com.google.common.reflect.TypeToken;
+
+/**
+ * Represents the name of a piece of typed configuration data for an entity.
+ * <p>
+ * Two ConfigKeys should be considered equal if they have the same FQN.
+ */
+public interface ConfigKey<T> {
+ /**
+ * Returns the description of the configuration parameter, for display.
+ */
+ String getDescription();
+
+ /**
+ * Returns the name of the configuration parameter, in a dot-separated namespace (FQN).
+ */
+ String getName();
+
+ /**
+ * Returns the constituent parts of the configuration parameter name as a {@link Collection}.
+ */
+ Collection<String> getNameParts();
+
+ /**
+ * Returns the Guava TypeToken, including info on generics.
+ */
+ TypeToken<T> getTypeToken();
+
+ /**
+ * Returns the type of the configuration parameter data.
+ * <p>
+ * This returns a "super" of T only in the case where T is generified,
+ * and in such cases it returns the Class instance for the unadorned T ---
+ * i.e. for List<String> this returns Class<List> ---
+ * this is of course because there is no actual Class<List<String>> instance.
+ */
+ Class<? super T> getType();
+
+ /**
+ * Returns the name of of the configuration parameter data type, as a {@link String}.
+ */
+ String getTypeName();
+
+ /**
+ * Returns the default value of the configuration parameter.
+ */
+ T getDefaultValue();
+
+ /**
+ * Returns true if a default configuration value has been set.
+ */
+ boolean hasDefaultValue();
+
+ /**
+ * @return True if the configuration can be changed at runtime.
+ */
+ boolean isReconfigurable();
+
+ /**
+ * @return The inheritance model, or <code>null</code> for the default in any context.
+ */
+ @Nullable ConfigInheritance getInheritance();
+
+ /** Interface for elements which want to be treated as a config key without actually being one
+ * (e.g. config attribute sensors).
+ */
+ public interface HasConfigKey<T> {
+ public ConfigKey<T> getConfigKey();
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/cf2f7a93/utils/common/src/main/java/org/apache/brooklyn/config/ConfigMap.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/org/apache/brooklyn/config/ConfigMap.java b/utils/common/src/main/java/org/apache/brooklyn/config/ConfigMap.java
new file mode 100644
index 0000000..665bbf6
--- /dev/null
+++ b/utils/common/src/main/java/org/apache/brooklyn/config/ConfigMap.java
@@ -0,0 +1,86 @@
+/*
+ * 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.config;
+
+import java.util.Map;
+
+import org.apache.brooklyn.config.ConfigKey.HasConfigKey;
+import org.apache.brooklyn.util.guava.Maybe;
+
+import com.google.common.base.Predicate;
+
+public interface ConfigMap {
+
+ /** @see #getConfig(ConfigKey, Object), with default value as per the key, or null */
+ public <T> T getConfig(ConfigKey<T> key);
+
+ /** @see #getConfig(ConfigKey, Object), with default value as per the key, or null */
+ public <T> T getConfig(HasConfigKey<T> key);
+
+ /**
+ * @see #getConfig(ConfigKey, Object), with provided default value if not set
+ * @deprecated since 0.7.0; use {@link #getConfig(HasConfigKey)}
+ */
+ @Deprecated
+ public <T> T getConfig(HasConfigKey<T> key, T defaultValue);
+
+ /**
+ * Returns value stored against the given key,
+ * resolved (if it is a Task, possibly blocking), and coerced to the appropriate type,
+ * or given default value if not set,
+ * unless the default value is null in which case it returns the default.
+ *
+ * @deprecated since 0.7.0; use {@link #getConfig(ConfigKey)}
+ */
+ @Deprecated
+ public <T> T getConfig(ConfigKey<T> key, T defaultValue);
+
+ /** as {@link #getConfigRaw(ConfigKey)} but returning null if not present
+ * @deprecated since 0.7.0 use {@link #getConfigRaw(ConfigKey)} */
+ @Deprecated
+ public Object getRawConfig(ConfigKey<?> key);
+
+ /** returns the value stored against the given key,
+ * <b>not</b> any default,
+ * <b>not</b> resolved (and guaranteed non-blocking),
+ * and <b>not</b> type-coerced.
+ * @param key key to look up
+ * @param includeInherited for {@link ConfigMap} instances which have an inheritance hierarchy,
+ * whether to traverse it or not; has no effects where there is no inheritance
+ * @return raw, unresolved, uncoerced value of key in map,
+ * but <b>not</b> any default on the key
+ */
+ public Maybe<Object> getConfigRaw(ConfigKey<?> key, boolean includeInherited);
+
+ /** returns a map of all config keys to their raw (unresolved+uncoerced) contents */
+ public Map<ConfigKey<?>,Object> getAllConfig();
+
+ /** returns submap matching the given filter predicate; see ConfigPredicates for common predicates */
+ public ConfigMap submap(Predicate<ConfigKey<?>> filter);
+
+ /** returns a read-only map view which has string keys (corresponding to the config key names);
+ * callers encouraged to use the typed keys (and so not use this method),
+ * but in some compatibility areas having a Properties-like view is useful */
+ public Map<String,Object> asMapWithStringKeys();
+
+ public int size();
+
+ public boolean isEmpty();
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/cf2f7a93/utils/common/src/main/java/org/apache/brooklyn/config/StringConfigMap.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/org/apache/brooklyn/config/StringConfigMap.java b/utils/common/src/main/java/org/apache/brooklyn/config/StringConfigMap.java
new file mode 100644
index 0000000..e0e8e8f
--- /dev/null
+++ b/utils/common/src/main/java/org/apache/brooklyn/config/StringConfigMap.java
@@ -0,0 +1,35 @@
+/*
+ * 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.config;
+
+import java.util.Map;
+
+/** convenience extension where map is principally strings or converted to strings
+ * (supporting BrooklynProperties) */
+public interface StringConfigMap extends ConfigMap {
+ /** @see #getFirst(java.util.Map, String...) */
+ public String getFirst(String... keys);
+ /** returns the value of the first key which is defined
+ * <p>
+ * takes the following flags:
+ * 'warnIfNone' or 'failIfNone' (both taking a boolean (to use default message) or a string (which is the message));
+ * and 'defaultIfNone' (a default value to return if there is no such property);
+ * defaults to no warning and null default value */
+ public String getFirst(@SuppressWarnings("rawtypes") Map flags, String... keys);
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/cf2f7a93/utils/common/src/main/java/org/apache/brooklyn/test/Asserts.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/org/apache/brooklyn/test/Asserts.java b/utils/common/src/main/java/org/apache/brooklyn/test/Asserts.java
new file mode 100644
index 0000000..20fc98d
--- /dev/null
+++ b/utils/common/src/main/java/org/apache/brooklyn/test/Asserts.java
@@ -0,0 +1,499 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.test;
+
+import groovy.lang.Closure;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.apache.brooklyn.test.Asserts;
+import org.apache.brooklyn.util.collections.MutableSet;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.apache.brooklyn.util.time.Duration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+import com.google.common.base.Supplier;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+
+/**
+ * TODO should move this to new package brooklyn.util.assertions
+ * and TODO should add a repeating() method which returns an AssertingRepeater extending Repeater
+ * and:
+ * <li> adds support for requireAllIterationsTrue
+ * <li> convenience run methods equivalent to succeedsEventually and succeedsContinually
+ */
+@Beta
+public class Asserts {
+
+ /**
+ * The default timeout for assertions. Alter in individual tests by giving a
+ * "timeout" entry in method flags.
+ */
+ public static final Duration DEFAULT_TIMEOUT = Duration.THIRTY_SECONDS;
+
+ private static final Logger log = LoggerFactory.getLogger(Asserts.class);
+
+ private Asserts() {}
+
+ // --- selected routines from testng.Assert for visibility without needing that package
+
+ /**
+ * Asserts that a condition is true. If it isn't,
+ * an AssertionError, with the given message, is thrown.
+ * @param condition the condition to evaluate
+ * @param message the assertion error message
+ */
+ public static void assertTrue(boolean condition, String message) {
+ if (!condition) fail(message);
+ }
+
+ /**
+ * Asserts that a condition is false. If it isn't,
+ * an AssertionError, with the given message, is thrown.
+ * @param condition the condition to evaluate
+ * @param message the assertion error message
+ */
+ public static void assertFalse(boolean condition, String message) {
+ if (condition) fail(message);
+ }
+
+ /**
+ * Fails a test with the given message.
+ * @param message the assertion error message
+ */
+ public static AssertionError fail(String message) {
+ throw new AssertionError(message);
+ }
+
+ public static void assertEqualsIgnoringOrder(Iterable<?> actual, Iterable<?> expected) {
+ assertEqualsIgnoringOrder(actual, expected, false, null);
+ }
+
+ public static void assertEqualsIgnoringOrder(Iterable<?> actual, Iterable<?> expected, boolean logDuplicates, String errmsg) {
+ Set<?> actualSet = Sets.newLinkedHashSet(actual);
+ Set<?> expectedSet = Sets.newLinkedHashSet(expected);
+ Set<?> extras = Sets.difference(actualSet, expectedSet);
+ Set<?> missing = Sets.difference(expectedSet, actualSet);
+ List<Object> duplicates = Lists.newArrayList(actual);
+ for (Object a : actualSet) {
+ duplicates.remove(a);
+ }
+ String fullErrmsg = "extras="+extras+"; missing="+missing
+ + (logDuplicates ? "; duplicates="+MutableSet.copyOf(duplicates) : "")
+ +"; actualSize="+Iterables.size(actual)+"; expectedSize="+Iterables.size(expected)
+ +"; actual="+actual+"; expected="+expected+"; "+errmsg;
+ assertTrue(extras.isEmpty(), fullErrmsg);
+ assertTrue(missing.isEmpty(), fullErrmsg);
+ assertTrue(Iterables.size(actual) == Iterables.size(expected), fullErrmsg);
+ assertTrue(actualSet.equals(expectedSet), fullErrmsg); // should be covered by extras/missing/size test
+ }
+
+ // --- new routines
+
+ public static <T> void eventually(Supplier<? extends T> supplier, Predicate<T> predicate) {
+ eventually(ImmutableMap.<String,Object>of(), supplier, predicate);
+ }
+
+ public static <T> void eventually(Map<String,?> flags, Supplier<? extends T> supplier, Predicate<T> predicate) {
+ eventually(flags, supplier, predicate, (String)null);
+ }
+
+ public static <T> void eventually(Map<String,?> flags, Supplier<? extends T> supplier, Predicate<T> predicate, String errMsg) {
+ Duration timeout = toDuration(flags.get("timeout"), Duration.ONE_SECOND);
+ Duration period = toDuration(flags.get("period"), Duration.millis(10));
+ long periodMs = period.toMilliseconds();
+ long startTime = System.currentTimeMillis();
+ long expireTime = startTime+timeout.toMilliseconds();
+
+ boolean first = true;
+ T supplied = supplier.get();
+ while (first || System.currentTimeMillis() <= expireTime) {
+ supplied = supplier.get();
+ if (predicate.apply(supplied)) {
+ return;
+ }
+ first = false;
+ if (periodMs > 0) sleep(periodMs);
+ }
+ fail("supplied="+supplied+"; predicate="+predicate+(errMsg!=null?"; "+errMsg:""));
+ }
+
+ // TODO improve here -- these methods aren't very useful without timeouts
+ public static <T> void continually(Supplier<? extends T> supplier, Predicate<T> predicate) {
+ continually(ImmutableMap.<String,Object>of(), supplier, predicate);
+ }
+
+ public static <T> void continually(Map<String,?> flags, Supplier<? extends T> supplier, Predicate<? super T> predicate) {
+ continually(flags, supplier, predicate, (String)null);
+ }
+
+ public static <T> void continually(Map<String,?> flags, Supplier<? extends T> supplier, Predicate<T> predicate, String errMsg) {
+ Duration duration = toDuration(flags.get("timeout"), Duration.ONE_SECOND);
+ Duration period = toDuration(flags.get("period"), Duration.millis(10));
+ long periodMs = period.toMilliseconds();
+ long startTime = System.currentTimeMillis();
+ long expireTime = startTime+duration.toMilliseconds();
+
+ boolean first = true;
+ while (first || System.currentTimeMillis() <= expireTime) {
+ assertTrue(predicate.apply(supplier.get()), "supplied="+supplier.get()+"; predicate="+predicate+(errMsg!=null?"; "+errMsg:""));
+ if (periodMs > 0) sleep(periodMs);
+ first = false;
+ }
+ }
+
+
+ /**
+ * Asserts given runnable succeeds in default duration.
+ * @see #DEFAULT_TIMEOUT
+ */
+ public static void succeedsEventually(Runnable r) {
+ succeedsEventually(ImmutableMap.<String,Object>of(), r);
+ }
+
+ public static void succeedsEventually(Map<String,?> flags, Runnable r) {
+ succeedsEventually(flags, toCallable(r));
+ }
+
+ /**
+ * Asserts given callable succeeds (runs without failure) in default duration.
+ * @see #DEFAULT_TIMEOUT
+ */
+ public static <T> T succeedsEventually(Callable<T> c) {
+ return succeedsEventually(ImmutableMap.<String,Object>of(), c);
+ }
+
+ // FIXME duplication with TestUtils.BooleanWithMessage
+ public static class BooleanWithMessage {
+ boolean value; String message;
+ public BooleanWithMessage(boolean value, String message) {
+ this.value = value; this.message = message;
+ }
+ public boolean asBoolean() {
+ return value;
+ }
+ public String toString() {
+ return message;
+ }
+ }
+
+ /**
+ * Convenience method for cases where we need to test until something is true.
+ *
+ * The runnable will be invoked periodically until it succesfully concludes.
+ * <p>
+ * The following flags are supported:
+ * <ul>
+ * <li>abortOnError (boolean, default true)
+ * <li>abortOnException - (boolean, default false)
+ * <li>timeout - (a Duration or an integer in millis, defaults to 30*SECONDS)
+ * <li>period - (a Duration or an integer in millis, for fixed retry time; if not set, defaults to exponentially increasing from 1 to 500ms)
+ * <li>minPeriod - (a Duration or an integer in millis; only used if period not explicitly set; the minimum period when exponentially increasing; defaults to 1ms)
+ * <li>maxPeriod - (a Duration or an integer in millis; only used if period not explicitly set; the maximum period when exponentially increasing; defaults to 500ms)
+ * <li>maxAttempts - (integer, Integer.MAX_VALUE)
+ * </ul>
+ *
+ * The following flags are deprecated:
+ * <ul>
+ * <li>useGroovyTruth - (defaults to false; any result code apart from 'false' will be treated as success including null; ignored for Runnables which aren't Callables)
+ * </ul>
+ *
+ * @param flags, accepts the flags listed above
+ * @param r
+ * @param finallyBlock
+ */
+ public static <T> T succeedsEventually(Map<String,?> flags, Callable<T> c) {
+ boolean abortOnException = get(flags, "abortOnException", false);
+ boolean abortOnError = get(flags, "abortOnError", false);
+ boolean useGroovyTruth = get(flags, "useGroovyTruth", false);
+ boolean logException = get(flags, "logException", true);
+
+ // To speed up tests, default is for the period to start small and increase...
+ Duration duration = toDuration(flags.get("timeout"), DEFAULT_TIMEOUT);
+ Duration fixedPeriod = toDuration(flags.get("period"), null);
+ Duration minPeriod = (fixedPeriod != null) ? fixedPeriod : toDuration(flags.get("minPeriod"), Duration.millis(1));
+ Duration maxPeriod = (fixedPeriod != null) ? fixedPeriod : toDuration(flags.get("maxPeriod"), Duration.millis(500));
+ int maxAttempts = get(flags, "maxAttempts", Integer.MAX_VALUE);
+ int attempt = 0;
+ long startTime = System.currentTimeMillis();
+ try {
+ Throwable lastException = null;
+ T result = null;
+ long lastAttemptTime = 0;
+ long expireTime = startTime+duration.toMilliseconds();
+ long sleepTimeBetweenAttempts = minPeriod.toMilliseconds();
+
+ while (attempt < maxAttempts && lastAttemptTime < expireTime) {
+ try {
+ attempt++;
+ lastAttemptTime = System.currentTimeMillis();
+ result = c.call();
+ if (log.isTraceEnabled()) log.trace("Attempt {} after {} ms: {}", new Object[] {attempt, System.currentTimeMillis() - startTime, result});
+ if (useGroovyTruth) {
+ if (groovyTruth(result)) return result;
+ } else if (Boolean.FALSE.equals(result)) {
+ if (result instanceof BooleanWithMessage)
+ log.warn("Test returned an instance of BooleanWithMessage but useGroovyTruth is not set! " +
+ "The result of this probably isn't what you intended.");
+ // FIXME surprising behaviour, "false" result here is acceptable
+ return result;
+ } else {
+ return result;
+ }
+ lastException = null;
+ } catch(Throwable e) {
+ lastException = e;
+ if (log.isTraceEnabled()) log.trace("Attempt {} after {} ms: {}", new Object[] {attempt, System.currentTimeMillis() - startTime, e.getMessage()});
+ if (abortOnException) throw e;
+ if (abortOnError && e instanceof Error) throw e;
+ }
+ long sleepTime = Math.min(sleepTimeBetweenAttempts, expireTime-System.currentTimeMillis());
+ if (sleepTime > 0) Thread.sleep(sleepTime);
+ sleepTimeBetweenAttempts = Math.min(sleepTimeBetweenAttempts*2, maxPeriod.toMilliseconds());
+ }
+
+ log.info("succeedsEventually exceeded max attempts or timeout - {} attempts lasting {} ms, for {}", new Object[] {attempt, System.currentTimeMillis()-startTime, c});
+ if (lastException != null)
+ throw lastException;
+ throw fail("invalid result: "+result);
+ } catch (Throwable t) {
+ if (logException) log.info("failed succeeds-eventually, "+attempt+" attempts, "+
+ (System.currentTimeMillis()-startTime)+"ms elapsed "+
+ "(rethrowing): "+t);
+ throw propagate(t);
+ }
+ }
+
+ public static <T> void succeedsContinually(Runnable r) {
+ succeedsContinually(ImmutableMap.<String,Object>of(), r);
+ }
+
+ public static <T> void succeedsContinually(Map<?,?> flags, Runnable r) {
+ succeedsContinually(flags, toCallable(r));
+ }
+
+ public static <T> T succeedsContinually(Callable<T> c) {
+ return succeedsContinually(ImmutableMap.<String,Object>of(), c);
+ }
+
+ public static <T> T succeedsContinually(Map<?,?> flags, Callable<T> job) {
+ Duration duration = toDuration(flags.get("timeout"), Duration.ONE_SECOND);
+ Duration period = toDuration(flags.get("period"), Duration.millis(10));
+ long periodMs = period.toMilliseconds();
+ long startTime = System.currentTimeMillis();
+ long expireTime = startTime+duration.toMilliseconds();
+ int attempt = 0;
+
+ boolean first = true;
+ T result = null;
+ while (first || System.currentTimeMillis() <= expireTime) {
+ attempt++;
+ try {
+ result = job.call();
+ } catch (Exception e) {
+ log.info("succeedsContinually failed - {} attempts lasting {} ms, for {} (rethrowing)", new Object[] {attempt, System.currentTimeMillis()-startTime, job});
+ throw propagate(e);
+ }
+ if (periodMs > 0) sleep(periodMs);
+ first = false;
+ }
+ return result;
+ }
+
+ private static Duration toDuration(Object duration, Duration defaultVal) {
+ if (duration == null)
+ return defaultVal;
+ else
+ return Duration.of(duration);
+ }
+
+ public static void assertFails(Runnable r) {
+ assertFailsWith(toCallable(r), Predicates.alwaysTrue());
+ }
+
+ public static void assertFails(Callable<?> c) {
+ assertFailsWith(c, Predicates.alwaysTrue());
+ }
+
+ public static void assertFailsWith(Callable<?> c, final Closure<Boolean> exceptionChecker) {
+ assertFailsWith(c, new Predicate<Throwable>() {
+ public boolean apply(Throwable input) {
+ return exceptionChecker.call(input);
+ }
+ });
+ }
+
+ public static void assertFailsWith(Runnable c, final Class<? extends Throwable> validException, final Class<? extends Throwable> ...otherValidExceptions) {
+ final List<Class<?>> validExceptions = ImmutableList.<Class<?>>builder()
+ .add(validException)
+ .addAll(ImmutableList.copyOf(otherValidExceptions))
+ .build();
+
+ assertFailsWith(c, new Predicate<Throwable>() {
+ public boolean apply(Throwable e) {
+ for (Class<?> validException: validExceptions) {
+ if (validException.isInstance(e)) return true;
+ }
+ fail("Test threw exception of unexpected type "+e.getClass()+"; expecting "+validExceptions);
+ return false;
+ }
+ });
+ }
+
+ public static void assertFailsWith(Runnable r, Predicate<? super Throwable> exceptionChecker) {
+ assertFailsWith(toCallable(r), exceptionChecker);
+ }
+
+ public static void assertFailsWith(Callable<?> c, Predicate<? super Throwable> exceptionChecker) {
+ boolean failed = false;
+ try {
+ c.call();
+ } catch (Throwable e) {
+ failed = true;
+ if (!exceptionChecker.apply(e)) {
+ log.debug("Test threw invalid exception (failing)", e);
+ fail("Test threw invalid exception: "+e);
+ }
+ log.debug("Test for exception successful ("+e+")");
+ }
+ if (!failed) fail("Test code should have thrown exception but did not");
+ }
+
+ public static void assertReturnsEventually(final Runnable r, Duration timeout) throws InterruptedException, ExecutionException, TimeoutException {
+ final AtomicReference<Throwable> throwable = new AtomicReference<Throwable>();
+ Runnable wrappedR = new Runnable() {
+ @Override public void run() {
+ try {
+ r.run();
+ } catch (Throwable t) {
+ throwable.set(t);
+ throw Exceptions.propagate(t);
+ }
+ }
+ };
+ Thread thread = new Thread(wrappedR, "assertReturnsEventually("+r+")");
+ try {
+ thread.start();
+ thread.join(timeout.toMilliseconds());
+ if (thread.isAlive()) {
+ throw new TimeoutException("Still running: r="+r+"; thread="+Arrays.toString(thread.getStackTrace()));
+ }
+ } catch (InterruptedException e) {
+ throw Exceptions.propagate(e);
+ } finally {
+ thread.interrupt();
+ }
+
+ if (throwable.get() != null) {
+ throw new ExecutionException(throwable.get());
+ }
+ }
+
+ public static <T> void assertThat(T object, Predicate<T> condition) {
+ if (condition.apply(object)) return;
+ fail("Failed "+condition+": "+object);
+ }
+
+ @SuppressWarnings("rawtypes")
+ private static boolean groovyTruth(Object o) {
+ // TODO Doesn't handle matchers (see http://docs.codehaus.org/display/GROOVY/Groovy+Truth)
+ if (o == null) {
+ return false;
+ } else if (o instanceof Boolean) {
+ return (Boolean)o;
+ } else if (o instanceof String) {
+ return !((String)o).isEmpty();
+ } else if (o instanceof Collection) {
+ return !((Collection)o).isEmpty();
+ } else if (o instanceof Map) {
+ return !((Map)o).isEmpty();
+ } else if (o instanceof Iterator) {
+ return ((Iterator)o).hasNext();
+ } else if (o instanceof Enumeration) {
+ return ((Enumeration)o).hasMoreElements();
+ } else {
+ return true;
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private static <T> T get(Map<String,?> map, String key, T defaultVal) {
+ Object val = map.get(key);
+ return (T) ((val == null) ? defaultVal : val);
+ }
+
+ private static Callable<?> toCallable(Runnable r) {
+ return (r instanceof Callable) ? (Callable<?>)r : new RunnableAdapter<Void>(r, null);
+ }
+
+ /** Same as {@link java.util.concurrent.Executors#callable(Runnable)}, except includes toString() */
+ static final class RunnableAdapter<T> implements Callable<T> {
+ final Runnable task;
+ final T result;
+ RunnableAdapter(Runnable task, T result) {
+ this.task = task;
+ this.result = result;
+ }
+ public T call() {
+ task.run();
+ return result;
+ }
+ @Override
+ public String toString() {
+ return "RunnableAdapter("+task+")";
+ }
+ }
+
+ private static void sleep(long periodMs) {
+ if (periodMs > 0) {
+ try {
+ Thread.sleep(periodMs);
+ } catch (InterruptedException e) {
+ throw propagate(e);
+ }
+ }
+ }
+
+ private static RuntimeException propagate(Throwable t) {
+ if (t instanceof InterruptedException) {
+ Thread.currentThread().interrupt();
+ }
+ if (t instanceof RuntimeException) throw (RuntimeException)t;
+ if (t instanceof Error) throw (Error)t;
+ throw new RuntimeException(t);
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/cf2f7a93/utils/common/src/main/java/org/apache/brooklyn/util/CommandLineUtil.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/CommandLineUtil.java b/utils/common/src/main/java/org/apache/brooklyn/util/CommandLineUtil.java
new file mode 100644
index 0000000..b072630
--- /dev/null
+++ b/utils/common/src/main/java/org/apache/brooklyn/util/CommandLineUtil.java
@@ -0,0 +1,53 @@
+/*
+ * 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;
+
+import java.util.List;
+
+// FIXME move to brooklyn.util.cli.CommandLineArgs, and change get to "remove"
+public class CommandLineUtil {
+
+ public static String getCommandLineOption (List<String> args, String param){
+ return getCommandLineOption(args, param, null);
+ }
+
+ /** given a list of args, e.g. --name Foo --parent Bob
+ * will return "Foo" as param name, and remove those entries from the args list
+ */
+ public static String getCommandLineOption(List<String> args, String param, String defaultValue) {
+ int i = args.indexOf(param);
+ if (i >= 0) {
+ String result = args.get(i + 1);
+ args.remove(i + 1);
+ args.remove(i);
+ return result;
+ } else {
+ return defaultValue;
+ }
+ }
+
+ public static int getCommandLineOptionInt(List<String> args, String param, int defaultValue) {
+ String s = getCommandLineOption(args, param,null);
+ if (s == null) return defaultValue;
+ return Integer.parseInt(s);
+ }
+
+ //we don't want instances.
+ private CommandLineUtil(){}
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/cf2f7a93/utils/common/src/main/java/org/apache/brooklyn/util/JavaGroovyEquivalents.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/JavaGroovyEquivalents.java b/utils/common/src/main/java/org/apache/brooklyn/util/JavaGroovyEquivalents.java
new file mode 100644
index 0000000..94f9a04
--- /dev/null
+++ b/utils/common/src/main/java/org/apache/brooklyn/util/JavaGroovyEquivalents.java
@@ -0,0 +1,180 @@
+/*
+ * 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;
+
+import groovy.lang.Closure;
+import groovy.lang.GString;
+import groovy.time.TimeDuration;
+
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.concurrent.Callable;
+import java.util.concurrent.Executors;
+
+import org.apache.brooklyn.util.time.Duration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Function;
+import com.google.common.base.Predicate;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Maps;
+
+// FIXME move to brooklyn.util.groovy
+public class JavaGroovyEquivalents {
+
+ private static final Logger log = LoggerFactory.getLogger(JavaGroovyEquivalents.class);
+
+ public static String join(Collection<?> collection, String separator) {
+ StringBuffer result = new StringBuffer();
+ Iterator<?> ci = collection.iterator();
+ if (ci.hasNext()) result.append(asNonnullString(ci.next()));
+ while (ci.hasNext()) {
+ result.append(separator);
+ result.append(asNonnullString(ci.next()));
+ }
+ return result.toString();
+ }
+
+ /** simple elvislike operators; uses groovy truth */
+ @SuppressWarnings("unchecked")
+ public static <T> Collection<T> elvis(Collection<T> preferred, Collection<?> fallback) {
+ // TODO Would be nice to not cast, but this is groovy equivalent! Let's fix generics in stage 2
+ return groovyTruth(preferred) ? preferred : (Collection<T>) fallback;
+ }
+ public static String elvis(String preferred, String fallback) {
+ return groovyTruth(preferred) ? preferred : fallback;
+ }
+ public static String elvisString(Object preferred, Object fallback) {
+ return elvis(asString(preferred), asString(fallback));
+ }
+ public static <T> T elvis(T preferred, T fallback) {
+ return groovyTruth(preferred) ? preferred : fallback;
+ }
+ public static <T> T elvis(Iterable<?> preferences) {
+ return elvis(Iterables.toArray(preferences, Object.class));
+ }
+ public static <T> T elvis(Object... preferences) {
+ if (preferences.length == 0) throw new IllegalArgumentException("preferences must not be empty for elvis");
+ for (Object contender : preferences) {
+ if (groovyTruth(contender)) return (T) fix(contender);
+ }
+ return (T) fix(preferences[preferences.length-1]);
+ }
+
+ public static Object fix(Object o) {
+ if (o instanceof GString) return (o.toString());
+ return o;
+ }
+
+ public static String asString(Object o) {
+ if (o==null) return null;
+ return o.toString();
+ }
+ public static String asNonnullString(Object o) {
+ if (o==null) return "null";
+ return o.toString();
+ }
+
+ public static boolean groovyTruth(Collection<?> c) {
+ return c != null && !c.isEmpty();
+ }
+ public static boolean groovyTruth(String s) {
+ return s != null && !s.isEmpty();
+ }
+ public static boolean groovyTruth(Object o) {
+ // TODO Doesn't handle matchers (see http://docs.codehaus.org/display/GROOVY/Groovy+Truth)
+ if (o == null) {
+ return false;
+ } else if (o instanceof Boolean) {
+ return (Boolean)o;
+ } else if (o instanceof String) {
+ return !((String)o).isEmpty();
+ } else if (o instanceof Collection) {
+ return !((Collection)o).isEmpty();
+ } else if (o instanceof Map) {
+ return !((Map)o).isEmpty();
+ } else if (o instanceof Iterator) {
+ return ((Iterator)o).hasNext();
+ } else if (o instanceof Enumeration) {
+ return ((Enumeration)o).hasMoreElements();
+ } else {
+ return true;
+ }
+ }
+
+ public static <T> Predicate<T> groovyTruthPredicate() {
+ return new Predicate<T>() {
+ @Override public boolean apply(T val) {
+ return groovyTruth(val);
+ }
+ };
+ }
+
+ public static Function<Object,Boolean> groovyTruthFunction() {
+ return new Function<Object, Boolean>() {
+ @Override public Boolean apply(Object input) {
+ return groovyTruth(input);
+ }
+ };
+ }
+
+ public static <K,V> Map<K,V> mapOf(K key1, V val1) {
+ Map<K,V> result = Maps.newLinkedHashMap();
+ result.put(key1, val1);
+ return result;
+ }
+
+ /** @deprecated since 0.6.0 use {@link Duration#of(Object)} */
+ @Deprecated
+ public static TimeDuration toTimeDuration(Object duration) {
+ // TODO Lazy coding here for large number values; but refactoring away from groovy anyway...
+
+ if (duration == null) {
+ return null;
+ } else if (duration instanceof TimeDuration) {
+ return (TimeDuration) duration;
+ } else if (duration instanceof Number) {
+ long d = ((Number)duration).longValue();
+ if (d <= Integer.MAX_VALUE && d >= Integer.MIN_VALUE) {
+ return new TimeDuration(0,0,0,(int)d);
+ } else {
+ log.warn("Number "+d+" too large to convert to TimeDuration; using Integer.MAX_VALUE instead");
+ return new TimeDuration(0,0,0,Integer.MAX_VALUE);
+ }
+ } else {
+ throw new IllegalArgumentException("Cannot convert "+duration+" of type "+duration.getClass().getName()+" to a TimeDuration");
+ }
+ }
+
+ public static <T> Predicate<T> toPredicate(final Closure<Boolean> c) {
+ return new Predicate<T>() {
+ @Override public boolean apply(T input) {
+ return c.call(input);
+ }
+ };
+ }
+
+ @SuppressWarnings("unchecked")
+ public static <T> Callable<T> toCallable(final Runnable job) {
+ return (Callable<T>) ((job instanceof Callable) ? (Callable<T>)job : Executors.callable(job));
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/cf2f7a93/utils/common/src/main/java/org/apache/brooklyn/util/ShellUtils.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/ShellUtils.java b/utils/common/src/main/java/org/apache/brooklyn/util/ShellUtils.java
new file mode 100644
index 0000000..7a9b1af
--- /dev/null
+++ b/utils/common/src/main/java/org/apache/brooklyn/util/ShellUtils.java
@@ -0,0 +1,180 @@
+/*
+ * 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;
+
+import groovy.io.GroovyPrintStream;
+import groovy.time.TimeDuration;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.apache.brooklyn.util.stream.StreamGobbler;
+import org.apache.brooklyn.util.stream.Streams;
+import org.apache.brooklyn.util.text.Strings;
+import org.slf4j.Logger;
+
+import com.google.common.collect.Maps;
+import com.google.common.io.Closer;
+
+/**
+ * @deprecated since 0.7; does not return exit status, stderr, etc, so utility is of very limited use; and is not used in core brooklyn at all!;
+ * use ProcessTool or SystemProcessTaskFactory.
+ */
+@Deprecated
+public class ShellUtils {
+
+ public static long TIMEOUT = 60*1000;
+
+ /**
+ * Executes the given command.
+ * <p>
+ * Uses {@code bash -l -c cmd} (to have a good PATH set), and defaults for other fields.
+ * <p>
+ * requires a logger and a context object (whose toString is used in the logger and in error messages)
+ * optionally takes a string to use as input to the command
+ *
+ * @see {@link #exec(String, String, Logger, Object)}
+ */
+ public static String[] exec(String cmd, Logger log, Object context) {
+ return exec(cmd, null, log, context);
+ }
+ /** @see {@link #exec(String[], String[], File, String, Logger, Object)} */
+ public static String[] exec(String cmd, String input, Logger log, Object context) {
+ return exec(new String[] { "bash", "-l", "-c", cmd }, null, null, input, log, context);
+ }
+ /** @see {@link #exec(Map, String[], String[], File, String, Logger, Object)} */
+ public static String[] exec(Map flags, String cmd, Logger log, Object context) {
+ return exec(flags, new String[] { "bash", "-l", "-c", cmd }, null, null, null, log, context);
+ }
+ /** @see {@link #exec(Map, String[], String[], File, String, Logger, Object)} */
+ public static String[] exec(Map flags, String cmd, String input, Logger log, Object context) {
+ return exec(flags, new String[] { "bash", "-l", "-c", cmd }, null, null, input, log, context);
+ }
+ /** @see {@link #exec(Map, String[], String[], File, String, Logger, Object)} */
+ public static String[] exec(String[] cmd, String[] envp, File dir, String input, Logger log, Object context) {
+ return exec(Maps.newLinkedHashMap(), cmd, envp, dir, input, log, context);
+ }
+
+ private static long getTimeoutMs(Map flags) {
+ long timeout = TIMEOUT;
+
+ Object tf = flags.get("timeout");
+
+ if (tf instanceof Number) {
+ timeout = ((Number) tf).longValue();
+ } else if (tf instanceof TimeDuration) {
+ timeout = ((TimeDuration) tf).toMilliseconds();
+ }
+
+ //if (tf != null) timeout = tf;
+
+ return timeout;
+ }
+
+ /**
+ * Executes the given command.
+ * <p>
+ * Uses the given environmnet (inherited if null) and cwd ({@literal .} if null),
+ * feeding it the given input stream (if not null) and logging I/O at debug (if not null).
+ * <p>
+ * flags: timeout (Duration), 0 for forever; default 60 seconds
+ *
+ * @throws IllegalStateException if return code non-zero
+ * @return lines from stdout.
+ */
+ public static String[] exec(Map flags, final String[] cmd, String[] envp, File dir, String input, final Logger log, final Object context) {
+ if (log.isDebugEnabled()) {
+ log.debug("Running local command: {}% {}", context, Strings.join(cmd, " "));
+ }
+ Closer closer = Closer.create();
+ try {
+ final Process proc = Runtime.getRuntime().exec(cmd, envp, dir); // Call *execute* on the string
+ ByteArrayOutputStream stdoutB = new ByteArrayOutputStream();
+ ByteArrayOutputStream stderrB = new ByteArrayOutputStream();
+ PrintStream stdoutP = new GroovyPrintStream(stdoutB);
+ PrintStream stderrP = new GroovyPrintStream(stderrB);
+ @SuppressWarnings("resource")
+ StreamGobbler stdoutG = new StreamGobbler(proc.getInputStream(), stdoutP, log).setLogPrefix("["+context+":stdout] ");
+ stdoutG.start();
+ closer.register(stdoutG);
+ @SuppressWarnings("resource")
+ StreamGobbler stderrG = new StreamGobbler(proc.getErrorStream(), stderrP, log).setLogPrefix("["+context+":stderr] ");
+ stderrG.start();
+ closer.register(stderrG);
+ if (input!=null && input.length()>0) {
+ proc.getOutputStream().write(input.getBytes());
+ proc.getOutputStream().flush();
+ }
+
+ final long timeout = getTimeoutMs(flags);
+ final AtomicBoolean ended = new AtomicBoolean(false);
+ final AtomicBoolean killed = new AtomicBoolean(false);
+
+ //if a timeout was specified, this thread will kill the process. This is a work around because the process.waitFor'
+ //doesn't accept a timeout.
+ Thread timeoutThread = new Thread(new Runnable() {
+ public void run() {
+ if (timeout <= 0) return;
+ try {
+ Thread.sleep(timeout);
+ if (!ended.get()) {
+ if (log.isDebugEnabled()) {
+ log.debug("Timeout exceeded for "+context+"% "+Strings.join(cmd, " "));
+ }
+ proc.destroy();
+ killed.set(true);
+ }
+ } catch (Exception e) { }
+ }
+ });
+ if (timeout > 0) timeoutThread.start();
+ int exitCode = proc.waitFor();
+ ended.set(true);
+ if (timeout > 0) timeoutThread.interrupt();
+
+ stdoutG.blockUntilFinished();
+ stderrG.blockUntilFinished();
+ if (exitCode!=0 || killed.get()) {
+ String message = killed.get() ? "terminated after timeout" : "exit code "+exitCode;
+ if (log.isDebugEnabled()) {
+ log.debug("Completed local command (problem, throwing): "+context+"% "+Strings.join(cmd, " ")+" - "+message);
+ }
+ String e = "Command failed ("+message+"): "+Strings.join(cmd, " ");
+ log.warn(e+"\n"+stdoutB+(stderrB.size()>0 ? "\n--\n"+stderrB : ""));
+ throw new IllegalStateException(e+" (details logged)");
+ }
+ if (log.isDebugEnabled()) {
+ log.debug("Completed local command: "+context+"% "+Strings.join(cmd, " ")+" - exit code 0");
+ }
+ return stdoutB.toString().split("\n");
+ } catch (IOException e) {
+ throw Exceptions.propagate(e);
+ } catch (InterruptedException e) {
+ throw Exceptions.propagate(e);
+ } finally {
+ Streams.closeQuietly(closer);
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/cf2f7a93/utils/common/src/main/java/org/apache/brooklyn/util/collections/CollectionFunctionals.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/collections/CollectionFunctionals.java b/utils/common/src/main/java/org/apache/brooklyn/util/collections/CollectionFunctionals.java
new file mode 100644
index 0000000..8446e55
--- /dev/null
+++ b/utils/common/src/main/java/org/apache/brooklyn/util/collections/CollectionFunctionals.java
@@ -0,0 +1,242 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.util.collections;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.annotation.Nullable;
+
+import org.apache.brooklyn.util.collections.QuorumCheck.QuorumChecks;
+
+import com.google.common.base.Function;
+import com.google.common.base.Functions;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+import com.google.common.base.Supplier;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Sets;
+
+/** things which it seems should be in guava, but i can't find
+ * @author alex */
+public class CollectionFunctionals {
+
+ private static final class EqualsSetPredicate implements Predicate<Iterable<?>> {
+ private final Iterable<?> target;
+
+ private EqualsSetPredicate(Iterable<?> target) {
+ this.target = target;
+ }
+
+ @Override
+ public boolean apply(@Nullable Iterable<?> input) {
+ if (input==null) return false;
+ return Sets.newHashSet(target).equals(Sets.newHashSet(input));
+ }
+ }
+
+ private static final class KeysOfMapFunction<K> implements Function<Map<K, ?>, Set<K>> {
+ @Override
+ public Set<K> apply(Map<K, ?> input) {
+ if (input==null) return null;
+ return input.keySet();
+ }
+
+ @Override public String toString() { return "keys"; }
+ }
+
+ private static final class SizeSupplier implements Supplier<Integer> {
+ private final Iterable<?> collection;
+
+ private SizeSupplier(Iterable<?> collection) {
+ this.collection = collection;
+ }
+
+ @Override
+ public Integer get() {
+ return Iterables.size(collection);
+ }
+
+ @Override public String toString() { return "sizeSupplier("+collection+")"; }
+ }
+
+ public static final class SizeFunction implements Function<Iterable<?>, Integer> {
+ private final Integer valueIfInputNull;
+
+ private SizeFunction(Integer valueIfInputNull) {
+ this.valueIfInputNull = valueIfInputNull;
+ }
+
+ @Override
+ public Integer apply(Iterable<?> input) {
+ if (input==null) return valueIfInputNull;
+ return Iterables.size(input);
+ }
+
+ @Override public String toString() { return "sizeFunction"; }
+ }
+
+ public static Supplier<Integer> sizeSupplier(final Iterable<?> collection) {
+ return new SizeSupplier(collection);
+ }
+
+ public static Function<Iterable<?>, Integer> sizeFunction() { return sizeFunction(null); }
+
+ public static Function<Iterable<?>, Integer> sizeFunction(final Integer valueIfInputNull) {
+ return new SizeFunction(valueIfInputNull);
+ }
+
+ public static final class FirstElementFunction<T> implements Function<Iterable<? extends T>, T> {
+ private FirstElementFunction() {
+ }
+
+ @Override
+ public T apply(Iterable<? extends T> input) {
+ if (input==null) return null;
+ return Iterables.get(input, 0);
+ }
+
+ @Override public String toString() { return "firstElementFunction"; }
+ }
+
+ public static <T> Function<Iterable<? extends T>, T> firstElement() {
+ return new FirstElementFunction<T>();
+ }
+
+ public static <K> Function<Map<K,?>,Set<K>> keys() {
+ return new KeysOfMapFunction<K>();
+ }
+
+ public static <K> Function<Map<K, ?>, Integer> mapSize() {
+ return mapSize(null);
+ }
+
+ public static <K> Function<Map<K, ?>, Integer> mapSize(Integer valueIfNull) {
+ return Functions.compose(CollectionFunctionals.sizeFunction(valueIfNull), CollectionFunctionals.<K>keys());
+ }
+
+ /** default guava Equals predicate will reflect order of target, and will fail when matching against a list;
+ * this treats them both as sets */
+ public static Predicate<Iterable<?>> equalsSetOf(Object... target) {
+ return equalsSet(Arrays.asList(target));
+ }
+ public static Predicate<Iterable<?>> equalsSet(final Iterable<?> target) {
+ return new EqualsSetPredicate(target);
+ }
+
+ public static Predicate<Iterable<?>> sizeEquals(int targetSize) {
+ return Predicates.compose(Predicates.equalTo(targetSize), CollectionFunctionals.sizeFunction());
+ }
+
+ public static Predicate<Iterable<?>> empty() {
+ return sizeEquals(0);
+ }
+
+ public static Predicate<Iterable<?>> notEmpty() {
+ return Predicates.not(empty());
+ }
+
+ public static <K> Predicate<Map<K,?>> mapSizeEquals(int targetSize) {
+ return Predicates.compose(Predicates.equalTo(targetSize), CollectionFunctionals.<K>mapSize());
+ }
+
+ public static <T,I extends Iterable<T>> Function<I, List<T>> limit(final int max) {
+ return new LimitFunction<T,I>(max);
+ }
+
+ private static final class LimitFunction<T, I extends Iterable<T>> implements Function<I, List<T>> {
+ private final int max;
+ private LimitFunction(int max) {
+ this.max = max;
+ }
+ @Override
+ public List<T> apply(I input) {
+ if (input==null) return null;
+ MutableList<T> result = MutableList.of();
+ for (T i: input) {
+ result.add(i);
+ if (result.size()>=max)
+ return result;
+ }
+ return result;
+ }
+ }
+
+ // ---------
+ public static <I,T extends Collection<I>> Predicate<T> contains(I item) {
+ return new CollectionContains<I,T>(item);
+ }
+
+ private static final class CollectionContains<I,T extends Collection<I>> implements Predicate<T> {
+ private final I item;
+ private CollectionContains(I item) {
+ this.item = item;
+ }
+ @Override
+ public boolean apply(T input) {
+ if (input==null) return false;
+ return input.contains(item);
+ }
+ @Override
+ public String toString() {
+ return "contains("+item+")";
+ }
+ }
+
+ // ---------
+
+ public static <T,TT extends Iterable<T>> Predicate<TT> all(Predicate<T> attributeSatisfies) {
+ return quorum(QuorumChecks.all(), attributeSatisfies);
+ }
+
+ public static <T,TT extends Iterable<T>> Predicate<TT> quorum(QuorumCheck quorumCheck, Predicate<T> attributeSatisfies) {
+ return new QuorumSatisfies<T, TT>(quorumCheck, attributeSatisfies);
+ }
+
+
+ private static final class QuorumSatisfies<I,T extends Iterable<I>> implements Predicate<T> {
+ private final Predicate<I> itemCheck;
+ private final QuorumCheck quorumCheck;
+ private QuorumSatisfies(QuorumCheck quorumCheck, Predicate<I> itemCheck) {
+ this.itemCheck = Preconditions.checkNotNull(itemCheck, "itemCheck");
+ this.quorumCheck = Preconditions.checkNotNull(quorumCheck, "quorumCheck");
+ }
+ @Override
+ public boolean apply(T input) {
+ if (input==null) return false;
+ int sizeHealthy = 0, totalSize = 0;
+ for (I item: input) {
+ totalSize++;
+ if (itemCheck.apply(item)) sizeHealthy++;
+ }
+ return quorumCheck.isQuorate(sizeHealthy, totalSize);
+ }
+ @Override
+ public String toString() {
+ return quorumCheck.toString()+"("+itemCheck+")";
+ }
+ }
+
+
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/cf2f7a93/utils/common/src/main/java/org/apache/brooklyn/util/collections/Jsonya.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/collections/Jsonya.java b/utils/common/src/main/java/org/apache/brooklyn/util/collections/Jsonya.java
new file mode 100644
index 0000000..ef7f451
--- /dev/null
+++ b/utils/common/src/main/java/org/apache/brooklyn/util/collections/Jsonya.java
@@ -0,0 +1,581 @@
+/*
+ * 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.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.Stack;
+
+import javax.annotation.Nonnull;
+
+import org.apache.brooklyn.util.guava.Maybe;
+import org.apache.brooklyn.util.text.StringEscapes.JavaStringEscapes;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.Function;
+import com.google.common.base.Functions;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Throwables;
+import com.google.common.primitives.Primitives;
+
+/** Jsonya = JSON-yet-another (tool)
+ * <p>
+ * provides conveniences for working with maps and lists containing maps and lists,
+ * and other datatypes too, easily convertible to json.
+ * <p>
+ * see {@link JsonyaTest} for examples
+ *
+ * @since 0.6.0
+ **/
+@Beta
+public class Jsonya {
+
+ private Jsonya() {}
+
+ /** creates a {@link Navigator} backed by the given map (focussed at the root) */
+ public static <T extends Map<?,?>> Navigator<T> of(T map) {
+ return new Navigator<T>(map, MutableMap.class);
+ }
+
+ /** creates a {@link Navigator} backed by the map at the focus of the given navigator */
+ public static <T extends Map<?,?>> Navigator<T> of(Navigator<T> navigator) {
+ return new Navigator<T>(navigator.getFocusMap(), MutableMap.class);
+ }
+
+ /** creates a {@link Navigator} backed by a newly created map;
+ * the map can be accessed by {@link Navigator#getMap()} */
+ public static Navigator<MutableMap<Object,Object>> newInstance() {
+ return new Navigator<MutableMap<Object,Object>>(new MutableMap<Object,Object>(), MutableMap.class);
+ }
+ /** convenience for {@link Navigator#at(Object, Object...)} on a {@link #newInstance()} */
+ public static Navigator<MutableMap<Object,Object>> at(Object ...pathSegments) {
+ return newInstance().atArray(pathSegments);
+ }
+
+ /** as {@link #newInstance()} but using the given translator to massage objects inserted into the Jsonya structure */
+ public static Navigator<MutableMap<Object,Object>> newInstanceTranslating(Function<Object,Object> translator) {
+ return newInstance().useTranslator(translator);
+ }
+
+ /** as {@link #newInstanceTranslating(Function)} using an identity function
+ * (functionally equivalent to {@link #newInstance()} but explicit about it */
+ public static Navigator<MutableMap<Object,Object>> newInstanceLiteral() {
+ return newInstanceTranslating(Functions.identity());
+ }
+
+ /** as {@link #newInstanceTranslating(Function)} using a function which only supports JSON primitives:
+ * maps and collections are traversed, strings and primitives are inserted, and everything else has toString applied.
+ * see {@link JsonPrimitiveDeepTranslator} */
+ public static Navigator<MutableMap<Object,Object>> newInstancePrimitive() {
+ return newInstanceTranslating(new JsonPrimitiveDeepTranslator());
+ }
+
+ /** convenience for converting an object x to something which consists only of json primitives, doing
+ * {@link #toString()} on anything which is not recognised. see {@link JsonPrimitiveDeepTranslator} */
+ public static Object convertToJsonPrimitive(Object x) {
+ if (x==null) return null;
+ if (x instanceof Map) return newInstancePrimitive().put((Map<?,?>)x).getRootMap();
+ return newInstancePrimitive().put("data", x).getRootMap().get("data");
+ }
+
+ /** tells whether {@link #convertToJsonPrimitive(Object)} returns an object which is identical to
+ * the equivalent literal json structure. this is typically equivalent to saying serializing to json then
+ * deserializing will produce something where the result is equal to the input,
+ * modulo a few edge cases such as longs becoming ints.
+ * note that the converse (input equal to output) may not be the case,
+ * e.g. if the input contains special subclasses of collections of maps who care about type preservation. */
+ public static boolean isJsonPrimitiveCompatible(Object x) {
+ if (x==null) return true;
+ return convertToJsonPrimitive(x).equals(x);
+ }
+
+ @SuppressWarnings({"rawtypes","unchecked"})
+ public static class Navigator<T extends Map<?,?>> {
+
+ protected final Object root;
+ protected final Class<? extends Map> mapType;
+ protected Object focus;
+ protected Stack<Object> focusStack = new Stack<Object>();
+ protected Function<Object,Void> creationInPreviousFocus;
+ protected Function<Object,Object> translator;
+
+ public Navigator(Object backingStore, Class<? extends Map> mapType) {
+ this.root = Preconditions.checkNotNull(backingStore);
+ this.focus = backingStore;
+ this.mapType = mapType;
+ }
+
+ // -------------- access and configuration
+
+ /** returns the object at the focus, or null if none */
+ public Object get() {
+ return focus;
+ }
+
+ /** as {@link #get()} but always wrapped in a {@link Maybe}, absent if null */
+ public @Nonnull Maybe<Object> getMaybe() {
+ return Maybe.fromNullable(focus);
+ }
+
+ /** returns the object at the focus, casted to the given type, null if none
+ * @throws ClassCastException if object exists here but of the wrong type */
+ public <V> V get(Class<V> type) {
+ return (V)focus;
+ }
+
+ /** as {@link #get(Class)} but always wrapped in a {@link Maybe}, absent if null
+ * @throws ClassCastException if object exists here but of the wrong type */
+ public @Nonnull <V> Maybe<V> getMaybe(Class<V> type) {
+ return Maybe.fromNullable(get(type));
+ }
+
+ /** gets the object at the indicated path from the current focus
+ * (without changing the path to that focus; use {@link #at(Object, Object...)} to change focus) */
+ // Jun 2014, semantics changed so that focus does not change, which is more natural
+ public Object get(Object pathSegment, Object ...furtherPathSegments) {
+ push();
+ at(pathSegment, furtherPathSegments);
+ Object result = get();
+ pop();
+ return result;
+ }
+
+ public Navigator<T> root() {
+ focus = root;
+ return this;
+ }
+
+ /** returns the object at the root */
+ public Object getRoot() {
+ return root;
+ }
+
+ /** returns the {@link Map} at the root, throwing if root is not a map */
+ public T getRootMap() {
+ return (T) root;
+ }
+
+ /** returns a {@link Map} at the given focus, creating if needed (so never null),
+ * throwing if it exists already and is not a map */
+ public T getFocusMap() {
+ map();
+ return (T)focus;
+ }
+
+ /** as {@link #getFocusMap()} but always wrapped in a {@link Maybe}, absent if null
+ * @throws ClassCastException if object exists here but of the wrong type */
+ public @Nonnull Maybe<T> getFocusMapMaybe() {
+ return Maybe.fromNullable(getFocusMap());
+ }
+
+ /** specifies a translator function to use when new data is added;
+ * by default everything is added as a literal (ie {@link Functions#identity()}),
+ * but if you want to do translation on the way in,
+ * set a translation function
+ * <p>
+ * note that translation should be idempotent as implementation may apply it multiple times in certain cases
+ */
+ public Navigator<T> useTranslator(Function<Object,Object> translator) {
+ this.translator = translator;
+ return this;
+ }
+
+ protected Object translate(Object x) {
+ if (translator==null) return x;
+ return translator.apply(x);
+ }
+
+ protected Object translateKey(Object x) {
+ if (translator==null) return x;
+ // this could return the toString to make it strict json
+ // but json libraries seem to do that so not strictly necessary
+ return translator.apply(x);
+ }
+
+ // ------------- navigation (map mainly)
+
+ /** pushes the current focus to a stack, so that this location will be restored on the corresponding {@link #pop()} */
+ public Navigator<T> push() {
+ focusStack.push(focus);
+ return this;
+ }
+
+ /** pops the most recently pushed focus, so that it returns to the last location {@link #push()}ed */
+ public Navigator<T> pop() {
+ focus = focusStack.pop();
+ return this;
+ }
+
+ /** returns the navigator moved to focus at the indicated key sequence in the given map */
+ public Navigator<T> at(Object pathSegment, Object ...furtherPathSegments) {
+ down(pathSegment);
+ return atArray(furtherPathSegments);
+ }
+ public Navigator<T> atArray(Object[] furtherPathSegments) {
+ for (Object p: furtherPathSegments)
+ down(p);
+ return this;
+ }
+
+ /** ensures the given focus is a map, creating if needed (and creating inside the list if it is in a list) */
+ public Navigator<T> map() {
+ if (focus==null) {
+ focus = newMap();
+ creationInPreviousFocus.apply(focus);
+ }
+ if (focus instanceof List) {
+ Map m = newMap();
+ ((List)focus).add(translate(m));
+ focus = m;
+ return this;
+ }
+ if (!(focus instanceof Map))
+ throw new IllegalStateException("focus here is "+focus+"; expected a map");
+ return this;
+ }
+
+ /** puts the given key-value pair at the current focus (or multiple such),
+ * creating a map if needed, replacing any values stored against keys supplied here;
+ * if you wish to merge deep maps, see {@link #add(Object, Object...)} */
+ public Navigator<T> put(Object k1, Object v1, Object ...kvOthers) {
+ map();
+ putInternal((Map)focus, k1, v1, kvOthers);
+ return this;
+ }
+
+ public Navigator<T> putIfNotNull(Object k1, Object v1) {
+ if (v1!=null) {
+ map();
+ putInternal((Map)focus, k1, v1);
+ }
+ return this;
+ }
+
+ protected void putInternal(Map target, Object k1, Object v1, Object ...kvOthers) {
+ assert (kvOthers.length % 2) == 0 : "even number of arguments required for put";
+ target.put(translateKey(k1), translate(v1));
+ for (int i=0; i<kvOthers.length; ) {
+ target.put(translateKey(kvOthers[i++]), translate(kvOthers[i++]));
+ }
+ }
+
+ /** as {@link #put(Object, Object, Object...)} for the kv-pairs in the given map; ignores null for convenience */
+ public Navigator<T> put(Map map) {
+ map();
+ if (map==null) return this;
+ ((Map)focus).putAll((Map)translate(map));
+ return this;
+ }
+
+ protected Map newMap() {
+ try {
+ return mapType.newInstance();
+ } catch (Exception e) {
+ throw Throwables.propagate(e);
+ }
+ }
+
+ /** utility for {@link #at(Object, Object...)}, taking one argument at a time */
+ protected Navigator<T> down(final Object pathSegment) {
+ if (focus instanceof List) {
+ return downList(pathSegment);
+ }
+ if ((focus instanceof Map) || focus==null) {
+ return downMap(pathSegment);
+ }
+ throw new IllegalStateException("focus here is "+focus+"; cannot descend to '"+pathSegment+"'");
+ }
+
+ protected Navigator<T> downMap(Object pathSegmentO) {
+ final Object pathSegment = translateKey(pathSegmentO);
+ final Map givenParentMap = (Map)focus;
+ if (givenParentMap!=null) {
+ creationInPreviousFocus = null;
+ focus = givenParentMap.get(pathSegment);
+ }
+ if (focus==null) {
+ final Function<Object, Void> previousCreation = creationInPreviousFocus;
+ creationInPreviousFocus = new Function<Object, Void>() {
+ public Void apply(Object input) {
+ creationInPreviousFocus = null;
+ Map parentMap = givenParentMap;
+ if (parentMap==null) {
+ parentMap = newMap();
+ previousCreation.apply(parentMap);
+ }
+ parentMap.put(pathSegment, translate(input));
+ return null;
+ }
+ };
+ }
+ return this;
+ }
+
+ protected Navigator<T> downList(final Object pathSegment) {
+ if (!(pathSegment instanceof Integer))
+ throw new IllegalStateException("focus here is a list ("+focus+"); cannot descend to '"+pathSegment+"'");
+ final List givenParentList = (List)focus;
+ // previous focus always non-null
+ creationInPreviousFocus = null;
+ focus = givenParentList.get((Integer)pathSegment);
+ if (focus==null) {
+ // don't need to worry about creation here; we don't create list entries simply by navigating
+ // TODO a nicer architecture would create a new object with focus for each traversal
+ // in that case we could create, filling other positions with null; but is there a need?
+ creationInPreviousFocus = new Function<Object, Void>() {
+ public Void apply(Object input) {
+ throw new IllegalStateException("cannot create "+input+" here because we are at a non-existent position in a list");
+ }
+ };
+ }
+ return this;
+ }
+
+ // ------------- navigation (list mainly)
+
+ /** ensures the given focus is a list */
+ public Navigator<T> list() {
+ if (focus==null) {
+ focus = newList();
+ creationInPreviousFocus.apply(focus);
+ }
+ if (!(focus instanceof List))
+ throw new IllegalStateException("focus here is "+focus+"; expected a list");
+ return this;
+ }
+
+ protected List newList() {
+ return new ArrayList();
+ }
+
+ /** adds the given items to the focus, whether a list or a map,
+ * creating the focus as a map if it doesn't already exist.
+ * to add items to a list which might not exist, precede by a call to {@link #list()}.
+ * <p>
+ * when adding items to a list, iterable and array arguments are flattened because
+ * that makes the most sense when working with deep maps (adding one map to another where both contain lists, for example);
+ * to prevent flattening use {@link #addUnflattened(Object, Object...)}
+ * <p>
+ * when adding to a map, arguments will be treated as things to put into the map,
+ * accepting either multiple arguments, as key1, value1, key2, value2, ...
+ * (and must be an event number); or a single argument which must be a map,
+ * in which case the value for each key in the supplied map is added to any existing value against that key in the target map
+ * (in other words, it will do a "deep put", where nested maps are effectively merged)
+ * <p>
+ * this implementation will currently throw if you attempt to add a non-map to anything present which is not a list;
+ * auto-conversion to a list may be added in a future version
+ * */
+ public Navigator<T> add(Object o1, Object ...others) {
+ if (focus==null) map();
+ addInternal(focus, focus, o1, others);
+ return this;
+ }
+
+ /** adds the given arguments to a list at this point (will not descend into maps, and will not flatten lists) */
+ public Navigator<T> addUnflattened(Object o1, Object ...others) {
+ ((Collection)focus).add(translate(o1));
+ for (Object oi: others) ((Collection)focus).add(translate(oi));
+ return this;
+ }
+
+ protected void addInternal(Object initialFocus, Object currentFocus, Object o1, Object ...others) {
+ if (currentFocus instanceof Map) {
+ Map target = (Map)currentFocus;
+ Map source;
+ if (others.length==0) {
+ // add as a map
+ if (o1==null)
+ // ignore if null
+ return ;
+ if (!(o1 instanceof Map))
+ throw new IllegalStateException("cannot add: focus here is "+currentFocus+" (in "+initialFocus+"); expected a collection, or a map (with a map being added, not "+o1+")");
+ source = (Map)translate(o1);
+ } else {
+ // build a source map from the arguments as key-value pairs
+ if ((others.length % 2)==0)
+ throw new IllegalArgumentException("cannot add an odd number of arguments to a map" +
+ " ("+o1+" then "+Arrays.toString(others)+" in "+currentFocus+" in "+initialFocus+")");
+ source = MutableMap.of(translateKey(o1), translate(others[0]));
+ for (int i=1; i<others.length; )
+ source.put(translateKey(others[i++]), translate(others[i++]));
+ }
+ // and add the source map to the target
+ for (Object entry : source.entrySet()) {
+ Object key = ((Map.Entry)entry).getKey();
+ Object sv = ((Map.Entry)entry).getValue();
+ Object tv = target.get(key);
+ if (!target.containsKey(key)) {
+ target.put(key, sv);
+ } else {
+ addInternal(initialFocus, tv, sv);
+ }
+ }
+ return;
+ }
+ // lists are easy to add to, but remember we have to flatten
+ if (!(currentFocus instanceof Collection))
+ // TODO a nicer architecture might replace the current target with a list (also above where single non-map argument is supplied)
+ throw new IllegalStateException("cannot add: focus here is "+currentFocus+"; expected a collection");
+ addFlattened((Collection)currentFocus, o1);
+ for (Object oi: others) addFlattened((Collection)currentFocus, oi);
+ }
+
+ protected void addFlattened(Collection target, Object item) {
+ if (item instanceof Iterable) {
+ for (Object i: (Iterable)item)
+ addFlattened(target, i);
+ return;
+ }
+ if (item.getClass().isArray()) {
+ for (Object i: ((Object[])item))
+ addFlattened(target, i);
+ return;
+ }
+ // nothing to flatten
+ target.add(translate(item));
+ }
+
+ /** Returns JSON serialized output for given focus in the given jsonya;
+ * applies a naive toString for specialized types */
+ @Override
+ public String toString() {
+ return render(get());
+ }
+ }
+
+ public static String render(Object focus) {
+ if (focus instanceof Map) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("{");
+ boolean first = true;
+ for (Object entry: ((Map<?,?>)focus).entrySet()) {
+ if (!first) sb.append(",");
+ else first = false;
+ sb.append(" ");
+ sb.append( render(((Map.Entry<?,?>)entry).getKey()) );
+ sb.append(": ");
+ sb.append( render(((Map.Entry<?,?>)entry).getValue()) );
+ }
+ sb.append(" }");
+ return sb.toString();
+ }
+ if (focus instanceof Collection) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("[");
+ boolean first = true;
+ for (Object entry: (Collection<?>)focus) {
+ if (!first) sb.append(",");
+ else first = false;
+ sb.append( render(entry) );
+ }
+ sb.append(" ]");
+ return sb.toString();
+ }
+ if (focus instanceof String) {
+ return JavaStringEscapes.wrapJavaString((String)focus);
+ }
+ if (focus == null || focus instanceof Number || focus instanceof Boolean)
+ return ""+focus;
+
+ return render(""+focus);
+ }
+
+ /** Converts an object to one which uses standard JSON objects where possible
+ * (strings, numbers, booleans, maps, lists), and uses toString elsewhere */
+ public static class JsonPrimitiveDeepTranslator implements Function<Object,Object> {
+ public static JsonPrimitiveDeepTranslator INSTANCE = new JsonPrimitiveDeepTranslator();
+
+ /** No need to instantiate except when subclassing. Use static {@link #INSTANCE}. */
+ protected JsonPrimitiveDeepTranslator() {}
+
+ @Override
+ public Object apply(Object input) {
+ return apply(input, new HashSet<Object>());
+ }
+
+ protected Object apply(Object input, Set<Object> stack) {
+ if (input==null) return applyNull(stack);
+
+ if (isPrimitiveOrBoxer(input.getClass()))
+ return applyPrimitiveOrBoxer(input, stack);
+
+ if (input instanceof String)
+ return applyString((String)input, stack);
+
+ stack = new HashSet<Object>(stack);
+ if (!stack.add(input))
+ // fail if object is self-recursive; don't even try toString as that is dangerous
+ // (extra measure of safety, since maps and lists generally fail elsewhere with recursive entries,
+ // eg in hashcode or toString)
+ return "[REF_ANCESTOR:"+stack.getClass()+"]";
+
+ if (input instanceof Collection<?>)
+ return applyCollection( (Collection<?>)input, stack );
+
+ if (input instanceof Map<?,?>)
+ return applyMap( (Map<?,?>)input, stack );
+
+ return applyOther(input, stack);
+ }
+
+ protected Object applyNull(Set<Object> stack) {
+ return null;
+ }
+
+ protected Object applyPrimitiveOrBoxer(Object input, Set<Object> stack) {
+ return input;
+ }
+
+ protected Object applyString(String input, Set<Object> stack) {
+ return input.toString();
+ }
+
+ protected Object applyCollection(Collection<?> input, Set<Object> stack) {
+ MutableList<Object> result = MutableList.of();
+
+ for (Object xi: input)
+ result.add(apply(xi, stack));
+
+ return result;
+ }
+
+ protected Object applyMap(Map<?, ?> input, Set<Object> stack) {
+ MutableMap<Object, Object> result = MutableMap.of();
+
+ for (Map.Entry<?,?> xi: input.entrySet())
+ result.put(apply(xi.getKey(), stack), apply(xi.getValue(), stack));
+
+ return result;
+ }
+
+ protected Object applyOther(Object input, Set<Object> stack) {
+ return input.toString();
+ }
+
+ public static boolean isPrimitiveOrBoxer(Class<?> type) {
+ return Primitives.allPrimitiveTypes().contains(type) || Primitives.allWrapperTypes().contains(type);
+ }
+ }
+
+}