From 9479ae6c14fa81b79dfefe327015aa6ead80b348 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Sat, 9 Jan 2016 18:00:33 +0000 Subject: [PATCH 01/42] pom.xml: version set to 0.2.0-SNAPSHOT Signed-off-by: Paul Campbell --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 59ab9da..47e6274 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 net.kemitix node - 0.1.0 + 0.2.0-SNAPSHOT jar Node From 85bd763e09ab16b2aada55856d4b950b633e04fd Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Sat, 9 Jan 2016 18:09:38 +0000 Subject: [PATCH 02/42] NodeItem.walkTree(): refactor using existing getChild() Signed-off-by: Paul Campbell --- src/main/java/net/kemitix/node/NodeItem.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/main/java/net/kemitix/node/NodeItem.java b/src/main/java/net/kemitix/node/NodeItem.java index d35366a..2a6b4ba 100644 --- a/src/main/java/net/kemitix/node/NodeItem.java +++ b/src/main/java/net/kemitix/node/NodeItem.java @@ -110,10 +110,7 @@ public class NodeItem implements Node { @Override public Optional> walkTree(@NonNull final List path) { if (path.size() > 0) { - Optional> found = children.stream() - .filter((Node child) -> path.get(0) - .equals(child.getData())) - .findFirst(); + Optional> found = getChild(path.get(0)); if (found.isPresent()) { if (path.size() > 1) { return found.get().walkTree(path.subList(1, path.size())); From 635af8089dad6511b0d54aaa56f3f909b30e3391 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Mon, 14 Mar 2016 14:17:14 +0000 Subject: [PATCH 03/42] Drop lombok dependency Signed-off-by: Paul Campbell --- pom.xml | 6 -- src/main/java/net/kemitix/node/NodeItem.java | 62 +++++++++++++++----- 2 files changed, 47 insertions(+), 21 deletions(-) diff --git a/pom.xml b/pom.xml index 47e6274..01cc354 100644 --- a/pom.xml +++ b/pom.xml @@ -31,12 +31,6 @@ 2016 - - org.projectlombok - lombok - 1.16.6 - - junit junit diff --git a/src/main/java/net/kemitix/node/NodeItem.java b/src/main/java/net/kemitix/node/NodeItem.java index 2a6b4ba..0751a3e 100644 --- a/src/main/java/net/kemitix/node/NodeItem.java +++ b/src/main/java/net/kemitix/node/NodeItem.java @@ -1,8 +1,5 @@ package net.kemitix.node; -import lombok.Getter; -import lombok.NonNull; - import java.util.HashSet; import java.util.List; import java.util.Optional; @@ -17,13 +14,10 @@ import java.util.Set; */ public class NodeItem implements Node { - @Getter private final T data; - @Getter private Node parent; - @Getter private Set> children; /** @@ -31,7 +25,7 @@ public class NodeItem implements Node { * * @param data the value of the node */ - public NodeItem(@NonNull final T data) { + public NodeItem(final T data) { this(data, null); } @@ -42,6 +36,9 @@ public class NodeItem implements Node { * @param parent the parent node */ public NodeItem(final T data, final Node parent) { + if (data == null) { + throw new NullPointerException("data"); + } this.data = data; if (parent != null) { setParent(parent); @@ -49,13 +46,31 @@ public class NodeItem implements Node { this.children = new HashSet<>(); } + @Override + public T getData() { + return data; + } + + @Override + public Node getParent() { + return parent; + } + + @Override + public Set> getChildren() { + return children; + } + /** * Make the current node a direct child of the parent. * * @param parent the new parent node */ @Override - public final void setParent(@NonNull final Node parent) { + public final void setParent(final Node parent) { + if (parent == null) { + throw new NullPointerException("parent"); + } if (this.equals(parent) || parent.isChildOf(this)) { throw new NodeException("Parent is a descendant"); } @@ -72,7 +87,10 @@ public class NodeItem implements Node { * @param child the node to add */ @Override - public void addChild(@NonNull final Node child) { + public void addChild(final Node child) { + if (child == null) { + throw new NullPointerException("child"); + } if (this.equals(child) || isChildOf(child)) { throw new NodeException("Child is an ancestor"); } @@ -104,11 +122,13 @@ public class NodeItem implements Node { * Walks the node tree using the path to select each child. * * @param path the path to the desired child - * * @return the child or null */ @Override - public Optional> walkTree(@NonNull final List path) { + public Optional> walkTree(final List path) { + if (path == null) { + throw new NullPointerException("path"); + } if (path.size() > 0) { Optional> found = getChild(path.get(0)); if (found.isPresent()) { @@ -128,7 +148,10 @@ public class NodeItem implements Node { * @param descendants the line of descendants from the current node */ @Override - public void createDescendantLine(@NonNull final List descendants) { + public void createDescendantLine(final List descendants) { + if (descendants == null) { + throw new NullPointerException("descendants"); + } if (!descendants.isEmpty()) { findOrCreateChild(descendants.get(0)) .createDescendantLine( @@ -145,7 +168,10 @@ public class NodeItem implements Node { * @return the found or created child node */ @Override - public Node findOrCreateChild(@NonNull final T child) { + public Node findOrCreateChild(final T child) { + if (child == null) { + throw new NullPointerException("child"); + } Optional> found = getChild(child); if (found.isPresent()) { return found.get(); @@ -162,7 +188,10 @@ public class NodeItem implements Node { * @return an {@link Optional} containing the child node if found */ @Override - public Optional> getChild(@NonNull final T child) { + public Optional> getChild(final T child) { + if (child == null) { + throw new NullPointerException("child"); + } return children.stream() .filter((Node t) -> t.getData().equals(child)) .findAny(); @@ -176,7 +205,10 @@ public class NodeItem implements Node { * @return the new child node */ @Override - public Node createChild(@NonNull final T child) { + public Node createChild(final T child) { + if (child == null) { + throw new NullPointerException("child"); + } return new NodeItem<>(child, this); } From fee38dc9299985b21c58aa05d4cb0da19a4e9862 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Mon, 14 Mar 2016 14:35:57 +0000 Subject: [PATCH 04/42] .idea: add IntelliJ configuration Signed-off-by: Paul Campbell --- .gitignore | 3 ++ .idea/codeStyleSettings.xml | 104 ++++++++++++++++++++++++++++++++++++ .idea/compiler.xml | 33 ++++++++++++ .idea/encodings.xml | 7 +++ .idea/misc.xml | 39 ++++++++++++++ .idea/modules.xml | 8 +++ .idea/vcs.xml | 6 +++ node.iml | 17 ++++++ 8 files changed, 217 insertions(+) create mode 100644 .idea/codeStyleSettings.xml create mode 100644 .idea/compiler.xml create mode 100644 .idea/encodings.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 node.iml diff --git a/.gitignore b/.gitignore index 5ec989b..25a8515 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,6 @@ # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* + +/.idea/libraries/ +/.idea/workspace.xml diff --git a/.idea/codeStyleSettings.xml b/.idea/codeStyleSettings.xml new file mode 100644 index 0000000..bcea045 --- /dev/null +++ b/.idea/codeStyleSettings.xml @@ -0,0 +1,104 @@ + + + + + + \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..6dda909 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,33 @@ + + + + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..c0bce70 --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..5c910ae --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + 1.8 + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..935c7b9 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/node.iml b/node.iml new file mode 100644 index 0000000..6ad1e66 --- /dev/null +++ b/node.iml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file From d0c6b07b66dfbcab49eb855775e8f0951e17f22c Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Mon, 14 Mar 2016 15:39:16 +0000 Subject: [PATCH 05/42] .idea - fixup --- .idea/inspectionProfiles/Project_Default.xml | 59 +++++++++++++++++++ .../inspectionProfiles/profiles_settings.xml | 7 +++ .idea/misc.xml | 3 + 3 files changed, 69 insertions(+) create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 .idea/inspectionProfiles/profiles_settings.xml diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..7df71eb --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,59 @@ + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..3b31283 --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index 5c910ae..0abd9e0 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,5 +1,8 @@ + + + diff --git a/pom.xml b/pom.xml index 01cc354..32577de 100644 --- a/pom.xml +++ b/pom.xml @@ -1,7 +1,6 @@ 4.0.0 - net.kemitix node 0.2.0-SNAPSHOT jar @@ -31,6 +30,12 @@ 2016 + + org.projectlombok + lombok + 1.16.8 + test + junit junit From 79170b898a48bd6230752714f50c78ccbcf2f5e1 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Mon, 14 Mar 2016 15:40:51 +0000 Subject: [PATCH 07/42] NodeItem: initialise children set in field and make final Signed-off-by: Paul Campbell --- src/main/java/net/kemitix/node/NodeItem.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/net/kemitix/node/NodeItem.java b/src/main/java/net/kemitix/node/NodeItem.java index 0751a3e..13efcb5 100644 --- a/src/main/java/net/kemitix/node/NodeItem.java +++ b/src/main/java/net/kemitix/node/NodeItem.java @@ -18,7 +18,7 @@ public class NodeItem implements Node { private Node parent; - private Set> children; + private final Set> children = new HashSet<>(); /** * Creates a root node. @@ -43,7 +43,6 @@ public class NodeItem implements Node { if (parent != null) { setParent(parent); } - this.children = new HashSet<>(); } @Override From d60f6ae0bbca06e12848eed7225d89684b515fc3 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Mon, 14 Mar 2016 15:41:23 +0000 Subject: [PATCH 08/42] NodeItem.isChildOf(): simplify if statements Signed-off-by: Paul Campbell --- src/main/java/net/kemitix/node/NodeItem.java | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/main/java/net/kemitix/node/NodeItem.java b/src/main/java/net/kemitix/node/NodeItem.java index 13efcb5..5321674 100644 --- a/src/main/java/net/kemitix/node/NodeItem.java +++ b/src/main/java/net/kemitix/node/NodeItem.java @@ -108,13 +108,8 @@ public class NodeItem implements Node { */ @Override public boolean isChildOf(final Node node) { - if (node.equals(parent)) { - return true; - } - if (parent != null) { - return parent.isChildOf(node); - } - return false; + return parent != null && (node.equals(parent) || parent.isChildOf( + node)); } /** From d9d19a849e448632cd2cae6d04f499263e05e89d Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Mon, 14 Mar 2016 15:42:07 +0000 Subject: [PATCH 09/42] NodeItem.findOrCreateChild(): use Optional.orElseGet() to simplify Signed-off-by: Paul Campbell --- src/main/java/net/kemitix/node/NodeItem.java | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/main/java/net/kemitix/node/NodeItem.java b/src/main/java/net/kemitix/node/NodeItem.java index 5321674..3e4186e 100644 --- a/src/main/java/net/kemitix/node/NodeItem.java +++ b/src/main/java/net/kemitix/node/NodeItem.java @@ -166,12 +166,7 @@ public class NodeItem implements Node { if (child == null) { throw new NullPointerException("child"); } - Optional> found = getChild(child); - if (found.isPresent()) { - return found.get(); - } else { - return createChild(child); - } + return getChild(child).orElseGet(() -> createChild(child)); } /** From 9daebcea464c0348aa03e9302f63cd7a02f3d9da Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Mon, 14 Mar 2016 15:42:53 +0000 Subject: [PATCH 10/42] {Node,NodeItem}: javadoc tidy Signed-off-by: Paul Campbell --- src/main/java/net/kemitix/node/Node.java | 4 ++-- src/main/java/net/kemitix/node/NodeItem.java | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/net/kemitix/node/Node.java b/src/main/java/net/kemitix/node/Node.java index 2dae709..002bead 100644 --- a/src/main/java/net/kemitix/node/Node.java +++ b/src/main/java/net/kemitix/node/Node.java @@ -7,8 +7,9 @@ import java.util.Set; /** * An interface for tree node items. * - * @author pcampbell * @param the type of data held in each node + * + * @author pcampbell */ public interface Node { @@ -21,7 +22,6 @@ public interface Node { /** * Fetch the parent node. - * *

* If the node is a root node, i.e. has no parent, then this will return * null. diff --git a/src/main/java/net/kemitix/node/NodeItem.java b/src/main/java/net/kemitix/node/NodeItem.java index 3e4186e..27ee22a 100644 --- a/src/main/java/net/kemitix/node/NodeItem.java +++ b/src/main/java/net/kemitix/node/NodeItem.java @@ -116,6 +116,7 @@ public class NodeItem implements Node { * Walks the node tree using the path to select each child. * * @param path the path to the desired child + * * @return the child or null */ @Override From 58363e474d5574f2f0a4585cfb6754e95f9a621a Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Mon, 14 Mar 2016 15:43:21 +0000 Subject: [PATCH 11/42] NodeItem: apply code style Signed-off-by: Paul Campbell --- src/main/java/net/kemitix/node/NodeItem.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/main/java/net/kemitix/node/NodeItem.java b/src/main/java/net/kemitix/node/NodeItem.java index 27ee22a..d11ff23 100644 --- a/src/main/java/net/kemitix/node/NodeItem.java +++ b/src/main/java/net/kemitix/node/NodeItem.java @@ -148,9 +148,8 @@ public class NodeItem implements Node { throw new NullPointerException("descendants"); } if (!descendants.isEmpty()) { - findOrCreateChild(descendants.get(0)) - .createDescendantLine( - descendants.subList(1, descendants.size())); + findOrCreateChild(descendants.get(0)).createDescendantLine( + descendants.subList(1, descendants.size())); } } @@ -183,8 +182,8 @@ public class NodeItem implements Node { throw new NullPointerException("child"); } return children.stream() - .filter((Node t) -> t.getData().equals(child)) - .findAny(); + .filter((Node t) -> t.getData().equals(child)) + .findAny(); } /** From 24ad03ccabca73b671a775491b444a26b357388b Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Mon, 14 Mar 2016 15:43:56 +0000 Subject: [PATCH 12/42] NodeExceptionTest: use lombok's val Signed-off-by: Paul Campbell --- .../java/net/kemitix/node/NodeExceptionTest.java | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/test/java/net/kemitix/node/NodeExceptionTest.java b/src/test/java/net/kemitix/node/NodeExceptionTest.java index 5e9f7ac..2d1422b 100644 --- a/src/test/java/net/kemitix/node/NodeExceptionTest.java +++ b/src/test/java/net/kemitix/node/NodeExceptionTest.java @@ -1,7 +1,6 @@ package net.kemitix.node; -import net.kemitix.node.NodeException; - +import lombok.val; import org.junit.Test; import static org.hamcrest.CoreMatchers.is; @@ -14,20 +13,15 @@ import static org.junit.Assert.assertThat; */ public class NodeExceptionTest { - /** - * Class under test. - */ - private NodeException nodeException; - /** * Test that message provided to constructor is returned. */ @Test public void shouldReturnConstructorMessage() { //given - final String message = "this is the message"; + val message = "this is the message"; //when - nodeException = new NodeException(message); + val nodeException = new NodeException(message); //then assertThat(nodeException.getMessage(), is(message)); } From 8187a28795c6ada0643c6d63121dd2d1ee5bf675 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Mon, 14 Mar 2016 15:51:44 +0000 Subject: [PATCH 13/42] NodeItemTest: use val and Assert.assertThat() * Lombok's val simplfies the local variable declarations * Assert.assertThat() gives better error messages * javadoc tidy Signed-off-by: Paul Campbell --- .../java/net/kemitix/node/NodeItemTest.java | 346 ++++++++++-------- 1 file changed, 195 insertions(+), 151 deletions(-) diff --git a/src/test/java/net/kemitix/node/NodeItemTest.java b/src/test/java/net/kemitix/node/NodeItemTest.java index c271ac3..e22605f 100644 --- a/src/test/java/net/kemitix/node/NodeItemTest.java +++ b/src/test/java/net/kemitix/node/NodeItemTest.java @@ -1,18 +1,17 @@ package net.kemitix.node; +import lombok.val; +import org.junit.Assert; import org.junit.Test; +import static org.hamcrest.CoreMatchers.hasItem; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.nullValue; + import java.util.Arrays; import java.util.Collections; import java.util.Optional; -import static org.hamcrest.CoreMatchers.hasItem; -import static org.hamcrest.CoreMatchers.is; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; - /** * Test for {@link NodeItem}. * @@ -26,22 +25,21 @@ public class NodeItemTest { private Node node; /** - * Test {@link NodeItem#Node(java.lang.Object) } that node data is - * recoverable. + * Test that node data is recoverable. */ @Test public void shouldReturnNodeData() { //given - final String data = "this node data"; + val data = "this node data"; //when node = new NodeItem<>(data); //then - assertThat(node.getData(), is(data)); + Assert.assertThat("can get the data from a node", node.getData(), + is(data)); } /** - * Test {@link NodeItem#Node(java.lang.Object) } that passing null as node - * data throws exception. + * Test that passing null as node data throws exception. */ @Test(expected = NullPointerException.class) public void shouldThrowNPEWhenDataIsNull() { @@ -50,78 +48,79 @@ public class NodeItemTest { } /** - * Test {@link NodeItem#Node(java.lang.Object) } that default node parent is - * null. + * Test that default node parent is null. */ @Test - public void shouldHaveNullForDefaulParent() { + public void shouldHaveNullForDefaultParent() { //given node = new NodeItem<>("data"); //then - assertNull(node.getParent()); + Assert.assertThat("node created without a parent has null as parent", + node.getParent(), nullValue()); } /** - * Test {@link NodeItem#Node(java.lang.Object, net.kemitix.node.Node) } that - * provided node parent is returned. + * Test that provided node parent is returned. */ @Test public void shouldReturnNodeParent() { //given - Node parent = new NodeItem<>("parent"); + val parent = new NodeItem("parent"); //when node = new NodeItem<>("subject", parent); //then - assertThat(node.getParent(), is(parent)); + Assert.assertThat("node created with a parent can return the parent", + node.getParent(), is(parent)); } /** - * Test {@link NodeItem#Node(java.lang.Object, net.kemitix.node.Node) } that - * setting the parent on a node where the proposed parent is a child of the - * node throws an exception. + * Test that setting the parent on a node where the proposed parent is a + * child of the node throws an exception. */ @Test(expected = NodeException.class) public void shouldThrowNEWhenSettingParentToAChild() { //given node = new NodeItem<>("subject"); - Node child = new NodeItem<>("child", node); + val child = new NodeItem("child", node); //when node.setParent(child); } /** - * Test {@link NodeItem#Node(java.lang.Object, net.kemitix.node.Node) } that - * when parent is added to created node, the created node is now a child of - * the parent. + * Test that when parent is added to created node, the created node is now a + * child of the parent. */ @Test public void shouldAddNewNodeAsChildToParent() { //given - Node parent = new NodeItem<>("parent"); + val parent = new NodeItem("parent"); //when node = new NodeItem<>("subject", parent); //then - assertThat(parent.getChildren(), hasItem(node)); + Assert.assertThat( + "when a node is created with a parent, the parent has the new" + + " node among it's children", parent.getChildren(), + hasItem(node)); } /** - * Test {@link NodeItem#setParent(net.kemitix.node.Node) } that we return - * the same parent when set. + * Test that we return the same parent when set. */ @Test public void shouldReturnSetParent() { //given node = new NodeItem<>("subject"); - Node parent = new NodeItem<>("parent"); + val parent = new NodeItem("parent"); //when node.setParent(parent); //then - assertThat(node.getParent(), is(parent)); + Assert.assertThat( + "when a node is assigned a new parent that parent can be " + + "returned", node.getParent(), is(parent)); } /** - * Test {@link NodeItem#setParent(net.kemitix.node.Node) } that we throw an - * exception when passed null. + * Test that we throw an exception when passed null. */ @Test(expected = NullPointerException.class) public void shouldThrowNPEWhenSetParentNull() { @@ -132,8 +131,8 @@ public class NodeItemTest { } /** - * Test {@link NodeItem#setParent(net.kemitix.node.Node) } that we throw an - * exceptions when attempting to node as its own parent. + * Test that we throw an exceptions when attempting to node as its own + * parent. */ @Test(expected = NodeException.class) public void shouldThrowNEWhenSetParentSelf() { @@ -144,44 +143,53 @@ public class NodeItemTest { } /** - * Test {@link NodeItem#setParent(net.kemitix.node.Node) } that when a node - * with an existing parent is assigned a new parent, that the old parent no - * longer sees it as one of its children. + * Test that when a node with an existing parent is assigned a new parent, + * that the old parent no longer sees it as one of its children. */ @Test public void shouldUpdateOldParentWhenNodeSetToNewParent() { //given node = new NodeItem<>("subject"); - Node child = node.createChild("child"); - Node newParent = new NodeItem<>("newParent"); + val child = node.createChild("child"); + val newParent = new NodeItem("newParent"); //when child.setParent(newParent); //then - assertThat(child.getParent(), is(newParent)); - assertFalse(node.getChild("child").isPresent()); + Assert.assertThat( + "when a node is assigned a new parent, the old parent is " + + "replaced", child.getParent(), is(newParent)); + Assert.assertThat( + "when a node is assigned a new parent, the old parent no " + + "longer has the node among it's children", + node.getChild("child").isPresent(), is(false)); } /** - * Test {@link NodeItem#addChild(net.kemitix.node.Node) } that when a node - * is added as a child to another node, that it's previous parent no longer - * has it as a child. + * Test that when a node is added as a child to another node, that it's + * previous parent no longer has it as a child. */ @Test public void shouldRemoveNodeFromOldParentWhenAddedAsChildToNewParent() { //given node = new NodeItem<>("subject"); - Node child = node.createChild("child"); - Node newParent = new NodeItem<>("newParent"); + val child = node.createChild("child"); + val newParent = new NodeItem("newParent"); //when newParent.addChild(child); //then - assertThat(child.getParent(), is(newParent)); - assertFalse(node.getChild("child").isPresent()); + Assert.assertThat( + "when a node with an existing parent is added as a child " + + "to another node, then the old parent is replaced", + child.getParent(), is(newParent)); + Assert.assertThat( + "when a node with an existing parent is added as a child to " + + "another node, then the old parent no longer has " + + "the node among it's children", + node.getChild("child").isPresent(), is(false)); } /** - * Test {@link NodeItem#addChild(net.kemitix.node.Node) } that adding null - * as a child throws an exception. + * Test that adding null as a child throws an exception. */ @Test(expected = NullPointerException.class) public void shouldThrowNPEWhenAddingNullAsChild() { @@ -192,23 +200,23 @@ public class NodeItemTest { } /** - * Test {@link NodeItem#addChild(net.kemitix.node.Node) } that adding a - * child is returned. + * Test that adding a child is returned. */ @Test public void shouldReturnAddedChild() { //given - Node child = new NodeItem<>("child"); node = new NodeItem<>("subject"); + val child = new NodeItem("child"); //when node.addChild(child); //then - assertThat(node.getChildren(), hasItem(child)); + Assert.assertThat( + "when a node is added as a child, the node is among the " + + "children", node.getChildren(), hasItem(child)); } /** - * Test {@link NodeItem#addChild(net.kemitix.node.Node) } that adding a node - * as it's own child throws an exception. + * Test that adding a node as it's own child throws an exception. */ @Test(expected = NodeException.class) public void shouldThrowNEWhenAddingANodeAsOwnChild() { @@ -219,8 +227,7 @@ public class NodeItemTest { } /** - * Test {@link NodeItem#addChild(net.kemitix.node.Node) } that adding a node - * to itself as a child causes an exception. + * Test that adding a node to itself as a child causes an exception. */ @Test(expected = NodeException.class) public void shouldThrowWhenAddingSelfAsChild() { @@ -231,89 +238,93 @@ public class NodeItemTest { } /** - * Test {@link NodeItem#addChild(net.kemitix.node.Node) } that adding the - * parent to node causes an exception. + * Test that adding the parent of a node to the node as a child causes an + * exception. */ @Test(expected = NodeException.class) public void shouldThrowWhenAddingParentAsChild() { //given - Node parent = new NodeItem<>("parent"); + val parent = new NodeItem("parent"); node = new NodeItem<>("subject", parent); //when node.addChild(parent); } /** - * Test {@link NodeItem#addChild(net.kemitix.node.Node) } that adding the - * grandparent to node causes an exception. + * Test that adding the grandparent to a node as a child causes an + * exception. */ @Test(expected = NodeException.class) public void shouldThrowWhenAddingGrandParentAsChild() { //given - Node grandParent = new NodeItem<>("grandparent"); - Node parent = new NodeItem<>("parent", grandParent); + val grandParent = new NodeItem("grandparent"); + val parent = new NodeItem("parent", grandParent); node = new NodeItem<>("subject", parent); //when node.addChild(grandParent); } /** - * Test {@link NodeItem#addChild(net.kemitix.node.Node) } that adding a - * child to a node, sets the child's parent node. + * Test that adding a child to a node, sets the child's parent node. */ @Test public void shouldSetParentOnChildWhenAddedAsChild() { //given - Node child = new NodeItem<>("child"); + val child = new NodeItem("child"); node = new NodeItem<>("subject"); //when node.addChild(child); //then - assertThat(child.getParent(), is(node)); + Assert.assertThat( + "when a node is added as a child, the child has the node as " + + "its parent", child.getParent(), is(node)); } /** - * Test {@link NodeItem#walkTree(java.util.List) } that we can walk a tree - * to the target node. + * Test that we can walk a tree to the target node. */ @Test public void shouldWalkTreeToNode() { //given - final String grandparent = "grandparent"; - Node grandParentNode = new NodeItem<>(grandparent); - final String parent = "parent"; - Node parentNode = new NodeItem<>(parent, grandParentNode); - final String subject = "subject"; + val grandparent = "grandparent"; + val grandParentNode = new NodeItem(grandparent); + val parent = "parent"; + val parentNode = new NodeItem(parent, grandParentNode); + val subject = "subject"; node = new NodeItem<>(subject, parentNode); //when - Optional> result = grandParentNode.walkTree(Arrays.asList( - parent, subject)); + val result = grandParentNode.walkTree(Arrays.asList(parent, subject)); //then - assertTrue(result.isPresent()); - assertThat(result.get(), is(node)); + Assert.assertThat("when we walk the tree to a node it is found", + result.isPresent(), is(true)); + if (result.isPresent()) { + Assert.assertThat( + "when we walk the tree to a node the correct node is found", + result.get(), is(node)); + } } /** - * Test {@link NodeItem#walkTree(java.util.List) } that we get an empty - * {@link Optional} when walking a path that doesn't exist. + * Test that we get an empty {@link Optional} when walking a path that + * doesn't exist. */ @Test - public void shouldNotFindNonExistantChildNode() { + public void shouldNotFindNonExistentChildNode() { //given - final String parent = "parent"; - Node parentNode = new NodeItem<>(parent); - final String subject = "subject"; + val parent = "parent"; + val parentNode = new NodeItem(parent); + val subject = "subject"; node = new NodeItem<>(subject, parentNode); //when - Optional> result = parentNode.walkTree(Arrays.asList( - subject, "no child")); + val result = parentNode.walkTree(Arrays.asList(subject, "no child")); //then - assertFalse(result.isPresent()); + Assert.assertThat( + "when we walk the tree to a node that doesn't exists, nothing" + + " is found", result.isPresent(), is(false)); } /** - * Test {@link NodeItem#walkTree(java.util.List) } that when we pass null we - * get an exception. + * Test that when we pass null we get an exception. */ @Test(expected = NullPointerException.class) public void shouldThrowNEWhenWalkTreeNull() { @@ -324,8 +335,8 @@ public class NodeItemTest { } /** - * Test {@link NodeItem#walkTree(java.util.List) } that when we pass an - * empty path we get and empty {@link Optional} as a result. + * Test that when we pass an empty path we get and empty {@link Optional} as + * a result. */ @Test public void shouldReturnEmptyForEmptyWalkTreePath() { @@ -336,37 +347,58 @@ public class NodeItemTest { } /** - * Test {@link NodeItem#createDescendantLine(java.util.List) } that we can - * create a chain of descendant nodes. + * Test that we can create a chain of descendant nodes. */ @Test public void shouldCreateDescendantNodes() { //given node = new NodeItem<>("subject"); - final String alphaData = "alpha"; - final String betaData = "beta"; - final String gammaData = "gamma"; + val alphaData = "alpha"; + val betaData = "beta"; + val gammaData = "gamma"; //when node.createDescendantLine( Arrays.asList(alphaData, betaData, gammaData)); //then - final Optional> alphaOptional = node.getChild(alphaData); - assertTrue(alphaOptional.isPresent()); - Node alpha = alphaOptional.get(); - assertThat(alpha.getParent(), is(node)); - final Optional> betaOptional = alpha.getChild(betaData); - assertTrue(betaOptional.isPresent()); - Node beta = betaOptional.get(); - assertThat(beta.getParent(), is(alpha)); - final Optional> gammaOptional = beta.getChild(gammaData); - assertTrue(gammaOptional.isPresent()); - Node gamma = gammaOptional.get(); - assertThat(gamma.getParent(), is(beta)); + val alphaOptional = node.getChild(alphaData); + Assert.assertThat( + "when creating a descendant line, the first element is found", + alphaOptional.isPresent(), is(true)); + if (alphaOptional.isPresent()) { + val alpha = alphaOptional.get(); + Assert.assertThat( + "when creating a descendant line, the first element has " + + "the current node as its parent", + alpha.getParent(), is(node)); + val betaOptional = alpha.getChild(betaData); + Assert.assertThat( + "when creating a descendant line, the second element is " + + "found", betaOptional.isPresent(), is(true)); + if (betaOptional.isPresent()) { + val beta = betaOptional.get(); + Assert.assertThat( + "when creating a descendant line, the second element " + + "has the first as its parent", + beta.getParent(), is(alpha)); + val gammaOptional = beta.getChild(gammaData); + Assert.assertThat( + "when creating a descendant line, the third element " + + "is found", gammaOptional.isPresent(), + is(true)); + if (gammaOptional.isPresent()) { + val gamma = gammaOptional.get(); + Assert.assertThat( + "when creating a descendant line, the third " + + "element has the second as its parent", + gamma.getParent(), is(beta)); + } + } + } } /** - * Test {@link NodeItem#createDescendantLine(java.util.List) } that if we - * pass null to create a chain of descendant nodes we get an exception. + * Test that if we pass null to create a chain of descendant nodes we get an + * exception. */ @Test(expected = NullPointerException.class) public void shouldThrowNPEWhenCreateDescendantNull() { @@ -377,8 +409,7 @@ public class NodeItemTest { } /** - * Test {@link NodeItem#createDescendantLine(java.util.List) } that if we - * pass an empty list nothing is changed. + * Test that if we pass an empty list nothing is changed. */ @Test public void shouldChangeNothingWhenCreateDescendantEmpty() { @@ -387,43 +418,46 @@ public class NodeItemTest { //when node.createDescendantLine(Collections.emptyList()); //then - assertThat(node.getChildren().size(), is(0)); + Assert.assertThat( + "when creating a descendant line from an empty list, nothing " + + "is created", node.getChildren().size(), is(0)); } /** - * Test {@link NodeItem#findOrCreateChild(java.lang.Object) } that we can - * find a child of a node. + * Test that we can find a child of a node. */ @Test public void shouldFindExistingChildNode() { //given node = new NodeItem<>("subject"); - final String childData = "child"; - Node child = new NodeItem<>(childData, node); + val childData = "child"; + val child = new NodeItem(childData, node); //when - Node found = node.findOrCreateChild(childData); + val found = node.findOrCreateChild(childData); //then - assertThat(found, is(child)); + Assert.assertThat( + "when searching for a child by data, the matching child is " + + "found", found, is(child)); } /** - * Test {@link NodeItem#findOrCreateChild(java.lang.Object) } that we create - * a missing child of a node. + * Test that we create a missing child of a node. */ @Test public void shouldFindCreateNewChildNode() { //given node = new NodeItem<>("subject"); - final String childData = "child"; + val childData = "child"; //when - Node found = node.findOrCreateChild(childData); + val found = node.findOrCreateChild(childData); //then - assertThat(found.getData(), is(childData)); + Assert.assertThat( + "when searching for a child by data, a new node is created", + found.getData(), is(childData)); } /** - * Test {@link NodeItem#findOrCreateChild(java.lang.Object) } that if we - * pass null we get an exception. + * Test that if we pass null we get an exception. */ @Test(expected = NullPointerException.class) public void shouldThrowNPEFWhenFindOrCreateChildNull() { @@ -434,26 +468,29 @@ public class NodeItemTest { } /** - * Test {@link NodeItem#getChild(java.lang.Object) } that we can get the - * node for a child. + * Test that we can get the node for a child. */ @Test public void shouldGetChild() { //given node = new NodeItem<>("subject"); - final String childData = "child"; - Node child = new NodeItem<>(childData); + val childData = "child"; + val child = new NodeItem(childData); node.addChild(child); //when - Optional> found = node.getChild(childData); + val found = node.getChild(childData); //then - assertTrue(found.isPresent()); - assertThat(found.get(), is(child)); + Assert.assertThat("when retrieving a child by its data, it is found", + found.isPresent(), is(true)); + if (found.isPresent()) { + Assert.assertThat( + "when retrieving a child by its data, it is the expected " + + "node", found.get(), is(child)); + } } /** - * Test {@link NodeItem#getChild(java.lang.Object) } that we throw an - * exception when passed null. + * Test that we throw an exception when passed null. */ @Test(expected = NullPointerException.class) public void shouldThrowNPEWhenGetChildNull() { @@ -464,22 +501,29 @@ public class NodeItemTest { } /** - * Test {@link NodeItem#createChild(java.lang.Object) } that we create a - * child as a child of the current node and with the current node as its - * parent. + * Test that we create a child as a child of the current node and with the + * current node as its parent. */ @Test - public void shoudCreateChild() { + public void shouldCreateChild() { //given node = new NodeItem<>("subject"); - final String childData = "child"; + val childData = "child"; //when - Node child = node.createChild(childData); + val child = node.createChild(childData); //then - assertThat(child.getParent(), is(node)); - final Optional> foundChild = node.getChild(childData); - assertTrue(foundChild.isPresent()); - assertThat(foundChild.get(), is(child)); + Assert.assertThat( + "when creating a child node, the child has the current node " + + "as its parent", child.getParent(), is(node)); + val foundChild = node.getChild(childData); + Assert.assertThat( + "when creating a child node, the child can be found by its " + + "data", foundChild.isPresent(), is(true)); + if (foundChild.isPresent()) { + Assert.assertThat( + "when creating a child node, the correct child can be " + + "found by its data", foundChild.get(), is(child)); + } } /** From 921cf98b13550c9255a071f17df5c9c58bb35a67 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Mon, 23 May 2016 11:57:50 +0100 Subject: [PATCH 14/42] Add assertj-core dependency for testing --- node.iml | 1 + pom.xml | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/node.iml b/node.iml index a66a2bf..b2d3a3d 100644 --- a/node.iml +++ b/node.iml @@ -14,5 +14,6 @@ + \ No newline at end of file diff --git a/pom.xml b/pom.xml index 32577de..c488538 100644 --- a/pom.xml +++ b/pom.xml @@ -14,6 +14,10 @@ 0.6.0 + + 3.4.1 + + https://github.com/kemitix/node/issues GitHub Issues @@ -48,5 +52,11 @@ 1.3 test + + org.assertj + assertj-core + ${assertj.version} + test + From 36efe5d83ab5e3f63c6cf3cd208b54a19c1600ce Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Tue, 24 May 2016 09:57:11 +0100 Subject: [PATCH 15/42] Node: may be empty, having no data * isEmpty() --- src/main/java/net/kemitix/node/Node.java | 7 +++++++ src/main/java/net/kemitix/node/NodeItem.java | 5 +++++ 2 files changed, 12 insertions(+) diff --git a/src/main/java/net/kemitix/node/Node.java b/src/main/java/net/kemitix/node/Node.java index 002bead..2d9c67e 100644 --- a/src/main/java/net/kemitix/node/Node.java +++ b/src/main/java/net/kemitix/node/Node.java @@ -20,6 +20,13 @@ public interface Node { */ T getData(); + /** + * Returns true if the node is empty (has no data). + * + * @return true is data is null + */ + boolean isEmpty(); + /** * Fetch the parent node. *

diff --git a/src/main/java/net/kemitix/node/NodeItem.java b/src/main/java/net/kemitix/node/NodeItem.java index d11ff23..c3a3b45 100644 --- a/src/main/java/net/kemitix/node/NodeItem.java +++ b/src/main/java/net/kemitix/node/NodeItem.java @@ -50,6 +50,11 @@ public class NodeItem implements Node { return data; } + @Override + public boolean isEmpty() { + return data == null; + } + @Override public Node getParent() { return parent; From b18020708bb21a1e44bd0de0bda57739b2901c6b Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Tue, 24 May 2016 10:59:16 +0100 Subject: [PATCH 16/42] Node: may have names and a functional name supplier Names, where present, must be unique for each parent. Node: * String getName() * void setName(String name) * boolean isNamed() NodeItem - replaces all constructor: * (data) * (data, name) * (data, nameSupplier) * (data, parent) * (data, nameSupplier, parent) The name supplier takes a node and generates a string at the time the node is constructed. The root node has a default name supplier that returns null which means that the node is considered unnamed. Other nodes may provide their own name supplier that would be used be new nodes created within their subtree. --- src/main/java/net/kemitix/node/Node.java | 21 ++++ src/main/java/net/kemitix/node/NodeItem.java | 111 +++++++++++++++++-- 2 files changed, 121 insertions(+), 11 deletions(-) diff --git a/src/main/java/net/kemitix/node/Node.java b/src/main/java/net/kemitix/node/Node.java index 2d9c67e..66f9910 100644 --- a/src/main/java/net/kemitix/node/Node.java +++ b/src/main/java/net/kemitix/node/Node.java @@ -13,6 +13,20 @@ import java.util.Set; */ public interface Node { + /** + * Fetch the name of the node. + * + * @return the name of the node + */ + String getName(); + + /** + * Sets the explicit name for a node. + * + * @param name the new name + */ + void setName(String name); + /** * Fetch the data held within the node. * @@ -112,4 +126,11 @@ public interface Node { */ Optional> walkTree(final List path); + /** + * Returns true if the Node has a name. + * + * @return true if the node has a name + */ + boolean isNamed(); + } diff --git a/src/main/java/net/kemitix/node/NodeItem.java b/src/main/java/net/kemitix/node/NodeItem.java index c3a3b45..6815350 100644 --- a/src/main/java/net/kemitix/node/NodeItem.java +++ b/src/main/java/net/kemitix/node/NodeItem.java @@ -4,6 +4,7 @@ import java.util.HashSet; import java.util.List; import java.util.Optional; import java.util.Set; +import java.util.function.Function; /** * Represents a tree of nodes. @@ -16,33 +17,108 @@ public class NodeItem implements Node { private final T data; - private Node parent; - private final Set> children = new HashSet<>(); + private Function, String> nameSupplier; + + private Node parent; + + private String name; + /** - * Creates a root node. + * Create unnamed root node. * - * @param data the value of the node + * @param data the data or null */ public NodeItem(final T data) { - this(data, null); + this.data = data; + this.nameSupplier = (n) -> null; + } + + /** + * Create named root node. + * + * @param data the data or null + * @param name the name + */ + public NodeItem(final T data, final String name) { + this(data); + this.name = name; + } + + /** + * Creates root node with a name supplier. + * + * @param data the data or null + * @param nameSupplier the name supplier function + */ + public NodeItem( + final T data, final Function, String> nameSupplier) { + this(data); + this.nameSupplier = nameSupplier; + name = generateName(); } /** * Creates a node with a parent. * - * @param data the value of the node + * @param data the data or null * @param parent the parent node */ public NodeItem(final T data, final Node parent) { - if (data == null) { - throw new NullPointerException("data"); - } this.data = data; - if (parent != null) { - setParent(parent); + setParent(parent); + this.name = generateName(); + } + + /** + * Creates a named node with a parent. + * + * @param data the data or null + * @param name the name + * @param parent the parent node + */ + public NodeItem(final T data, final String name, final Node parent) { + this.data = data; + this.name = name; + setParent(parent); + } + + /** + * Creates a node with a name supplier and a parent. + * + * @param data the data or null + * @param nameSupplier the name supplier function + * @param parent the parent node + */ + public NodeItem( + final T data, final Function, String> nameSupplier, + final Node parent) { + this(data, nameSupplier); + setParent(parent); + } + + private String generateName() { + return getNameSupplier().apply(this); + } + + private Function, String> getNameSupplier() { + if (nameSupplier != null) { + return nameSupplier; } + // no test for parent as root nodes will always have a default name + // supplier + return ((NodeItem) parent).getNameSupplier(); + } + + @Override + public String getName() { + return name; + } + + @Override + public void setName(final String name) { + this.name = name; } @Override @@ -98,6 +174,14 @@ public class NodeItem implements Node { if (this.equals(child) || isChildOf(child)) { throw new NodeException("Child is an ancestor"); } + if (child.isNamed()) { + final Optional> existingChild = findChildNamed( + child.getName()); + if (existingChild.isPresent() && existingChild.get() != child) { + throw new NodeException( + "Node with that name already exists here"); + } + } children.add(child); if (child.getParent() == null || !child.getParent().equals(this)) { child.setParent(this); @@ -202,6 +286,11 @@ public class NodeItem implements Node { public Node createChild(final T child) { if (child == null) { throw new NullPointerException("child"); + @Override + public boolean isNamed() { + return name != null && name.length() > 0; + } + } return new NodeItem<>(child, this); } From b278fc0f988725d2c09b1df5ee783a9b026748ae Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Tue, 24 May 2016 11:03:19 +0100 Subject: [PATCH 17/42] Node: add {get,find}ChildNamed() methods Both methods look for a child with the given name as an immediate child of the current node. findChildNamed: Will return an Optional containing the found node or empty. getChildNames: Is more insistent and will return the found node itself. If a node by that name is not found, then a NodeException will be thrown. --- src/main/java/net/kemitix/node/Node.java | 19 +++++++++++++++++++ src/main/java/net/kemitix/node/NodeItem.java | 19 +++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/src/main/java/net/kemitix/node/Node.java b/src/main/java/net/kemitix/node/Node.java index 66f9910..c569773 100644 --- a/src/main/java/net/kemitix/node/Node.java +++ b/src/main/java/net/kemitix/node/Node.java @@ -127,6 +127,25 @@ public interface Node { Optional> walkTree(final List path); /** + /** + * Searches for a child with the name given. + * + * @param name the name of the child + * + * @return an Optional containing the child found or empty + */ + Optional> findChildNamed(String name); + + /** + * Returns the child with the given name. If one can't be found a + * NodeException is thrown. + * + * @param name the name of the child + * + * @return the node + */ + Node getChildNamed(String name); + * Returns true if the Node has a name. * * @return true if the node has a name diff --git a/src/main/java/net/kemitix/node/NodeItem.java b/src/main/java/net/kemitix/node/NodeItem.java index 6815350..fb8908b 100644 --- a/src/main/java/net/kemitix/node/NodeItem.java +++ b/src/main/java/net/kemitix/node/NodeItem.java @@ -286,6 +286,25 @@ public class NodeItem implements Node { public Node createChild(final T child) { if (child == null) { throw new NullPointerException("child"); + @Override + public Optional> findChildNamed(final String named) { + if (named == null) { + throw new NullPointerException("name"); + } + return children.stream() + .filter((Node t) -> t.getName().equals(named)) + .findAny(); + } + + @Override + public Node getChildNamed(final String named) { + final Optional> optional = findChildNamed(named); + if (optional.isPresent()) { + return optional.get(); + } + throw new NodeException("Named child not found"); + } + @Override public boolean isNamed() { return name != null && name.length() > 0; From b2c3032ec06ccd0df5d7062c2583bd8ddb4e2291 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Tue, 24 May 2016 11:25:59 +0100 Subject: [PATCH 18/42] Node:drawTree(): creates a String representing the node in the tree The NodeItem implementation on includes nodes with names, or where they have child nodes. In which case the are shown as '(unnamed)'. --- src/main/java/net/kemitix/node/Node.java | 10 ++++++++++ src/main/java/net/kemitix/node/NodeItem.java | 15 +++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/src/main/java/net/kemitix/node/Node.java b/src/main/java/net/kemitix/node/Node.java index c569773..0a96c66 100644 --- a/src/main/java/net/kemitix/node/Node.java +++ b/src/main/java/net/kemitix/node/Node.java @@ -146,6 +146,16 @@ public interface Node { */ Node getChildNamed(String name); + /** + * Draw a representation of the tree. + * + * @param depth current depth for recursion + * + * @return a representation of the tree + */ + String drawTree(int depth); + + /** * Returns true if the Node has a name. * * @return true if the node has a name diff --git a/src/main/java/net/kemitix/node/NodeItem.java b/src/main/java/net/kemitix/node/NodeItem.java index fb8908b..88764e4 100644 --- a/src/main/java/net/kemitix/node/NodeItem.java +++ b/src/main/java/net/kemitix/node/NodeItem.java @@ -305,6 +305,21 @@ public class NodeItem implements Node { throw new NodeException("Named child not found"); } + @Override + public String drawTree(final int depth) { + final StringBuilder sb = new StringBuilder(); + final String unnamed = "(unnamed)"; + if (isNamed()) { + sb.append(String.format("[%1$" + (depth + name.length()) + "s]\n", + name)); + } else if (!children.isEmpty()) { + sb.append(String.format("[%1$" + (depth + unnamed) + "s]\n", + unnamed)); + } + getChildren().stream().forEach(c -> sb.append(c.drawTree(depth + 1))); + return sb.toString(); + } + @Override public boolean isNamed() { return name != null && name.length() > 0; From 9fca56b4c6781fd1b2cd2041c241d2630ce3bba3 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Tue, 24 May 2016 11:30:55 +0100 Subject: [PATCH 19/42] Node: remove{Child,Parent}(): split trees branches apart removeChild(node): removes the node from children of the current node, making the child node into a new root node. removeParent(): removes the current from from it's parent's children, making itself into a new root node. --- src/main/java/net/kemitix/node/Node.java | 11 +++++++++++ src/main/java/net/kemitix/node/NodeItem.java | 20 ++++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/src/main/java/net/kemitix/node/Node.java b/src/main/java/net/kemitix/node/Node.java index 0a96c66..4d95501 100644 --- a/src/main/java/net/kemitix/node/Node.java +++ b/src/main/java/net/kemitix/node/Node.java @@ -162,4 +162,15 @@ public interface Node { */ boolean isNamed(); + /** + * Remove the node from the children. + * + * @param node the node to be removed + */ + void removeChild(Node node); + + /** + * Removes the parent from the node. Makes the node into a new root node. + */ + void removeParent(); } diff --git a/src/main/java/net/kemitix/node/NodeItem.java b/src/main/java/net/kemitix/node/NodeItem.java index 88764e4..c02cf86 100644 --- a/src/main/java/net/kemitix/node/NodeItem.java +++ b/src/main/java/net/kemitix/node/NodeItem.java @@ -305,6 +305,26 @@ public class NodeItem implements Node { throw new NodeException("Named child not found"); } + @Override + public void removeChild(final Node node) { + if (children.remove(node)) { + node.removeParent(); + } + } + + @Override + public void removeParent() { + if (parent != null) { + parent.removeChild(this); + parent = null; + if (nameSupplier == null) { + // this is now a root node, so must provide a default name + // supplier + nameSupplier = n -> null; + } + } + } + @Override public String drawTree(final int depth) { final StringBuilder sb = new StringBuilder(); From 45bd77bbcad81d1455c02ec5b77937d8e1ba15ea Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Tue, 24 May 2016 11:35:33 +0100 Subject: [PATCH 20/42] Node.placeNodeIn(): add node to tree under the path of named elements Any intervening nodes that don't exist will be created. e.g. placeNodeIn(node, "alpha", "beta") Will add node: [root] "alpha" "beta" node --- src/main/java/net/kemitix/node/Node.java | 8 ++++ src/main/java/net/kemitix/node/NodeItem.java | 41 ++++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/src/main/java/net/kemitix/node/Node.java b/src/main/java/net/kemitix/node/Node.java index 4d95501..5ce9d56 100644 --- a/src/main/java/net/kemitix/node/Node.java +++ b/src/main/java/net/kemitix/node/Node.java @@ -127,6 +127,14 @@ public interface Node { Optional> walkTree(final List path); /** + * Places the node in the tree under by the path. Intervening empty + * nodes are created as needed. + * + * @param node the node to place + * @param path the path to contain the new node + */ + void placeNodeIn(Node node, String... path); + /** * Searches for a child with the name given. * diff --git a/src/main/java/net/kemitix/node/NodeItem.java b/src/main/java/net/kemitix/node/NodeItem.java index c02cf86..f338e9a 100644 --- a/src/main/java/net/kemitix/node/NodeItem.java +++ b/src/main/java/net/kemitix/node/NodeItem.java @@ -1,5 +1,6 @@ package net.kemitix.node; +import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Optional; @@ -325,6 +326,46 @@ public class NodeItem implements Node { } } + @Override + public void placeNodeIn(final Node nodeItem, final String... path) { + if (path.length == 0) { + if (nodeItem.isNamed()) { + final Optional> childNamed = findChildNamed( + nodeItem.getName()); + if (childNamed.isPresent()) { + final Node existing = childNamed.get(); + if (!existing.isEmpty()) { + throw new NodeException( + "A non-empty node with that name already " + + "exists here"); + } else { + existing.getChildren().forEach(nodeItem::addChild); + existing.removeParent(); + nodeItem.setParent(this); + return; + } + } + } + addChild(nodeItem); + return; + } + String item = path[0]; + final Optional> childNamed = findChildNamed(item); + Node child; + if (!childNamed.isPresent()) { + child = new NodeItem<>(null, item, this); + } else { + child = childNamed.get(); + if (child.isEmpty()) { + if (path.length == 1) { + getChildren().forEach(nodeItem::addChild); + nodeItem.setParent(this); + } + } + } + child.placeNodeIn(nodeItem, Arrays.copyOfRange(path, 1, path.length)); + } + @Override public String drawTree(final int depth) { final StringBuilder sb = new StringBuilder(); From b456e183161caba29c015abd69a65de81fdc9463 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Tue, 24 May 2016 11:53:13 +0100 Subject: [PATCH 21/42] NodeItem: fix up --- src/main/java/net/kemitix/node/NodeItem.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/net/kemitix/node/NodeItem.java b/src/main/java/net/kemitix/node/NodeItem.java index f338e9a..bc87179 100644 --- a/src/main/java/net/kemitix/node/NodeItem.java +++ b/src/main/java/net/kemitix/node/NodeItem.java @@ -287,6 +287,10 @@ public class NodeItem implements Node { public Node createChild(final T child) { if (child == null) { throw new NullPointerException("child"); + } + return new NodeItem<>(child, this); + } + @Override public Optional> findChildNamed(final String named) { if (named == null) { @@ -386,8 +390,4 @@ public class NodeItem implements Node { return name != null && name.length() > 0; } - } - return new NodeItem<>(child, this); - } - } From 180f325f798b88343fa9ccfcb778af8f0f3ee674 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Tue, 24 May 2016 11:53:52 +0100 Subject: [PATCH 22/42] NodeItemTest: overhaul of test suite --- .../java/net/kemitix/node/NodeItemTest.java | 493 +++++++++++++----- 1 file changed, 350 insertions(+), 143 deletions(-) diff --git a/src/test/java/net/kemitix/node/NodeItemTest.java b/src/test/java/net/kemitix/node/NodeItemTest.java index e22605f..623ea94 100644 --- a/src/test/java/net/kemitix/node/NodeItemTest.java +++ b/src/test/java/net/kemitix/node/NodeItemTest.java @@ -1,13 +1,15 @@ package net.kemitix.node; import lombok.val; -import org.junit.Assert; +import org.assertj.core.api.SoftAssertions; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; -import static org.hamcrest.CoreMatchers.hasItem; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.nullValue; +import static org.assertj.core.api.Assertions.assertThat; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; import java.util.Arrays; import java.util.Collections; import java.util.Optional; @@ -19,32 +21,51 @@ import java.util.Optional; */ public class NodeItemTest { - /** - * Class under test. - */ + @Rule + public ExpectedException exception = ExpectedException.none(); + private Node node; - /** - * Test that node data is recoverable. - */ @Test - public void shouldReturnNodeData() { + public void getDataReturnsData() { //given val data = "this node data"; //when node = new NodeItem<>(data); //then - Assert.assertThat("can get the data from a node", node.getData(), - is(data)); + assertThat(node.getData()).as("can get the data from a node"). + isSameAs(data); } - /** - * Test that passing null as node data throws exception. - */ - @Test(expected = NullPointerException.class) - public void shouldThrowNPEWhenDataIsNull() { + @Test + public void canCreateAnEmptyAndUnnamedNode() { //when node = new NodeItem<>(null); + //then + SoftAssertions softly = new SoftAssertions(); + softly.assertThat(node.isEmpty()).as("node is empty").isTrue(); + softly.assertThat(node.isNamed()).as("node is unnamed").isFalse(); + softly.assertAll(); + } + + @Test + public void canCreateNodeWithParentAndCustomNameSupplier() { + //given + node = new NodeItem<>(null, n -> "root name supplier"); + //when + val child = new NodeItem<>(null, n -> "overridden", node); + //then + assertThat(child.getName()).isEqualTo("overridden"); + } + + @Test + public void canSetName() { + //given + node = new NodeItem<>(null); + //when + node.setName("named"); + //then + assertThat(node.getName()).isEqualTo("named"); } /** @@ -53,10 +74,10 @@ public class NodeItemTest { @Test public void shouldHaveNullForDefaultParent() { //given - node = new NodeItem<>("data"); + node = new NodeItem<>("data", Node::getData); //then - Assert.assertThat("node created without a parent has null as parent", - node.getParent(), nullValue()); + assertThat(node.getParent()).as( + "node created without a parent has null as parent").isNull(); } /** @@ -65,23 +86,26 @@ public class NodeItemTest { @Test public void shouldReturnNodeParent() { //given - val parent = new NodeItem("parent"); + val parent = new NodeItem("parent", Node::getData); //when node = new NodeItem<>("subject", parent); //then - Assert.assertThat("node created with a parent can return the parent", - node.getParent(), is(parent)); + assertThat(node.getParent()).as( + "node created with a parent can return the parent") + .isSameAs(parent); } /** * Test that setting the parent on a node where the proposed parent is a * child of the node throws an exception. */ - @Test(expected = NodeException.class) - public void shouldThrowNEWhenSettingParentToAChild() { + @Test + public void setParentShouldThrowNodeExceptionWhenParentIsAChild() { //given - node = new NodeItem<>("subject"); + node = new NodeItem<>("subject", Node::getData); val child = new NodeItem("child", node); + exception.expect(NodeException.class); + exception.expectMessage("Parent is a descendant"); //when node.setParent(child); } @@ -91,16 +115,16 @@ public class NodeItemTest { * child of the parent. */ @Test + @SuppressWarnings("unchecked") public void shouldAddNewNodeAsChildToParent() { //given - val parent = new NodeItem("parent"); + val parent = new NodeItem("parent", Node::getData); //when node = new NodeItem<>("subject", parent); //then - Assert.assertThat( + assertThat(parent.getChildren()).as( "when a node is created with a parent, the parent has the new" - + " node among it's children", parent.getChildren(), - hasItem(node)); + + " node among it's children").contains(node); } /** @@ -109,23 +133,25 @@ public class NodeItemTest { @Test public void shouldReturnSetParent() { //given - node = new NodeItem<>("subject"); - val parent = new NodeItem("parent"); + node = new NodeItem<>("subject", Node::getData); + val parent = new NodeItem("parent", Node::getData); //when node.setParent(parent); //then - Assert.assertThat( + assertThat(node.getParent()).as( "when a node is assigned a new parent that parent can be " - + "returned", node.getParent(), is(parent)); + + "returned").isSameAs(parent); } /** * Test that we throw an exception when passed null. */ - @Test(expected = NullPointerException.class) + @Test public void shouldThrowNPEWhenSetParentNull() { //given - node = new NodeItem<>("subject"); + node = new NodeItem<>("subject", Node::getData); + exception.expect(NullPointerException.class); + exception.expectMessage("parent"); //when node.setParent(null); } @@ -134,10 +160,12 @@ public class NodeItemTest { * Test that we throw an exceptions when attempting to node as its own * parent. */ - @Test(expected = NodeException.class) - public void shouldThrowNEWhenSetParentSelf() { + @Test + public void setParentShouldThrowNodeExceptionWhenParentIsSelf() { //given - node = new NodeItem<>("subject"); + node = new NodeItem<>("subject", Node::getData); + exception.expect(NodeException.class); + exception.expectMessage("Parent is a descendant"); //when node.setParent(node); } @@ -149,19 +177,18 @@ public class NodeItemTest { @Test public void shouldUpdateOldParentWhenNodeSetToNewParent() { //given - node = new NodeItem<>("subject"); + node = new NodeItem<>("subject", Node::getData); val child = node.createChild("child"); - val newParent = new NodeItem("newParent"); + val newParent = new NodeItem("newParent", Node::getData); //when child.setParent(newParent); //then - Assert.assertThat( + assertThat(child.getParent()).as( "when a node is assigned a new parent, the old parent is " - + "replaced", child.getParent(), is(newParent)); - Assert.assertThat( + + "replaced").isSameAs(newParent); + assertThat(node.getChild("child").isPresent()).as( "when a node is assigned a new parent, the old parent no " - + "longer has the node among it's children", - node.getChild("child").isPresent(), is(false)); + + "longer has the node among it's children").isFalse(); } /** @@ -171,30 +198,31 @@ public class NodeItemTest { @Test public void shouldRemoveNodeFromOldParentWhenAddedAsChildToNewParent() { //given - node = new NodeItem<>("subject"); + node = new NodeItem<>("subject", Node::getData); val child = node.createChild("child"); - val newParent = new NodeItem("newParent"); + val newParent = new NodeItem("newParent", Node::getData); //when newParent.addChild(child); //then - Assert.assertThat( + assertThat(child.getParent()).as( "when a node with an existing parent is added as a child " - + "to another node, then the old parent is replaced", - child.getParent(), is(newParent)); - Assert.assertThat( + + "to another node, then the old parent is replaced") + .isSameAs(newParent); + assertThat(node.getChild("child").isPresent()).as( "when a node with an existing parent is added as a child to " + "another node, then the old parent no longer has " - + "the node among it's children", - node.getChild("child").isPresent(), is(false)); + + "the node among it's children").isFalse(); } /** * Test that adding null as a child throws an exception. */ - @Test(expected = NullPointerException.class) + @Test public void shouldThrowNPEWhenAddingNullAsChild() { //given - node = new NodeItem<>("subject"); + node = new NodeItem<>("subject", Node::getData); + exception.expect(NullPointerException.class); + exception.expectMessage("child"); //when node.addChild(null); } @@ -203,25 +231,28 @@ public class NodeItemTest { * Test that adding a child is returned. */ @Test + @SuppressWarnings("unchecked") public void shouldReturnAddedChild() { //given - node = new NodeItem<>("subject"); - val child = new NodeItem("child"); + node = new NodeItem<>("subject", Node::getData); + val child = new NodeItem("child", Node::getData); //when node.addChild(child); //then - Assert.assertThat( + assertThat(node.getChildren()).as( "when a node is added as a child, the node is among the " - + "children", node.getChildren(), hasItem(child)); + + "children").contains(child); } /** * Test that adding a node as it's own child throws an exception. */ - @Test(expected = NodeException.class) - public void shouldThrowNEWhenAddingANodeAsOwnChild() { + @Test + public void addChildShouldThrowNodeExceptionWhenAddingANodeAsOwnChild() { //given - node = new NodeItem<>("subject"); + node = new NodeItem<>("subject", Node::getData); + exception.expect(NodeException.class); + exception.expectMessage("Child is an ancestor"); //then node.addChild(node); } @@ -229,10 +260,12 @@ public class NodeItemTest { /** * Test that adding a node to itself as a child causes an exception. */ - @Test(expected = NodeException.class) - public void shouldThrowWhenAddingSelfAsChild() { + @Test + public void addChildShouldThrowNodeExceptionWhenAddingSelfAsChild() { //given - node = new NodeItem<>("subject"); + node = new NodeItem<>("subject", Node::getData); + exception.expect(NodeException.class); + exception.expectMessage("Child is an ancestor"); //when node.addChild(node); } @@ -241,11 +274,13 @@ public class NodeItemTest { * Test that adding the parent of a node to the node as a child causes an * exception. */ - @Test(expected = NodeException.class) - public void shouldThrowWhenAddingParentAsChild() { + @Test + public void addChildShouldThrowNodeExceptionWhenChildIsParent() { //given - val parent = new NodeItem("parent"); + val parent = new NodeItem("parent", Node::getData); node = new NodeItem<>("subject", parent); + exception.expect(NodeException.class); + exception.expectMessage("Child is an ancestor"); //when node.addChild(parent); } @@ -254,12 +289,14 @@ public class NodeItemTest { * Test that adding the grandparent to a node as a child causes an * exception. */ - @Test(expected = NodeException.class) - public void shouldThrowWhenAddingGrandParentAsChild() { + @Test + public void addChildShouldThrowNodeExceptionWhenAddingGrandParentAsChild() { //given - val grandParent = new NodeItem("grandparent"); + val grandParent = new NodeItem("grandparent", Node::getData); val parent = new NodeItem("parent", grandParent); node = new NodeItem<>("subject", parent); + exception.expect(NodeException.class); + exception.expectMessage("Child is an ancestor"); //when node.addChild(grandParent); } @@ -270,14 +307,14 @@ public class NodeItemTest { @Test public void shouldSetParentOnChildWhenAddedAsChild() { //given - val child = new NodeItem("child"); - node = new NodeItem<>("subject"); + val child = new NodeItem("child", Node::getData); + node = new NodeItem<>("subject", Node::getData); //when node.addChild(child); //then - Assert.assertThat( + assertThat(child.getParent()).as( "when a node is added as a child, the child has the node as " - + "its parent", child.getParent(), is(node)); + + "its parent").isSameAs(node); } /** @@ -287,7 +324,7 @@ public class NodeItemTest { public void shouldWalkTreeToNode() { //given val grandparent = "grandparent"; - val grandParentNode = new NodeItem(grandparent); + val grandParentNode = new NodeItem(grandparent, Node::getData); val parent = "parent"; val parentNode = new NodeItem(parent, grandParentNode); val subject = "subject"; @@ -295,12 +332,12 @@ public class NodeItemTest { //when val result = grandParentNode.walkTree(Arrays.asList(parent, subject)); //then - Assert.assertThat("when we walk the tree to a node it is found", - result.isPresent(), is(true)); + assertThat(result.isPresent()).as( + "when we walk the tree to a node it is found").isTrue(); if (result.isPresent()) { - Assert.assertThat( - "when we walk the tree to a node the correct node is found", - result.get(), is(node)); + assertThat(result.get()).as( + "when we walk the tree to a node the correct node is found") + .isSameAs(node); } } @@ -312,24 +349,26 @@ public class NodeItemTest { public void shouldNotFindNonExistentChildNode() { //given val parent = "parent"; - val parentNode = new NodeItem(parent); + val parentNode = new NodeItem(parent, Node::getData); val subject = "subject"; node = new NodeItem<>(subject, parentNode); //when val result = parentNode.walkTree(Arrays.asList(subject, "no child")); //then - Assert.assertThat( + assertThat(result.isPresent()).as( "when we walk the tree to a node that doesn't exists, nothing" - + " is found", result.isPresent(), is(false)); + + " is found").isFalse(); } /** * Test that when we pass null we get an exception. */ - @Test(expected = NullPointerException.class) + @Test public void shouldThrowNEWhenWalkTreeNull() { //given - node = new NodeItem<>("subject"); + node = new NodeItem<>("subject", Node::getData); + exception.expect(NullPointerException.class); + exception.expectMessage("path"); //when node.walkTree(null); } @@ -341,9 +380,11 @@ public class NodeItemTest { @Test public void shouldReturnEmptyForEmptyWalkTreePath() { //given - node = new NodeItem<>("subject"); + node = new NodeItem<>("subject", Node::getData); //when - node.walkTree(Collections.emptyList()); + val result = node.walkTree(Collections.emptyList()); + //then + assertThat(result).isEmpty(); } /** @@ -352,7 +393,7 @@ public class NodeItemTest { @Test public void shouldCreateDescendantNodes() { //given - node = new NodeItem<>("subject"); + node = new NodeItem<>("subject", Node::getData); val alphaData = "alpha"; val betaData = "beta"; val gammaData = "gamma"; @@ -361,36 +402,34 @@ public class NodeItemTest { Arrays.asList(alphaData, betaData, gammaData)); //then val alphaOptional = node.getChild(alphaData); - Assert.assertThat( - "when creating a descendant line, the first element is found", - alphaOptional.isPresent(), is(true)); + assertThat(alphaOptional.isPresent()).as( + "when creating a descendant line, the first element is found") + .isTrue(); if (alphaOptional.isPresent()) { val alpha = alphaOptional.get(); - Assert.assertThat( + assertThat(alpha.getParent()).as( "when creating a descendant line, the first element has " - + "the current node as its parent", - alpha.getParent(), is(node)); + + "the current node as its parent").isSameAs(node); val betaOptional = alpha.getChild(betaData); - Assert.assertThat( + assertThat(betaOptional.isPresent()).as( "when creating a descendant line, the second element is " - + "found", betaOptional.isPresent(), is(true)); + + "found").isTrue(); if (betaOptional.isPresent()) { val beta = betaOptional.get(); - Assert.assertThat( + assertThat(beta.getParent()).as( "when creating a descendant line, the second element " - + "has the first as its parent", - beta.getParent(), is(alpha)); + + "has the first as its parent") + .isSameAs(alpha); val gammaOptional = beta.getChild(gammaData); - Assert.assertThat( + assertThat(gammaOptional.isPresent()).as( "when creating a descendant line, the third element " - + "is found", gammaOptional.isPresent(), - is(true)); + + "is found").isTrue(); if (gammaOptional.isPresent()) { val gamma = gammaOptional.get(); - Assert.assertThat( + assertThat(gamma.getParent()).as( "when creating a descendant line, the third " - + "element has the second as its parent", - gamma.getParent(), is(beta)); + + "element has the second as its parent") + .isSameAs(beta); } } } @@ -400,10 +439,12 @@ public class NodeItemTest { * Test that if we pass null to create a chain of descendant nodes we get an * exception. */ - @Test(expected = NullPointerException.class) - public void shouldThrowNPEWhenCreateDescendantNull() { + @Test + public void createDescendantLineShouldThrowNPEWhenDescendantsAreNull() { //given - node = new NodeItem<>("subject"); + node = new NodeItem<>("subject", Node::getData); + exception.expect(NullPointerException.class); + exception.expectMessage("descendants"); //when node.createDescendantLine(null); } @@ -414,13 +455,13 @@ public class NodeItemTest { @Test public void shouldChangeNothingWhenCreateDescendantEmpty() { //given - node = new NodeItem<>("subject"); + node = new NodeItem<>("subject", Node::getData); //when node.createDescendantLine(Collections.emptyList()); //then - Assert.assertThat( + assertThat(node.getChildren()).as( "when creating a descendant line from an empty list, nothing " - + "is created", node.getChildren().size(), is(0)); + + "is created").isEmpty(); } /** @@ -429,15 +470,15 @@ public class NodeItemTest { @Test public void shouldFindExistingChildNode() { //given - node = new NodeItem<>("subject"); + node = new NodeItem<>("subject", Node::getData); val childData = "child"; val child = new NodeItem(childData, node); //when val found = node.findOrCreateChild(childData); //then - Assert.assertThat( + assertThat(found).as( "when searching for a child by data, the matching child is " - + "found", found, is(child)); + + "found").isSameAs(child); } /** @@ -446,23 +487,25 @@ public class NodeItemTest { @Test public void shouldFindCreateNewChildNode() { //given - node = new NodeItem<>("subject"); + node = new NodeItem<>("subject", Node::getData); val childData = "child"; //when val found = node.findOrCreateChild(childData); //then - Assert.assertThat( - "when searching for a child by data, a new node is created", - found.getData(), is(childData)); + assertThat(found.getData()).as( + "when searching for a child by data, a new node is created") + .isSameAs(childData); } /** * Test that if we pass null we get an exception. */ - @Test(expected = NullPointerException.class) - public void shouldThrowNPEFWhenFindOrCreateChildNull() { + @Test + public void findOrCreateChildShouldThrowNPEFWhenChildIsNull() { //given - node = new NodeItem<>("subject"); + node = new NodeItem<>("subject", Node::getData); + exception.expect(NullPointerException.class); + exception.expectMessage("child"); //when node.findOrCreateChild(null); } @@ -473,29 +516,31 @@ public class NodeItemTest { @Test public void shouldGetChild() { //given - node = new NodeItem<>("subject"); + node = new NodeItem<>("subject", Node::getData); val childData = "child"; - val child = new NodeItem(childData); + val child = new NodeItem(childData, Node::getData); node.addChild(child); //when val found = node.getChild(childData); //then - Assert.assertThat("when retrieving a child by its data, it is found", - found.isPresent(), is(true)); + assertThat(found.isPresent()).as( + "when retrieving a child by its data, it is found").isTrue(); if (found.isPresent()) { - Assert.assertThat( + assertThat(found.get()).as( "when retrieving a child by its data, it is the expected " - + "node", found.get(), is(child)); + + "node").isSameAs(child); } } /** * Test that we throw an exception when passed null. */ - @Test(expected = NullPointerException.class) - public void shouldThrowNPEWhenGetChildNull() { + @Test + public void getChildShouldThrowNPEWhenThereIsNoChild() { //given - node = new NodeItem<>("subject"); + node = new NodeItem<>("data", Node::getData); + exception.expect(NullPointerException.class); + exception.expectMessage("child"); //when node.getChild(null); } @@ -507,34 +552,196 @@ public class NodeItemTest { @Test public void shouldCreateChild() { //given - node = new NodeItem<>("subject"); + node = new NodeItem<>("subject", Node::getData); val childData = "child"; //when val child = node.createChild(childData); //then - Assert.assertThat( + assertThat(child.getParent()).as( "when creating a child node, the child has the current node " - + "as its parent", child.getParent(), is(node)); + + "as its parent").isSameAs(node); val foundChild = node.getChild(childData); - Assert.assertThat( + assertThat(foundChild.isPresent()).as( "when creating a child node, the child can be found by its " - + "data", foundChild.isPresent(), is(true)); + + "data").isTrue(); if (foundChild.isPresent()) { - Assert.assertThat( + assertThat(foundChild.get()).as( "when creating a child node, the correct child can be " - + "found by its data", foundChild.get(), is(child)); + + "found by its data").isSameAs(child); } } /** * Test that we throw an exception when passed null. */ - @Test(expected = NullPointerException.class) - public void shouldThrowNPEWhenCreateChildNull() { + @Test + public void createChildShouldThrowNPEWhenChildIsNull() { //given - node = new NodeItem<>("subject"); + node = new NodeItem<>("subject", Node::getData); + exception.expect(NullPointerException.class); + exception.expectMessage("child"); //when node.createChild(null); } + @Test + public void getNameShouldBeCorrect() { + //given + node = new NodeItem<>("subject", Node::getData); + //then + assertThat(node.getName()).isEqualTo("subject"); + } + + @Test + public void getNameShouldUseParentNameSupplier() { + //given + val root = new NodeItem("root", Node::getData); + node = new NodeItem<>("child", root); + //then + assertThat(node.getName()).isEqualTo("child"); + } + + @Test + public void getNameShouldReturnNameForNonStringData() { + val root = new NodeItem(LocalDate.parse("2016-05-23"), + n -> n.getData().format(DateTimeFormatter.BASIC_ISO_DATE)); + //then + assertThat(root.getName()).isEqualTo("20160523"); + } + + @Test + public void getNameShouldUseClosestNameSupplier() { + node = new NodeItem<>("root", Node::getData); + val child = new NodeItem("child", Object::toString); + node.addChild(child); + val grandChild = new NodeItem<>("grandchild", child); + //then + assertThat(node.getName()).isEqualTo("root"); + assertThat(child.getName()).isNotEqualTo("child"); + assertThat(grandChild.getName()).isNotEqualTo("grandchild"); + } + + @Test + public void getNameShouldWorkWithoutNameSupplier() { + node = new NodeItem<>(null, "root"); + val namedchild = new NodeItem<>("named", "Alice", node); + //then + assertThat(node.getName()).isEqualTo("root"); + assertThat(namedchild.getName()).isEqualTo("Alice"); + } + + @Test + public void canCreateRootNodeWithoutData() { + node = new NodeItem<>(null, "empty"); + assertThat(node.getData()).isNull(); + } + + @Test + public void canCreateRootNodeWithoutDataButWithNameSupplier() { + node = new NodeItem<>(null, Node::getData); + assertThat(node.getData()).isNull(); + } + + @Test + public void getChildNamedFindsChild() { + //given + node = new NodeItem<>(null, "root"); + val alpha = new NodeItem(null, "alpha"); + val beta = new NodeItem(null, "beta"); + node.addChild(alpha); + node.addChild(beta); + //when + val result = node.getChildNamed("alpha"); + //then + assertThat(result).isSameAs(alpha); + } + + @Test + public void getChildNamedFindsNothing() { + //given + node = new NodeItem<>(null, "root"); + val alpha = new NodeItem(null, "alpha"); + val beta = new NodeItem(null, "beta"); + node.addChild(alpha); + node.addChild(beta); + exception.expect(NodeException.class); + exception.expectMessage("Named child not found"); + //when + node.getChildNamed("gamma"); + } + + @Test + public void nodeNamesAreUniqueWithinAParent() { + //given + node = new NodeItem<>(null, "root"); + val alpha = new NodeItem(null, "alpha"); + node.addChild(alpha); + val beta = new NodeItem(null, "alpha"); + exception.expect(NodeException.class); + exception.expectMessage("Node with that name already exists here"); + //when + node.addChild(beta); + } + + @Test + public void canPlaceNodeInTreeByPathNames() { + //given + node = new NodeItem<>(null, "root"); // create a root + val four = new NodeItem("data", "four"); + //when + node.placeNodeIn(four, "one", "two", "three"); + //then + val three = four.getParent(); + assertThat(four.getParent()).as("add node to a tree").isNotNull(); + assertThat(three.getName()).isEqualTo("three"); + val two = three.getParent(); + assertThat(two.getName()).isEqualTo("two"); + val one = two.getParent(); + assertThat(one.getName()).isEqualTo("one"); + assertThat(one.getParent()).isSameAs(node); + assertThat(node.getChildNamed("one") + .getChildNamed("two") + .getChildNamed("three") + .getChildNamed("four")).isSameAs(four); + } + + @Test + @SuppressWarnings("unchecked") + public void canPlaceInTreeUnderExistingNode() { + //given + node = new NodeItem<>(null, "root"); + val child = new NodeItem("child data", "child"); + val grandchild = new NodeItem("grandchild data", "grandchild"); + //when + node.placeNodeIn(child); // as root/child + node.placeNodeIn(grandchild, "child"); // as root/child/grandchild + //then + assertThat(node.getChildNamed("child")).as("child").isSameAs(child); + assertThat(node.getChildNamed("child").getChildNamed("grandchild")).as( + "grandchild").isSameAs(grandchild); + } + + @Test + @SuppressWarnings("unchecked") + public void canPlaceInTreeAboveExistingNode() { + //given + node = new NodeItem<>(null, "root"); + val child = new NodeItem("child data", "child"); + val grandchild = new NodeItem("grandchild data", "grandchild"); + //when + node.placeNodeIn(grandchild, "child"); + node.placeNodeIn(child); + //then + assertThat(node.getChildNamed("child")).as("child").isSameAs(child); + assertThat(node.getChildNamed("child").getChildNamed("grandchild")).as( + "grandchild").isSameAs(grandchild); + } + + @Test + public void removingParentFromNodeWithNoParentIsNoop() { + //given + node = new NodeItem<>(null); + //when + node.removeParent(); + } } From aaab7bbe67beefe24736c909fe71768fbac7c917 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Tue, 24 May 2016 11:55:49 +0100 Subject: [PATCH 23/42] Reorganise code --- src/main/java/net/kemitix/node/Node.java | 14 +- src/main/java/net/kemitix/node/NodeItem.java | 192 +++++++++---------- 2 files changed, 103 insertions(+), 103 deletions(-) diff --git a/src/main/java/net/kemitix/node/Node.java b/src/main/java/net/kemitix/node/Node.java index 5ce9d56..a8dccdf 100644 --- a/src/main/java/net/kemitix/node/Node.java +++ b/src/main/java/net/kemitix/node/Node.java @@ -51,6 +51,13 @@ public interface Node { */ Node getParent(); + /** + * Make the current node a direct child of the parent. + * + * @param parent the new parent node + */ + void setParent(final Node parent); + /** * Fetches the child nodes. * @@ -110,13 +117,6 @@ public interface Node { */ boolean isChildOf(final Node node); - /** - * Make the current node a direct child of the parent. - * - * @param parent the new parent node - */ - void setParent(final Node parent); - /** * Walks the node tree using the path to select each child. * diff --git a/src/main/java/net/kemitix/node/NodeItem.java b/src/main/java/net/kemitix/node/NodeItem.java index bc87179..a756483 100644 --- a/src/main/java/net/kemitix/node/NodeItem.java +++ b/src/main/java/net/kemitix/node/NodeItem.java @@ -26,16 +26,6 @@ public class NodeItem implements Node { private String name; - /** - * Create unnamed root node. - * - * @param data the data or null - */ - public NodeItem(final T data) { - this.data = data; - this.nameSupplier = (n) -> null; - } - /** * Create named root node. * @@ -47,6 +37,16 @@ public class NodeItem implements Node { this.name = name; } + /** + * Create unnamed root node. + * + * @param data the data or null + */ + public NodeItem(final T data) { + this.data = data; + this.nameSupplier = (n) -> null; + } + /** * Creates root node with a name supplier. * @@ -142,26 +142,6 @@ public class NodeItem implements Node { return children; } - /** - * Make the current node a direct child of the parent. - * - * @param parent the new parent node - */ - @Override - public final void setParent(final Node parent) { - if (parent == null) { - throw new NullPointerException("parent"); - } - if (this.equals(parent) || parent.isChildOf(this)) { - throw new NodeException("Parent is a descendant"); - } - if (this.parent != null) { - this.parent.getChildren().remove(this); - } - this.parent = parent; - parent.addChild(this); - } - /** * Adds the child to the node. * @@ -190,40 +170,18 @@ public class NodeItem implements Node { } /** - * Checks if the node is an ancestor. + * Creates a new node and adds it as a child of the current node. * - * @param node the potential ancestor + * @param child the child node's data * - * @return true if the node is an ancestor + * @return the new child node */ @Override - public boolean isChildOf(final Node node) { - return parent != null && (node.equals(parent) || parent.isChildOf( - node)); - } - - /** - * Walks the node tree using the path to select each child. - * - * @param path the path to the desired child - * - * @return the child or null - */ - @Override - public Optional> walkTree(final List path) { - if (path == null) { - throw new NullPointerException("path"); + public Node createChild(final T child) { + if (child == null) { + throw new NullPointerException("child"); } - if (path.size() > 0) { - Optional> found = getChild(path.get(0)); - if (found.isPresent()) { - if (path.size() > 1) { - return found.get().walkTree(path.subList(1, path.size())); - } - return found; - } - } - return Optional.empty(); + return new NodeItem<>(child, this); } /** @@ -277,57 +235,60 @@ public class NodeItem implements Node { } /** - * Creates a new node and adds it as a child of the current node. + * Checks if the node is an ancestor. * - * @param child the child node's data + * @param node the potential ancestor * - * @return the new child node + * @return true if the node is an ancestor */ @Override - public Node createChild(final T child) { - if (child == null) { - throw new NullPointerException("child"); - } - return new NodeItem<>(child, this); + public boolean isChildOf(final Node node) { + return parent != null && (node.equals(parent) || parent.isChildOf( + node)); } + /** + * Make the current node a direct child of the parent. + * + * @param parent the new parent node + */ @Override - public Optional> findChildNamed(final String named) { - if (named == null) { - throw new NullPointerException("name"); + public final void setParent(final Node parent) { + if (parent == null) { + throw new NullPointerException("parent"); } - return children.stream() - .filter((Node t) -> t.getName().equals(named)) - .findAny(); + if (this.equals(parent) || parent.isChildOf(this)) { + throw new NodeException("Parent is a descendant"); + } + if (this.parent != null) { + this.parent.getChildren().remove(this); + } + this.parent = parent; + parent.addChild(this); } + /** + * Walks the node tree using the path to select each child. + * + * @param path the path to the desired child + * + * @return the child or null + */ @Override - public Node getChildNamed(final String named) { - final Optional> optional = findChildNamed(named); - if (optional.isPresent()) { - return optional.get(); + public Optional> walkTree(final List path) { + if (path == null) { + throw new NullPointerException("path"); } - throw new NodeException("Named child not found"); - } - - @Override - public void removeChild(final Node node) { - if (children.remove(node)) { - node.removeParent(); - } - } - - @Override - public void removeParent() { - if (parent != null) { - parent.removeChild(this); - parent = null; - if (nameSupplier == null) { - // this is now a root node, so must provide a default name - // supplier - nameSupplier = n -> null; + if (path.size() > 0) { + Optional> found = getChild(path.get(0)); + if (found.isPresent()) { + if (path.size() > 1) { + return found.get().walkTree(path.subList(1, path.size())); + } + return found; } } + return Optional.empty(); } @Override @@ -370,6 +331,25 @@ public class NodeItem implements Node { child.placeNodeIn(nodeItem, Arrays.copyOfRange(path, 1, path.length)); } + @Override + public Optional> findChildNamed(final String named) { + if (named == null) { + throw new NullPointerException("name"); + } + return children.stream() + .filter((Node t) -> t.getName().equals(named)) + .findAny(); + } + + @Override + public Node getChildNamed(final String named) { + final Optional> optional = findChildNamed(named); + if (optional.isPresent()) { + return optional.get(); + } + throw new NodeException("Named child not found"); + } + @Override public String drawTree(final int depth) { final StringBuilder sb = new StringBuilder(); @@ -390,4 +370,24 @@ public class NodeItem implements Node { return name != null && name.length() > 0; } + @Override + public void removeChild(final Node node) { + if (children.remove(node)) { + node.removeParent(); + } + } + + @Override + public void removeParent() { + if (parent != null) { + parent.removeChild(this); + parent = null; + if (nameSupplier == null) { + // this is now a root node, so must provide a default name + // supplier + nameSupplier = n -> null; + } + } + } + } From d82a7d6e9ffcbca13f82ce3bf68f61e97679a366 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Tue, 24 May 2016 13:13:55 +0100 Subject: [PATCH 24/42] NoteItem: replace empty target node properly --- src/main/java/net/kemitix/node/NodeItem.java | 42 +++++++--------- .../java/net/kemitix/node/NodeItemTest.java | 49 +++++++++++++++++++ 2 files changed, 68 insertions(+), 23 deletions(-) diff --git a/src/main/java/net/kemitix/node/NodeItem.java b/src/main/java/net/kemitix/node/NodeItem.java index a756483..b77da2a 100644 --- a/src/main/java/net/kemitix/node/NodeItem.java +++ b/src/main/java/net/kemitix/node/NodeItem.java @@ -294,24 +294,26 @@ public class NodeItem implements Node { @Override public void placeNodeIn(final Node nodeItem, final String... path) { if (path.length == 0) { - if (nodeItem.isNamed()) { - final Optional> childNamed = findChildNamed( - nodeItem.getName()); - if (childNamed.isPresent()) { - final Node existing = childNamed.get(); - if (!existing.isEmpty()) { - throw new NodeException( - "A non-empty node with that name already " - + "exists here"); - } else { - existing.getChildren().forEach(nodeItem::addChild); - existing.removeParent(); - nodeItem.setParent(this); - return; - } - } + if (!nodeItem.isNamed()) { // nothing to conflict with + addChild(nodeItem); + return; + } + final Optional> childNamed = findChildNamed( + nodeItem.getName()); + if (!childNamed.isPresent()) { // nothing with the same name exists + addChild(nodeItem); + return; + } + // we have an existing node with the same name + final Node existing = childNamed.get(); + if (!existing.isEmpty()) { + throw new NodeException( + "A non-empty node with that name already exists here"); + } else { + existing.getChildren().forEach(nodeItem::addChild); + existing.removeParent(); + addChild(nodeItem); } - addChild(nodeItem); return; } String item = path[0]; @@ -321,12 +323,6 @@ public class NodeItem implements Node { child = new NodeItem<>(null, item, this); } else { child = childNamed.get(); - if (child.isEmpty()) { - if (path.length == 1) { - getChildren().forEach(nodeItem::addChild); - nodeItem.setParent(this); - } - } } child.placeNodeIn(nodeItem, Arrays.copyOfRange(path, 1, path.length)); } diff --git a/src/test/java/net/kemitix/node/NodeItemTest.java b/src/test/java/net/kemitix/node/NodeItemTest.java index 623ea94..5f5a5b0 100644 --- a/src/test/java/net/kemitix/node/NodeItemTest.java +++ b/src/test/java/net/kemitix/node/NodeItemTest.java @@ -744,4 +744,53 @@ public class NodeItemTest { //when node.removeParent(); } + + @Test + public void placeNodeInTreeWhereNonEmptyNodeWithSameNameExists() { + //given + exception.expect(NodeException.class); + exception.expectMessage( + "A non-empty node with that name already exists here"); + node = new NodeItem<>(null); + val child = new NodeItem(null, "child", node); + new NodeItem<>("data", "grandchild", child); + // root -> child -> grandchild + // only grandchild has data + //when + // attempt to add another node called 'grandchild' to 'child' + node.placeNodeIn(new NodeItem<>("cuckoo", "grandchild"), "child"); + } + + @Test + @SuppressWarnings("unchecked") + public void placeNodeInTreeWhenAddedNodeIsUnnamed() { + //given + node = new NodeItem<>(null); + final Node newNode = new NodeItem<>(null); + //when + node.placeNodeIn(newNode); + //then + assertThat(node.getChildren()).containsOnly(newNode); + } + + @Test + @SuppressWarnings("unchecked") + public void placeNodeInTreeWhenEmptyChildWithTargetNameExists() { + //given + node = new NodeItem<>(null); + final NodeItem child = new NodeItem<>(null, "child"); + final NodeItem target = new NodeItem<>(null, "target"); + node.addChild(child); + child.addChild(target); + final NodeItem addMe = new NodeItem<>("I'm new", "target"); + assertThat(addMe.getParent()).isNull(); + //when + // addMe should replace target as the sole descendant of child + node.placeNodeIn(addMe, "child"); + //then + assertThat(child.getChildren()).as("child only contains new node") + .containsOnly(addMe); + assertThat(target.getParent()).as("old node is removed from tree") + .isNull(); + } } From 6f81a62162d27c43ca74f05cf2e522eba794f636 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Tue, 24 May 2016 13:33:53 +0100 Subject: [PATCH 25/42] NodeItem.removeParent(): use same name supplier in the new root node --- src/main/java/net/kemitix/node/NodeItem.java | 8 ++- .../java/net/kemitix/node/NodeItemTest.java | 63 +++++++++++++++++++ 2 files changed, 68 insertions(+), 3 deletions(-) diff --git a/src/main/java/net/kemitix/node/NodeItem.java b/src/main/java/net/kemitix/node/NodeItem.java index b77da2a..8ef24a6 100644 --- a/src/main/java/net/kemitix/node/NodeItem.java +++ b/src/main/java/net/kemitix/node/NodeItem.java @@ -376,12 +376,14 @@ public class NodeItem implements Node { @Override public void removeParent() { if (parent != null) { - parent.removeChild(this); + Node oldParent = parent; + Function, String> supplier = getNameSupplier(); parent = null; - if (nameSupplier == null) { + oldParent.removeChild(this); + if (this.nameSupplier == null) { // this is now a root node, so must provide a default name // supplier - nameSupplier = n -> null; + this.nameSupplier = supplier; } } } diff --git a/src/test/java/net/kemitix/node/NodeItemTest.java b/src/test/java/net/kemitix/node/NodeItemTest.java index 5f5a5b0..ec9953f 100644 --- a/src/test/java/net/kemitix/node/NodeItemTest.java +++ b/src/test/java/net/kemitix/node/NodeItemTest.java @@ -793,4 +793,67 @@ public class NodeItemTest { assertThat(target.getParent()).as("old node is removed from tree") .isNull(); } + + @Test + public void findChildNamedShouldThrowNPEWhenNameIsNull() { + //given + exception.expect(NullPointerException.class); + exception.expectMessage("name"); + node = new NodeItem<>(null); + //when + node.findChildNamed(null); + } + + @Test + public void isNamedNull() { + //given + node = new NodeItem<>(null); + //then + assertThat(node.isNamed()).isFalse(); + } + + @Test + public void isNamedEmpty() { + //given + node = new NodeItem<>(null, ""); + //then + assertThat(node.isNamed()).isFalse(); + } + + @Test + public void isNamedNamed() { + //given + node = new NodeItem<>(null, "named"); + //then + assertThat(node.isNamed()).isTrue(); + } + + @Test + public void removeParentNodeProvidesSameNameSupplier() { + // once a node has it's parent removed it should provide a default name + // provider + //given + node = new NodeItem<>("data", Node::getData); // name provider: getData + final NodeItem child = new NodeItem<>("other", node); + assertThat(node.getName()).as("initial root name").isEqualTo("data"); + assertThat(child.getName()).as("initial child name").isEqualTo("other"); + //when + child.removeParent(); + //then + assertThat(node.getName()).as("final root name").isEqualTo("data"); + assertThat(child.getName()).as("final child name").isEqualTo("other"); + } + + @Test + @SuppressWarnings("unchecked") + public void removeChildRemovesTheChild() { + //given + node = new NodeItem<>(null); + Node child = node.createChild("child"); + assertThat(node.getChildren()).containsExactly(child); + //then + node.removeChild(child); + //then + assertThat(node.getChildren()).isEmpty(); + } } From 3053eab9b038f889a4071e66616e3cce869fc5f6 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Tue, 24 May 2016 13:50:38 +0100 Subject: [PATCH 26/42] NodeItem.drawTree(): fix typo --- src/main/java/net/kemitix/node/NodeItem.java | 5 +++-- .../java/net/kemitix/node/NodeItemTest.java | 22 +++++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/main/java/net/kemitix/node/NodeItem.java b/src/main/java/net/kemitix/node/NodeItem.java index 8ef24a6..1ef3bf6 100644 --- a/src/main/java/net/kemitix/node/NodeItem.java +++ b/src/main/java/net/kemitix/node/NodeItem.java @@ -354,8 +354,9 @@ public class NodeItem implements Node { sb.append(String.format("[%1$" + (depth + name.length()) + "s]\n", name)); } else if (!children.isEmpty()) { - sb.append(String.format("[%1$" + (depth + unnamed) + "s]\n", - unnamed)); + sb.append( + String.format("[%1$" + (depth + unnamed.length()) + "s]\n", + unnamed)); } getChildren().stream().forEach(c -> sb.append(c.drawTree(depth + 1))); return sb.toString(); diff --git a/src/test/java/net/kemitix/node/NodeItemTest.java b/src/test/java/net/kemitix/node/NodeItemTest.java index ec9953f..200321f 100644 --- a/src/test/java/net/kemitix/node/NodeItemTest.java +++ b/src/test/java/net/kemitix/node/NodeItemTest.java @@ -856,4 +856,26 @@ public class NodeItemTest { //then assertThat(node.getChildren()).isEmpty(); } + + @Test + public void drawTreeIsCorrect() { + //given + node = new NodeItem<>(null, "root"); + val bob = new NodeItem(null, "bob", node); + val alice = new NodeItem(null, "alice", node); + new NodeItem<>(null, "dave", alice); + new NodeItem<>(null, bob); // has no name and no children so no included + val kim = new NodeItem(null, node); // nameless mother + new NodeItem<>(null, "lucy", kim); + //when + val tree = node.drawTree(0); + //then + String[] lines = tree.split("\n"); + assertThat(lines).contains("[root]", "[ alice]", "[ dave]", + "[ (unnamed)]", "[ lucy]", "[ bob]"); + assertThat(lines).containsSubsequence("[root]", "[ alice]", "[ dave]"); + assertThat(lines).containsSubsequence("[root]", "[ (unnamed)]", + "[ lucy]"); + assertThat(lines).containsSubsequence("[root]", "[ bob]"); + } } From e9c6300ad41b7670987dc6329121da651ab1a421 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Tue, 24 May 2016 14:10:46 +0100 Subject: [PATCH 27/42] Node: remove pointless final in parameters in interface --- src/main/java/net/kemitix/node/Node.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/main/java/net/kemitix/node/Node.java b/src/main/java/net/kemitix/node/Node.java index a8dccdf..9c432dc 100644 --- a/src/main/java/net/kemitix/node/Node.java +++ b/src/main/java/net/kemitix/node/Node.java @@ -56,7 +56,7 @@ public interface Node { * * @param parent the new parent node */ - void setParent(final Node parent); + void setParent(Node parent); /** * Fetches the child nodes. @@ -70,7 +70,7 @@ public interface Node { * * @param child the node to add */ - void addChild(final Node child); + void addChild(Node child); /** * Creates a new node and adds it as a child of the current node. @@ -79,7 +79,7 @@ public interface Node { * * @return the new child node */ - Node createChild(final T child); + Node createChild(T child); /** * Populates the tree with the path of nodes, each being a child of the @@ -87,7 +87,7 @@ public interface Node { * * @param descendants the line of descendants from the current node */ - void createDescendantLine(final List descendants); + void createDescendantLine(List descendants); /** * Looks for a child node and returns it, creating a new child node if one @@ -97,7 +97,7 @@ public interface Node { * * @return the found or created child node */ - Node findOrCreateChild(final T child); + Node findOrCreateChild(T child); /** * Fetches the node for the child if present. @@ -106,7 +106,7 @@ public interface Node { * * @return an {@link Optional} containing the child node if found */ - Optional> getChild(final T child); + Optional> getChild(T child); /** * Checks if the node is an ancestor. @@ -115,7 +115,7 @@ public interface Node { * * @return true if the node is an ancestor */ - boolean isChildOf(final Node node); + boolean isChildOf(Node node); /** * Walks the node tree using the path to select each child. @@ -124,7 +124,7 @@ public interface Node { * * @return the child or null */ - Optional> walkTree(final List path); + Optional> walkTree(List path); /** * Places the node in the tree under by the path. Intervening empty From 2ed0024bc0e7ab7d01f3ed11f165f9decb709d73 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Tue, 24 May 2016 14:25:55 +0100 Subject: [PATCH 28/42] Node: clean up method names Deprecate: * findOrCreateChild() Rename: * getChild() => findChild() * isChildOf() => isDescendantOf() * walkTree() => findInPath() * placeNodeIn() => insertInPath() * findChildNamed() => findChildByName() * getChildNamed() => getChildByName() --- src/main/java/net/kemitix/node/Node.java | 16 ++--- src/main/java/net/kemitix/node/NodeItem.java | 34 +++++----- .../java/net/kemitix/node/NodeItemTest.java | 62 +++++++++---------- 3 files changed, 57 insertions(+), 55 deletions(-) diff --git a/src/main/java/net/kemitix/node/Node.java b/src/main/java/net/kemitix/node/Node.java index 9c432dc..8094888 100644 --- a/src/main/java/net/kemitix/node/Node.java +++ b/src/main/java/net/kemitix/node/Node.java @@ -73,7 +73,7 @@ public interface Node { void addChild(Node child); /** - * Creates a new node and adds it as a child of the current node. + * Creates a new unnamed node and adds it as a child of the current node. * * @param child the child node's data * @@ -97,6 +97,7 @@ public interface Node { * * @return the found or created child node */ + @Deprecated Node findOrCreateChild(T child); /** @@ -106,7 +107,8 @@ public interface Node { * * @return an {@link Optional} containing the child node if found */ - Optional> getChild(T child); + Optional> findChild(T child); + /** * Checks if the node is an ancestor. @@ -115,7 +117,7 @@ public interface Node { * * @return true if the node is an ancestor */ - boolean isChildOf(Node node); + boolean isDescendantOf(Node node); /** * Walks the node tree using the path to select each child. @@ -124,7 +126,7 @@ public interface Node { * * @return the child or null */ - Optional> walkTree(List path); + Optional> findInPath(List path); /** * Places the node in the tree under by the path. Intervening empty @@ -133,7 +135,7 @@ public interface Node { * @param node the node to place * @param path the path to contain the new node */ - void placeNodeIn(Node node, String... path); + void insertInPath(Node node, String... path); /** * Searches for a child with the name given. @@ -142,7 +144,7 @@ public interface Node { * * @return an Optional containing the child found or empty */ - Optional> findChildNamed(String name); + Optional> findChildByName(String name); /** * Returns the child with the given name. If one can't be found a @@ -152,7 +154,7 @@ public interface Node { * * @return the node */ - Node getChildNamed(String name); + Node getChildByName(String name); /** * Draw a representation of the tree. diff --git a/src/main/java/net/kemitix/node/NodeItem.java b/src/main/java/net/kemitix/node/NodeItem.java index 1ef3bf6..9175667 100644 --- a/src/main/java/net/kemitix/node/NodeItem.java +++ b/src/main/java/net/kemitix/node/NodeItem.java @@ -152,11 +152,11 @@ public class NodeItem implements Node { if (child == null) { throw new NullPointerException("child"); } - if (this.equals(child) || isChildOf(child)) { + if (this.equals(child) || isDescendantOf(child)) { throw new NodeException("Child is an ancestor"); } if (child.isNamed()) { - final Optional> existingChild = findChildNamed( + final Optional> existingChild = findChildByName( child.getName()); if (existingChild.isPresent() && existingChild.get() != child) { throw new NodeException( @@ -214,7 +214,7 @@ public class NodeItem implements Node { if (child == null) { throw new NullPointerException("child"); } - return getChild(child).orElseGet(() -> createChild(child)); + return findChild(child).orElseGet(() -> createChild(child)); } /** @@ -225,7 +225,7 @@ public class NodeItem implements Node { * @return an {@link Optional} containing the child node if found */ @Override - public Optional> getChild(final T child) { + public Optional> findChild(final T child) { if (child == null) { throw new NullPointerException("child"); } @@ -242,8 +242,8 @@ public class NodeItem implements Node { * @return true if the node is an ancestor */ @Override - public boolean isChildOf(final Node node) { - return parent != null && (node.equals(parent) || parent.isChildOf( + public boolean isDescendantOf(final Node node) { + return parent != null && (node.equals(parent) || parent.isDescendantOf( node)); } @@ -257,7 +257,7 @@ public class NodeItem implements Node { if (parent == null) { throw new NullPointerException("parent"); } - if (this.equals(parent) || parent.isChildOf(this)) { + if (this.equals(parent) || parent.isDescendantOf(this)) { throw new NodeException("Parent is a descendant"); } if (this.parent != null) { @@ -275,15 +275,15 @@ public class NodeItem implements Node { * @return the child or null */ @Override - public Optional> walkTree(final List path) { + public Optional> findInPath(final List path) { if (path == null) { throw new NullPointerException("path"); } if (path.size() > 0) { - Optional> found = getChild(path.get(0)); + Optional> found = findChild(path.get(0)); if (found.isPresent()) { if (path.size() > 1) { - return found.get().walkTree(path.subList(1, path.size())); + return found.get().findInPath(path.subList(1, path.size())); } return found; } @@ -292,13 +292,13 @@ public class NodeItem implements Node { } @Override - public void placeNodeIn(final Node nodeItem, final String... path) { + public void insertInPath(final Node nodeItem, final String... path) { if (path.length == 0) { if (!nodeItem.isNamed()) { // nothing to conflict with addChild(nodeItem); return; } - final Optional> childNamed = findChildNamed( + final Optional> childNamed = findChildByName( nodeItem.getName()); if (!childNamed.isPresent()) { // nothing with the same name exists addChild(nodeItem); @@ -317,18 +317,18 @@ public class NodeItem implements Node { return; } String item = path[0]; - final Optional> childNamed = findChildNamed(item); + final Optional> childNamed = findChildByName(item); Node child; if (!childNamed.isPresent()) { child = new NodeItem<>(null, item, this); } else { child = childNamed.get(); } - child.placeNodeIn(nodeItem, Arrays.copyOfRange(path, 1, path.length)); + child.insertInPath(nodeItem, Arrays.copyOfRange(path, 1, path.length)); } @Override - public Optional> findChildNamed(final String named) { + public Optional> findChildByName(final String named) { if (named == null) { throw new NullPointerException("name"); } @@ -338,8 +338,8 @@ public class NodeItem implements Node { } @Override - public Node getChildNamed(final String named) { - final Optional> optional = findChildNamed(named); + public Node getChildByName(final String named) { + final Optional> optional = findChildByName(named); if (optional.isPresent()) { return optional.get(); } diff --git a/src/test/java/net/kemitix/node/NodeItemTest.java b/src/test/java/net/kemitix/node/NodeItemTest.java index 200321f..771c207 100644 --- a/src/test/java/net/kemitix/node/NodeItemTest.java +++ b/src/test/java/net/kemitix/node/NodeItemTest.java @@ -186,7 +186,7 @@ public class NodeItemTest { assertThat(child.getParent()).as( "when a node is assigned a new parent, the old parent is " + "replaced").isSameAs(newParent); - assertThat(node.getChild("child").isPresent()).as( + assertThat(node.findChild("child").isPresent()).as( "when a node is assigned a new parent, the old parent no " + "longer has the node among it's children").isFalse(); } @@ -208,7 +208,7 @@ public class NodeItemTest { "when a node with an existing parent is added as a child " + "to another node, then the old parent is replaced") .isSameAs(newParent); - assertThat(node.getChild("child").isPresent()).as( + assertThat(node.findChild("child").isPresent()).as( "when a node with an existing parent is added as a child to " + "another node, then the old parent no longer has " + "the node among it's children").isFalse(); @@ -330,7 +330,7 @@ public class NodeItemTest { val subject = "subject"; node = new NodeItem<>(subject, parentNode); //when - val result = grandParentNode.walkTree(Arrays.asList(parent, subject)); + val result = grandParentNode.findInPath(Arrays.asList(parent, subject)); //then assertThat(result.isPresent()).as( "when we walk the tree to a node it is found").isTrue(); @@ -353,7 +353,7 @@ public class NodeItemTest { val subject = "subject"; node = new NodeItem<>(subject, parentNode); //when - val result = parentNode.walkTree(Arrays.asList(subject, "no child")); + val result = parentNode.findInPath(Arrays.asList(subject, "no child")); //then assertThat(result.isPresent()).as( "when we walk the tree to a node that doesn't exists, nothing" @@ -370,7 +370,7 @@ public class NodeItemTest { exception.expect(NullPointerException.class); exception.expectMessage("path"); //when - node.walkTree(null); + node.findInPath(null); } /** @@ -382,7 +382,7 @@ public class NodeItemTest { //given node = new NodeItem<>("subject", Node::getData); //when - val result = node.walkTree(Collections.emptyList()); + val result = node.findInPath(Collections.emptyList()); //then assertThat(result).isEmpty(); } @@ -401,7 +401,7 @@ public class NodeItemTest { node.createDescendantLine( Arrays.asList(alphaData, betaData, gammaData)); //then - val alphaOptional = node.getChild(alphaData); + val alphaOptional = node.findChild(alphaData); assertThat(alphaOptional.isPresent()).as( "when creating a descendant line, the first element is found") .isTrue(); @@ -410,7 +410,7 @@ public class NodeItemTest { assertThat(alpha.getParent()).as( "when creating a descendant line, the first element has " + "the current node as its parent").isSameAs(node); - val betaOptional = alpha.getChild(betaData); + val betaOptional = alpha.findChild(betaData); assertThat(betaOptional.isPresent()).as( "when creating a descendant line, the second element is " + "found").isTrue(); @@ -420,7 +420,7 @@ public class NodeItemTest { "when creating a descendant line, the second element " + "has the first as its parent") .isSameAs(alpha); - val gammaOptional = beta.getChild(gammaData); + val gammaOptional = beta.findChild(gammaData); assertThat(gammaOptional.isPresent()).as( "when creating a descendant line, the third element " + "is found").isTrue(); @@ -521,7 +521,7 @@ public class NodeItemTest { val child = new NodeItem(childData, Node::getData); node.addChild(child); //when - val found = node.getChild(childData); + val found = node.findChild(childData); //then assertThat(found.isPresent()).as( "when retrieving a child by its data, it is found").isTrue(); @@ -542,7 +542,7 @@ public class NodeItemTest { exception.expect(NullPointerException.class); exception.expectMessage("child"); //when - node.getChild(null); + node.findChild(null); } /** @@ -560,7 +560,7 @@ public class NodeItemTest { assertThat(child.getParent()).as( "when creating a child node, the child has the current node " + "as its parent").isSameAs(node); - val foundChild = node.getChild(childData); + val foundChild = node.findChild(childData); assertThat(foundChild.isPresent()).as( "when creating a child node, the child can be found by its " + "data").isTrue(); @@ -651,7 +651,7 @@ public class NodeItemTest { node.addChild(alpha); node.addChild(beta); //when - val result = node.getChildNamed("alpha"); + val result = node.getChildByName("alpha"); //then assertThat(result).isSameAs(alpha); } @@ -667,7 +667,7 @@ public class NodeItemTest { exception.expect(NodeException.class); exception.expectMessage("Named child not found"); //when - node.getChildNamed("gamma"); + node.getChildByName("gamma"); } @Test @@ -689,7 +689,7 @@ public class NodeItemTest { node = new NodeItem<>(null, "root"); // create a root val four = new NodeItem("data", "four"); //when - node.placeNodeIn(four, "one", "two", "three"); + node.insertInPath(four, "one", "two", "three"); //then val three = four.getParent(); assertThat(four.getParent()).as("add node to a tree").isNotNull(); @@ -699,10 +699,10 @@ public class NodeItemTest { val one = two.getParent(); assertThat(one.getName()).isEqualTo("one"); assertThat(one.getParent()).isSameAs(node); - assertThat(node.getChildNamed("one") - .getChildNamed("two") - .getChildNamed("three") - .getChildNamed("four")).isSameAs(four); + assertThat(node.getChildByName("one") + .getChildByName("two") + .getChildByName("three") + .getChildByName("four")).isSameAs(four); } @Test @@ -713,11 +713,11 @@ public class NodeItemTest { val child = new NodeItem("child data", "child"); val grandchild = new NodeItem("grandchild data", "grandchild"); //when - node.placeNodeIn(child); // as root/child - node.placeNodeIn(grandchild, "child"); // as root/child/grandchild + node.insertInPath(child); // as root/child + node.insertInPath(grandchild, "child"); // as root/child/grandchild //then - assertThat(node.getChildNamed("child")).as("child").isSameAs(child); - assertThat(node.getChildNamed("child").getChildNamed("grandchild")).as( + assertThat(node.getChildByName("child")).as("child").isSameAs(child); + assertThat(node.getChildByName("child").getChildByName("grandchild")).as( "grandchild").isSameAs(grandchild); } @@ -729,11 +729,11 @@ public class NodeItemTest { val child = new NodeItem("child data", "child"); val grandchild = new NodeItem("grandchild data", "grandchild"); //when - node.placeNodeIn(grandchild, "child"); - node.placeNodeIn(child); + node.insertInPath(grandchild, "child"); + node.insertInPath(child); //then - assertThat(node.getChildNamed("child")).as("child").isSameAs(child); - assertThat(node.getChildNamed("child").getChildNamed("grandchild")).as( + assertThat(node.getChildByName("child")).as("child").isSameAs(child); + assertThat(node.getChildByName("child").getChildByName("grandchild")).as( "grandchild").isSameAs(grandchild); } @@ -758,7 +758,7 @@ public class NodeItemTest { // only grandchild has data //when // attempt to add another node called 'grandchild' to 'child' - node.placeNodeIn(new NodeItem<>("cuckoo", "grandchild"), "child"); + node.insertInPath(new NodeItem<>("cuckoo", "grandchild"), "child"); } @Test @@ -768,7 +768,7 @@ public class NodeItemTest { node = new NodeItem<>(null); final Node newNode = new NodeItem<>(null); //when - node.placeNodeIn(newNode); + node.insertInPath(newNode); //then assertThat(node.getChildren()).containsOnly(newNode); } @@ -786,7 +786,7 @@ public class NodeItemTest { assertThat(addMe.getParent()).isNull(); //when // addMe should replace target as the sole descendant of child - node.placeNodeIn(addMe, "child"); + node.insertInPath(addMe, "child"); //then assertThat(child.getChildren()).as("child only contains new node") .containsOnly(addMe); @@ -801,7 +801,7 @@ public class NodeItemTest { exception.expectMessage("name"); node = new NodeItem<>(null); //when - node.findChildNamed(null); + node.findChildByName(null); } @Test From fc55bacb049bb298ff765bd15835d62b22afd62f Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Tue, 24 May 2016 14:30:00 +0100 Subject: [PATCH 29/42] Node.setData(): add ability to change node's data after instantiation --- src/main/java/net/kemitix/node/Node.java | 7 +++++++ src/main/java/net/kemitix/node/NodeItem.java | 7 ++++++- src/test/java/net/kemitix/node/NodeItemTest.java | 10 ++++++++++ 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/main/java/net/kemitix/node/Node.java b/src/main/java/net/kemitix/node/Node.java index 8094888..a01efa7 100644 --- a/src/main/java/net/kemitix/node/Node.java +++ b/src/main/java/net/kemitix/node/Node.java @@ -34,6 +34,13 @@ public interface Node { */ T getData(); + /** + * Set the data held within the node. + * + * @param data the node's data + */ + void setData(T data); + /** * Returns true if the node is empty (has no data). * diff --git a/src/main/java/net/kemitix/node/NodeItem.java b/src/main/java/net/kemitix/node/NodeItem.java index 9175667..5938e71 100644 --- a/src/main/java/net/kemitix/node/NodeItem.java +++ b/src/main/java/net/kemitix/node/NodeItem.java @@ -16,7 +16,7 @@ import java.util.function.Function; */ public class NodeItem implements Node { - private final T data; + private T data; private final Set> children = new HashSet<>(); @@ -127,6 +127,11 @@ public class NodeItem implements Node { return data; } + @Override + public void setData(final T data) { + this.data = data; + } + @Override public boolean isEmpty() { return data == null; diff --git a/src/test/java/net/kemitix/node/NodeItemTest.java b/src/test/java/net/kemitix/node/NodeItemTest.java index 771c207..8cd5ca4 100644 --- a/src/test/java/net/kemitix/node/NodeItemTest.java +++ b/src/test/java/net/kemitix/node/NodeItemTest.java @@ -878,4 +878,14 @@ public class NodeItemTest { "[ lucy]"); assertThat(lines).containsSubsequence("[root]", "[ bob]"); } + + @Test + public void canChangeNodeData() { + //given + node = new NodeItem<>("initial"); + //when + node.setData("updated"); + //then + assertThat(node.getData()).isEqualTo("updated"); + } } From 04599861c0dc5da89b6981794857ca30d0c73ab6 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Tue, 24 May 2016 14:43:36 +0100 Subject: [PATCH 30/42] Node.createChild(data, name): can create named child nodes # Conflicts: # src/test/java/net/kemitix/node/NodeItemTest.java --- src/main/java/net/kemitix/node/Node.java | 9 +++++++++ src/main/java/net/kemitix/node/NodeItem.java | 7 +++++++ src/test/java/net/kemitix/node/NodeItemTest.java | 13 +++++++++++++ 3 files changed, 29 insertions(+) diff --git a/src/main/java/net/kemitix/node/Node.java b/src/main/java/net/kemitix/node/Node.java index a01efa7..04bec0e 100644 --- a/src/main/java/net/kemitix/node/Node.java +++ b/src/main/java/net/kemitix/node/Node.java @@ -88,6 +88,15 @@ public interface Node { */ Node createChild(T child); + /** + * Creates a new named node and adds it as a child of the current node. + * + * @param child the child node's data + * + * @return the new child node + */ + Node createChild(T child, String name); + /** * Populates the tree with the path of nodes, each being a child of the * previous node in the path. diff --git a/src/main/java/net/kemitix/node/NodeItem.java b/src/main/java/net/kemitix/node/NodeItem.java index 5938e71..c9d70f8 100644 --- a/src/main/java/net/kemitix/node/NodeItem.java +++ b/src/main/java/net/kemitix/node/NodeItem.java @@ -189,6 +189,13 @@ public class NodeItem implements Node { return new NodeItem<>(child, this); } + @Override + public Node createChild(final T child, final String name) { + Node node = createChild(child); + node.setName(name); + return node; + } + /** * Populates the tree with the path of nodes, each being a child of the * previous node in the path. diff --git a/src/test/java/net/kemitix/node/NodeItemTest.java b/src/test/java/net/kemitix/node/NodeItemTest.java index 8cd5ca4..53b0c07 100644 --- a/src/test/java/net/kemitix/node/NodeItemTest.java +++ b/src/test/java/net/kemitix/node/NodeItemTest.java @@ -888,4 +888,17 @@ public class NodeItemTest { //then assertThat(node.getData()).isEqualTo("updated"); } + + @Test + @SuppressWarnings("unchecked") + public void canCreateNamedChild() { + //given + node = new NodeItem<>(null); + //when + Node child = node.createChild("child data", "child name"); + //then + assertThat(child.getName()).isEqualTo("child name"); + assertThat(child.getParent()).isSameAs(node); + assertThat(node.getChildren()).containsExactly(child); + } } From f95029d3485c66ab79e6ad1133b6ea5ddc7c11bf Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Tue, 24 May 2016 14:39:23 +0100 Subject: [PATCH 31/42] Node.getChild(): method to get a child or throw an exception if not found --- src/main/java/net/kemitix/node/Node.java | 10 +++++++++ src/main/java/net/kemitix/node/NodeItem.java | 8 +++++++ .../java/net/kemitix/node/NodeItemTest.java | 21 +++++++++++++++++++ 3 files changed, 39 insertions(+) diff --git a/src/main/java/net/kemitix/node/Node.java b/src/main/java/net/kemitix/node/Node.java index 04bec0e..e2f074f 100644 --- a/src/main/java/net/kemitix/node/Node.java +++ b/src/main/java/net/kemitix/node/Node.java @@ -125,6 +125,16 @@ public interface Node { */ Optional> findChild(T child); + /** + * Fetches the node for the child if present. + * + * @param child the child's data to search for + * + * @return the child node if found + * + * @throws NodeException if the node is not found + */ + Node getChild(T child); /** * Checks if the node is an ancestor. diff --git a/src/main/java/net/kemitix/node/NodeItem.java b/src/main/java/net/kemitix/node/NodeItem.java index c9d70f8..acd883d 100644 --- a/src/main/java/net/kemitix/node/NodeItem.java +++ b/src/main/java/net/kemitix/node/NodeItem.java @@ -246,6 +246,14 @@ public class NodeItem implements Node { .findAny(); } + @Override + public Node getChild(final T child) { + Optional> optional = findChild(child); + if (optional.isPresent()) + return optional.get(); + throw new NodeException("Child not found"); + } + /** * Checks if the node is an ancestor. * diff --git a/src/test/java/net/kemitix/node/NodeItemTest.java b/src/test/java/net/kemitix/node/NodeItemTest.java index 53b0c07..251d690 100644 --- a/src/test/java/net/kemitix/node/NodeItemTest.java +++ b/src/test/java/net/kemitix/node/NodeItemTest.java @@ -901,4 +901,25 @@ public class NodeItemTest { assertThat(child.getParent()).isSameAs(node); assertThat(node.getChildren()).containsExactly(child); } + + @Test + public void canGetChildWhenFound() { + //given + node = new NodeItem<>("data"); + Node child = new NodeItem<>("child data", "child name", node); + //when + Node found = node.getChild("child data"); + //then + assertThat(found).isSameAs(child); + } + + @Test + public void canGetChildWhenNotFound() { + //given + exception.expect(NodeException.class); + exception.expectMessage("Child not found"); + node = new NodeItem<>("data"); + //when + node.getChild("child data"); + } } From a31c4501ac73d2401879f0b5fe2012c668293d8c Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Tue, 24 May 2016 15:05:53 +0100 Subject: [PATCH 32/42] Fix typos --- src/main/java/net/kemitix/node/Node.java | 1 + src/main/java/net/kemitix/node/NodeItem.java | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/net/kemitix/node/Node.java b/src/main/java/net/kemitix/node/Node.java index e2f074f..19cc60a 100644 --- a/src/main/java/net/kemitix/node/Node.java +++ b/src/main/java/net/kemitix/node/Node.java @@ -92,6 +92,7 @@ public interface Node { * Creates a new named node and adds it as a child of the current node. * * @param child the child node's data + * @param name the name * * @return the new child node */ diff --git a/src/main/java/net/kemitix/node/NodeItem.java b/src/main/java/net/kemitix/node/NodeItem.java index acd883d..ddb402a 100644 --- a/src/main/java/net/kemitix/node/NodeItem.java +++ b/src/main/java/net/kemitix/node/NodeItem.java @@ -190,6 +190,7 @@ public class NodeItem implements Node { } @Override + @SuppressWarnings("hiddenfield") public Node createChild(final T child, final String name) { Node node = createChild(child); node.setName(name); @@ -249,8 +250,9 @@ public class NodeItem implements Node { @Override public Node getChild(final T child) { Optional> optional = findChild(child); - if (optional.isPresent()) + if (optional.isPresent()) { return optional.get(); + } throw new NodeException("Child not found"); } From 080d995ba0995788b5560b4df234711e928df136 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Tue, 24 May 2016 16:36:10 +0100 Subject: [PATCH 33/42] NodeItemTest: more tests --- .../java/net/kemitix/node/NodeItemTest.java | 55 ++++++++++++++++++- 1 file changed, 53 insertions(+), 2 deletions(-) diff --git a/src/test/java/net/kemitix/node/NodeItemTest.java b/src/test/java/net/kemitix/node/NodeItemTest.java index 251d690..da5df99 100644 --- a/src/test/java/net/kemitix/node/NodeItemTest.java +++ b/src/test/java/net/kemitix/node/NodeItemTest.java @@ -717,7 +717,8 @@ public class NodeItemTest { node.insertInPath(grandchild, "child"); // as root/child/grandchild //then assertThat(node.getChildByName("child")).as("child").isSameAs(child); - assertThat(node.getChildByName("child").getChildByName("grandchild")).as( + assertThat( + node.getChildByName("child").getChildByName("grandchild")).as( "grandchild").isSameAs(grandchild); } @@ -733,7 +734,8 @@ public class NodeItemTest { node.insertInPath(child); //then assertThat(node.getChildByName("child")).as("child").isSameAs(child); - assertThat(node.getChildByName("child").getChildByName("grandchild")).as( + assertThat( + node.getChildByName("child").getChildByName("grandchild")).as( "grandchild").isSameAs(grandchild); } @@ -745,6 +747,17 @@ public class NodeItemTest { node.removeParent(); } + @Test + public void removingParentFromNodeWithParentRemovesParent() { + //given + node = new NodeItem<>(null); + NodeItem child = new NodeItem<>(null, node); + //when + child.removeParent(); + //then + assertThat(child.getParent()).isNull(); + } + @Test public void placeNodeInTreeWhereNonEmptyNodeWithSameNameExists() { //given @@ -922,4 +935,42 @@ public class NodeItemTest { //when node.getChild("child data"); } + + @Test + @SuppressWarnings("unchecked") + public void constructorWithNameSupplierAndParentBeChildOfParent() { + //given + node = new NodeItem<>(null); + //when + NodeItem child = new NodeItem<>(null, Node::getData, node); + //then + assertThat(child.getParent()).isSameAs(node); + assertThat(node.getChildren()).containsExactly(child); + } + + @Test + @SuppressWarnings("unchecked") + public void removeParentCopiesRootNameSupplier() { + //given + node = new NodeItem<>("root data", n -> "root supplier"); + val child = new NodeItem<>("child data", node); + assertThat(child.getName()).isEqualTo("root supplier"); + //when + child.removeParent(); + //then + assertThat(child.getName()).isEqualTo("root supplier"); + } + + @Test + @SuppressWarnings("unchecked") + public void removeParentDoesNotReplaceLocalNameSupplier() { + //given + node = new NodeItem<>("root data", n -> "root supplier"); + val child = new NodeItem<>("child data", n -> "local supplier", node); + assertThat(child.getName()).isEqualTo("local supplier"); + //when + child.removeParent(); + //then + assertThat(child.getName()).isEqualTo("local supplier"); + } } From 7de6c5ad4980001b0e39bb2f465b80563bf8461e Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Tue, 24 May 2016 22:03:14 +0100 Subject: [PATCH 34/42] Node.getName(): generated each time called when using a name supplier Previously the name was set when the node was created and only a call to setName() would have changed it. isNamed() uses the generated name, so this may change if the generated name is even null or the empty string. --- src/main/java/net/kemitix/node/Node.java | 6 +++-- src/main/java/net/kemitix/node/NodeItem.java | 8 +++--- .../java/net/kemitix/node/NodeItemTest.java | 26 +++++++++++++++++++ 3 files changed, 35 insertions(+), 5 deletions(-) diff --git a/src/main/java/net/kemitix/node/Node.java b/src/main/java/net/kemitix/node/Node.java index 19cc60a..cba5129 100644 --- a/src/main/java/net/kemitix/node/Node.java +++ b/src/main/java/net/kemitix/node/Node.java @@ -14,7 +14,8 @@ import java.util.Set; public interface Node { /** - * Fetch the name of the node. + * Fetch the name of the node. Where a node's name is determined via a name + * supplier, the name may be regenerated each time this method is called. * * @return the name of the node */ @@ -193,7 +194,8 @@ public interface Node { String drawTree(int depth); /** - * Returns true if the Node has a name. + * Returns true if the Node has a name. Where a name supplier is used, the + * generated name is used. * * @return true if the node has a name */ diff --git a/src/main/java/net/kemitix/node/NodeItem.java b/src/main/java/net/kemitix/node/NodeItem.java index ddb402a..a6c7dea 100644 --- a/src/main/java/net/kemitix/node/NodeItem.java +++ b/src/main/java/net/kemitix/node/NodeItem.java @@ -57,7 +57,6 @@ public class NodeItem implements Node { final T data, final Function, String> nameSupplier) { this(data); this.nameSupplier = nameSupplier; - name = generateName(); } /** @@ -69,7 +68,6 @@ public class NodeItem implements Node { public NodeItem(final T data, final Node parent) { this.data = data; setParent(parent); - this.name = generateName(); } /** @@ -114,6 +112,9 @@ public class NodeItem implements Node { @Override public String getName() { + if (name == null) { + return generateName(); + } return name; } @@ -386,7 +387,8 @@ public class NodeItem implements Node { @Override public boolean isNamed() { - return name != null && name.length() > 0; + String currentName = getName(); + return currentName != null && currentName.length() > 0; } @Override diff --git a/src/test/java/net/kemitix/node/NodeItemTest.java b/src/test/java/net/kemitix/node/NodeItemTest.java index da5df99..866ac24 100644 --- a/src/test/java/net/kemitix/node/NodeItemTest.java +++ b/src/test/java/net/kemitix/node/NodeItemTest.java @@ -13,6 +13,7 @@ import java.time.format.DateTimeFormatter; import java.util.Arrays; import java.util.Collections; import java.util.Optional; +import java.util.concurrent.atomic.AtomicInteger; /** * Test for {@link NodeItem}. @@ -973,4 +974,29 @@ public class NodeItemTest { //then assertThat(child.getName()).isEqualTo("local supplier"); } + + @Test + public void getNameWithNameSupplierIsRecalculatedEachCall() { + val counter = new AtomicInteger(0); + node = new NodeItem<>(null, + n -> Integer.toString(counter.incrementAndGet())); + //then + assertThat(node.getName()).isNotEqualTo(node.getName()); + } + + @Test + public void isNamedWithNameSupplierIsRecalculatedEachCall() { + val counter = new AtomicInteger(0); + node = new NodeItem<>(null, n -> { + // alternate between even numbers and nulls: null, 2, null, 4, null + final int i = counter.incrementAndGet(); + if (i % 2 == 0) { + return Integer.toString(i); + } + return null; + }); + //then + assertThat(node.isNamed()).isFalse(); + assertThat(node.isNamed()).isTrue(); + } } From 96ac0f720f62f60007681b801bffbacd338ccd27 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Tue, 24 May 2016 22:04:00 +0100 Subject: [PATCH 35/42] Node.setName(): ensure that setting name to null enables the name supplier --- src/main/java/net/kemitix/node/Node.java | 3 ++- src/test/java/net/kemitix/node/NodeItemTest.java | 12 ++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/main/java/net/kemitix/node/Node.java b/src/main/java/net/kemitix/node/Node.java index cba5129..9d82efd 100644 --- a/src/main/java/net/kemitix/node/Node.java +++ b/src/main/java/net/kemitix/node/Node.java @@ -22,7 +22,8 @@ public interface Node { String getName(); /** - * Sets the explicit name for a node. + * Sets the explicit name for a node. Setting the name to null will clear + * the name and revert to the parent's name supplier. * * @param name the new name */ diff --git a/src/test/java/net/kemitix/node/NodeItemTest.java b/src/test/java/net/kemitix/node/NodeItemTest.java index 866ac24..dde300a 100644 --- a/src/test/java/net/kemitix/node/NodeItemTest.java +++ b/src/test/java/net/kemitix/node/NodeItemTest.java @@ -975,6 +975,18 @@ public class NodeItemTest { assertThat(child.getName()).isEqualTo("local supplier"); } + @Test + public void setNameToNullRevertsToParentNameSupplier() { + //given + node = new NodeItem<>(null, n -> "root supplier"); + val child = new NodeItem(null, "child name", node); + assertThat(child.getName()).isEqualTo("child name"); + //when + child.setName(null); + //then + assertThat(child.getName()).isEqualTo("root supplier"); + } + @Test public void getNameWithNameSupplierIsRecalculatedEachCall() { val counter = new AtomicInteger(0); From eebd2936338d509ea30f320b3bf8e4b602e14a9f Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Tue, 24 May 2016 22:16:35 +0100 Subject: [PATCH 36/42] NodeItem.insertInPath(): use setData() to when target is already in tree Where an empty node is already found in the tree where we want to insert a node, use the setData() method to place the data. This replaces the previous process of attempting to swapping in the new node. --- src/main/java/net/kemitix/node/NodeItem.java | 4 +--- src/test/java/net/kemitix/node/NodeItemTest.java | 12 +++++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/main/java/net/kemitix/node/NodeItem.java b/src/main/java/net/kemitix/node/NodeItem.java index a6c7dea..b724048 100644 --- a/src/main/java/net/kemitix/node/NodeItem.java +++ b/src/main/java/net/kemitix/node/NodeItem.java @@ -333,9 +333,7 @@ public class NodeItem implements Node { throw new NodeException( "A non-empty node with that name already exists here"); } else { - existing.getChildren().forEach(nodeItem::addChild); - existing.removeParent(); - addChild(nodeItem); + existing.setData(nodeItem.getData()); } return; } diff --git a/src/test/java/net/kemitix/node/NodeItemTest.java b/src/test/java/net/kemitix/node/NodeItemTest.java index dde300a..664ec82 100644 --- a/src/test/java/net/kemitix/node/NodeItemTest.java +++ b/src/test/java/net/kemitix/node/NodeItemTest.java @@ -734,7 +734,9 @@ public class NodeItemTest { node.insertInPath(grandchild, "child"); node.insertInPath(child); //then - assertThat(node.getChildByName("child")).as("child").isSameAs(child); + assertThat(node.getChildByName("child").getData()).as("data in tree") + .isSameAs( + "child data"); assertThat( node.getChildByName("child").getChildByName("grandchild")).as( "grandchild").isSameAs(grandchild); @@ -798,14 +800,14 @@ public class NodeItemTest { child.addChild(target); final NodeItem addMe = new NodeItem<>("I'm new", "target"); assertThat(addMe.getParent()).isNull(); + assertThat(child.getChildByName("target").isEmpty()).as( + "target starts empty").isTrue(); //when // addMe should replace target as the sole descendant of child node.insertInPath(addMe, "child"); //then - assertThat(child.getChildren()).as("child only contains new node") - .containsOnly(addMe); - assertThat(target.getParent()).as("old node is removed from tree") - .isNull(); + assertThat(child.getChildByName("target").getData()).as( + "target now contains data").isEqualTo("I'm new"); } @Test From d82b0290a1e91e5735e4af3c585e6cb7e392eabb Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Tue, 24 May 2016 22:35:24 +0100 Subject: [PATCH 37/42] NodeItemTest: demonstrate using dynamic name supplier to generate node paths --- .../java/net/kemitix/node/NodeItemTest.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/test/java/net/kemitix/node/NodeItemTest.java b/src/test/java/net/kemitix/node/NodeItemTest.java index dde300a..a8c95af 100644 --- a/src/test/java/net/kemitix/node/NodeItemTest.java +++ b/src/test/java/net/kemitix/node/NodeItemTest.java @@ -14,6 +14,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.Optional; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Function; /** * Test for {@link NodeItem}. @@ -1011,4 +1012,21 @@ public class NodeItemTest { assertThat(node.isNamed()).isFalse(); assertThat(node.isNamed()).isTrue(); } + + @Test + public void canUseNameSupplierToBuildFullPath() { + //given + final Function, String> pathNameSupplier = node -> { + Node parent = node.getParent(); + if (parent == null) { + return ""; + } + return parent.getName() + "/" + node.getData(); + }; + node = new NodeItem<>(null, pathNameSupplier); + val child = new NodeItem("child", node); + val grandchild = new NodeItem("grandchild", child); + //then + assertThat(grandchild.getName()).isEqualTo("/child/grandchild"); + } } From c4ab0010d10c2375b8de54b90203794fd1c9bac1 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Tue, 24 May 2016 23:30:25 +0100 Subject: [PATCH 38/42] .idea: upgrade findbugs and assertion verification --- .idea/inspectionProfiles/Project_Default.xml | 3 +- node.iml | 161 +++++++++++++++++++ 2 files changed, 163 insertions(+), 1 deletion(-) diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml index 7df71eb..31e175c 100644 --- a/.idea/inspectionProfiles/Project_Default.xml +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -9,6 +9,7 @@ + @@ -50,7 +51,7 @@ - diff --git a/node.iml b/node.iml index b2d3a3d..bb89d96 100644 --- a/node.iml +++ b/node.iml @@ -16,4 +16,165 @@ + + + \ No newline at end of file From 7398107dcc42818024d59fda276457b1ee099e80 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Tue, 24 May 2016 23:31:26 +0100 Subject: [PATCH 39/42] .idea/findbugs-idea.xml: added --- .idea/findbugs-idea.xml | 213 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 213 insertions(+) create mode 100644 .idea/findbugs-idea.xml diff --git a/.idea/findbugs-idea.xml b/.idea/findbugs-idea.xml new file mode 100644 index 0000000..9548eb2 --- /dev/null +++ b/.idea/findbugs-idea.xml @@ -0,0 +1,213 @@ + + + + + + + + + + \ No newline at end of file From fbb2a206ba24769452b6a0d1dc04be2e616db4a4 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Tue, 24 May 2016 23:31:54 +0100 Subject: [PATCH 40/42] .idea/checkstyle-idea.xml: added --- .idea/checkstyle-idea.xml | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .idea/checkstyle-idea.xml diff --git a/.idea/checkstyle-idea.xml b/.idea/checkstyle-idea.xml new file mode 100644 index 0000000..9d5b48d --- /dev/null +++ b/.idea/checkstyle-idea.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file From 6a19dbda10f3f31e28102973e5b52eb72608b837 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Wed, 25 May 2016 11:44:50 +0100 Subject: [PATCH 41/42] pom.xml: version set to 0.2.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index c488538..248d924 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 node - 0.2.0-SNAPSHOT + 0.2.0 jar Node From 9348fcdf8fcbdbecd7f297793ad350c13b59c0ce Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Wed, 25 May 2016 11:44:50 +0100 Subject: [PATCH 42/42] CHANGELOG --- CHANGELOG | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index ea41f65..6b8d986 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,18 @@ CHANGELOG ========= +0.2.0 +------ + +* Demo of full-path node name +* insertInPath() uses setData() +* Dynamic node names +* New tests following mutation testing +* Tidy the Node interface +* Named nodes +* Update code style +* Drop lombok in production (still using it in test) + 0.1.0 ------