promote snake-yaml dependency and Yamls utility to utils package, and use yaml parsing for map coercion to be much more flexible in terms of string-to-map coercion
Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/a4f7a4c0 Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/a4f7a4c0 Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/a4f7a4c0 Branch: refs/heads/master Commit: a4f7a4c0e3db5220e936bcffb5d52c3731b22175 Parents: f94c2ae Author: Alex Heneveld <alex.henev...@cloudsoftcorp.com> Authored: Thu Jul 17 23:29:12 2014 -0400 Committer: Alex Heneveld <alex.henev...@cloudsoftcorp.com> Committed: Fri Jul 18 09:53:52 2014 -0400 ---------------------------------------------------------------------- camp/camp-base/pom.xml | 5 - .../java/io/brooklyn/camp/spi/pdp/Artifact.java | 3 +- .../camp/spi/pdp/ArtifactRequirement.java | 3 +- .../java/io/brooklyn/camp/spi/pdp/Service.java | 3 +- .../camp/spi/pdp/ServiceCharacteristic.java | 3 +- .../brooklyn/camp/spi/resolve/PdpProcessor.java | 2 +- .../main/java/io/brooklyn/util/yaml/Yamls.java | 89 +------------- .../pdp/DeploymentPlanToyInterpreterTest.java | 2 +- .../java/brooklyn/util/flags/TypeCoercions.java | 85 +++++++++----- .../util/internal/TypeCoercionsTest.java | 72 ++++++++++-- usage/launcher/pom.xml | 7 ++ utils/common/pom.xml | 5 + .../src/main/java/brooklyn/util/yaml/Yamls.java | 115 +++++++++++++++++++ 13 files changed, 253 insertions(+), 141 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a4f7a4c0/camp/camp-base/pom.xml ---------------------------------------------------------------------- diff --git a/camp/camp-base/pom.xml b/camp/camp-base/pom.xml index b80ef0c..e8638b1 100644 --- a/camp/camp-base/pom.xml +++ b/camp/camp-base/pom.xml @@ -57,11 +57,6 @@ <artifactId>commons-compress</artifactId> <version>${commons-compress.version}</version> </dependency> - <dependency> - <groupId>org.yaml</groupId> - <artifactId>snakeyaml</artifactId> - <version>${snakeyaml.version}</version> - </dependency> <!-- just for logging, not exported --> <!-- http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a4f7a4c0/camp/camp-base/src/main/java/io/brooklyn/camp/spi/pdp/Artifact.java ---------------------------------------------------------------------- diff --git a/camp/camp-base/src/main/java/io/brooklyn/camp/spi/pdp/Artifact.java b/camp/camp-base/src/main/java/io/brooklyn/camp/spi/pdp/Artifact.java index 72f356e..697a3ba 100644 --- a/camp/camp-base/src/main/java/io/brooklyn/camp/spi/pdp/Artifact.java +++ b/camp/camp-base/src/main/java/io/brooklyn/camp/spi/pdp/Artifact.java @@ -18,8 +18,6 @@ */ package io.brooklyn.camp.spi.pdp; -import io.brooklyn.util.yaml.Yamls; - import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -27,6 +25,7 @@ import java.util.Map; import org.apache.commons.lang3.builder.ToStringBuilder; import brooklyn.util.collections.MutableMap; +import brooklyn.util.yaml.Yamls; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a4f7a4c0/camp/camp-base/src/main/java/io/brooklyn/camp/spi/pdp/ArtifactRequirement.java ---------------------------------------------------------------------- diff --git a/camp/camp-base/src/main/java/io/brooklyn/camp/spi/pdp/ArtifactRequirement.java b/camp/camp-base/src/main/java/io/brooklyn/camp/spi/pdp/ArtifactRequirement.java index 50f5335..da9936a 100644 --- a/camp/camp-base/src/main/java/io/brooklyn/camp/spi/pdp/ArtifactRequirement.java +++ b/camp/camp-base/src/main/java/io/brooklyn/camp/spi/pdp/ArtifactRequirement.java @@ -18,13 +18,12 @@ */ package io.brooklyn.camp.spi.pdp; -import io.brooklyn.util.yaml.Yamls; - import java.util.Map; import org.apache.commons.lang3.builder.ToStringBuilder; import brooklyn.util.collections.MutableMap; +import brooklyn.util.yaml.Yamls; import com.google.common.collect.ImmutableMap; http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a4f7a4c0/camp/camp-base/src/main/java/io/brooklyn/camp/spi/pdp/Service.java ---------------------------------------------------------------------- diff --git a/camp/camp-base/src/main/java/io/brooklyn/camp/spi/pdp/Service.java b/camp/camp-base/src/main/java/io/brooklyn/camp/spi/pdp/Service.java index 241b80c..5921176 100644 --- a/camp/camp-base/src/main/java/io/brooklyn/camp/spi/pdp/Service.java +++ b/camp/camp-base/src/main/java/io/brooklyn/camp/spi/pdp/Service.java @@ -18,8 +18,6 @@ */ package io.brooklyn.camp.spi.pdp; -import io.brooklyn.util.yaml.Yamls; - import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -27,6 +25,7 @@ import java.util.Map; import org.apache.commons.lang3.builder.ToStringBuilder; import brooklyn.util.collections.MutableMap; +import brooklyn.util.yaml.Yamls; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a4f7a4c0/camp/camp-base/src/main/java/io/brooklyn/camp/spi/pdp/ServiceCharacteristic.java ---------------------------------------------------------------------- diff --git a/camp/camp-base/src/main/java/io/brooklyn/camp/spi/pdp/ServiceCharacteristic.java b/camp/camp-base/src/main/java/io/brooklyn/camp/spi/pdp/ServiceCharacteristic.java index cc5227d..8b27e2a 100644 --- a/camp/camp-base/src/main/java/io/brooklyn/camp/spi/pdp/ServiceCharacteristic.java +++ b/camp/camp-base/src/main/java/io/brooklyn/camp/spi/pdp/ServiceCharacteristic.java @@ -18,13 +18,12 @@ */ package io.brooklyn.camp.spi.pdp; -import io.brooklyn.util.yaml.Yamls; - import java.util.Map; import org.apache.commons.lang3.builder.ToStringBuilder; import brooklyn.util.collections.MutableMap; +import brooklyn.util.yaml.Yamls; import com.google.common.collect.ImmutableMap; http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a4f7a4c0/camp/camp-base/src/main/java/io/brooklyn/camp/spi/resolve/PdpProcessor.java ---------------------------------------------------------------------- diff --git a/camp/camp-base/src/main/java/io/brooklyn/camp/spi/resolve/PdpProcessor.java b/camp/camp-base/src/main/java/io/brooklyn/camp/spi/resolve/PdpProcessor.java index 897ff23..3252aaf 100644 --- a/camp/camp-base/src/main/java/io/brooklyn/camp/spi/resolve/PdpProcessor.java +++ b/camp/camp-base/src/main/java/io/brooklyn/camp/spi/resolve/PdpProcessor.java @@ -27,7 +27,6 @@ import io.brooklyn.camp.spi.pdp.DeploymentPlan; import io.brooklyn.camp.spi.pdp.Service; import io.brooklyn.camp.spi.resolve.interpret.PlanInterpretationContext; import io.brooklyn.camp.spi.resolve.interpret.PlanInterpretationNode; -import io.brooklyn.util.yaml.Yamls; import java.io.InputStream; import java.io.Reader; @@ -41,6 +40,7 @@ import org.apache.commons.compress.archivers.ArchiveStreamFactory; import org.yaml.snakeyaml.error.YAMLException; import brooklyn.util.exceptions.Exceptions; +import brooklyn.util.yaml.Yamls; import com.google.common.annotations.VisibleForTesting; http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a4f7a4c0/camp/camp-base/src/main/java/io/brooklyn/util/yaml/Yamls.java ---------------------------------------------------------------------- diff --git a/camp/camp-base/src/main/java/io/brooklyn/util/yaml/Yamls.java b/camp/camp-base/src/main/java/io/brooklyn/util/yaml/Yamls.java index d275ce8..44974f0 100644 --- a/camp/camp-base/src/main/java/io/brooklyn/util/yaml/Yamls.java +++ b/camp/camp-base/src/main/java/io/brooklyn/util/yaml/Yamls.java @@ -18,90 +18,7 @@ */ package io.brooklyn.util.yaml; -import java.io.Reader; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.google.common.collect.Iterables; - -public class Yamls { - - private static final Logger log = LoggerFactory.getLogger(Yamls.class); - - @SuppressWarnings({ "unchecked", "rawtypes" }) - public static <T> T getAs(Object x, Class<T> type) { - if (x==null) return null; - if (x instanceof Iterable || x instanceof Iterator) { - List result = new ArrayList(); - Iterator xi; - if (Iterator.class.isAssignableFrom(x.getClass())) { - xi = (Iterator)x; - } else { - xi = ((Iterable)x).iterator(); - } - while (xi.hasNext()) { - result.add( xi.next() ); - } - if (type.isAssignableFrom(Iterable.class)) return (T)result; - if (type.isAssignableFrom(Iterator.class)) return (T)result.iterator(); - if (type.isAssignableFrom(List.class)) return (T)result; - x = Iterables.getOnlyElement(result); - } - // TODO more coercion? - return (T)x; - } - - @SuppressWarnings("rawtypes") - public static void dump(int depth, Object r) { - if (r instanceof Iterable) { - for (Object ri : ((Iterable)r)) - dump(depth+1, ri); - } else if (r instanceof Map) { - for (Object re: ((Map)r).entrySet()) { - for (int i=0; i<depth; i++) System.out.print(" "); - System.out.println(((Entry)re).getKey()+":"); - dump(depth+1, ((Entry)re).getValue()); - } - } else { - for (int i=0; i<depth; i++) System.out.print(" "); - if (r==null) System.out.println("<null>"); - else System.out.println("<"+r.getClass().getSimpleName()+">"+" "+r); - } - } - - /** simplifies new Yaml().loadAll, and converts to list to prevent single-use iterable bug in yaml */ - @SuppressWarnings("unchecked") - public static Iterable<Object> parseAll(String yaml) { - Iterable<Object> result = new org.yaml.snakeyaml.Yaml().loadAll(yaml); - return (List<Object>) getAs(result, List.class); - } - - /** as {@link #parseAll(String)} */ - @SuppressWarnings("unchecked") - public static Iterable<Object> parseAll(Reader yaml) { - Iterable<Object> result = new org.yaml.snakeyaml.Yaml().loadAll(yaml); - return (List<Object>) getAs(result, List.class); - } - - public static Object removeMultinameAttribute(Map<String,Object> obj, String ...equivalentNames) { - Object result = null; - for (String name: equivalentNames) { - Object candidate = obj.remove(name); - if (candidate!=null) { - if (result==null) result = candidate; - else if (!result.equals(candidate)) { - log.warn("Different values for attributes "+Arrays.toString(equivalentNames)+"; " + - "preferring '"+result+"' to '"+candidate+"'"); - } - } - } - return result; - } +/** @deprecated since 0.7.0 use {@link brooklyn.util.yaml.Yamls} */ +@Deprecated +public class Yamls extends brooklyn.util.yaml.Yamls { } http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a4f7a4c0/camp/camp-base/src/test/java/io/brooklyn/camp/spi/pdp/DeploymentPlanToyInterpreterTest.java ---------------------------------------------------------------------- diff --git a/camp/camp-base/src/test/java/io/brooklyn/camp/spi/pdp/DeploymentPlanToyInterpreterTest.java b/camp/camp-base/src/test/java/io/brooklyn/camp/spi/pdp/DeploymentPlanToyInterpreterTest.java index de922a1..29f8abf 100644 --- a/camp/camp-base/src/test/java/io/brooklyn/camp/spi/pdp/DeploymentPlanToyInterpreterTest.java +++ b/camp/camp-base/src/test/java/io/brooklyn/camp/spi/pdp/DeploymentPlanToyInterpreterTest.java @@ -21,7 +21,6 @@ package io.brooklyn.camp.spi.pdp; import io.brooklyn.camp.BasicCampPlatform; import io.brooklyn.camp.spi.resolve.PlanInterpreter.PlanInterpreterAdapter; import io.brooklyn.camp.spi.resolve.interpret.PlanInterpretationNode; -import io.brooklyn.util.yaml.Yamls; import java.util.Map; @@ -32,6 +31,7 @@ import org.testng.annotations.Test; import brooklyn.util.stream.Streams; import brooklyn.util.text.Strings; +import brooklyn.util.yaml.Yamls; @Test public class DeploymentPlanToyInterpreterTest { http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a4f7a4c0/core/src/main/java/brooklyn/util/flags/TypeCoercions.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/brooklyn/util/flags/TypeCoercions.java b/core/src/main/java/brooklyn/util/flags/TypeCoercions.java index 1ee45ae..7e92ed5 100644 --- a/core/src/main/java/brooklyn/util/flags/TypeCoercions.java +++ b/core/src/main/java/brooklyn/util/flags/TypeCoercions.java @@ -18,7 +18,9 @@ */ package brooklyn.util.flags; -import java.io.IOException; +import groovy.lang.Closure; +import groovy.time.TimeDuration; + import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.lang.reflect.Modifier; @@ -39,23 +41,9 @@ import java.util.concurrent.atomic.AtomicLong; import javax.annotation.Nullable; import javax.annotation.concurrent.GuardedBy; -import org.codehaus.jackson.map.ObjectMapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.google.common.base.CaseFormat; -import com.google.common.base.Function; -import com.google.common.base.Preconditions; -import com.google.common.base.Predicate; -import com.google.common.base.Splitter; -import com.google.common.collect.HashBasedTable; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Table; -import com.google.common.net.HostAndPort; -import com.google.common.primitives.Primitives; -import com.google.common.reflect.TypeToken; - import brooklyn.entity.basic.ClosureEntityFactory; import brooklyn.entity.basic.ConfigurableEntityFactory; import brooklyn.entity.basic.ConfigurableEntityFactoryFromEntityFactory; @@ -70,10 +58,23 @@ import brooklyn.util.net.Cidr; import brooklyn.util.net.Networking; import brooklyn.util.net.UserAndHostAndPort; import brooklyn.util.text.StringEscapes.JavaStringEscapes; +import brooklyn.util.text.Strings; import brooklyn.util.time.Duration; -import groovy.lang.Closure; -import groovy.time.TimeDuration; +import brooklyn.util.yaml.Yamls; +import com.google.common.base.CaseFormat; +import com.google.common.base.Function; +import com.google.common.base.Preconditions; +import com.google.common.base.Predicate; +import com.google.common.collect.HashBasedTable; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Table; +import com.google.common.net.HostAndPort; +import com.google.common.primitives.Primitives; +import com.google.common.reflect.TypeToken; + +@SuppressWarnings("rawtypes") public class TypeCoercions { private static final Logger log = LoggerFactory.getLogger(TypeCoercions.class); @@ -103,7 +104,7 @@ public class TypeCoercions { } /** @see #coerce(Object, Class) */ - @SuppressWarnings({ "unchecked", "rawtypes" }) + @SuppressWarnings({ "unchecked" }) public static <T> T coerce(Object value, TypeToken<T> targetTypeToken) { if (value==null) return null; // does not actually cast generified contents; that is left to the caller @@ -433,12 +434,14 @@ public class TypeCoercions { } }); registerAdapter(Collection.class, Set.class, new Function<Collection,Set>() { + @SuppressWarnings("unchecked") @Override public Set apply(Collection input) { return new LinkedHashSet(input); } }); registerAdapter(Collection.class, List.class, new Function<Collection,List>() { + @SuppressWarnings("unchecked") @Override public List apply(Collection input) { return new ArrayList(input); @@ -485,12 +488,14 @@ public class TypeCoercions { } }); registerAdapter(Closure.class, ConfigurableEntityFactory.class, new Function<Closure,ConfigurableEntityFactory>() { + @SuppressWarnings("unchecked") @Override public ConfigurableEntityFactory apply(Closure input) { return new ClosureEntityFactory(input); } }); registerAdapter(EntityFactory.class, ConfigurableEntityFactory.class, new Function<EntityFactory,ConfigurableEntityFactory>() { + @SuppressWarnings("unchecked") @Override public ConfigurableEntityFactory apply(EntityFactory input) { if (input instanceof ConfigurableEntityFactory) return (ConfigurableEntityFactory)input; @@ -498,6 +503,7 @@ public class TypeCoercions { } }); registerAdapter(Closure.class, EntityFactory.class, new Function<Closure,EntityFactory>() { + @SuppressWarnings("unchecked") @Override public EntityFactory apply(Closure input) { return new ClosureEntityFactory(input); @@ -530,6 +536,7 @@ public class TypeCoercions { } }); registerAdapter(Object.class, TimeDuration.class, new Function<Object,TimeDuration>() { + @SuppressWarnings("deprecation") @Override public TimeDuration apply(final Object input) { log.warn("deprecated automatic coercion of Object to TimeDuration (set breakpoint in TypeCoercions to inspect, convert to Duration)"); @@ -627,20 +634,40 @@ public class TypeCoercions { registerAdapter(String.class, Map.class, new Function<String,Map>() { @Override public Map apply(final String input) { - // Auto-detect JSON. This allows complex data structures to be received over the REST API. + Exception error = null; + + // first try wrapping in braces if needed + if (!input.trim().startsWith("{")) { + try { + return apply("{ "+input+" }"); + } catch (Exception e) { + Exceptions.propagateIfFatal(e); + // prefer this error + error = e; + // fall back to parsing without braces, e.g. if it's multiline + } + } + try { - if (!input.isEmpty() && input.charAt(0) == '{') { - return new ObjectMapper().readValue(input, Map.class); + return Yamls.getAs( Yamls.parseAll(input), Map.class ); + } catch (Exception e) { + Exceptions.propagateIfFatal(e); + if (error!=null && input.indexOf('\n')==-1) { + // prefer the original error if it wasn't braced and wasn't multiline + e = error; } - } catch (IOException e) { - // just fall through to the map parsing + throw new IllegalArgumentException("Cannot parse string as map with flexible YAML parsing; "+ + (e instanceof ClassCastException ? "yaml treats it as a string" : + (e instanceof IllegalArgumentException && Strings.isNonEmpty(e.getMessage())) ? e.getMessage() : + ""+e) ); } - - // TODO would be nice to accept YAML for complex data structures too, but it's not as simple as JSON to auto-detect. - - // Simple map parsing - supports "key1=value1,key2=value2" style input - // TODO we should respect quoted strings etc - return ImmutableMap.copyOf(Splitter.on(",").trimResults().omitEmptyStrings().withKeyValueSeparator("=").split(input)); + + // NB: previously we supported this also, when we did json above; + // yaml support is better as it supports quotes (and better than json because it allows dropping quotes) + // snake-yaml, our parser, also accepts key=value -- although i'm not sure this is strictly yaml compliant; + // our tests will catch it if snake behaviour changes, and we can reinstate this + // (but note it doesn't do quotes; see http://code.google.com/p/guava-libraries/issues/detail?id=412 for that): +// return ImmutableMap.copyOf(Splitter.on(",").trimResults().omitEmptyStrings().withKeyValueSeparator("=").split(input)); } }); } http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a4f7a4c0/core/src/test/java/brooklyn/util/internal/TypeCoercionsTest.java ---------------------------------------------------------------------- diff --git a/core/src/test/java/brooklyn/util/internal/TypeCoercionsTest.java b/core/src/test/java/brooklyn/util/internal/TypeCoercionsTest.java index 05b4c72..6efffa2 100644 --- a/core/src/test/java/brooklyn/util/internal/TypeCoercionsTest.java +++ b/core/src/test/java/brooklyn/util/internal/TypeCoercionsTest.java @@ -32,17 +32,17 @@ import org.slf4j.LoggerFactory; import org.testng.Assert; import org.testng.annotations.Test; +import brooklyn.entity.basic.Lifecycle; +import brooklyn.util.flags.ClassCoercionException; +import brooklyn.util.flags.TypeCoercions; +import brooklyn.util.text.StringPredicates; + import com.google.common.base.Predicate; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.reflect.TypeToken; -import brooklyn.entity.basic.Lifecycle; -import brooklyn.util.flags.ClassCoercionException; -import brooklyn.util.flags.TypeCoercions; -import brooklyn.util.text.StringPredicates; - public class TypeCoercionsTest { private static final Logger log = LoggerFactory.getLogger(TypeCoercionsTest.class); @@ -191,15 +191,65 @@ public class TypeCoercionsTest { } @Test - public void testStringToMapCoercion() { - Map<?,?> s = TypeCoercions.coerce("a=1,b=2,c=3", Map.class); - Assert.assertEquals(s, ImmutableMap.of("a", "1", "b", "2", "c", "3")); + public void testJsonStringToMapCoercion() { + Map<?,?> s = TypeCoercions.coerce("{ \"a\" : \"1\", b : 2 }", Map.class); + Assert.assertEquals(s, ImmutableMap.of("a", "1", "b", 2)); } @Test - public void testJsonStringToMapCoercion() { - Map<?,?> s = TypeCoercions.coerce("{ \"a\" : \"1\", \"b\" : \"2\", \"c\" : \"3\" }", Map.class); - Assert.assertEquals(s, ImmutableMap.of("a", "1", "b", "2", "c", "3")); + public void testJsonStringWithoutQuotesToMapCoercion() { + Map<?,?> s = TypeCoercions.coerce("{ a : 1 }", Map.class); + Assert.assertEquals(s, ImmutableMap.of("a", 1)); + } + + @Test + public void testJsonComplexTypesToMapCoercion() { + Map<?,?> s = TypeCoercions.coerce("{ a : [1, \"2\", '\"3\"'], b: { c: d, 'e': \"f\" } }", Map.class); + Assert.assertEquals(s, ImmutableMap.of("a", ImmutableList.<Object>of(1, "2", "\"3\""), + "b", ImmutableMap.of("c", "d", "e", "f"))); + } + + @Test + public void testJsonStringWithoutBracesToMapCoercion() { + Map<?,?> s = TypeCoercions.coerce("a : 1", Map.class); + Assert.assertEquals(s, ImmutableMap.of("a", 1)); + } + + @Test + public void testJsonStringWithoutBracesWithMultipleToMapCoercion() { + Map<?,?> s = TypeCoercions.coerce("a : 1, b : 2", Map.class); + Assert.assertEquals(s, ImmutableMap.of("a", 1, "b", 2)); + } + + @Test + public void testKeyEqualsValueStringToMapCoercion() { + Map<?,?> s = TypeCoercions.coerce("a=1,b=2", Map.class); + Assert.assertEquals(s, ImmutableMap.of("a", "1", "b", "2")); + } + + @Test(expectedExceptions=IllegalArgumentException.class) + public void testJsonStringWithoutBracesOrSpaceDisallowedAsMapCoercion() { + // yaml requires spaces after the colon + Map<?,?> s = TypeCoercions.coerce("a:1,b:2", Map.class); + Assert.assertEquals(s, ImmutableMap.of("a", 1, "b", 2)); + } + + @Test + public void testEqualsInBracesMapCoercion() { + Map<?,?> s = TypeCoercions.coerce("{ a = 1, b = '2' }", Map.class); + Assert.assertEquals(s, ImmutableMap.of("a", 1, "b", "2")); + } + + @Test + public void testKeyEqualsOrColonValueWithBracesStringToMapCoercion() { + Map<?,?> s = TypeCoercions.coerce("{ a=1, b: 2 }", Map.class); + Assert.assertEquals(s, ImmutableMap.of("a", "1", "b", 2)); + } + + @Test + public void testKeyEqualsOrColonValueWithoutBracesStringToMapCoercion() { + Map<?,?> s = TypeCoercions.coerce("a=1, b: 2", Map.class); + Assert.assertEquals(s, ImmutableMap.of("a", "1", "b", 2)); } @Test http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a4f7a4c0/usage/launcher/pom.xml ---------------------------------------------------------------------- diff --git a/usage/launcher/pom.xml b/usage/launcher/pom.xml index 57d3e24..1fb4fcd 100644 --- a/usage/launcher/pom.xml +++ b/usage/launcher/pom.xml @@ -86,6 +86,13 @@ </dependency> <dependency> <groupId>io.brooklyn</groupId> + <artifactId>brooklyn-software-base</artifactId> + <version>${project.version}</version> + <classifier>tests</classifier> + <scope>test</scope> + </dependency> + <dependency> + <groupId>io.brooklyn</groupId> <artifactId>brooklyn-software-webapp</artifactId> <version>${project.version}</version> <scope>test</scope> http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a4f7a4c0/utils/common/pom.xml ---------------------------------------------------------------------- diff --git a/utils/common/pom.xml b/utils/common/pom.xml index 24dec9f..54de72a 100644 --- a/utils/common/pom.xml +++ b/utils/common/pom.xml @@ -56,6 +56,11 @@ <artifactId>commons-io</artifactId> <version>${commons-io.version}</version> </dependency> + <dependency> + <groupId>org.yaml</groupId> + <artifactId>snakeyaml</artifactId> + <version>${snakeyaml.version}</version> + </dependency> <dependency> <groupId>org.testng</groupId> http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a4f7a4c0/utils/common/src/main/java/brooklyn/util/yaml/Yamls.java ---------------------------------------------------------------------- diff --git a/utils/common/src/main/java/brooklyn/util/yaml/Yamls.java b/utils/common/src/main/java/brooklyn/util/yaml/Yamls.java new file mode 100644 index 0000000..a22432f --- /dev/null +++ b/utils/common/src/main/java/brooklyn/util/yaml/Yamls.java @@ -0,0 +1,115 @@ +/* + * 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 brooklyn.util.yaml; + +import java.io.Reader; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.collect.Iterables; + +public class Yamls { + + private static final Logger log = LoggerFactory.getLogger(Yamls.class); + + /** returns the given yaml object (map or list or primitive) as the given yaml-supperted type + * (map or list or primitive e.g. string, number, boolean). + * <p> + * if the object is an iterable containing a single element, and the type is not an iterable, + * this will attempt to unwrap it. + * + * @throws IllegalArgumentException if the input is an iterable not containing a single element, + * and the cast is requested to a non-iterable type + * @throws ClassCastException if cannot be casted */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + public static <T> T getAs(Object x, Class<T> type) { + if (x==null) return null; + if (x instanceof Iterable || x instanceof Iterator) { + List result = new ArrayList(); + Iterator xi; + if (Iterator.class.isAssignableFrom(x.getClass())) { + xi = (Iterator)x; + } else { + xi = ((Iterable)x).iterator(); + } + while (xi.hasNext()) { + result.add( xi.next() ); + } + if (type.isAssignableFrom(Iterable.class)) return (T)result; + if (type.isAssignableFrom(Iterator.class)) return (T)result.iterator(); + if (type.isAssignableFrom(List.class)) return (T)result; + x = Iterables.getOnlyElement(result); + } + return (T)x; + } + + @SuppressWarnings("rawtypes") + public static void dump(int depth, Object r) { + if (r instanceof Iterable) { + for (Object ri : ((Iterable)r)) + dump(depth+1, ri); + } else if (r instanceof Map) { + for (Object re: ((Map)r).entrySet()) { + for (int i=0; i<depth; i++) System.out.print(" "); + System.out.println(((Entry)re).getKey()+":"); + dump(depth+1, ((Entry)re).getValue()); + } + } else { + for (int i=0; i<depth; i++) System.out.print(" "); + if (r==null) System.out.println("<null>"); + else System.out.println("<"+r.getClass().getSimpleName()+">"+" "+r); + } + } + + /** simplifies new Yaml().loadAll, and converts to list to prevent single-use iterable bug in yaml */ + @SuppressWarnings("unchecked") + public static Iterable<Object> parseAll(String yaml) { + Iterable<Object> result = new org.yaml.snakeyaml.Yaml().loadAll(yaml); + return (List<Object>) getAs(result, List.class); + } + + /** as {@link #parseAll(String)} */ + @SuppressWarnings("unchecked") + public static Iterable<Object> parseAll(Reader yaml) { + Iterable<Object> result = new org.yaml.snakeyaml.Yaml().loadAll(yaml); + return (List<Object>) getAs(result, List.class); + } + + public static Object removeMultinameAttribute(Map<String,Object> obj, String ...equivalentNames) { + Object result = null; + for (String name: equivalentNames) { + Object candidate = obj.remove(name); + if (candidate!=null) { + if (result==null) result = candidate; + else if (!result.equals(candidate)) { + log.warn("Different values for attributes "+Arrays.toString(equivalentNames)+"; " + + "preferring '"+result+"' to '"+candidate+"'"); + } + } + } + return result; + } +}