Return-Path: X-Original-To: archive-asf-public-internal@cust-asf2.ponee.io Delivered-To: archive-asf-public-internal@cust-asf2.ponee.io Received: from cust-asf.ponee.io (cust-asf.ponee.io [163.172.22.183]) by cust-asf2.ponee.io (Postfix) with ESMTP id 908EF200B4C for ; Thu, 7 Jul 2016 11:18:37 +0200 (CEST) Received: by cust-asf.ponee.io (Postfix) id 8F582160A85; Thu, 7 Jul 2016 09:18:37 +0000 (UTC) Delivered-To: archive-asf-public@cust-asf.ponee.io Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by cust-asf.ponee.io (Postfix) with SMTP id 94B26160A72 for ; Thu, 7 Jul 2016 11:18:36 +0200 (CEST) Received: (qmail 47783 invoked by uid 500); 7 Jul 2016 09:18:35 -0000 Mailing-List: contact commits-help@brooklyn.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@brooklyn.apache.org Delivered-To: mailing list commits@brooklyn.apache.org Received: (qmail 47715 invoked by uid 99); 7 Jul 2016 09:18:35 -0000 Received: from git1-us-west.apache.org (HELO git1-us-west.apache.org) (140.211.11.23) by apache.org (qpsmtpd/0.29) with ESMTP; Thu, 07 Jul 2016 09:18:35 +0000 Received: by git1-us-west.apache.org (ASF Mail Server at git1-us-west.apache.org, from userid 33) id 9685CE07FE; Thu, 7 Jul 2016 09:18:35 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: heneveld@apache.org To: commits@brooklyn.apache.org Date: Thu, 07 Jul 2016 09:18:37 -0000 Message-Id: <7ec43dddcac740df88086963bde4b67c@git.apache.org> In-Reply-To: <0974a625d0544bef8d8b6d1ff1d5ade4@git.apache.org> References: <0974a625d0544bef8d8b6d1ff1d5ade4@git.apache.org> X-Mailer: ASF-Git Admin Mailer Subject: [03/10] brooklyn-server git commit: Jsonya allows primitives and lists at the root now archived-at: Thu, 07 Jul 2016 09:18:37 -0000 Jsonya allows primitives and lists at the root now bringing it in line with json which has always allowed lists and now allows primitives, as root objects Project: http://git-wip-us.apache.org/repos/asf/brooklyn-server/repo Commit: http://git-wip-us.apache.org/repos/asf/brooklyn-server/commit/86440030 Tree: http://git-wip-us.apache.org/repos/asf/brooklyn-server/tree/86440030 Diff: http://git-wip-us.apache.org/repos/asf/brooklyn-server/diff/86440030 Branch: refs/heads/master Commit: 864400306d0433e26a51e96639de2d2b08b9bc4f Parents: 785342a Author: Alex Heneveld Authored: Fri Jun 24 11:00:14 2016 +0100 Committer: Alex Heneveld Committed: Fri Jun 24 22:52:28 2016 +0100 ---------------------------------------------------------------------- .../brooklyn/util/collections/Jsonya.java | 68 ++++++++-- .../brooklyn/util/collections/JsonyaTest.java | 130 ++++++++++++++++++- 2 files changed, 179 insertions(+), 19 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/86440030/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 index ef7f451..6a13dbf 100644 --- 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 @@ -66,7 +66,7 @@ public class Jsonya { /** creates a {@link Navigator} backed by a newly created map; * the map can be accessed by {@link Navigator#getMap()} */ public static Navigator> newInstance() { - return new Navigator>(new MutableMap(), MutableMap.class); + return new Navigator>(MutableMap.class); } /** convenience for {@link Navigator#at(Object, Object...)} on a {@link #newInstance()} */ public static Navigator> at(Object ...pathSegments) { @@ -113,7 +113,7 @@ public class Jsonya { @SuppressWarnings({"rawtypes","unchecked"}) public static class Navigator> { - protected final Object root; + protected Object root; protected final Class mapType; protected Object focus; protected Stack focusStack = new Stack(); @@ -125,6 +125,19 @@ public class Jsonya { this.focus = backingStore; this.mapType = mapType; } + + public Navigator(Class mapType) { + this.root = null; + this.focus = null; + this.mapType = mapType; + this.creationInPreviousFocus = new Function() { + @Override + public Void apply(Object o) { + root = o; + return null; + } + }; + } // -------------- access and configuration @@ -227,16 +240,21 @@ public class Jsonya { return this; } - /** returns the navigator moved to focus at the indicated key sequence in the given map */ + /** returns the navigator moved to focus at the indicated key sequence in the given map, creating the path needed */ public Navigator at(Object pathSegment, Object ...furtherPathSegments) { - down(pathSegment); + down(pathSegment, false); return atArray(furtherPathSegments); } public Navigator atArray(Object[] furtherPathSegments) { for (Object p: furtherPathSegments) - down(p); + down(p, false); return this; } + /** returns the navigator moved to focus at the indicated key sequence in the given map, failing if not available */ + public Navigator atExisting(Object pathSegment, Object ...furtherPathSegments) { + down(pathSegment, true); + return atArray(furtherPathSegments); + } /** ensures the given focus is a map, creating if needed (and creating inside the list if it is in a list) */ public Navigator map() { @@ -297,17 +315,17 @@ public class Jsonya { } /** utility for {@link #at(Object, Object...)}, taking one argument at a time */ - protected Navigator down(final Object pathSegment) { + protected Navigator down(final Object pathSegment, boolean requireExisting) { if (focus instanceof List) { - return downList(pathSegment); + return downList(pathSegment, requireExisting); } if ((focus instanceof Map) || focus==null) { - return downMap(pathSegment); + return downMap(pathSegment, requireExisting); } throw new IllegalStateException("focus here is "+focus+"; cannot descend to '"+pathSegment+"'"); } - protected Navigator downMap(Object pathSegmentO) { + protected Navigator downMap(Object pathSegmentO, boolean requireExisting) { final Object pathSegment = translateKey(pathSegmentO); final Map givenParentMap = (Map)focus; if (givenParentMap!=null) { @@ -315,6 +333,9 @@ public class Jsonya { focus = givenParentMap.get(pathSegment); } if (focus==null) { + if (requireExisting) { + throw new IllegalStateException("No key '"+pathSegmentO+"' found to descend"); + } final Function previousCreation = creationInPreviousFocus; creationInPreviousFocus = new Function() { public Void apply(Object input) { @@ -332,7 +353,7 @@ public class Jsonya { return this; } - protected Navigator downList(final Object pathSegment) { + protected Navigator downList(final Object pathSegment, boolean requireExisting) { if (!(pathSegment instanceof Integer)) throw new IllegalStateException("focus here is a list ("+focus+"); cannot descend to '"+pathSegment+"'"); final List givenParentList = (List)focus; @@ -340,6 +361,10 @@ public class Jsonya { creationInPreviousFocus = null; focus = givenParentList.get((Integer)pathSegment); if (focus==null) { + if (requireExisting) { + throw new IllegalStateException("No index '"+pathSegment+"' found to descend"); + } + // 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? @@ -370,7 +395,9 @@ public class Jsonya { } /** 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. + * creating the focus if it doesn't already exist. + * if there is just one argument being added and the focus doesn't exist, that item is set as the focus. + * if there are more than one argument the focus is made as a map (and an even number of arguments is required). * to add items to a list which might not exist, precede by a call to {@link #list()}. *

* when adding items to a list, iterable and array arguments are flattened because @@ -387,7 +414,19 @@ public class Jsonya { * auto-conversion to a list may be added in a future version * */ public Navigator add(Object o1, Object ...others) { - if (focus==null) map(); + if (focus==null) { + if (others.length>0) { + // default to map, but only if multiple args given + map(); + } else { + // if single arg and no focus, focus becomes the arg, and no need to add + focus = o1; + if (creationInPreviousFocus!=null) { + creationInPreviousFocus.apply(o1); + } + return this; + } + } addInternal(focus, focus, o1, others); return this; } @@ -404,12 +443,12 @@ public class Jsonya { 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)) + 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 @@ -487,6 +526,7 @@ public class Jsonya { for (Object entry: (Collection)focus) { if (!first) sb.append(","); else first = false; + sb.append( " " ); sb.append( render(entry) ); } sb.append(" ]"); http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/86440030/utils/common/src/test/java/org/apache/brooklyn/util/collections/JsonyaTest.java ---------------------------------------------------------------------- diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/collections/JsonyaTest.java b/utils/common/src/test/java/org/apache/brooklyn/util/collections/JsonyaTest.java index 9ce77d2..4bf94a6 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/collections/JsonyaTest.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/collections/JsonyaTest.java @@ -22,9 +22,7 @@ import java.util.Arrays; import java.util.List; import java.util.Map; -import org.apache.brooklyn.util.collections.Jsonya; -import org.apache.brooklyn.util.collections.MutableMap; -import org.apache.brooklyn.util.collections.MutableSet; +import org.apache.brooklyn.test.Asserts; import org.apache.brooklyn.util.collections.Jsonya.Navigator; import org.testng.Assert; import org.testng.annotations.Test; @@ -110,9 +108,14 @@ public class JsonyaTest { Assert.assertEquals( n.root().at("europe").getFocusMap().size(), 3 ); } - @Test(expectedExceptions=Exception.class) + @Test public void testJsonyaDeepSimpleFailure() { - Jsonya.of(europeMap()).at("euroope").add("spain"); + try { + Jsonya.of(europeMap()).atExisting("euroope"); + Asserts.shouldHaveFailedPreviously(); + } catch (Exception e) { + Asserts.expectedFailureContainsIgnoreCase(e, "euroope", "no", "found"); + } } @Test @@ -190,4 +193,121 @@ public class JsonyaTest { Assert.assertFalse(m.getFocusMap().containsKey("edinburgh")); } + @Test + public void testAddMapAddsReference() { + MutableMap map = MutableMap.of("a", 1, "b", 2); + Navigator> j = Jsonya.newInstance().add("root", map); + Assert.assertEquals( j.toString(), "{ \"root\": { \"a\": 1, \"b\": 2 } }"); + map.put("b", 1); + Assert.assertEquals( j.toString(), "{ \"root\": { \"a\": 1, \"b\": 1 } }"); + } + + @Test + public void testAddListAddsReference() { + MutableList list = MutableList.of("a", "b"); + Navigator> j = Jsonya.newInstance().add("root", list); + Assert.assertEquals( j.toString(), "{ \"root\": [ \"a\", \"b\" ] }"); + list.append("c"); + Assert.assertEquals( j.toString(), "{ \"root\": [ \"a\", \"b\", \"c\" ] }"); + } + + @Test + public void testAddListToExistingAddsCopy() { + MutableList list = MutableList.of("a", "b"); + Navigator> j = Jsonya.newInstance().at("root").list().add(list).root(); + Assert.assertEquals( j.toString(), "{ \"root\": [ \"a\", \"b\" ] }"); + list.append("c"); + Assert.assertEquals( j.toString(), "{ \"root\": [ \"a\", \"b\" ] }"); + } + + @Test + public void testAddMapToExistingAddsCopy() { + MutableMap map = MutableMap.of("a", 1, "b", 2); + Navigator> j = Jsonya.newInstance().at("root").map().add(map).root(); + Assert.assertEquals( j.toString(), "{ \"root\": { \"a\": 1, \"b\": 2 } }"); + map.put("b", 1); + Assert.assertEquals( j.toString(), "{ \"root\": { \"a\": 1, \"b\": 2 } }"); + } + + @Test + public void testAddMapToExistingRootAddsCopy() { + MutableMap map = MutableMap.of("a", 1, "b", 2); + Navigator> j = Jsonya.newInstance().map().add(map); + Assert.assertEquals( j.toString(), "{ \"a\": 1, \"b\": 2 }"); + map.put("b", 1); + Assert.assertEquals( j.toString(), "{ \"a\": 1, \"b\": 2 }"); + } + + @Test + public void testAddListToExistingRootAddsCopy() { + MutableList list = MutableList.of("a", "b"); + Navigator> j = Jsonya.newInstance().list().add(list); + Assert.assertEquals( j.toString(), "[ \"a\", \"b\" ]"); + list.append("c"); + Assert.assertEquals( j.toString(), "[ \"a\", \"b\" ]"); + } + + @Test + public void testAddMapAtRootAddsReference() { + MutableMap map = MutableMap.of("a", 1, "b", 2); + Navigator> j = Jsonya.newInstance().add(map); + Assert.assertEquals( j.toString(), "{ \"a\": 1, \"b\": 2 }"); + map.put("b", 1); + Assert.assertEquals( j.toString(), "{ \"a\": 1, \"b\": 1 }"); + } + + @Test + public void testAddListAtRootAddsReference() { + MutableList list = MutableList.of("a", "b"); + Navigator> j = Jsonya.newInstance().add(list); + Assert.assertEquals( j.toString(), "[ \"a\", \"b\" ]"); + list.append("c"); + Assert.assertEquals( j.toString(), "[ \"a\", \"b\", \"c\" ]"); + } + + @Test + public void testAddStringToList() { + Navigator> j = Jsonya.newInstance().at("root").list().add("a", "b").root(); + Assert.assertEquals( j.toString(), "{ \"root\": [ \"a\", \"b\" ] }"); + } + + @Test + public void testAddStringToListAtRoot() { + Navigator> j = Jsonya.newInstance().list().add("a", "b").root(); + Assert.assertEquals( j.toString(), "[ \"a\", \"b\" ]" ); + Assert.assertEquals( j.get(), MutableList.of("a", "b") ); + } + + @Test + public void testAddStringToRoot() { + Navigator> j = Jsonya.newInstance().add("a"); + Assert.assertEquals( j.toString(), "\"a\""); + Assert.assertEquals( j.get(), "a"); + } + + @Test + public void testAddStringsAtRootDefaultsToMap() { + Navigator> j = Jsonya.newInstance().add("a", 1); + Assert.assertEquals( j.toString(), "{ \"a\": 1 }"); + } + + @Test + public void testAddOddStringsAtRootIsError() { + try { + Jsonya.newInstance().add("a", 1, "b"); + Asserts.shouldHaveFailedPreviously(); + } catch (Exception e) { + Asserts.expectedFailureContainsIgnoreCase(e, "odd"); + } + } + + @Test + public void testAddStringAtKey() { + Navigator> j = Jsonya.newInstance().at("root").add("value").root(); + Assert.assertEquals( j.get(), MutableMap.of("root", "value")); + } + public void testAddStringAtKeySequence() { + Navigator> j = Jsonya.newInstance().at("1", "2").add("value"); + Assert.assertEquals( j.get(), MutableMap.of("1", MutableMap.of("2", "value"))); + } }