respect generics better when coercing

previous commit used yaml parse even for things like List<String>; now do 
string-list parse in that case.
means the json adapters have access to the type token, but that means running 
them earlier and ensuring we
do a re-coercion if needed.


Project: http://git-wip-us.apache.org/repos/asf/brooklyn-server/repo
Commit: http://git-wip-us.apache.org/repos/asf/brooklyn-server/commit/3e57b14b
Tree: http://git-wip-us.apache.org/repos/asf/brooklyn-server/tree/3e57b14b
Diff: http://git-wip-us.apache.org/repos/asf/brooklyn-server/diff/3e57b14b

Branch: refs/heads/master
Commit: 3e57b14b220bd7a994a9143d83bc123879086aff
Parents: 8d081d0
Author: Alex Heneveld <alex.henev...@cloudsoftcorp.com>
Authored: Fri Aug 31 10:28:57 2018 +0100
Committer: Alex Heneveld <alex.henev...@cloudsoftcorp.com>
Committed: Fri Aug 31 10:28:57 2018 +0100

----------------------------------------------------------------------
 .../brooklyn/util/core/flags/TypeCoercions.java |   4 +-
 .../util/core/internal/TypeCoercionsTest.java   |  11 ++
 .../brooklyn/util/javalang/Reflections.java     |  12 ++
 .../util/javalang/StackTraceSimplifier.java     |   1 +
 .../coerce/CommonAdaptorTypeCoercions.java      | 121 ++++++++++++-------
 .../javalang/coerce/TypeCoercerExtensible.java  |  38 +++++-
 .../util/javalang/coerce/TypeCoercionsTest.java |  35 ------
 .../brooklyn/util/text/StringEscapesTest.java   |   4 +-
 8 files changed, 142 insertions(+), 84 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/3e57b14b/core/src/main/java/org/apache/brooklyn/util/core/flags/TypeCoercions.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/brooklyn/util/core/flags/TypeCoercions.java 
b/core/src/main/java/org/apache/brooklyn/util/core/flags/TypeCoercions.java
index 3b3f874..84b3b10 100644
--- a/core/src/main/java/org/apache/brooklyn/util/core/flags/TypeCoercions.java
+++ b/core/src/main/java/org/apache/brooklyn/util/core/flags/TypeCoercions.java
@@ -290,7 +290,9 @@ public class TypeCoercions {
                     }
                     Maybe<Object> result = 
Reflections.invokeConstructorFromArgs(clazz);
                     if (result.isPresentAndNonNull() && 
supertype.isInstance(result.get())) {
-                        return (T) result.get();
+                        @SuppressWarnings("unchecked")
+                        T rT = (T) result.get();
+                        return rT;
                     } else if (result.isPresent()) {
                         throw new IllegalStateException("Object is not a " + 
supertype.getSimpleName()+": " + result.get());
                     } else {

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/3e57b14b/core/src/test/java/org/apache/brooklyn/util/core/internal/TypeCoercionsTest.java
----------------------------------------------------------------------
diff --git 
a/core/src/test/java/org/apache/brooklyn/util/core/internal/TypeCoercionsTest.java
 
b/core/src/test/java/org/apache/brooklyn/util/core/internal/TypeCoercionsTest.java
index b6a20e5..a88effc 100644
--- 
a/core/src/test/java/org/apache/brooklyn/util/core/internal/TypeCoercionsTest.java
+++ 
b/core/src/test/java/org/apache/brooklyn/util/core/internal/TypeCoercionsTest.java
@@ -35,6 +35,7 @@ import java.util.Set;
 import java.util.concurrent.CopyOnWriteArrayList;
 
 import org.apache.brooklyn.core.entity.lifecycle.Lifecycle;
+import org.apache.brooklyn.util.collections.MutableMap;
 import org.apache.brooklyn.util.collections.MutableSet;
 import org.apache.brooklyn.util.core.ClassLoaderUtils;
 import org.apache.brooklyn.util.core.flags.TypeCoercions;
@@ -325,6 +326,16 @@ public class TypeCoercionsTest {
         Assert.assertEquals(s, ImmutableMap.of("a", "1", "b", 2));
     }
 
+    @SuppressWarnings("serial")
+    @Test
+    public void testYamlMapsDontGoTooFarWhenWantingListOfString() {
+        List<?> s = TypeCoercions.coerce("[ a: 1, b: 2 ]", List.class);
+        Assert.assertEquals(s, ImmutableList.of(MutableMap.of("a", 1), 
MutableMap.of("b", 2)));
+        
+        s = TypeCoercions.coerce("[ a: 1, b : 2 ]", new 
TypeToken<List<String>>() {});
+        Assert.assertEquals(s, ImmutableList.of("a: 1", "b : 2"));
+    }
+
     @Test
     public void testURItoStringCoercion() {
         String s = TypeCoercions.coerce(URI.create("http://localhost:1234/";), 
String.class);

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/3e57b14b/utils/common/src/main/java/org/apache/brooklyn/util/javalang/Reflections.java
----------------------------------------------------------------------
diff --git 
a/utils/common/src/main/java/org/apache/brooklyn/util/javalang/Reflections.java 
b/utils/common/src/main/java/org/apache/brooklyn/util/javalang/Reflections.java
index be6e9a4..8718d69 100644
--- 
a/utils/common/src/main/java/org/apache/brooklyn/util/javalang/Reflections.java
+++ 
b/utils/common/src/main/java/org/apache/brooklyn/util/javalang/Reflections.java
@@ -31,6 +31,7 @@ import java.lang.reflect.Field;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
+import java.lang.reflect.TypeVariable;
 import java.net.URL;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -61,6 +62,7 @@ import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
+import com.google.common.reflect.TypeToken;
 
 /**
  * Reflection utilities
@@ -1140,4 +1142,14 @@ public class Reflections {
     public static boolean trySetAccessible(Method method) {
         return MethodAccessibleReflections.trySetAccessible(method);
     }
+
+    public static TypeToken<?>[] getGenericParameterTypeTokens(TypeToken<?> t) 
{
+        Class<?> rawType = t.getRawType();
+        TypeVariable<?>[] pT = rawType.getTypeParameters();
+        TypeToken<?> pTT[] = new TypeToken<?>[pT.length];
+        for (int i=0; i<pT.length; i++) {
+            pTT[i] = t.resolveType(pT[i]);
+        }
+        return pTT;
+    }
 }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/3e57b14b/utils/common/src/main/java/org/apache/brooklyn/util/javalang/StackTraceSimplifier.java
----------------------------------------------------------------------
diff --git 
a/utils/common/src/main/java/org/apache/brooklyn/util/javalang/StackTraceSimplifier.java
 
b/utils/common/src/main/java/org/apache/brooklyn/util/javalang/StackTraceSimplifier.java
index 6425fac..20b2141 100644
--- 
a/utils/common/src/main/java/org/apache/brooklyn/util/javalang/StackTraceSimplifier.java
+++ 
b/utils/common/src/main/java/org/apache/brooklyn/util/javalang/StackTraceSimplifier.java
@@ -45,6 +45,7 @@ public class StackTraceSimplifier {
                 System.getProperty(DEFAULT_BLACKLIST_SYSTEM_PROPERTY_NAME,
                         "java.," +
                         "javax.," +
+                        "jdk.," +
                         "sun.," +
                         "groovy.," +
                         "org.codehaus.groovy.," +

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/3e57b14b/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 3c31bbc..0842f19 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
@@ -34,10 +34,13 @@ import java.util.concurrent.atomic.AtomicLong;
 
 import javax.annotation.Nullable;
 
+import org.apache.brooklyn.util.collections.MutableList;
 import org.apache.brooklyn.util.collections.MutableSet;
 import org.apache.brooklyn.util.collections.QuorumCheck;
 import org.apache.brooklyn.util.collections.QuorumCheck.QuorumChecks;
 import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.apache.brooklyn.util.guava.Maybe;
+import org.apache.brooklyn.util.javalang.Reflections;
 import org.apache.brooklyn.util.net.Cidr;
 import org.apache.brooklyn.util.net.Networking;
 import org.apache.brooklyn.util.net.UserAndHostAndPort;
@@ -54,6 +57,7 @@ import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
 import com.google.common.net.HostAndPort;
+import com.google.common.reflect.TypeToken;
 
 public class CommonAdaptorTypeCoercions {
 
@@ -76,6 +80,10 @@ public class CommonAdaptorTypeCoercions {
     public synchronized <A,B> Function<? super A,B> registerAdapter(Class<A> 
sourceType, Class<B> targetType, Function<? super A,B> fn) {
         return coercer.registerAdapter(sourceType, targetType, fn);
     }
+    /** Registers an adapter for use with type coercion. */
+    public synchronized void registerAdapter(TryCoercer coerceFn) {
+        coercer.registerAdapter(coerceFn);
+    }
     
     @SuppressWarnings("rawtypes")
     public void registerStandardAdapters() {
@@ -363,61 +371,92 @@ public class CommonAdaptorTypeCoercions {
         });        
     }
     
-    @SuppressWarnings("rawtypes")
     public void registerCollectionJsonAdapters() {
-        registerAdapter(String.class, List.class, new Function<String,List>() {
-            @Override
-            public List<Object> apply(final String input) {
-                return JavaStringEscapes.tryUnwrapJsonishList(input).orNull();
-            }
-        });
-        registerAdapter(String.class, Set.class, new Function<String,Set>() {
-            @Override
-            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(new CoerceStringToCollections());
+    }
+
+    /** Does a rough coercion of the string to the indicated Collection or Map 
type.
+     * Only looks at generics enough to choose the right parser.
+     * Expects the caller {@link TypeCoercerExtensible} to recurse inside the 
collection/map.
+     */
+    public static class CoerceStringToCollections implements TryCoercer {
+        @SuppressWarnings("unchecked")
+        @Override
+        public <T> Maybe<T> tryCoerce(Object input, TypeToken<T> type) {
+            if (!(input instanceof String)) return null;
+            String inputS = (String)input;
+            
+            Class<? super T> rawType = type.getRawType();
+            
+            if (Collection.class.isAssignableFrom(rawType)) {
+                TypeToken<?> parameters[] = 
Reflections.getGenericParameterTypeTokens(type);
+                Maybe<?> resultM = null;
+                Collection<?> result = null;
+                if (parameters.length==1 && 
CharSequence.class.isAssignableFrom(parameters[0].getRawType())) {
+                    // for list of strings, use special parse
+                    result = 
JavaStringEscapes.unwrapJsonishListStringIfPossible(inputS);
+                } else {
+                    // any other type, use YAMLish parse
+                    resultM = JavaStringEscapes.tryUnwrapJsonishList(inputS);
+                    result = (Collection<?>) resultM.orNull();
+                }
+                if (result==null) {
+                    if (resultM!=null) return Maybe.Absent.castAbsent(resultM);
+                    return null;
+                }
+                if (rawType.isAssignableFrom(MutableList.class)) {
+                    return Maybe.of((T) 
MutableList.copyOf(result).asUnmodifiable());
+                }
+                if (rawType.isAssignableFrom(MutableSet.class)) {
+                    return Maybe.of((T) 
MutableSet.copyOf(result).asUnmodifiable());
+                }
+                if (rawType.isInstance(result)) {
+                    return Maybe.of((T) result);
+                }
+                // the type is not a collection we can deal with
+                return null;
             }
-        });
-        registerAdapter(String.class, Map.class, new Function<String,Map>() {
-            @Override
-            public Map apply(final String input) {
-                Exception error = null;
+            
+            if (Map.class.isAssignableFrom(rawType)) {
                 
-                // first try wrapping in braces if needed
-                if (!input.trim().startsWith("{")) {
+                Function<String,Maybe<Map<?,?>>> parseYaml = (in) -> {
                     try {
-                        return apply("{ "+input+" }");
+                        return Maybe.of(Yamls.getAs( Yamls.parseAll(in), 
Map.class ));
                     } catch (Exception e) {
                         Exceptions.propagateIfFatal(e);
-                        // prefer this error
-                        error = e;
-                        // fall back to parsing without braces, e.g. if it's 
multiline
+                        return Maybe.absent(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) ));
                     }
+                };
+                
+                Maybe<Map<?, ?>> r1 = null;
+                
+                // first try wrapping in braces if needed
+                if (!inputS.trim().startsWith("{")) {
+                    r1 = parseYaml.apply("{ "+inputS+" }");
+                    if (r1.isPresent()) return (Maybe<T>) r1;
+                    // fall back to parsing without braces, e.g. if it's 
multiline
                 }
-
-                try {
-                    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;
-                    }
-                    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) );
-                }
+    
+                Maybe<Map<?, ?>> r2 = parseYaml.apply(inputS);
+                if (r2.isPresent()) return (Maybe<T>) r2;
+                
+                // absent - prefer the first error if it wasn't multiline
+                return (Maybe<T>) ((r1!=null && inputS.indexOf('\n')==-1) ? r1 
: r2);
 
                 // 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));
+    //            return 
ImmutableMap.copyOf(Splitter.on(",").trimResults().omitEmptyStrings().withKeyValueSeparator("=").split(input));
+    
             }
-        });
+            
+            // other types not supported here
+            return null;
+        }
     }
-
 }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/3e57b14b/utils/common/src/main/java/org/apache/brooklyn/util/javalang/coerce/TypeCoercerExtensible.java
----------------------------------------------------------------------
diff --git 
a/utils/common/src/main/java/org/apache/brooklyn/util/javalang/coerce/TypeCoercerExtensible.java
 
b/utils/common/src/main/java/org/apache/brooklyn/util/javalang/coerce/TypeCoercerExtensible.java
index a4b4846..64e91b4 100644
--- 
a/utils/common/src/main/java/org/apache/brooklyn/util/javalang/coerce/TypeCoercerExtensible.java
+++ 
b/utils/common/src/main/java/org/apache/brooklyn/util/javalang/coerce/TypeCoercerExtensible.java
@@ -27,7 +27,6 @@ import java.util.Map;
 import java.util.Set;
 
 import org.apache.brooklyn.util.exceptions.Exceptions;
-import org.apache.brooklyn.util.exceptions.PropagatedRuntimeException;
 import org.apache.brooklyn.util.guava.AnyExceptionSupplier;
 import org.apache.brooklyn.util.guava.Maybe;
 import org.apache.brooklyn.util.guava.TypeTokens;
@@ -149,7 +148,24 @@ public class TypeCoercerExtensible implements TypeCoercer {
         targetTypeToken = TypeTokens.getTypeToken(targetTypeToken, targetType);
         for (TryCoercer coercer : genericCoercers) {
             result = coercer.tryCoerce(value, targetTypeToken);
-            if (result!=null && result.isPresent()) return result;
+            
+            if (result!=null && result.isPresent()) {
+                // Check if need to unwrap again (e.g. if want List<Integer> 
and are given a String "1,2,3"
+                // then we'll have so far converted to List.of("1", "2", "3"). 
Call recursively.
+                // First check that value has changed, to avoid stack overflow!
+                if (!Objects.equal(value, result.get()) && 
!Objects.equal(value.getClass(), result.get().getClass()) && 
targetTypeToken.getType() instanceof ParameterizedType) {
+                    Maybe<T> resultM = tryCoerce(result.get(), 
targetTypeToken);
+                    if (resultM!=null) {
+                        if (resultM.isPresent()) return resultM;
+                        // if couldn't coerce parameterized types then back 
out of this coercer
+                        result = resultM;
+                    }
+                } else {
+                    return result;
+                }
+            }
+            
+            // remember any error if we were first
             if (result!=null && firstError==null) firstError = result;
         }
         
@@ -183,9 +199,16 @@ public class TypeCoercerExtensible implements TypeCoercer {
                             // Could duplicate check for `result instanceof 
Collection` etc; but recursive call
                             // will be fine as if that doesn't match we'll 
safely reach `targetType.isInstance(value)`
                             // and just return the result.
-                            return tryCoerce(resultT, targetTypeToken);
+                            Maybe<T> resultM = tryCoerce(resultT, 
targetTypeToken);
+                            if (resultM!=null) {
+                                if (resultM.isPresent()) return resultM;
+                                // if couldn't coerce parameterized types then 
back out of this coercer
+                                // but remember the error if we were first
+                                if (firstError==null) firstError = resultM;
+                            }
+                        } else {
+                            return Maybe.of(resultT);
                         }
-                        return Maybe.of(resultT);
                     } catch (Exception e) {
                         Exceptions.propagateIfFatal(e);
                         if (log.isDebugEnabled()) {
@@ -206,8 +229,11 @@ public class TypeCoercerExtensible implements TypeCoercer {
             }
         }
 
-        //not found
-        if (firstError!=null) return firstError;
+        // not found
+        if (firstError!=null) {
+            // it might be nice to have more than just the first error but for 
now that's all we remember
+            return firstError;
+        }
         return Maybe.absent(new ClassCoercionException("Cannot coerce type 
"+value.getClass().getCanonicalName()+" to "+targetTypeToken+" ("+value+"): no 
adapter known"));
     }
 

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/3e57b14b/utils/common/src/test/java/org/apache/brooklyn/util/javalang/coerce/TypeCoercionsTest.java
----------------------------------------------------------------------
diff --git 
a/utils/common/src/test/java/org/apache/brooklyn/util/javalang/coerce/TypeCoercionsTest.java
 
b/utils/common/src/test/java/org/apache/brooklyn/util/javalang/coerce/TypeCoercionsTest.java
index 5e6ad9a..7296f79 100644
--- 
a/utils/common/src/test/java/org/apache/brooklyn/util/javalang/coerce/TypeCoercionsTest.java
+++ 
b/utils/common/src/test/java/org/apache/brooklyn/util/javalang/coerce/TypeCoercionsTest.java
@@ -447,41 +447,6 @@ public class TypeCoercionsTest {
         }
     }
 
-    // TODO Asserting this undesirable behaviur, to preserve backwards 
compatibility!
-    // Would much prefer that we fail-fast. However, I worry that some 
entity's java declared
-    // ConfigKey<List<Foo>>, and rely on erasure so they can pass in other 
representations and 
-    // coerce it themsevles in some way.
-    // I checked things like JcloudsLocation.JCLOUDS_LOCATION_CUSTOMIZERS - 
those look ok.
-    //
-    // Expect to get a log.warn about that now. Could assert that using 
LogWatcher.
-    @Test
-    public void testListOfFromThrowingException() {
-        @SuppressWarnings("serial")
-        TypeToken<List<WithFromThrowingException>> typeToken = new 
TypeToken<List<WithFromThrowingException>>() {};
-        List<String> rawVal = ImmutableList.of("myval");
-        
-        List<WithFromThrowingException> val = coercer.coerce(rawVal, 
typeToken);
-        assertEquals(val, rawVal);
-
-        List<WithFromThrowingException> val2 = coercer.tryCoerce(rawVal, 
typeToken).get();
-        assertEquals(val2, rawVal);
-    }
-
-    // See comment on testListOfFromThrowingException for why we're asserting 
this undesirable
-    // behaviour.
-    @Test
-    public void testMapOfFromThrowingException() {
-        @SuppressWarnings("serial")
-        TypeToken<Map<String, WithFromThrowingException>> typeToken = new 
TypeToken<Map<String, WithFromThrowingException>>() {};
-        ImmutableMap<String, String> rawVal = ImmutableMap.of("mykey", 
"myval");
-        
-        Map<String, WithFromThrowingException> val = coercer.coerce(rawVal, 
typeToken);
-        assertEquals(val, rawVal);
-
-        Map<String, WithFromThrowingException> val2 = 
coercer.tryCoerce(rawVal, typeToken).get();
-        assertEquals(val2, rawVal);
-    }
-
     @Test
     public void testCoerceStringToNumber() {
         assertEquals(coerce("1", Number.class), Double.valueOf(1));

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/3e57b14b/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 b23a2a7..8739484 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
@@ -161,7 +161,7 @@ public class StringEscapesTest {
             JavaStringEscapes.tryUnwrapJsonishList("\"hello\", world").get());
         Assert.assertEquals(MutableList.of("hello", "world"),
             JavaStringEscapes.tryUnwrapJsonishList("[ \"hello\", world 
]").get());
-        Assert.assertEquals(MutableList.of(""),
+        Assert.assertEquals(MutableList.of(),
             JavaStringEscapes.tryUnwrapJsonishList(" ").get());
         Assert.assertEquals(MutableList.of(""),
             JavaStringEscapes.tryUnwrapJsonishList("\"\"").get());
@@ -169,6 +169,8 @@ public class StringEscapesTest {
             JavaStringEscapes.tryUnwrapJsonishList(",,x,").get());
         Assert.assertEquals(MutableList.of("","","x",""),
             JavaStringEscapes.tryUnwrapJsonishList("\"\",,x,\"\"").get());
+        Assert.assertEquals(MutableList.<Object>of(MutableMap.of("a", 
1),MutableMap.of("b", 2)),
+            JavaStringEscapes.tryUnwrapJsonishList("[ a : 1, b : 2 ]").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());

Reply via email to