coercing from string to list can now handle objects now have two methods for parsing a list as a string, depending whether it wants object or string
Project: http://git-wip-us.apache.org/repos/asf/brooklyn-server/repo Commit: http://git-wip-us.apache.org/repos/asf/brooklyn-server/commit/89c692e4 Tree: http://git-wip-us.apache.org/repos/asf/brooklyn-server/tree/89c692e4 Diff: http://git-wip-us.apache.org/repos/asf/brooklyn-server/diff/89c692e4 Branch: refs/heads/master Commit: 89c692e4951deb17cbca05567dea74ce2dd78860 Parents: ddc6c71 Author: Alex Heneveld <alex.henev...@cloudsoftcorp.com> Authored: Fri Aug 31 00:24:32 2018 +0100 Committer: Alex Heneveld <alex.henev...@cloudsoftcorp.com> Committed: Fri Aug 31 00:24:32 2018 +0100 ---------------------------------------------------------------------- .../core/location/BasicLocationRegistry.java | 2 +- .../brooklyn/core/location/PortRanges.java | 2 +- .../location/multi/MultiLocationResolver.java | 2 +- .../LocalhostLocationResolverTest.java | 4 +- .../main/java/org/apache/brooklyn/cli/Main.java | 2 +- .../coerce/CommonAdaptorTypeCoercions.java | 10 +- .../brooklyn/util/text/StringEscapes.java | 118 ++++++++++++++++++- .../brooklyn/util/text/StringEscapesTest.java | 80 ++++++++++++- 8 files changed, 206 insertions(+), 14 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/89c692e4/core/src/main/java/org/apache/brooklyn/core/location/BasicLocationRegistry.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/core/location/BasicLocationRegistry.java b/core/src/main/java/org/apache/brooklyn/core/location/BasicLocationRegistry.java index b28e560..ae77d90 100644 --- a/core/src/main/java/org/apache/brooklyn/core/location/BasicLocationRegistry.java +++ b/core/src/main/java/org/apache/brooklyn/core/location/BasicLocationRegistry.java @@ -513,7 +513,7 @@ public class BasicLocationRegistry implements LocationRegistry { @Override public List<Location> getListOfLocationsManaged(Object l) { if (l==null) l = Collections.emptyList(); - if (l instanceof String) l = JavaStringEscapes.unwrapJsonishListIfPossible((String)l); + if (l instanceof String) l = JavaStringEscapes.unwrapJsonishListStringIfPossible((String)l); if (l instanceof Iterable) return getFromIterableListOfLocationsManaged((Iterable<?>)l); throw new IllegalArgumentException("Location list must be supplied as a collection or a string, not "+ JavaClassNames.simpleClassName(l)+"/"+l); http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/89c692e4/core/src/main/java/org/apache/brooklyn/core/location/PortRanges.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/core/location/PortRanges.java b/core/src/main/java/org/apache/brooklyn/core/location/PortRanges.java index 22a037c..b3368e0 100644 --- a/core/src/main/java/org/apache/brooklyn/core/location/PortRanges.java +++ b/core/src/main/java/org/apache/brooklyn/core/location/PortRanges.java @@ -196,7 +196,7 @@ public class PortRanges { for (Object o: c) { if (o instanceof Integer) l.add(fromInteger((Integer)o)); else if (o instanceof String) - for (String string : JavaStringEscapes.unwrapJsonishListIfPossible((String)o)) + for (String string : JavaStringEscapes.unwrapJsonishListStringIfPossible((String)o)) l.add(fromString(string)); else if (o instanceof Iterable) l.add(fromIterable((Iterable<?>)o)); else if (o instanceof int[]) l.add(fromIterable(Ints.asList((int[])o))); http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/89c692e4/core/src/main/java/org/apache/brooklyn/location/multi/MultiLocationResolver.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/location/multi/MultiLocationResolver.java b/core/src/main/java/org/apache/brooklyn/location/multi/MultiLocationResolver.java index 765be5f..8ab1b98 100644 --- a/core/src/main/java/org/apache/brooklyn/location/multi/MultiLocationResolver.java +++ b/core/src/main/java/org/apache/brooklyn/location/multi/MultiLocationResolver.java @@ -99,7 +99,7 @@ public class MultiLocationResolver implements LocationResolver { Object targetSpecs = locationArgs.remove("targets"); if (targetSpecs instanceof String) { - for (String targetSpec : JavaStringEscapes.unwrapJsonishListIfPossible((String)targetSpecs)) { + for (String targetSpec : JavaStringEscapes.unwrapJsonishListStringIfPossible((String)targetSpecs)) { targets.add(managementContext.getLocationRegistry().getLocationSpec(targetSpec, ImmutableMap.of()).get()); } } else if (targetSpecs instanceof Iterable) { http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/89c692e4/core/src/test/java/org/apache/brooklyn/location/localhost/LocalhostLocationResolverTest.java ---------------------------------------------------------------------- diff --git a/core/src/test/java/org/apache/brooklyn/location/localhost/LocalhostLocationResolverTest.java b/core/src/test/java/org/apache/brooklyn/location/localhost/LocalhostLocationResolverTest.java index 3e17883..1dc9a81 100644 --- a/core/src/test/java/org/apache/brooklyn/location/localhost/LocalhostLocationResolverTest.java +++ b/core/src/test/java/org/apache/brooklyn/location/localhost/LocalhostLocationResolverTest.java @@ -159,14 +159,14 @@ public class LocalhostLocationResolverTest { @Test public void testRegistryCommaResolution() throws NoMachinesAvailableException { List<Location> l; - l = getLocationResolver().getListOfLocationsManaged(JavaStringEscapes.unwrapJsonishListIfPossible("localhost,localhost,localhost")); + l = getLocationResolver().getListOfLocationsManaged(JavaStringEscapes.unwrapJsonishListStringIfPossible("localhost,localhost,localhost")); assertEquals(l.size(), 3, "l="+l); assertTrue(l.get(0) instanceof LocalhostMachineProvisioningLocation, "l="+l); assertTrue(l.get(1) instanceof LocalhostMachineProvisioningLocation, "l="+l); assertTrue(l.get(2) instanceof LocalhostMachineProvisioningLocation, "l="+l); // And check works if comma in brackets - l = getLocationResolver().getListOfLocationsManaged(JavaStringEscapes.unwrapJsonishListIfPossible( + l = getLocationResolver().getListOfLocationsManaged(JavaStringEscapes.unwrapJsonishListStringIfPossible( "[ \"byon:(hosts=\\\"192.168.0.1\\\",user=bob)\", \"byon:(hosts=\\\"192.168.0.2\\\",user=bob2)\" ]")); assertEquals(l.size(), 2, "l="+l); assertTrue(l.get(0) instanceof FixedListMachineProvisioningLocation, "l="+l); http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/89c692e4/server-cli/src/main/java/org/apache/brooklyn/cli/Main.java ---------------------------------------------------------------------- diff --git a/server-cli/src/main/java/org/apache/brooklyn/cli/Main.java b/server-cli/src/main/java/org/apache/brooklyn/cli/Main.java index cc473de..4711b9f 100644 --- a/server-cli/src/main/java/org/apache/brooklyn/cli/Main.java +++ b/server-cli/src/main/java/org/apache/brooklyn/cli/Main.java @@ -552,7 +552,7 @@ public class Main extends AbstractMain { .ignoreCatalogErrors(!startupFailOnCatalogErrors) .ignoreWebErrors(startupContinueOnWebErrors) .ignoreAppErrors(!startupFailOnManagedAppsErrors) - .locations(Strings.isBlank(locations) ? ImmutableList.<String>of() : JavaStringEscapes.unwrapJsonishListIfPossible(locations)); + .locations(Strings.isBlank(locations) ? ImmutableList.<String>of() : JavaStringEscapes.unwrapJsonishListStringIfPossible(locations)); launcher.restServer(!noConsole); if (useHttps) { http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/89c692e4/utils/common/src/main/java/org/apache/brooklyn/util/javalang/coerce/CommonAdaptorTypeCoercions.java ---------------------------------------------------------------------- diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/javalang/coerce/CommonAdaptorTypeCoercions.java b/utils/common/src/main/java/org/apache/brooklyn/util/javalang/coerce/CommonAdaptorTypeCoercions.java index 4072bc1..3c31bbc 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/javalang/coerce/CommonAdaptorTypeCoercions.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/javalang/coerce/CommonAdaptorTypeCoercions.java @@ -367,14 +367,16 @@ public class CommonAdaptorTypeCoercions { public void registerCollectionJsonAdapters() { registerAdapter(String.class, List.class, new Function<String,List>() { @Override - public List<String> apply(final String input) { - return JavaStringEscapes.unwrapJsonishListIfPossible(input); + public List<Object> apply(final String input) { + return JavaStringEscapes.tryUnwrapJsonishList(input).orNull(); } }); registerAdapter(String.class, Set.class, new Function<String,Set>() { @Override - public Set<String> apply(final String input) { - return MutableSet.copyOf(JavaStringEscapes.unwrapJsonishListIfPossible(input)).asUnmodifiable(); + public Set<Object> apply(final String input) { + List<Object> l = JavaStringEscapes.tryUnwrapJsonishList(input).orNull(); + if (l==null) return null; + return MutableSet.copyOf(l).asUnmodifiable(); } }); registerAdapter(String.class, Map.class, new Function<String,Map>() { http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/89c692e4/utils/common/src/main/java/org/apache/brooklyn/util/text/StringEscapes.java ---------------------------------------------------------------------- diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/text/StringEscapes.java b/utils/common/src/main/java/org/apache/brooklyn/util/text/StringEscapes.java index f713ff6..f3df2b2 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/text/StringEscapes.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/text/StringEscapes.java @@ -23,16 +23,21 @@ import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.util.ArrayList; import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import javax.annotation.Nonnull; import org.apache.brooklyn.util.collections.MutableList; import org.apache.brooklyn.util.exceptions.Exceptions; +import org.apache.brooklyn.util.guava.Maybe; import org.apache.brooklyn.util.net.URLParamEncoder; +import org.apache.brooklyn.util.yaml.Yamls; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.base.Function; +import com.google.common.collect.Iterables; public class StringEscapes { @@ -315,12 +320,121 @@ public class StringEscapes { throw new IllegalArgumentException("String '"+s+"' is not a valid Java string (unterminated string)"); } + /** @deprecated since 1.0.0, use {@link #unwrapJsonishListStringIfPossible(String)} (old semantics) + * or {@link #unwrapJsonishListStringIfPossible(String)} (improved) */ + public static List<String> unwrapJsonishListIfPossible(String input) { + return unwrapJsonishListStringIfPossible(input); + } + + /** converts a comma separated list in a single string to a list of json primitives or maps, + * falling back to returning the input. + * <p> + * specifically: + * <li> 1) if of form <code>[ X ]</code> (in brackets after trim), parse as YAML; + * if parse succeeds return the result, or if parse fails, return {@link Maybe#absent()}. + * <ll> 2) if not of form <code>[ X ]</code>, wrap in brackets and parse as YAML, + * and if that succeeds and is a list, return the result. + * <li> 3) otherwise, expect comma-separated tokens which after trimming are of the form "A" or B, + * where "A" is a valid Java string or C is any string not starting with ' + * and not containing " or ,. return the list of those tokens, where A and B + * are their string value, and C as a primitive if it is a number or boolean or null, + * or else a string, including the empty string if empty. + * <li> 4) if such tokens are not found, return {@link Maybe#absent()}. + * <p> + * @see #unwrapOptionallyQuotedJavaStringList(String) + **/ + public static Maybe<List<Object>> tryUnwrapJsonishList(String input) { + if (input==null) return Maybe.absent("null input cannot unwrap to a list"); + String inputT = input.trim(); + + String inputYaml = null; + if (!inputT.startsWith("[") && !inputT.endsWith("]")) { + inputYaml = "[" + inputT + "]"; + } + if (inputT.startsWith("[") && inputT.endsWith("]")) { + inputYaml = inputT; + } + if (inputYaml!=null) { + try { + Object r = Iterables.getOnlyElement( Yamls.parseAll(inputYaml) ); + if (r instanceof List) { + @SuppressWarnings("unchecked") + List<Object> result = (List<Object>)r; + return Maybe.of(result); + } + } catch (Exception e) {} + if (inputT.startsWith("[")) { + // if supplied as yaml, don't allow failures + return Maybe.absent("Supplied format looked like YAML but could not parse as YAML"); + } + } + + List<Object> result = MutableList.of(); + + // double quote: ^ \s* " ([not quote or backslash] or [backslash any-char])* " \s* (, or $) + Pattern dq = Pattern.compile("^\\s*(\"([^\"\\\\]|[\\\\.])*\")\\s*(,|$)"); + // could also support this, but we need new unescape routines +// // single quote: ^ \s* ' ([not quote or backslash] or [backslash any-char])* ' \s* (, or $) +// Pattern sq = Pattern.compile("^\\s*'([^\'\\\\]|[\\\\.])'*\\s*(,|$)"); + // no quote: ^ \s* (empty, or [not ' or " or space] ([not , or "]* [not , or " or space])?) \s* (, or $) + Pattern nq = Pattern.compile("^\\s*(|[^,\"\\s]([^,\"]*[^,\"\\s])?)\\s*(,|$)"); + + int removedChars = 0; + while (true) { + Object ri; + Matcher m = dq.matcher(input); + if (m.find()) { + try { + ri = unwrapJavaString(m.group(1)); + } catch (Exception e) { + Exceptions.propagateIfFatal(e); + return Maybe.absent("Could not match valid quote pattern" + + (removedChars>0 ? " at position "+removedChars : "") + + ": "+ Exceptions.collapseText(e)); + } + } else { + m = nq.matcher(input); + if (m.find()) { + String w = m.group(1); + + ri = w; + if (w.matches("[0-9]*.[0-9]+")) { + try { + ri = Double.parseDouble(w); + } catch (Exception e) {} + } else if (w.matches("[0-9]+")) { + try { + ri = Integer.parseInt(w); + } catch (Exception e) {} + } else if (Boolean.TRUE.toString().equals(w)) { + ri = true; + } else if (Boolean.FALSE.toString().equals(w)) { + ri = false; + } else if ("null".equals(w)) { + ri = null; + } + + } else { + return Maybe.absent("Could not match valid quote pattern" + + (removedChars>0 ? " at position "+removedChars : "")); + } + } + + result.add(ri); + + input = input.substring(m.end()); + removedChars += m.end(); + if (!m.group(0).endsWith(",")) break; + } + return Maybe.of(result); + } + /** converts a comma separated list in a single string to a list of strings, * doing what would be expected if given java or json style string as input, * and falling back to returning the input. * <p> * this method does <b>not</b> throw exceptions on invalid input, - * but just returns that input + * but just returns that input wrapped in a list * <p> * specifically, uses the following rules (executed once in sequence: * <li> 1) if of form <code>[ X ]</code> (in brackets after trim), @@ -337,7 +451,7 @@ public class StringEscapes { * <p> * @see #unwrapOptionallyQuotedJavaStringList(String) **/ - public static List<String> unwrapJsonishListIfPossible(String input) { + public static List<String> unwrapJsonishListStringIfPossible(String input) { try { return unwrapOptionallyQuotedJavaStringList(input); } catch (Exception e) { http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/89c692e4/utils/common/src/test/java/org/apache/brooklyn/util/text/StringEscapesTest.java ---------------------------------------------------------------------- diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/text/StringEscapesTest.java b/utils/common/src/test/java/org/apache/brooklyn/util/text/StringEscapesTest.java index b864240..b23a2a7 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/text/StringEscapesTest.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/text/StringEscapesTest.java @@ -18,8 +18,9 @@ */ package org.apache.brooklyn.util.text; +import org.apache.brooklyn.test.Asserts; import org.apache.brooklyn.util.collections.MutableList; -import org.apache.brooklyn.util.text.StringEscapes; +import org.apache.brooklyn.util.collections.MutableMap; import org.apache.brooklyn.util.text.StringEscapes.BashStringEscapes; import org.apache.brooklyn.util.text.StringEscapes.JavaStringEscapes; import org.testng.Assert; @@ -83,8 +84,9 @@ public class StringEscapesTest { Assert.assertEquals(JavaStringEscapes.wrapJavaString("Hello \"World\""), "\"Hello \\\"World\\\"\""); } + @SuppressWarnings("deprecation") @Test - public void testJavaLists() { + public void testJavaListDeprecated() { Assert.assertEquals(MutableList.of("hello", "world"), JavaStringEscapes.unwrapQuotedJavaStringList("\"hello\", \"world\"", ",")); try { @@ -115,4 +117,78 @@ public class StringEscapesTest { JavaStringEscapes.unwrapJsonishListIfPossible("\"\",,x,\"\"")); } + @Test + public void testJavaListString() { + Assert.assertEquals(MutableList.of("hello", "world"), + JavaStringEscapes.unwrapQuotedJavaStringList("\"hello\", \"world\"", ",")); + try { + JavaStringEscapes.unwrapQuotedJavaStringList("\"hello\", world", ","); + Assert.fail("Should have thrown"); + } catch (Exception e) { /* expected */ } + + Assert.assertEquals(MutableList.of("hello", "world"), + JavaStringEscapes.unwrapJsonishListStringIfPossible("\"hello\", \"world\"")); + Assert.assertEquals(MutableList.of("hello"), + JavaStringEscapes.unwrapJsonishListStringIfPossible("hello")); + Assert.assertEquals(MutableList.of("hello", "world"), + JavaStringEscapes.unwrapJsonishListStringIfPossible("hello, world")); + Assert.assertEquals(MutableList.of("hello", "world"), + JavaStringEscapes.unwrapJsonishListStringIfPossible("\"hello\", world")); + Assert.assertEquals(MutableList.of("hello", "world"), + JavaStringEscapes.unwrapJsonishListStringIfPossible("[ \"hello\", world ]")); + // if can't parse e.g. because contains double quote, then returns original string as single element list + Assert.assertEquals(MutableList.of("hello\", \"world\""), + JavaStringEscapes.unwrapJsonishListStringIfPossible("hello\", \"world\"")); + Assert.assertEquals(MutableList.of(), + JavaStringEscapes.unwrapJsonishListStringIfPossible(" ")); + Assert.assertEquals(MutableList.of(""), + JavaStringEscapes.unwrapJsonishListStringIfPossible("\"\"")); + Assert.assertEquals(MutableList.of("x"), + JavaStringEscapes.unwrapJsonishListStringIfPossible(",,x,")); + Assert.assertEquals(MutableList.of("","x",""), + JavaStringEscapes.unwrapJsonishListStringIfPossible("\"\",,x,\"\"")); + } + + @Test + public void testJavaListObject() { + Assert.assertEquals(MutableList.of("hello", "world"), + JavaStringEscapes.tryUnwrapJsonishList("\"hello\", \"world\"").get()); + Assert.assertEquals(MutableList.of("hello"), + JavaStringEscapes.tryUnwrapJsonishList("hello").get()); + Assert.assertEquals(MutableList.of("hello", "world"), + JavaStringEscapes.tryUnwrapJsonishList("hello, world").get()); + Assert.assertEquals(MutableList.of("hello", "world"), + JavaStringEscapes.tryUnwrapJsonishList("\"hello\", world").get()); + Assert.assertEquals(MutableList.of("hello", "world"), + JavaStringEscapes.tryUnwrapJsonishList("[ \"hello\", world ]").get()); + Assert.assertEquals(MutableList.of(""), + JavaStringEscapes.tryUnwrapJsonishList(" ").get()); + Assert.assertEquals(MutableList.of(""), + JavaStringEscapes.tryUnwrapJsonishList("\"\"").get()); + Assert.assertEquals(MutableList.of("","","x",""), + JavaStringEscapes.tryUnwrapJsonishList(",,x,").get()); + Assert.assertEquals(MutableList.of("","","x",""), + JavaStringEscapes.tryUnwrapJsonishList("\"\",,x,\"\"").get()); + + Assert.assertEquals(MutableList.<Object>of(1, 2, "buckle my shoe", true, "true", null, "null", ","), + JavaStringEscapes.tryUnwrapJsonishList("1, 2, buckle my shoe, true, \"true\", null, \"null\", \",\"").get()); + + try { + JavaStringEscapes.tryUnwrapJsonishList("\"hello").get(); + Asserts.shouldHaveFailedPreviously(); + } catch (Exception e) { Asserts.expectedFailureDoesNotContain(e, "position"); } + try { + JavaStringEscapes.tryUnwrapJsonishList(", \"hello").get(); + Asserts.shouldHaveFailedPreviously(); + } catch (Exception e) { + Asserts.expectedFailureContains(e, "position 1"); + } + + Assert.assertEquals(MutableList.of(MutableMap.of("a", "b"), "world"), + JavaStringEscapes.tryUnwrapJsonishList("[ { a: b }, world ]").get()); + + Assert.assertEquals(MutableList.of(MutableMap.of("a", MutableList.<Object>of("b", 2)), "world"), + JavaStringEscapes.tryUnwrapJsonishList("{ a: [ b, 2 ] }, world").get()); + } + }