From 39833edf27c11be1d5f61f0faf97753157ac8cb5 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Thu, 26 May 2016 13:52:45 +0100 Subject: [PATCH 01/41] pom.xml: version set to 0.4.0-SNAPSHOT --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 1906316..b657ec4 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 node - 0.3.0 + 0.4.0-SNAPSHOT jar Node From d04d1741c9d6b429b4a8187614f26f25aac0f415 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Fri, 3 Jun 2016 16:48:49 +0100 Subject: [PATCH 02/41] Report code coverage to coveralls.io --- .travis.yml | 2 ++ README.md | 3 +++ pom.xml | 11 +++++++++++ 3 files changed, 16 insertions(+) diff --git a/.travis.yml b/.travis.yml index 968917c..8d04cab 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,3 +1,5 @@ language: java jdk: - oraclejdk8 +after_success: + - mvn clean test jacoco:report coveralls:report diff --git a/README.md b/README.md index b80d2ec..5a78753 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,6 @@ +[![Build Status](https://travis-ci.org/kemitix/node.svg?branch=develop)](https://travis-ci.org/kemitix/node) +[![Coverage Status](https://coveralls.io/repos/github/kemitix/node/badge.svg?branch=develop)](https://coveralls.io/github/kemitix/node?branch=develop) + # node A parent/children data structure diff --git a/pom.xml b/pom.xml index b657ec4..dd9ddbf 100644 --- a/pom.xml +++ b/pom.xml @@ -16,6 +16,7 @@ 3.4.1 + 4.2.0 @@ -33,6 +34,16 @@ 2016 + + + + org.eluder.coveralls + coveralls-maven-plugin + ${coveralls-maven-plugin.version} + + + + org.projectlombok From 8bb8c40368632d6d3116aefd02a3cc03687908b6 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Fri, 10 Jun 2016 10:33:53 +0100 Subject: [PATCH 03/41] Update .gitignore to use template --- .gitignore | 49 ++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 40 insertions(+), 9 deletions(-) diff --git a/.gitignore b/.gitignore index 25a8515..4ea327c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,3 @@ -/target -/nbproject -*.class - -# Mobile Tools for Java (J2ME) -.mtj.tmp/ - # Package Files # *.jar *.war @@ -13,5 +6,43 @@ # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* -/.idea/libraries/ -/.idea/workspace.xml +# maven build outputs +target/ + +# netbeans legacy +nbproject/ +nbactions.xml + +# eclipse legacy +.project + +# intellij +.idea/libraries/ +.idea/workspace.xml +.idea/uiDesigner.xml +.idea/compiler.xml +.idea/misc.xml +.idea/checkstyle.xml +.idea/artifacts/ +.idea/dataSources* +.idea/tasks.xml +.idea/dictionaries/ +.idea/shelf/ +.idea/dynamic.xml +.idea/sqlDataSources.xml +.idea/gradle.xml +.idea/mongoSettings.xml + +# Spring +spring.log +logs/ +/application.properties +/bootstrap.properties + +# Composer-style +vendor + +# Git and temp files +*.orig +*.patch +*~ From 680432614ea65159a2c0dc58d8104dd97fd55bca Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Fri, 10 Jun 2016 10:33:53 +0100 Subject: [PATCH 04/41] Remove ignored files --- .idea/compiler.xml | 33 --------------------------------- .idea/misc.xml | 42 ------------------------------------------ 2 files changed, 75 deletions(-) delete mode 100644 .idea/compiler.xml delete mode 100644 .idea/misc.xml diff --git a/.idea/compiler.xml b/.idea/compiler.xml deleted file mode 100644 index 6dda909..0000000 --- a/.idea/compiler.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index 0abd9e0..0000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - 1.8 - - - - - - - - \ No newline at end of file From 8bcce3418d007f4837dd92f4f469d18deb7c6ab9 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Sun, 21 Aug 2016 10:48:52 +0100 Subject: [PATCH 05/41] {.idea|*.iml}: remove intellij project files --- .idea/checkstyle-idea.xml | 10 - .idea/codeStyleSettings.xml | 104 --------- .idea/encodings.xml | 7 - .idea/findbugs-idea.xml | 213 ------------------ .idea/inspectionProfiles/Project_Default.xml | 60 ----- .../inspectionProfiles/profiles_settings.xml | 7 - .idea/modules.xml | 8 - .idea/vcs.xml | 6 - node.iml | 180 --------------- 9 files changed, 595 deletions(-) delete mode 100644 .idea/checkstyle-idea.xml delete mode 100644 .idea/codeStyleSettings.xml delete mode 100644 .idea/encodings.xml delete mode 100644 .idea/findbugs-idea.xml delete mode 100644 .idea/inspectionProfiles/Project_Default.xml delete mode 100644 .idea/inspectionProfiles/profiles_settings.xml delete mode 100644 .idea/modules.xml delete mode 100644 .idea/vcs.xml delete mode 100644 node.iml diff --git a/.idea/checkstyle-idea.xml b/.idea/checkstyle-idea.xml deleted file mode 100644 index 9d5b48d..0000000 --- a/.idea/checkstyle-idea.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/codeStyleSettings.xml b/.idea/codeStyleSettings.xml deleted file mode 100644 index bcea045..0000000 --- a/.idea/codeStyleSettings.xml +++ /dev/null @@ -1,104 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml deleted file mode 100644 index c0bce70..0000000 --- a/.idea/encodings.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/.idea/findbugs-idea.xml b/.idea/findbugs-idea.xml deleted file mode 100644 index 9548eb2..0000000 --- a/.idea/findbugs-idea.xml +++ /dev/null @@ -1,213 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml deleted file mode 100644 index 31e175c..0000000 --- a/.idea/inspectionProfiles/Project_Default.xml +++ /dev/null @@ -1,60 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml deleted file mode 100644 index 3b31283..0000000 --- a/.idea/inspectionProfiles/profiles_settings.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index 935c7b9..0000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 35eb1dd..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/node.iml b/node.iml deleted file mode 100644 index bb89d96..0000000 --- a/node.iml +++ /dev/null @@ -1,180 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file From bc5320b5736826e26c396fdcc295b989c9955e90 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Sun, 21 Aug 2016 10:49:05 +0100 Subject: [PATCH 06/41] .gitignore: ignore intellij project files --- .gitignore | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/.gitignore b/.gitignore index 4ea327c..b311b8d 100644 --- a/.gitignore +++ b/.gitignore @@ -17,21 +17,8 @@ nbactions.xml .project # intellij -.idea/libraries/ -.idea/workspace.xml -.idea/uiDesigner.xml -.idea/compiler.xml -.idea/misc.xml -.idea/checkstyle.xml -.idea/artifacts/ -.idea/dataSources* -.idea/tasks.xml -.idea/dictionaries/ -.idea/shelf/ -.idea/dynamic.xml -.idea/sqlDataSources.xml -.idea/gradle.xml -.idea/mongoSettings.xml +.idea/ +*.iml # Spring spring.log From f945af160a6ac2b688c70e86d079bc727195e055 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Sun, 21 Aug 2016 11:29:42 +0100 Subject: [PATCH 07/41] NodeItem: deprecate dynamic node names With the aim of moving towards immutable objects, node shouldn't have dynamic names. I've not found a use for them in my own projects. --- src/main/java/net/kemitix/node/NodeItem.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/java/net/kemitix/node/NodeItem.java b/src/main/java/net/kemitix/node/NodeItem.java index ec71f11..899bf5c 100644 --- a/src/main/java/net/kemitix/node/NodeItem.java +++ b/src/main/java/net/kemitix/node/NodeItem.java @@ -52,7 +52,10 @@ public class NodeItem implements Node { * * @param data the data or null * @param nameSupplier the name supplier function + * + * @deprecated dynamic names don't work on immutable objects */ + @Deprecated public NodeItem( final T data, final Function, String> nameSupplier) { this(data); @@ -89,7 +92,10 @@ public class NodeItem implements Node { * @param data the data or null * @param nameSupplier the name supplier function * @param parent the parent node + * + * @deprecated dynamic names don't work on immutable objects */ + @Deprecated public NodeItem( final T data, final Function, String> nameSupplier, final Node parent) { From 39721e77c1636441c473e8f197e81d3d4c6c8f29 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Sun, 21 Aug 2016 11:36:46 +0100 Subject: [PATCH 08/41] pom.xml: add dependency on net.trajano.commons to utility class testing --- pom.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pom.xml b/pom.xml index dd9ddbf..3071a0d 100644 --- a/pom.xml +++ b/pom.xml @@ -17,6 +17,7 @@ 3.4.1 4.2.0 + 2.1.0 @@ -69,5 +70,11 @@ ${assertj.version} test + + net.trajano.commons + commons-testing + ${trajano-commons-testing.version} + test + From 51e8194db76037a10619f3c493b1064ea1bd75f7 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Sun, 21 Aug 2016 11:37:15 +0100 Subject: [PATCH 09/41] Nodes: add utility class to create Node instances --- src/main/java/net/kemitix/node/Nodes.java | 66 +++++++++++++++++++ src/test/java/net/kemitix/node/NodesTest.java | 62 +++++++++++++++++ 2 files changed, 128 insertions(+) create mode 100644 src/main/java/net/kemitix/node/Nodes.java create mode 100644 src/test/java/net/kemitix/node/NodesTest.java diff --git a/src/main/java/net/kemitix/node/Nodes.java b/src/main/java/net/kemitix/node/Nodes.java new file mode 100644 index 0000000..24dc50e --- /dev/null +++ b/src/main/java/net/kemitix/node/Nodes.java @@ -0,0 +1,66 @@ +package net.kemitix.node; + +/** + * Utility class for {@link Node} items. + * + * @author pcampbell + */ +public final class Nodes { + + private Nodes() { + } + + /** + * Creates a new unnamed root node. + * + * @param data the data the node will contain + * @param the type of the data + * + * @return the new node + */ + public static Node unnamedRoot(final T data) { + return new NodeItem<>(data); + } + + /** + * Creates a new named root node. + * + * @param data the data the node will contain + * @param name the name of the node + * @param the type of the data + * + * @return the new node + */ + public static Node namedRoot(final T data, final String name) { + return new NodeItem<>(data, name); + } + + /** + * Creates a new unnamed child node. + * + * @param data the data the node will contain + * @param parent the parent of the node + * @param the type of the data + * + * @return the new node + */ + public static Node unnamedChild(final T data, final Node parent) { + return new NodeItem<>(data, parent); + } + + /** + * Creates a new named child node. + * + * @param data the data the node will contain + * @param name the name of the node + * @param parent the parent of the node + * @param the type of the data + * + * @return the new node + */ + public static Node namedChild( + final T data, final String name, final Node parent) { + return new NodeItem<>(data, name, parent); + } + +} diff --git a/src/test/java/net/kemitix/node/NodesTest.java b/src/test/java/net/kemitix/node/NodesTest.java new file mode 100644 index 0000000..ace4f79 --- /dev/null +++ b/src/test/java/net/kemitix/node/NodesTest.java @@ -0,0 +1,62 @@ +package net.kemitix.node; + +import lombok.val; +import org.assertj.core.api.SoftAssertions; +import org.junit.Test; + +import static net.trajano.commons.testing.UtilityClassTestUtil + .assertUtilityClassWellDefined; + +/** + * Tests for {@link Nodes}. + * + * @author pcampbell + */ +public class NodesTest { + + @Test + public void shouldBeValidUtilityClass() throws Exception { + assertUtilityClassWellDefined(Nodes.class); + } + + @Test + public void shouldCreateUnnamedRoot() throws Exception { + val node = Nodes.unnamedRoot("data"); + SoftAssertions softly = new SoftAssertions(); + softly.assertThat(node.getData()).contains("data"); + softly.assertThat(node.getName()).isNull(); + softly.assertAll(); + } + + @Test + public void shouldCreateNamedRoot() throws Exception { + val node = Nodes.namedRoot("data", "name"); + SoftAssertions softly = new SoftAssertions(); + softly.assertThat(node.getData()).contains("data"); + softly.assertThat(node.getName()).isEqualTo("name"); + softly.assertAll(); + } + + @Test + public void shouldCreateUnnamedChild() throws Exception { + val parent = Nodes.unnamedRoot("root"); + val node = Nodes.unnamedChild("data", parent); + SoftAssertions softly = new SoftAssertions(); + softly.assertThat(node.getData()).contains("data"); + softly.assertThat(node.getName()).isNull(); + softly.assertThat(node.getParent()).contains(parent); + softly.assertAll(); + } + + @Test + public void shouldCreateNamedChild() throws Exception { + val parent = Nodes.unnamedRoot("root"); + val node = Nodes.namedChild("data", "child", parent); + SoftAssertions softly = new SoftAssertions(); + softly.assertThat(node.getData()).contains("data"); + softly.assertThat(node.getName()).isEqualTo("child"); + softly.assertThat(node.getParent()).contains(parent); + softly.assertAll(); + } + +} From e9b43cb73c87a628b1a06eba5b67a96b8c9557ae Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Sun, 21 Aug 2016 12:04:24 +0100 Subject: [PATCH 10/41] [BREAKING] NodeItem: require instantiation using Nodes This breaks the existing API where NodeItem could be instantiated directly. --- src/main/java/net/kemitix/node/NodeItem.java | 8 +- .../java/net/kemitix/node/NodeItemTest.java | 225 +++++++++--------- 2 files changed, 117 insertions(+), 116 deletions(-) diff --git a/src/main/java/net/kemitix/node/NodeItem.java b/src/main/java/net/kemitix/node/NodeItem.java index 899bf5c..f54b6eb 100644 --- a/src/main/java/net/kemitix/node/NodeItem.java +++ b/src/main/java/net/kemitix/node/NodeItem.java @@ -32,7 +32,7 @@ public class NodeItem implements Node { * @param data the data or null * @param name the name */ - public NodeItem(final T data, final String name) { + NodeItem(final T data, final String name) { this(data); this.name = name; } @@ -42,7 +42,7 @@ public class NodeItem implements Node { * * @param data the data or null */ - public NodeItem(final T data) { + NodeItem(final T data) { this.data = data; this.nameSupplier = (n) -> null; } @@ -68,7 +68,7 @@ public class NodeItem implements Node { * @param data the data or null * @param parent the parent node */ - public NodeItem(final T data, final Node parent) { + NodeItem(final T data, final Node parent) { this.data = data; setParent(parent); } @@ -80,7 +80,7 @@ public class NodeItem implements Node { * @param name the name * @param parent the parent node */ - public NodeItem(final T data, final String name, final Node parent) { + NodeItem(final T data, final String name, final Node parent) { this.data = data; this.name = name; setParent(parent); diff --git a/src/test/java/net/kemitix/node/NodeItemTest.java b/src/test/java/net/kemitix/node/NodeItemTest.java index 700b053..87b5a69 100644 --- a/src/test/java/net/kemitix/node/NodeItemTest.java +++ b/src/test/java/net/kemitix/node/NodeItemTest.java @@ -33,7 +33,7 @@ public class NodeItemTest { //given val data = "this node data"; //when - node = new NodeItem<>(data); + node = Nodes.unnamedRoot(data); //then assertThat(node.getData()).as("can get the data from a node"). contains(data); @@ -42,7 +42,7 @@ public class NodeItemTest { @Test public void canCreateAnEmptyAndUnnamedNode() { //when - node = new NodeItem<>(null); + node = Nodes.unnamedRoot(null); //then SoftAssertions softly = new SoftAssertions(); softly.assertThat(node.isEmpty()).as("node is empty").isTrue(); @@ -64,7 +64,7 @@ public class NodeItemTest { @Test public void canSetName() { //given - node = new NodeItem<>(null); + node = Nodes.unnamedRoot(null); //when node.setName("named"); //then @@ -77,7 +77,7 @@ public class NodeItemTest { @Test public void shouldHaveNullForDefaultParent() { //given - node = new NodeItem<>("data"); + node = Nodes.unnamedRoot("data"); //then assertThat(node.getParent()).as( "node created without a parent has no parent").isEmpty(); @@ -89,9 +89,9 @@ public class NodeItemTest { @Test public void shouldReturnNodeParent() { //given - val parent = new NodeItem("parent"); + val parent = Nodes.unnamedRoot("parent"); //when - node = new NodeItem<>("subject", parent); + node = Nodes.unnamedChild("subject", parent); //then assertThat(node.getParent()).as( "node created with a parent can return the parent") @@ -105,8 +105,8 @@ public class NodeItemTest { @Test public void setParentShouldThrowNodeExceptionWhenParentIsAChild() { //given - node = new NodeItem<>("subject"); - val child = new NodeItem("child", node); + node = Nodes.unnamedRoot("subject"); + val child = Nodes.unnamedChild("child", node); exception.expect(NodeException.class); exception.expectMessage("Parent is a descendant"); //when @@ -121,9 +121,9 @@ public class NodeItemTest { @SuppressWarnings("unchecked") public void shouldAddNewNodeAsChildToParent() { //given - val parent = new NodeItem("parent"); + val parent = Nodes.unnamedRoot("parent"); //when - node = new NodeItem<>("subject", parent); + node = Nodes.unnamedChild("subject", parent); //then assertThat(parent.getChildren()).as( "when a node is created with a parent, the parent has the new" @@ -136,8 +136,8 @@ public class NodeItemTest { @Test public void shouldReturnSetParent() { //given - node = new NodeItem<>("subject"); - val parent = new NodeItem("parent"); + node = Nodes.unnamedRoot("subject"); + val parent = Nodes.unnamedRoot("parent"); //when node.setParent(parent); //then @@ -152,7 +152,7 @@ public class NodeItemTest { @Test public void shouldThrowNPEWhenSetParentNull() { //given - node = new NodeItem<>("subject"); + node = Nodes.unnamedRoot("subject"); exception.expect(NullPointerException.class); exception.expectMessage("parent"); //when @@ -166,7 +166,7 @@ public class NodeItemTest { @Test public void setParentShouldThrowNodeExceptionWhenParentIsSelf() { //given - node = new NodeItem<>("subject"); + node = Nodes.unnamedRoot("subject"); exception.expect(NodeException.class); exception.expectMessage("Parent is a descendant"); //when @@ -180,9 +180,9 @@ public class NodeItemTest { @Test public void shouldUpdateOldParentWhenNodeSetToNewParent() { //given - node = new NodeItem<>("subject"); + node = Nodes.unnamedRoot("subject"); val child = node.createChild("child"); - val newParent = new NodeItem("newParent"); + val newParent = Nodes.unnamedRoot("newParent"); //when child.setParent(newParent); //then @@ -201,9 +201,9 @@ public class NodeItemTest { @Test public void shouldRemoveNodeFromOldParentWhenAddedAsChildToNewParent() { //given - node = new NodeItem<>("subject"); + node = Nodes.unnamedRoot("subject"); val child = node.createChild("child"); - val newParent = new NodeItem("newParent"); + val newParent = Nodes.unnamedRoot("newParent"); //when newParent.addChild(child); //then @@ -223,7 +223,7 @@ public class NodeItemTest { @Test public void shouldThrowNPEWhenAddingNullAsChild() { //given - node = new NodeItem<>("subject"); + node = Nodes.unnamedRoot("subject"); exception.expect(NullPointerException.class); exception.expectMessage("child"); //when @@ -237,8 +237,8 @@ public class NodeItemTest { @SuppressWarnings("unchecked") public void shouldReturnAddedChild() { //given - node = new NodeItem<>("subject"); - val child = new NodeItem("child"); + node = Nodes.unnamedRoot("subject"); + val child = Nodes.unnamedRoot("child"); //when node.addChild(child); //then @@ -253,7 +253,7 @@ public class NodeItemTest { @Test public void addChildShouldThrowNodeExceptionWhenAddingANodeAsOwnChild() { //given - node = new NodeItem<>("subject"); + node = Nodes.unnamedRoot("subject"); exception.expect(NodeException.class); exception.expectMessage("Child is an ancestor"); //then @@ -266,7 +266,7 @@ public class NodeItemTest { @Test public void addChildShouldThrowNodeExceptionWhenAddingSelfAsChild() { //given - node = new NodeItem<>("subject"); + node = Nodes.unnamedRoot("subject"); exception.expect(NodeException.class); exception.expectMessage("Child is an ancestor"); //when @@ -280,8 +280,8 @@ public class NodeItemTest { @Test public void addChildShouldThrowNodeExceptionWhenChildIsParent() { //given - val parent = new NodeItem("parent"); - node = new NodeItem<>("subject", parent); + val parent = Nodes.unnamedRoot("parent"); + node = Nodes.unnamedChild("subject", parent); exception.expect(NodeException.class); exception.expectMessage("Child is an ancestor"); //when @@ -295,9 +295,9 @@ public class NodeItemTest { @Test public void addChildShouldThrowNodeExceptionWhenAddingGrandParentAsChild() { //given - val grandParent = new NodeItem("grandparent"); - val parent = new NodeItem("parent", grandParent); - node = new NodeItem<>("subject", parent); + val grandParent = Nodes.unnamedRoot("grandparent"); + val parent = Nodes.unnamedChild("parent", grandParent); + node = Nodes.unnamedChild("subject", parent); exception.expect(NodeException.class); exception.expectMessage("Child is an ancestor"); //when @@ -310,8 +310,8 @@ public class NodeItemTest { @Test public void shouldSetParentOnChildWhenAddedAsChild() { //given - node = new NodeItem<>("subject"); - val child = new NodeItem("child"); + node = Nodes.unnamedRoot("subject"); + val child = Nodes.unnamedRoot("child"); //when node.addChild(child); //then @@ -327,11 +327,11 @@ public class NodeItemTest { public void shouldWalkTreeToNode() { //given val grandparent = "grandparent"; - val grandParentNode = new NodeItem(grandparent); + val grandParentNode = Nodes.unnamedRoot(grandparent); val parent = "parent"; - val parentNode = new NodeItem(parent, grandParentNode); + val parentNode = Nodes.unnamedChild(parent, grandParentNode); val subject = "subject"; - node = new NodeItem<>(subject, parentNode); + node = Nodes.unnamedChild(subject, parentNode); //when val result = grandParentNode.findInPath(Arrays.asList(parent, subject)); //then @@ -352,9 +352,9 @@ public class NodeItemTest { public void shouldNotFindNonExistentChildNode() { //given val parent = "parent"; - val parentNode = new NodeItem(parent); + val parentNode = Nodes.unnamedRoot(parent); val subject = "subject"; - node = new NodeItem<>(subject, parentNode); + node = Nodes.unnamedChild(subject, parentNode); //when val result = parentNode.findInPath(Arrays.asList(subject, "no child")); //then @@ -369,7 +369,7 @@ public class NodeItemTest { @Test public void shouldThrowNEWhenWalkTreeNull() { //given - node = new NodeItem<>("subject"); + node = Nodes.unnamedRoot("subject"); exception.expect(NullPointerException.class); exception.expectMessage("path"); //when @@ -383,7 +383,7 @@ public class NodeItemTest { @Test public void shouldReturnEmptyForEmptyWalkTreePath() { //given - node = new NodeItem<>("subject"); + node = Nodes.unnamedRoot("subject"); //when val result = node.findInPath(Collections.emptyList()); //then @@ -396,7 +396,7 @@ public class NodeItemTest { @Test public void shouldCreateDescendantNodes() { //given - node = new NodeItem<>("subject"); + node = Nodes.unnamedRoot("subject"); val alphaData = "alpha"; val betaData = "beta"; val gammaData = "gamma"; @@ -445,7 +445,7 @@ public class NodeItemTest { @Test public void createDescendantLineShouldThrowNPEWhenDescendantsAreNull() { //given - node = new NodeItem<>("subject"); + node = Nodes.unnamedRoot("subject"); exception.expect(NullPointerException.class); exception.expectMessage("descendants"); //when @@ -458,7 +458,7 @@ public class NodeItemTest { @Test public void shouldChangeNothingWhenCreateDescendantEmpty() { //given - node = new NodeItem<>("subject"); + node = Nodes.unnamedRoot("subject"); //when node.createDescendantLine(Collections.emptyList()); //then @@ -473,9 +473,9 @@ public class NodeItemTest { @Test public void shouldFindExistingChildNode() { //given - node = new NodeItem<>("subject"); + node = Nodes.unnamedRoot("subject"); val childData = "child"; - val child = new NodeItem(childData, node); + val child = Nodes.unnamedChild(childData, node); //when val found = node.findOrCreateChild(childData); //then @@ -490,7 +490,7 @@ public class NodeItemTest { @Test public void shouldFindCreateNewChildNode() { //given - node = new NodeItem<>("subject"); + node = Nodes.unnamedRoot("subject"); val childData = "child"; //when val found = node.findOrCreateChild(childData); @@ -506,7 +506,7 @@ public class NodeItemTest { @Test public void findOrCreateChildShouldThrowNPEFWhenChildIsNull() { //given - node = new NodeItem<>("subject"); + node = Nodes.unnamedRoot("subject"); exception.expect(NullPointerException.class); exception.expectMessage("child"); //when @@ -519,9 +519,9 @@ public class NodeItemTest { @Test public void shouldGetChild() { //given - node = new NodeItem<>("subject"); + node = Nodes.unnamedRoot("subject"); val childData = "child"; - val child = new NodeItem(childData); + val child = Nodes.unnamedRoot(childData); node.addChild(child); //when val found = node.findChild(childData); @@ -541,7 +541,7 @@ public class NodeItemTest { @Test public void getChildShouldThrowNPEWhenThereIsNoChild() { //given - node = new NodeItem<>("data"); + node = Nodes.unnamedRoot("data"); exception.expect(NullPointerException.class); exception.expectMessage("child"); //when @@ -555,7 +555,7 @@ public class NodeItemTest { @Test public void shouldCreateChild() { //given - node = new NodeItem<>("subject"); + node = Nodes.unnamedRoot("subject"); val childData = "child"; //when val child = node.createChild(childData); @@ -580,7 +580,7 @@ public class NodeItemTest { @Test public void createChildShouldThrowNPEWhenChildIsNull() { //given - node = new NodeItem<>("subject"); + node = Nodes.unnamedRoot("subject"); exception.expect(NullPointerException.class); exception.expectMessage("child"); //when @@ -599,7 +599,7 @@ public class NodeItemTest { public void getNameShouldUseParentNameSupplier() { //given val root = new NodeItem("root", n -> n.getData().get()); - node = new NodeItem<>("child", root); + node = Nodes.unnamedChild("child", root); //then assertThat(node.getName()).isEqualTo("child"); } @@ -622,7 +622,7 @@ public class NodeItemTest { node = new NodeItem<>("root", n -> n.getData().get()); val child = new NodeItem("child", Object::toString); node.addChild(child); - val grandChild = new NodeItem<>("grandchild", child); + val grandChild = Nodes.unnamedChild("grandchild", child); //then assertThat(node.getName()).isEqualTo("root"); assertThat(child.getName()).isNotEqualTo("child"); @@ -631,8 +631,8 @@ public class NodeItemTest { @Test public void getNameShouldWorkWithoutNameSupplier() { - node = new NodeItem<>(null, "root"); - val namedchild = new NodeItem<>("named", "Alice", node); + node = Nodes.namedRoot(null, "root"); + val namedchild = Nodes.namedChild("named", "Alice", node); //then assertThat(node.getName()).isEqualTo("root"); assertThat(namedchild.getName()).isEqualTo("Alice"); @@ -640,22 +640,22 @@ public class NodeItemTest { @Test public void canCreateRootNodeWithoutData() { - node = new NodeItem<>(null, "empty"); + node = Nodes.namedRoot(null, "empty"); assertThat(node.getData()).isEmpty(); } @Test public void canCreateRootNodeWithoutDataButWithNameSupplier() { - node = new NodeItem<>(null); + node = Nodes.unnamedRoot(null); assertThat(node.getData()).isEmpty(); } @Test public void getChildNamedFindsChild() { //given - node = new NodeItem<>(null, "root"); - val alpha = new NodeItem(null, "alpha"); - val beta = new NodeItem(null, "beta"); + node = Nodes.namedRoot("root data", "root"); + val alpha = Nodes.namedRoot("alpha data", "alpha"); + val beta = Nodes.namedRoot("beta data", "beta"); node.addChild(alpha); node.addChild(beta); //when @@ -667,9 +667,9 @@ public class NodeItemTest { @Test public void getChildNamedFindsNothing() { //given - node = new NodeItem<>(null, "root"); - val alpha = new NodeItem(null, "alpha"); - val beta = new NodeItem(null, "beta"); + node = Nodes.namedRoot("root data", "root"); + val alpha = Nodes.namedRoot("alpha data", "alpha"); + val beta = Nodes.namedRoot("beta data", "beta"); node.addChild(alpha); node.addChild(beta); exception.expect(NodeException.class); @@ -681,10 +681,10 @@ public class NodeItemTest { @Test public void nodeNamesAreUniqueWithinAParent() { //given - node = new NodeItem<>(null, "root"); - val alpha = new NodeItem(null, "alpha"); + node = Nodes.namedRoot("root data", "root"); + val alpha = Nodes.namedRoot("alpha data", "alpha"); node.addChild(alpha); - val beta = new NodeItem(null, "alpha"); + val beta = Nodes.namedRoot("beta data", "alpha"); exception.expect(NodeException.class); exception.expectMessage("Node with that name already exists here"); //when @@ -694,8 +694,8 @@ public class NodeItemTest { @Test public void canPlaceNodeInTreeByPathNames() { //given - node = new NodeItem<>(null, "root"); // create a root - val four = new NodeItem("data", "four"); + node = Nodes.namedRoot("root data", "root"); // create a root + val four = Nodes.namedRoot("data", "four"); //when node.insertInPath(four, "one", "two", "three"); //then @@ -717,9 +717,9 @@ public class NodeItemTest { @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"); + node = Nodes.namedRoot(null, "root"); + val child = Nodes.namedRoot("child data", "child"); + val grandchild = Nodes.namedRoot("grandchild data", "grandchild"); //when node.insertInPath(child); // as root/child node.insertInPath(grandchild, "child"); // as root/child/grandchild @@ -734,9 +734,9 @@ public class NodeItemTest { @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"); + node = Nodes.namedRoot(null, "root"); + val child = Nodes.namedRoot("child data", "child"); + val grandchild = Nodes.namedRoot("grandchild data", "grandchild"); //when node.insertInPath(grandchild, "child"); node.insertInPath(child); @@ -752,7 +752,7 @@ public class NodeItemTest { @Test public void removingParentFromNodeWithNoParentIsNoop() { //given - node = new NodeItem<>(null); + node = Nodes.unnamedRoot(null); //when node.removeParent(); } @@ -760,8 +760,8 @@ public class NodeItemTest { @Test public void removingParentFromNodeWithParentRemovesParent() { //given - node = new NodeItem<>(null); - val child = new NodeItem(null, node); + node = Nodes.unnamedRoot(null); + val child = Nodes.unnamedChild("child data", node); //when child.removeParent(); //then @@ -775,22 +775,22 @@ public class NodeItemTest { exception.expect(NodeException.class); exception.expectMessage( "A non-empty node named 'grandchild' already exists here"); - node = new NodeItem<>(null); - val child = new NodeItem(null, "child", node); - new NodeItem<>("data", "grandchild", child); + node = Nodes.unnamedRoot(null); + val child = Nodes.namedChild("child data", "child", node); + Nodes.namedChild("data", "grandchild", child); // root -> child -> grandchild // only grandchild has data //when // attempt to add another node called 'grandchild' to 'child' - node.insertInPath(new NodeItem<>("cuckoo", "grandchild"), "child"); + node.insertInPath(Nodes.namedRoot("cuckoo", "grandchild"), "child"); } @Test @SuppressWarnings("unchecked") public void placeNodeInTreeWhenAddedNodeIsUnnamed() { //given - node = new NodeItem<>(null); - final Node newNode = new NodeItem<>(null); + node = Nodes.unnamedRoot(null); + final Node newNode = Nodes.unnamedRoot(null); //when node.insertInPath(newNode); //then @@ -801,12 +801,12 @@ public class NodeItemTest { @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 = Nodes.unnamedRoot(null); + final Node child = Nodes.namedRoot(null, "child"); + final Node target = Nodes.namedRoot(null, "target"); node.addChild(child); child.addChild(target); - final NodeItem addMe = new NodeItem<>("I'm new", "target"); + val addMe = Nodes.namedRoot("I'm new", "target"); assertThat(addMe.getParent()).isEmpty(); assertThat(child.getChildByName("target").isEmpty()).as( "target starts empty").isTrue(); @@ -823,7 +823,7 @@ public class NodeItemTest { //given exception.expect(NullPointerException.class); exception.expectMessage("name"); - node = new NodeItem<>(null); + node = Nodes.unnamedRoot(null); //when node.findChildByName(null); } @@ -831,7 +831,7 @@ public class NodeItemTest { @Test public void isNamedNull() { //given - node = new NodeItem<>(null); + node = Nodes.unnamedRoot(null); //then assertThat(node.isNamed()).isFalse(); } @@ -839,7 +839,7 @@ public class NodeItemTest { @Test public void isNamedEmpty() { //given - node = new NodeItem<>(null, ""); + node = Nodes.namedRoot(null, ""); //then assertThat(node.isNamed()).isFalse(); } @@ -847,7 +847,7 @@ public class NodeItemTest { @Test public void isNamedNamed() { //given - node = new NodeItem<>(null, "named"); + node = Nodes.namedRoot(null, "named"); //then assertThat(node.isNamed()).isTrue(); } @@ -858,7 +858,7 @@ public class NodeItemTest { // provider //given node = new NodeItem<>("data", n -> n.getData().get()); - final NodeItem child = new NodeItem<>("other", node); + val child = Nodes.unnamedChild("other", node); assertThat(node.getName()).as("initial root name").isEqualTo("data"); assertThat(child.getName()).as("initial child name").isEqualTo("other"); //when @@ -872,7 +872,7 @@ public class NodeItemTest { @SuppressWarnings("unchecked") public void removeChildRemovesTheChild() { //given - node = new NodeItem<>(null); + node = Nodes.unnamedRoot(null); Node child = node.createChild("child"); assertThat(node.getChildren()).containsExactly(child); //then @@ -885,13 +885,14 @@ public class NodeItemTest { @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); + node = Nodes.namedRoot(null, "root"); + val bob = Nodes.namedChild("bob data", "bob", node); + val alice = Nodes.namedChild("alice data", "alice", node); + Nodes.namedChild("dave data", "dave", alice); + Nodes.unnamedChild("bob's child's data", + bob); // has no name and no children so no included + val kim = Nodes.unnamedChild("kim data", node); // nameless mother + Nodes.namedChild("lucy data", "lucy", kim); //when val tree = node.drawTree(0); //then @@ -907,7 +908,7 @@ public class NodeItemTest { @Test public void canChangeNodeData() { //given - node = new NodeItem<>("initial"); + node = Nodes.unnamedRoot("initial"); //when node.setData("updated"); //then @@ -918,7 +919,7 @@ public class NodeItemTest { @SuppressWarnings("unchecked") public void canCreateNamedChild() { //given - node = new NodeItem<>(null); + node = Nodes.unnamedRoot(null); //when Node child = node.createChild("child data", "child name"); //then @@ -930,10 +931,10 @@ public class NodeItemTest { @Test public void canGetChildWhenFound() { //given - node = new NodeItem<>("data"); - Node child = new NodeItem<>("child data", "child name", node); + node = Nodes.unnamedRoot("data"); + val child = Nodes.namedChild("child data", "child name", node); //when - Node found = node.getChild("child data"); + val found = node.getChild("child data"); //then assertThat(found).isSameAs(child); } @@ -943,7 +944,7 @@ public class NodeItemTest { //given exception.expect(NodeException.class); exception.expectMessage("Child not found"); - node = new NodeItem<>("data"); + node = Nodes.unnamedRoot("data"); //when node.getChild("child data"); } @@ -952,9 +953,9 @@ public class NodeItemTest { @SuppressWarnings("unchecked") public void constructorWithNameSupplierAndParentBeChildOfParent() { //given - node = new NodeItem<>(null); + node = Nodes.unnamedRoot(null); //when - NodeItem child = new NodeItem<>(null, node); + val child = Nodes.unnamedChild("child data", node); //then assertThat(child.getParent()).contains(node); assertThat(node.getChildren()).containsExactly(child); @@ -965,7 +966,7 @@ public class NodeItemTest { public void removeParentCopiesRootNameSupplier() { //given node = new NodeItem<>("root data", n -> "root supplier"); - val child = new NodeItem<>("child data", node); + val child = Nodes.unnamedChild("child data", node); assertThat(child.getName()).isEqualTo("root supplier"); //when child.removeParent(); @@ -990,7 +991,7 @@ public class NodeItemTest { public void setNameToNullRevertsToParentNameSupplier() { //given node = new NodeItem<>(null, n -> "root supplier"); - val child = new NodeItem(null, "child name", node); + val child = Nodes.namedChild("child data", "child name", node); assertThat(child.getName()).isEqualTo("child name"); //when child.setName(null); @@ -1034,8 +1035,8 @@ public class NodeItemTest { return ""; }; node = new NodeItem<>(null, pathNameSupplier); - val child = new NodeItem("child", node); - val grandchild = new NodeItem("grandchild", child); + val child = Nodes.unnamedChild("child", node); + val grandchild = Nodes.unnamedChild("grandchild", child); //then assertThat(grandchild.getName()).isEqualTo("/child/grandchild"); } @@ -1043,8 +1044,8 @@ public class NodeItemTest { @Test public void canSafelyHandleFindChildWhenAChildHasNoData() { //given - node = new NodeItem<>(null); - new NodeItem<>(null, node); + node = Nodes.unnamedRoot(null); + Nodes.unnamedChild(null, node); //when node.findChild("data"); } From 2da1d9aa3dfd747146fa46390f780a9b2a3c1393 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Sun, 21 Aug 2016 12:27:50 +0100 Subject: [PATCH 11/41] NodeItem: add deprecated to implementation of deprecated interface method --- src/main/java/net/kemitix/node/NodeItem.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/net/kemitix/node/NodeItem.java b/src/main/java/net/kemitix/node/NodeItem.java index f54b6eb..3afbe8b 100644 --- a/src/main/java/net/kemitix/node/NodeItem.java +++ b/src/main/java/net/kemitix/node/NodeItem.java @@ -235,6 +235,7 @@ public class NodeItem implements Node { * @return the found or created child node */ @Override + @Deprecated public Node findOrCreateChild(final T child) { if (child == null) { throw new NullPointerException("child"); From 1b0b0222815a284703f1e1a79e77f35b1992f7c4 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Sun, 21 Aug 2016 12:36:40 +0100 Subject: [PATCH 12/41] NodeItem.findChild(): rewrite stream filter --- 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 3afbe8b..9e34a83 100644 --- a/src/main/java/net/kemitix/node/NodeItem.java +++ b/src/main/java/net/kemitix/node/NodeItem.java @@ -255,10 +255,10 @@ public class NodeItem implements Node { if (child == null) { throw new NullPointerException("child"); } - return children.stream() - .filter(n -> !n.isEmpty()) - .filter(n -> n.getData().get().equals(child)) - .findAny(); + return children.stream().filter(node -> { + final Optional d = node.getData(); + return d.isPresent() && d.get().equals(child); + }).findAny(); } @Override From 7a10498a31b51f78e3af567a9d5cd62070cd0b0c Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Sun, 21 Aug 2016 12:42:21 +0100 Subject: [PATCH 13/41] NodeItem: remove dynamic name support for nodes --- src/main/java/net/kemitix/node/NodeItem.java | 32 ---- .../java/net/kemitix/node/NodeItemTest.java | 168 ------------------ 2 files changed, 200 deletions(-) diff --git a/src/main/java/net/kemitix/node/NodeItem.java b/src/main/java/net/kemitix/node/NodeItem.java index 9e34a83..1a83c73 100644 --- a/src/main/java/net/kemitix/node/NodeItem.java +++ b/src/main/java/net/kemitix/node/NodeItem.java @@ -47,21 +47,6 @@ public class NodeItem implements Node { this.nameSupplier = (n) -> null; } - /** - * Creates root node with a name supplier. - * - * @param data the data or null - * @param nameSupplier the name supplier function - * - * @deprecated dynamic names don't work on immutable objects - */ - @Deprecated - public NodeItem( - final T data, final Function, String> nameSupplier) { - this(data); - this.nameSupplier = nameSupplier; - } - /** * Creates a node with a parent. * @@ -86,23 +71,6 @@ public class NodeItem implements Node { 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 - * - * @deprecated dynamic names don't work on immutable objects - */ - @Deprecated - public NodeItem( - final T data, final Function, String> nameSupplier, - final Node parent) { - this(data, nameSupplier); - setParent(parent); - } - private String generateName() { return getNameSupplier().apply(this); } diff --git a/src/test/java/net/kemitix/node/NodeItemTest.java b/src/test/java/net/kemitix/node/NodeItemTest.java index 87b5a69..247e7fb 100644 --- a/src/test/java/net/kemitix/node/NodeItemTest.java +++ b/src/test/java/net/kemitix/node/NodeItemTest.java @@ -8,13 +8,9 @@ import org.junit.rules.ExpectedException; 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; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Function; /** * Test for {@link NodeItem}. @@ -50,17 +46,6 @@ public class NodeItemTest { 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"); - assertThat(child.getParent()).contains(node); - } - @Test public void canSetName() { //given @@ -587,69 +572,12 @@ public class NodeItemTest { node.createChild(null); } - @Test - public void getNameShouldBeCorrect() { - //given - node = new NodeItem<>("subject", n -> n.getData().get()); - //then - assertThat(node.getName()).isEqualTo("subject"); - } - - @Test - public void getNameShouldUseParentNameSupplier() { - //given - val root = new NodeItem("root", n -> n.getData().get()); - node = Nodes.unnamedChild("child", root); - //then - assertThat(node.getName()).isEqualTo("child"); - } - - @Test - public void getNameShouldReturnNameForNonStringData() { - val root = new NodeItem(LocalDate.parse("2016-05-23"), n -> { - if (n.isEmpty()) { - return null; - } - return n.getData().get().format(DateTimeFormatter.BASIC_ISO_DATE); - - }); - //then - assertThat(root.getName()).isEqualTo("20160523"); - } - - @Test - public void getNameShouldUseClosestNameSupplier() { - node = new NodeItem<>("root", n -> n.getData().get()); - val child = new NodeItem("child", Object::toString); - node.addChild(child); - val grandChild = Nodes.unnamedChild("grandchild", child); - //then - assertThat(node.getName()).isEqualTo("root"); - assertThat(child.getName()).isNotEqualTo("child"); - assertThat(grandChild.getName()).isNotEqualTo("grandchild"); - } - - @Test - public void getNameShouldWorkWithoutNameSupplier() { - node = Nodes.namedRoot(null, "root"); - val namedchild = Nodes.namedChild("named", "Alice", node); - //then - assertThat(node.getName()).isEqualTo("root"); - assertThat(namedchild.getName()).isEqualTo("Alice"); - } - @Test public void canCreateRootNodeWithoutData() { node = Nodes.namedRoot(null, "empty"); assertThat(node.getData()).isEmpty(); } - @Test - public void canCreateRootNodeWithoutDataButWithNameSupplier() { - node = Nodes.unnamedRoot(null); - assertThat(node.getData()).isEmpty(); - } - @Test public void getChildNamedFindsChild() { //given @@ -852,22 +780,6 @@ public class NodeItemTest { 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", n -> n.getData().get()); - val child = Nodes.unnamedChild("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() { @@ -961,86 +873,6 @@ public class NodeItemTest { assertThat(node.getChildren()).containsExactly(child); } - @Test - @SuppressWarnings("unchecked") - public void removeParentCopiesRootNameSupplier() { - //given - node = new NodeItem<>("root data", n -> "root supplier"); - val child = Nodes.unnamedChild("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"); - } - - @Test - public void setNameToNullRevertsToParentNameSupplier() { - //given - node = new NodeItem<>(null, n -> "root supplier"); - val child = Nodes.namedChild("child data", "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); - 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(); - } - - @Test - public void canUseNameSupplierToBuildFullPath() { - //given - final Function, String> pathNameSupplier = node -> { - Optional> parent = node.getParent(); - if (parent.isPresent()) { - return parent.get().getName() + "/" + node.getData().get(); - } - return ""; - }; - node = new NodeItem<>(null, pathNameSupplier); - val child = Nodes.unnamedChild("child", node); - val grandchild = Nodes.unnamedChild("grandchild", child); - //then - assertThat(grandchild.getName()).isEqualTo("/child/grandchild"); - } - @Test public void canSafelyHandleFindChildWhenAChildHasNoData() { //given From cf6bc727173e4a55d1744009d90ad757cc5cbe7c Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Sun, 21 Aug 2016 15:39:02 +0100 Subject: [PATCH 14/41] NodeItem: hide package class - use Node interface only --- src/main/java/net/kemitix/node/NodeItem.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/net/kemitix/node/NodeItem.java b/src/main/java/net/kemitix/node/NodeItem.java index 1a83c73..e688355 100644 --- a/src/main/java/net/kemitix/node/NodeItem.java +++ b/src/main/java/net/kemitix/node/NodeItem.java @@ -14,7 +14,7 @@ import java.util.function.Function; * * @author pcampbell */ -public class NodeItem implements Node { +class NodeItem implements Node { private T data; From be0685538f8c032544f1e6246289502dc2a146a1 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Sun, 21 Aug 2016 19:08:59 +0100 Subject: [PATCH 15/41] AbstractNodeItem: added --- .../net/kemitix/node/AbstractNodeItem.java | 157 ++++++++++++++++++ 1 file changed, 157 insertions(+) create mode 100644 src/main/java/net/kemitix/node/AbstractNodeItem.java diff --git a/src/main/java/net/kemitix/node/AbstractNodeItem.java b/src/main/java/net/kemitix/node/AbstractNodeItem.java new file mode 100644 index 0000000..6aa90e7 --- /dev/null +++ b/src/main/java/net/kemitix/node/AbstractNodeItem.java @@ -0,0 +1,157 @@ +package net.kemitix.node; + +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +/** + * An abstract node item, providing default implementations for most read-only + * operations. + * + * @param the type of data stored in each node + * + * @author pcampbell + */ +abstract class AbstractNodeItem implements Node { + + private T data; + + private String name; + + private Node parent; + + private final Set> children; + + protected AbstractNodeItem( + final T data, final String name, final Node parent, + final Set> children) { + this.data = data; + this.name = name; + this.parent = parent; + this.children = children; + } + + @Override + public String getName() { + return name; + } + + @Override + public Optional getData() { + return Optional.ofNullable(data); + } + + @Override + public boolean isEmpty() { + return data == null; + } + + @Override + public Optional> getParent() { + return Optional.ofNullable(parent); + } + + @Override + public Set> getChildren() { + return new HashSet<>(children); + } + + /** + * Fetches the node for the child if present. + * + * @param child the child's data to search for + * + * @return an {@link Optional} containing the child node if found + */ + @Override + public Optional> findChild(final T child) { + if (child == null) { + throw new NullPointerException("child"); + } + return children.stream().filter(node -> { + final Optional d = node.getData(); + return d.isPresent() && d.get().equals(child); + }).findAny(); + } + + @Override + public Node getChild(final T child) { + return findChild(child).orElseThrow( + () -> new NodeException("Child not found")); + } + + /** + * Checks if the node is an ancestor. + * + * @param node the potential ancestor + * + * @return true if the node is an ancestor + */ + @Override + public boolean isDescendantOf(final Node node) { + return parent != null && (node.equals(parent) || parent.isDescendantOf( + 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> findInPath(final List path) { + if (path == null) { + throw new NullPointerException("path"); + } + if (path.size() > 0) { + Optional> found = findChild(path.get(0)); + if (found.isPresent()) { + if (path.size() > 1) { + return found.get().findInPath(path.subList(1, path.size())); + } + return found; + } + } + return Optional.empty(); + } + + @Override + public Optional> findChildByName(final String named) { + if (named == null) { + throw new NullPointerException("name"); + } + return children.stream() + .filter(n -> n.getName().equals(named)) + .findAny(); + } + + @Override + public Node getChildByName(final String named) { + return findChildByName(named).orElseThrow( + () -> 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.length()) + "s]\n", + unnamed)); + } + getChildren().forEach(c -> sb.append(c.drawTree(depth + 1))); + return sb.toString(); + } + + @Override + public boolean isNamed() { + return name != null && name.length() > 0; + } +} From 421ad743f01fb42d05ea2cadb8c07ae343a2d5d3 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Sun, 21 Aug 2016 19:09:12 +0100 Subject: [PATCH 16/41] ImmutableNodeItem: added --- .../net/kemitix/node/ImmutableNodeItem.java | 95 ++++ .../kemitix/node/ImmutableNodeItemTest.java | 449 ++++++++++++++++++ 2 files changed, 544 insertions(+) create mode 100644 src/main/java/net/kemitix/node/ImmutableNodeItem.java create mode 100644 src/test/java/net/kemitix/node/ImmutableNodeItemTest.java diff --git a/src/main/java/net/kemitix/node/ImmutableNodeItem.java b/src/main/java/net/kemitix/node/ImmutableNodeItem.java new file mode 100644 index 0000000..7b80a59 --- /dev/null +++ b/src/main/java/net/kemitix/node/ImmutableNodeItem.java @@ -0,0 +1,95 @@ +package net.kemitix.node; + +import java.util.List; +import java.util.Set; + +/** + * Represents an immutable tree of nodes. + * + *

Due to the use of generics the data within a node may not be immutable. + * (We can't create a defensive copy.) So if a user were to use {@code + * getData()} they could then modify the original data within the node. This + * wouldn't affect the integrity of the node tree structure, however.

+ * + * @param the type of data stored in each node + * + * @author pcampbell + */ +final class ImmutableNodeItem extends AbstractNodeItem { + + private static final String IMMUTABLE_OBJECT = "Immutable object"; + + private ImmutableNodeItem( + final T data, final String name, final Node parent, + final Set> children) { + super(data, name, parent, children); + } + + static ImmutableNodeItem newRoot( + final T data, final String name, final Set> children) { + return new ImmutableNodeItem<>(data, name, null, children); + } + + static ImmutableNodeItem newChild( + final T data, final String name, final Node parent, + final Set> children) { + return new ImmutableNodeItem<>(data, name, parent, children); + } + + @Override + public void setName(final String name) { + throw new UnsupportedOperationException(IMMUTABLE_OBJECT); + } + + @Override + public void setData(final T data) { + throw new UnsupportedOperationException(IMMUTABLE_OBJECT); + } + + @Override + public void setParent(final Node parent) { + throw new UnsupportedOperationException(IMMUTABLE_OBJECT); + } + + @Override + public void addChild(final Node child) { + throw new UnsupportedOperationException(IMMUTABLE_OBJECT); + } + + @Override + public Node createChild(final T child) { + throw new UnsupportedOperationException(IMMUTABLE_OBJECT); + } + + @Override + public Node createChild(final T child, final String name) { + throw new UnsupportedOperationException(IMMUTABLE_OBJECT); + } + + @Override + public void createDescendantLine(final List descendants) { + throw new UnsupportedOperationException(IMMUTABLE_OBJECT); + } + + @Override + public Node findOrCreateChild(final T child) { + return findChild(child).orElseThrow( + () -> new UnsupportedOperationException(IMMUTABLE_OBJECT)); + } + + @Override + public void insertInPath(final Node node, final String... path) { + throw new UnsupportedOperationException(IMMUTABLE_OBJECT); + } + + @Override + public void removeChild(final Node node) { + throw new UnsupportedOperationException(IMMUTABLE_OBJECT); + } + + @Override + public void removeParent() { + throw new UnsupportedOperationException(IMMUTABLE_OBJECT); + } + +} diff --git a/src/test/java/net/kemitix/node/ImmutableNodeItemTest.java b/src/test/java/net/kemitix/node/ImmutableNodeItemTest.java new file mode 100644 index 0000000..9475ff5 --- /dev/null +++ b/src/test/java/net/kemitix/node/ImmutableNodeItemTest.java @@ -0,0 +1,449 @@ +package net.kemitix.node; + +import lombok.val; +import org.assertj.core.api.SoftAssertions; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Optional; + +/** + * Test for {@link ImmutableNodeItem}. + * + * @author pcampbell + */ +public class ImmutableNodeItemTest { + + private static final String IMMUTABLE_OBJECT = "Immutable object"; + + @Rule + public ExpectedException exception = ExpectedException.none(); + + private Node immutableNode; + + private void expectImmutableException() { + exception.expect(UnsupportedOperationException.class); + exception.expectMessage(IMMUTABLE_OBJECT); + } + + @Test + public void getDataReturnsData() { + //given + val data = "this immutableNode data"; + //when + immutableNode = Nodes.asImmutable(Nodes.unnamedRoot(data)); + //then + assertThat(immutableNode.getData()).as( + "can get the data from a immutableNode"). + contains(data); + } + + @Test + public void canCreateAnEmptyAndUnnamedNode() { + //when + immutableNode = Nodes.asImmutable(Nodes.unnamedRoot(null)); + //then + SoftAssertions softly = new SoftAssertions(); + softly.assertThat(immutableNode.isEmpty()) + .as("immutableNode is empty") + .isTrue(); + softly.assertThat(immutableNode.isNamed()) + .as("immutableNode is unnamed") + .isFalse(); + softly.assertAll(); + } + + @Test + public void shouldThrowExceptionOnSetName() { + //given + immutableNode = Nodes.asImmutable(Nodes.unnamedRoot(null)); + expectImmutableException(); + //when + immutableNode.setName("named"); + } + + @Test + public void rootNodeShouldHaveNoParent() { + //given + immutableNode = Nodes.asImmutable(Nodes.unnamedRoot("data")); + //then + assertThat(immutableNode.getParent()).as( + "immutableNode created without a parent has no parent") + .isEmpty(); + } + + @Test + public void shouldContainImmutableCopyOfChild() { + //given + val parent = Nodes.unnamedRoot("root"); + val child = Nodes.namedChild("child", "child", parent); + //when + immutableNode = Nodes.asImmutable(parent); + //then + val immutableChild = immutableNode.getChildByName("child"); + assertThat(immutableChild).isNotSameAs(child); + assertThat(immutableChild.getName()).isEqualTo("child"); + } + + @Test + public void childShouldHaveImmutableParent() { + //given + val parent = Nodes.namedRoot("parent", "root"); + Nodes.namedChild("subject", "child", parent); + //when + immutableNode = Nodes.asImmutable(parent); + //then + // get the immutable node's child's parent + val immutableChild = immutableNode.getChildByName("child"); + final Optional> optionalParent + = immutableChild.getParent(); + if (optionalParent.isPresent()) { + val p = optionalParent.get(); + assertThat(p).hasFieldOrPropertyWithValue("name", "root") + .hasFieldOrPropertyWithValue("data", + Optional.of("parent")); + } + } + + @Test + public void shouldNotBeAbleToAddChildToImmutableTree() { + //given + immutableNode = Nodes.asImmutable(Nodes.unnamedRoot("root")); + expectImmutableException(); + //when + Nodes.unnamedChild("child", immutableNode); + } + + @Test + public void shouldThrowExceptionWhenSetParent() { + //given + immutableNode = Nodes.asImmutable(Nodes.unnamedRoot("subject")); + expectImmutableException(); + //when + immutableNode.setParent(null); + } + + @Test + public void shouldThrowExceptionWhenAddingChild() { + //given + immutableNode = Nodes.asImmutable(Nodes.unnamedRoot("subject")); + expectImmutableException(); + //when + immutableNode.addChild(Nodes.unnamedRoot("child")); + } + + /** + * Test that we can walk a tree to the target node. + */ + @Test + public void shouldWalkTreeToNode() { + //given + val root = Nodes.unnamedRoot("root"); + Nodes.namedChild("child", "child", Nodes.unnamedChild("parent", root)); + immutableNode = Nodes.asImmutable(root); + //when + val result = immutableNode.findInPath(Arrays.asList("parent", "child")); + //then + assertThat(result.isPresent()).isTrue(); + if (result.isPresent()) { + assertThat(result.get().getName()).isEqualTo("child"); + } + } + + /** + * Test that we get an empty {@link Optional} when walking a path that + * doesn't exist. + */ + @Test + public void shouldNotFindNonExistentChildNode() { + //given + val root = Nodes.unnamedRoot("root"); + Nodes.unnamedChild("child", Nodes.unnamedChild("parent", root)); + immutableNode = Nodes.asImmutable(root); + //when + val result = immutableNode.findInPath( + Arrays.asList("parent", "no child")); + //then + assertThat(result.isPresent()).isFalse(); + } + + /** + * Test that when we pass null we get an exception. + */ + @Test + public void shouldThrowNEWhenWalkTreeNull() { + //given + immutableNode = Nodes.asImmutable(Nodes.unnamedRoot("subject")); + exception.expect(NullPointerException.class); + exception.expectMessage("path"); + //when + immutableNode.findInPath(null); + } + + /** + * Test that when we pass an empty path we get and empty {@link Optional} as + * a result. + */ + @Test + public void shouldReturnEmptyForEmptyWalkTreePath() { + //given + immutableNode = Nodes.asImmutable(Nodes.unnamedRoot("subject")); + //when + val result = immutableNode.findInPath(Collections.emptyList()); + //then + assertThat(result).isEmpty(); + } + + /** + * Test that we can find a child of a immutableNode. + */ + @Test + public void shouldFindExistingChildNode() { + //given + val root = Nodes.unnamedRoot("root"); + Nodes.unnamedChild("child", root); + immutableNode = Nodes.asImmutable(root); + //when + val result = immutableNode.findChild("child"); + //then + assertThat(result.isPresent()).isTrue(); + if (result.isPresent()) { + assertThat(result.get().getData()).contains("child"); + } + } + + /** + * Test that if we pass null we get an exception. + */ + @Test + public void findOrCreateChildShouldThrowNPEFWhenChildIsNull() { + //given + immutableNode = Nodes.asImmutable(Nodes.unnamedRoot("subject")); + exception.expect(NullPointerException.class); + exception.expectMessage("child"); + //when + immutableNode.findOrCreateChild(null); + } + + /** + * Test that we throw an exception when passed null. + */ + @Test + public void getChildShouldThrowNPEWhenThereIsNoChild() { + //given + immutableNode = Nodes.asImmutable(Nodes.unnamedRoot("data")); + exception.expect(NullPointerException.class); + exception.expectMessage("child"); + //when + immutableNode.findChild(null); + } + + @Test + public void getChildNamedFindsChild() { + //given + val root = Nodes.namedRoot("root data", "root"); + val alpha = Nodes.namedRoot("alpha data", "alpha"); + val beta = Nodes.namedRoot("beta data", "beta"); + root.addChild(alpha); + root.addChild(beta); + immutableNode = Nodes.asImmutable(root); + //when + val result = immutableNode.getChildByName("alpha"); + //then + assertThat(result.getName()).isEqualTo(alpha.getName()); + } + + @Test + public void getChildNamedFindsNothing() { + //given + val root = Nodes.namedRoot("root data", "root"); + val alpha = Nodes.namedRoot("alpha data", "alpha"); + val beta = Nodes.namedRoot("beta data", "beta"); + root.addChild(alpha); + root.addChild(beta); + exception.expect(NodeException.class); + exception.expectMessage("Named child not found"); + immutableNode = Nodes.asImmutable(root); + //when + immutableNode.getChildByName("gamma"); + } + + @Test + public void removingParentThrowsException() { + //given + immutableNode = Nodes.asImmutable(Nodes.unnamedRoot(null)); + expectImmutableException(); + //when + immutableNode.removeParent(); + } + + @Test + public void findChildNamedShouldThrowNPEWhenNameIsNull() { + //given + exception.expect(NullPointerException.class); + exception.expectMessage("name"); + immutableNode = Nodes.asImmutable(Nodes.unnamedRoot(null)); + //when + immutableNode.findChildByName(null); + } + + @Test + public void isNamedNull() { + //given + immutableNode = Nodes.asImmutable(Nodes.unnamedRoot(null)); + //then + assertThat(immutableNode.isNamed()).isFalse(); + } + + @Test + public void isNamedEmpty() { + //given + immutableNode = Nodes.asImmutable(Nodes.namedRoot(null, "")); + //then + assertThat(immutableNode.isNamed()).isFalse(); + } + + @Test + public void isNamedNamed() { + //given + immutableNode = Nodes.asImmutable(Nodes.namedRoot(null, "named")); + //then + assertThat(immutableNode.isNamed()).isTrue(); + } + + @Test + public void removeChildThrowsExceptions() { + //given + immutableNode = Nodes.asImmutable(Nodes.unnamedRoot(null)); + expectImmutableException(); + //then + immutableNode.removeChild(null); + } + + @Test + public void drawTreeIsCorrect() { + //given + val root = Nodes.namedRoot("root data", "root"); + val bob = Nodes.namedChild("bob data", "bob", root); + val alice = Nodes.namedChild("alice data", "alice", root); + Nodes.namedChild("dave data", "dave", alice); + Nodes.unnamedChild("bob's child's data", + bob); // has no name and no children so no included + val kim = Nodes.unnamedChild("kim data", root); // nameless mother + Nodes.namedChild("lucy data", "lucy", kim); + immutableNode = Nodes.asImmutable(root); + //when + val tree = immutableNode.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]"); + } + + @Test + public void setDataShouldThrowException() { + //given + immutableNode = Nodes.asImmutable(Nodes.unnamedRoot("initial")); + expectImmutableException(); + //when + immutableNode.setData("updated"); + } + + @Test + public void createChildThrowsException() { + //given + immutableNode = Nodes.asImmutable(Nodes.unnamedRoot(null)); + expectImmutableException(); + //when + immutableNode.createChild("child data", "child name"); + } + + @Test + public void canGetChildWhenFound() { + //given + val root = Nodes.unnamedRoot("data"); + val child = Nodes.namedChild("child data", "child name", root); + immutableNode = Nodes.asImmutable(root); + //when + val found = immutableNode.getChild("child data"); + //then + assertThat(found.getName()).isEqualTo(child.getName()); + } + + @Test + public void canGetChildWhenNotFound() { + //given + exception.expect(NodeException.class); + exception.expectMessage("Child not found"); + immutableNode = Nodes.asImmutable(Nodes.unnamedRoot("data")); + //when + immutableNode.getChild("child data"); + } + + @Test + public void canSafelyHandleFindChildWhenAChildHasNoData() { + //given + val root = Nodes.unnamedRoot(""); + Nodes.unnamedChild(null, root); + immutableNode = Nodes.asImmutable(root); + //when + immutableNode.findChild("data"); + } + + @Test + public void createChildShouldThrowException() { + //given + immutableNode = Nodes.asImmutable(Nodes.unnamedRoot("")); + expectImmutableException(); + //when + immutableNode.createChild("child"); + } + + @Test + public void createDescendantLineShouldThrowException() { + //given + immutableNode = Nodes.asImmutable(Nodes.unnamedRoot("")); + expectImmutableException(); + //when + immutableNode.createDescendantLine( + Arrays.asList("child", "grandchild")); + } + + @Test + public void insertInPathShouldThrowException() { + //given + immutableNode = Nodes.asImmutable(Nodes.unnamedRoot("")); + expectImmutableException(); + //when + immutableNode.insertInPath(null, ""); + } + + @Test + public void findOrCreateChildShouldReturnChildWhenChildIsFound() { + //given + val root = Nodes.unnamedRoot(""); + Nodes.namedChild("child", "child", root); + immutableNode = Nodes.asImmutable(root); + //when + val found = immutableNode.findOrCreateChild("child"); + assertThat(found).extracting(Node::getName).contains("child"); + } + + @Test + public void findOrCreateChildShouldThrowExceptionWhenChildNotFound() { + //given + immutableNode = Nodes.asImmutable(Nodes.unnamedRoot("")); + expectImmutableException(); + //when + immutableNode.findOrCreateChild("child"); + } +} From f1a73366bd3dffb4e54dc8074c82568442f09430 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Sun, 21 Aug 2016 19:09:47 +0100 Subject: [PATCH 17/41] Nodes.asImmutable: added to create an immutable copy of a node tree --- src/main/java/net/kemitix/node/Nodes.java | 43 ++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/src/main/java/net/kemitix/node/Nodes.java b/src/main/java/net/kemitix/node/Nodes.java index 24dc50e..ecc5f39 100644 --- a/src/main/java/net/kemitix/node/Nodes.java +++ b/src/main/java/net/kemitix/node/Nodes.java @@ -1,5 +1,9 @@ package net.kemitix.node; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + /** * Utility class for {@link Node} items. * @@ -52,7 +56,7 @@ public final class Nodes { * Creates a new named child node. * * @param data the data the node will contain - * @param name the name of the node + * @param name the name of the node * @param parent the parent of the node * @param the type of the data * @@ -63,4 +67,41 @@ public final class Nodes { return new NodeItem<>(data, name, parent); } + /** + * Creates an immutable copy of an existing node tree. + * + * @param root the root node of the source tree + * @param the type of the data + * + * @return the immutable copy of the tree + */ + public static Node asImmutable(final Node root) { + if (root.getParent().isPresent()) { + throw new IllegalArgumentException("source must be the root node"); + } + final Set> children = getImmutableChildren(root); + return ImmutableNodeItem.newRoot(root.getData().orElse(null), + root.getName(), children); + } + + private static Set> getImmutableChildren(final Node source) { + return source.getChildren() + .stream() + .map(Nodes::asImmutableChild) + .collect(Collectors.toSet()); + } + + private static Node asImmutableChild( + final Node source) { + final Optional> sourceParent = source.getParent(); + if (sourceParent.isPresent()) { + return ImmutableNodeItem.newChild(source.getData().orElse(null), + source.getName(), sourceParent.get(), + getImmutableChildren(source)); + } else { + throw new IllegalArgumentException( + "source must not be the root node"); + } + } + } From 08b934a6d6f9d19e7946b7ec8e5645ecaf9168da Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Mon, 22 Aug 2016 08:44:48 +0100 Subject: [PATCH 18/41] pom.xml: upgrade kemitix-parent to 2.0.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 3071a0d..9c0e8b4 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ net.kemitix kemitix-parent - 0.6.0 + 2.0.0 From acce849819baeb3df94c12e7eb9e09c036f817c4 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Mon, 22 Aug 2016 08:45:00 +0100 Subject: [PATCH 19/41] checkstyle.xml: removed --- checkstyle.xml | 192 ------------------------------------------------- 1 file changed, 192 deletions(-) delete mode 100644 checkstyle.xml diff --git a/checkstyle.xml b/checkstyle.xml deleted file mode 100644 index e54bdb6..0000000 --- a/checkstyle.xml +++ /dev/null @@ -1,192 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file From e108d61ac51c0c66eb62a575fe0acafa017b793c Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Mon, 22 Aug 2016 08:48:31 +0100 Subject: [PATCH 20/41] pom.xml: move lombok to compile scope --- pom.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/pom.xml b/pom.xml index 9c0e8b4..a502cc3 100644 --- a/pom.xml +++ b/pom.xml @@ -50,7 +50,6 @@ org.projectlombok lombok 1.16.8 - test junit From d99fcdcebeb8e8661af754923d5100591ca45185 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Mon, 22 Aug 2016 08:51:18 +0100 Subject: [PATCH 21/41] NodeItem: replace manual null checks with @NonNull --- src/main/java/net/kemitix/node/NodeItem.java | 42 +++++--------------- 1 file changed, 10 insertions(+), 32 deletions(-) diff --git a/src/main/java/net/kemitix/node/NodeItem.java b/src/main/java/net/kemitix/node/NodeItem.java index e688355..3f6f6bc 100644 --- a/src/main/java/net/kemitix/node/NodeItem.java +++ b/src/main/java/net/kemitix/node/NodeItem.java @@ -1,5 +1,7 @@ package net.kemitix.node; +import lombok.NonNull; + import java.util.Arrays; import java.util.HashSet; import java.util.List; @@ -128,10 +130,7 @@ class NodeItem implements Node { * @param child the node to add */ @Override - public void addChild(final Node child) { - if (child == null) { - throw new NullPointerException("child"); - } + public void addChild(@NonNull final Node child) { if (this.equals(child) || isDescendantOf(child)) { throw new NodeException("Child is an ancestor"); } @@ -162,10 +161,7 @@ class NodeItem implements Node { * @return the new child node */ @Override - public Node createChild(final T child) { - if (child == null) { - throw new NullPointerException("child"); - } + public Node createChild(@NonNull final T child) { return new NodeItem<>(child, this); } @@ -184,10 +180,7 @@ class NodeItem implements Node { * @param descendants the line of descendants from the current node */ @Override - public void createDescendantLine(final List descendants) { - if (descendants == null) { - throw new NullPointerException("descendants"); - } + public void createDescendantLine(@NonNull final List descendants) { if (!descendants.isEmpty()) { findOrCreateChild(descendants.get(0)).createDescendantLine( descendants.subList(1, descendants.size())); @@ -204,10 +197,7 @@ class NodeItem implements Node { */ @Override @Deprecated - public Node findOrCreateChild(final T child) { - if (child == null) { - throw new NullPointerException("child"); - } + public Node findOrCreateChild(@NonNull final T child) { return findChild(child).orElseGet(() -> createChild(child)); } @@ -219,10 +209,7 @@ class NodeItem implements Node { * @return an {@link Optional} containing the child node if found */ @Override - public Optional> findChild(final T child) { - if (child == null) { - throw new NullPointerException("child"); - } + public Optional> findChild(@NonNull final T child) { return children.stream().filter(node -> { final Optional d = node.getData(); return d.isPresent() && d.get().equals(child); @@ -257,10 +244,7 @@ class NodeItem implements Node { * @param parent the new parent node */ @Override - public final void setParent(final Node parent) { - if (parent == null) { - throw new NullPointerException("parent"); - } + public final void setParent(@NonNull final Node parent) { if (this.equals(parent) || parent.isDescendantOf(this)) { throw new NodeException("Parent is a descendant"); } @@ -279,10 +263,7 @@ class NodeItem implements Node { * @return the child or null */ @Override - public Optional> findInPath(final List path) { - if (path == null) { - throw new NullPointerException("path"); - } + public Optional> findInPath(@NonNull final List path) { if (path.size() > 0) { Optional> found = findChild(path.get(0)); if (found.isPresent()) { @@ -330,10 +311,7 @@ class NodeItem implements Node { } @Override - public Optional> findChildByName(final String named) { - if (named == null) { - throw new NullPointerException("name"); - } + public Optional> findChildByName(@NonNull final String named) { return children.stream() .filter((Node t) -> t.getName().equals(named)) .findAny(); From b2130442e50a67f7bd58e8e7b8c702ad58acb713 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Mon, 22 Aug 2016 08:52:42 +0100 Subject: [PATCH 22/41] AbstractNodeItem: replace manual null checks with @NonNull --- .../java/net/kemitix/node/AbstractNodeItem.java | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/src/main/java/net/kemitix/node/AbstractNodeItem.java b/src/main/java/net/kemitix/node/AbstractNodeItem.java index 6aa90e7..4815d93 100644 --- a/src/main/java/net/kemitix/node/AbstractNodeItem.java +++ b/src/main/java/net/kemitix/node/AbstractNodeItem.java @@ -1,5 +1,7 @@ package net.kemitix.node; +import lombok.NonNull; + import java.util.HashSet; import java.util.List; import java.util.Optional; @@ -65,10 +67,7 @@ abstract class AbstractNodeItem implements Node { * @return an {@link Optional} containing the child node if found */ @Override - public Optional> findChild(final T child) { - if (child == null) { - throw new NullPointerException("child"); - } + public Optional> findChild(@NonNull final T child) { return children.stream().filter(node -> { final Optional d = node.getData(); return d.isPresent() && d.get().equals(child); @@ -102,10 +101,7 @@ abstract class AbstractNodeItem implements Node { * @return the child or null */ @Override - public Optional> findInPath(final List path) { - if (path == null) { - throw new NullPointerException("path"); - } + public Optional> findInPath(@NonNull final List path) { if (path.size() > 0) { Optional> found = findChild(path.get(0)); if (found.isPresent()) { @@ -119,10 +115,7 @@ abstract class AbstractNodeItem implements Node { } @Override - public Optional> findChildByName(final String named) { - if (named == null) { - throw new NullPointerException("name"); - } + public Optional> findChildByName(@NonNull final String named) { return children.stream() .filter(n -> n.getName().equals(named)) .findAny(); From 2a435a849e74add511897a2545ccf37433ab368d Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Mon, 22 Aug 2016 08:58:58 +0100 Subject: [PATCH 23/41] NodeItem: refactor formatting name by depth Remove duplicate string literals. --- src/main/java/net/kemitix/node/NodeItem.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/main/java/net/kemitix/node/NodeItem.java b/src/main/java/net/kemitix/node/NodeItem.java index 3f6f6bc..3afd79a 100644 --- a/src/main/java/net/kemitix/node/NodeItem.java +++ b/src/main/java/net/kemitix/node/NodeItem.java @@ -331,17 +331,18 @@ class NodeItem implements Node { final StringBuilder sb = new StringBuilder(); final String unnamed = "(unnamed)"; if (isNamed()) { - sb.append(String.format("[%1$" + (depth + name.length()) + "s]\n", - name)); + sb.append(formatByDepth(name, depth)); } else if (!children.isEmpty()) { - sb.append( - String.format("[%1$" + (depth + unnamed.length()) + "s]\n", - unnamed)); + sb.append(formatByDepth(unnamed, depth)); } getChildren().stream().forEach(c -> sb.append(c.drawTree(depth + 1))); return sb.toString(); } + private String formatByDepth(final String value, final int depth) { + return String.format("[%1$" + (depth + value.length()) + "s]\n", value); + } + @Override public boolean isNamed() { String currentName = getName(); From 6253a3226f6d7bd1a6f9559a215a762a5bc741b5 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Mon, 22 Aug 2016 08:59:52 +0100 Subject: [PATCH 24/41] NodeItem: simplify stream().forEach() --- src/main/java/net/kemitix/node/NodeItem.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/net/kemitix/node/NodeItem.java b/src/main/java/net/kemitix/node/NodeItem.java index 3afd79a..caa23e0 100644 --- a/src/main/java/net/kemitix/node/NodeItem.java +++ b/src/main/java/net/kemitix/node/NodeItem.java @@ -335,7 +335,7 @@ class NodeItem implements Node { } else if (!children.isEmpty()) { sb.append(formatByDepth(unnamed, depth)); } - getChildren().stream().forEach(c -> sb.append(c.drawTree(depth + 1))); + getChildren().forEach(c -> sb.append(c.drawTree(depth + 1))); return sb.toString(); } From 3d94eaeb32647c8360d4185809a15b6384220ed2 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Mon, 22 Aug 2016 09:01:34 +0100 Subject: [PATCH 25/41] AbstractNodeItem: refactor formatting name by depth Remove duplicate string literals. --- src/main/java/net/kemitix/node/AbstractNodeItem.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/main/java/net/kemitix/node/AbstractNodeItem.java b/src/main/java/net/kemitix/node/AbstractNodeItem.java index 4815d93..f177c73 100644 --- a/src/main/java/net/kemitix/node/AbstractNodeItem.java +++ b/src/main/java/net/kemitix/node/AbstractNodeItem.java @@ -132,17 +132,18 @@ abstract class AbstractNodeItem implements Node { final StringBuilder sb = new StringBuilder(); final String unnamed = "(unnamed)"; if (isNamed()) { - sb.append(String.format("[%1$" + (depth + name.length()) + "s]\n", - name)); + sb.append(formatByDepth(name, depth)); } else if (!children.isEmpty()) { - sb.append( - String.format("[%1$" + (depth + unnamed.length()) + "s]\n", - unnamed)); + sb.append(formatByDepth(unnamed, depth)); } getChildren().forEach(c -> sb.append(c.drawTree(depth + 1))); return sb.toString(); } + private String formatByDepth(final String value, final int depth) { + return String.format("[%1$" + (depth + value.length()) + "s]\n", value); + } + @Override public boolean isNamed() { return name != null && name.length() > 0; From 5c129b54b8af4a127ef3eb30b7b209f97990ada2 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Mon, 22 Aug 2016 09:04:16 +0100 Subject: [PATCH 26/41] NodeItem: put trailing comments on their own line --- src/main/java/net/kemitix/node/NodeItem.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/net/kemitix/node/NodeItem.java b/src/main/java/net/kemitix/node/NodeItem.java index caa23e0..e5db2fd 100644 --- a/src/main/java/net/kemitix/node/NodeItem.java +++ b/src/main/java/net/kemitix/node/NodeItem.java @@ -279,13 +279,15 @@ class NodeItem implements Node { @Override public void insertInPath(final Node nodeItem, final String... path) { if (path.length == 0) { - if (!nodeItem.isNamed()) { // nothing to conflict with + if (!nodeItem.isNamed()) { + // nothing to conflict with addChild(nodeItem); return; } String nodeName = nodeItem.getName(); final Optional> childNamed = findChildByName(nodeName); - if (!childNamed.isPresent()) { // nothing with the same name exists + if (!childNamed.isPresent()) { + // nothing with the same name exists addChild(nodeItem); return; } From 37247e93bc28ae8b0a36df8a7c1797c548192121 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Sun, 4 Sep 2016 18:44:29 +0100 Subject: [PATCH 27/41] node: change javadoc element order to Atclause order The default configuration of the AtclauseOrder is a bit strange. --- src/main/java/net/kemitix/node/AbstractNodeItem.java | 4 ++-- src/main/java/net/kemitix/node/ImmutableNodeItem.java | 4 ++-- src/main/java/net/kemitix/node/Node.java | 4 ++-- src/main/java/net/kemitix/node/NodeItem.java | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/main/java/net/kemitix/node/AbstractNodeItem.java b/src/main/java/net/kemitix/node/AbstractNodeItem.java index f177c73..f7efaf9 100644 --- a/src/main/java/net/kemitix/node/AbstractNodeItem.java +++ b/src/main/java/net/kemitix/node/AbstractNodeItem.java @@ -11,9 +11,9 @@ import java.util.Set; * An abstract node item, providing default implementations for most read-only * operations. * - * @param the type of data stored in each node + * @author Paul Campbell * - * @author pcampbell + * @param the type of data stored in each node */ abstract class AbstractNodeItem implements Node { diff --git a/src/main/java/net/kemitix/node/ImmutableNodeItem.java b/src/main/java/net/kemitix/node/ImmutableNodeItem.java index 7b80a59..dbc85ec 100644 --- a/src/main/java/net/kemitix/node/ImmutableNodeItem.java +++ b/src/main/java/net/kemitix/node/ImmutableNodeItem.java @@ -11,9 +11,9 @@ import java.util.Set; * getData()} they could then modify the original data within the node. This * wouldn't affect the integrity of the node tree structure, however.

* - * @param the type of data stored in each node + * @author Paul Campbell * - * @author pcampbell + * @param the type of data stored in each node */ final class ImmutableNodeItem extends AbstractNodeItem { diff --git a/src/main/java/net/kemitix/node/Node.java b/src/main/java/net/kemitix/node/Node.java index 7cda730..f1fa7ba 100644 --- a/src/main/java/net/kemitix/node/Node.java +++ b/src/main/java/net/kemitix/node/Node.java @@ -7,9 +7,9 @@ import java.util.Set; /** * An interface for tree node items. * - * @param the type of data held in each node + * @author Paul Campbell * - * @author pcampbell + * @param the type of data held in each node */ public interface Node { diff --git a/src/main/java/net/kemitix/node/NodeItem.java b/src/main/java/net/kemitix/node/NodeItem.java index e5db2fd..812adf4 100644 --- a/src/main/java/net/kemitix/node/NodeItem.java +++ b/src/main/java/net/kemitix/node/NodeItem.java @@ -12,9 +12,9 @@ import java.util.function.Function; /** * Represents a tree of nodes. * - * @param the type of data stored in each node + * @author Paul Campbell * - * @author pcampbell + * @param the type of data stored in each node */ class NodeItem implements Node { From 9f3aec202af31a3ef83f59da503e3ac0de099c40 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Sun, 4 Sep 2016 18:59:22 +0100 Subject: [PATCH 28/41] {Abstract}NodeItem: reduce nested if statements --- .../net/kemitix/node/AbstractNodeItem.java | 15 ++++----- src/main/java/net/kemitix/node/NodeItem.java | 31 +++++++++---------- 2 files changed, 22 insertions(+), 24 deletions(-) diff --git a/src/main/java/net/kemitix/node/AbstractNodeItem.java b/src/main/java/net/kemitix/node/AbstractNodeItem.java index f7efaf9..3097d4a 100644 --- a/src/main/java/net/kemitix/node/AbstractNodeItem.java +++ b/src/main/java/net/kemitix/node/AbstractNodeItem.java @@ -102,14 +102,15 @@ abstract class AbstractNodeItem implements Node { */ @Override public Optional> findInPath(@NonNull final List path) { - if (path.size() > 0) { - Optional> found = findChild(path.get(0)); - if (found.isPresent()) { - if (path.size() > 1) { - return found.get().findInPath(path.subList(1, path.size())); - } - return found; + if (path.isEmpty()) { + return Optional.empty(); + } + Optional> found = findChild(path.get(0)); + if (found.isPresent()) { + if (path.size() > 1) { + return found.get().findInPath(path.subList(1, path.size())); } + return found; } return Optional.empty(); } diff --git a/src/main/java/net/kemitix/node/NodeItem.java b/src/main/java/net/kemitix/node/NodeItem.java index 812adf4..2ed9b02 100644 --- a/src/main/java/net/kemitix/node/NodeItem.java +++ b/src/main/java/net/kemitix/node/NodeItem.java @@ -2,19 +2,15 @@ package net.kemitix.node; import lombok.NonNull; -import java.util.Arrays; -import java.util.HashSet; -import java.util.List; -import java.util.Optional; -import java.util.Set; +import java.util.*; import java.util.function.Function; /** * Represents a tree of nodes. * - * @author Paul Campbell - * * @param the type of data stored in each node + * + * @author Paul Campbell */ class NodeItem implements Node { @@ -147,7 +143,7 @@ class NodeItem implements Node { Optional> childParent = child.getParent(); boolean isOrphan = !childParent.isPresent(); boolean hasDifferentParent = !isOrphan && !childParent.get() - .equals(this); + .equals(this); if (isOrphan || hasDifferentParent) { child.setParent(this); } @@ -264,14 +260,15 @@ class NodeItem implements Node { */ @Override public Optional> findInPath(@NonNull final List path) { - if (path.size() > 0) { - Optional> found = findChild(path.get(0)); - if (found.isPresent()) { - if (path.size() > 1) { - return found.get().findInPath(path.subList(1, path.size())); - } - return found; + if (path.isEmpty()) { + return Optional.empty(); + } + Optional> found = findChild(path.get(0)); + if (found.isPresent()) { + if (path.size() > 1) { + return found.get().findInPath(path.subList(1, path.size())); } + return found; } return Optional.empty(); } @@ -315,8 +312,8 @@ class NodeItem implements Node { @Override public Optional> findChildByName(@NonNull final String named) { return children.stream() - .filter((Node t) -> t.getName().equals(named)) - .findAny(); + .filter((Node t) -> t.getName().equals(named)) + .findAny(); } @Override From 02d07605c0f6e818298869b4c55d37d3d32bd98d Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Sun, 4 Sep 2016 19:20:16 +0100 Subject: [PATCH 29/41] NodeFindInPathTestsCategory: added --- .../net/kemitix/node/NodeFindInPathTestsCategory.java | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 src/test/java/net/kemitix/node/NodeFindInPathTestsCategory.java diff --git a/src/test/java/net/kemitix/node/NodeFindInPathTestsCategory.java b/src/test/java/net/kemitix/node/NodeFindInPathTestsCategory.java new file mode 100644 index 0000000..979190f --- /dev/null +++ b/src/test/java/net/kemitix/node/NodeFindInPathTestsCategory.java @@ -0,0 +1,9 @@ +package net.kemitix.node; + +/** + * Category marker for tests relating to implementations of Node.findInPath(...). + * + * @author Paul Campbell + */ +public interface NodeFindInPathTestsCategory { +} From 6987f927fe4e29a0c8f1594e0a0558554f8b620f Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Sun, 4 Sep 2016 19:21:18 +0100 Subject: [PATCH 30/41] {Immutable}NodeItemTest: categorise findInPath tests --- .../java/net/kemitix/node/ImmutableNodeItemTest.java | 9 +++++++-- src/test/java/net/kemitix/node/NodeItemTest.java | 9 +++++++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/test/java/net/kemitix/node/ImmutableNodeItemTest.java b/src/test/java/net/kemitix/node/ImmutableNodeItemTest.java index 9475ff5..91eee8a 100644 --- a/src/test/java/net/kemitix/node/ImmutableNodeItemTest.java +++ b/src/test/java/net/kemitix/node/ImmutableNodeItemTest.java @@ -4,14 +4,15 @@ import lombok.val; import org.assertj.core.api.SoftAssertions; import org.junit.Rule; import org.junit.Test; +import org.junit.experimental.categories.Category; import org.junit.rules.ExpectedException; -import static org.assertj.core.api.Assertions.assertThat; - import java.util.Arrays; import java.util.Collections; import java.util.Optional; +import static org.assertj.core.api.Assertions.assertThat; + /** * Test for {@link ImmutableNodeItem}. * @@ -141,6 +142,7 @@ public class ImmutableNodeItemTest { * Test that we can walk a tree to the target node. */ @Test + @Category(NodeFindInPathTestsCategory.class) public void shouldWalkTreeToNode() { //given val root = Nodes.unnamedRoot("root"); @@ -160,6 +162,7 @@ public class ImmutableNodeItemTest { * doesn't exist. */ @Test + @Category(NodeFindInPathTestsCategory.class) public void shouldNotFindNonExistentChildNode() { //given val root = Nodes.unnamedRoot("root"); @@ -176,6 +179,7 @@ public class ImmutableNodeItemTest { * Test that when we pass null we get an exception. */ @Test + @Category(NodeFindInPathTestsCategory.class) public void shouldThrowNEWhenWalkTreeNull() { //given immutableNode = Nodes.asImmutable(Nodes.unnamedRoot("subject")); @@ -190,6 +194,7 @@ public class ImmutableNodeItemTest { * a result. */ @Test + @Category(NodeFindInPathTestsCategory.class) public void shouldReturnEmptyForEmptyWalkTreePath() { //given immutableNode = Nodes.asImmutable(Nodes.unnamedRoot("subject")); diff --git a/src/test/java/net/kemitix/node/NodeItemTest.java b/src/test/java/net/kemitix/node/NodeItemTest.java index 247e7fb..2702ae0 100644 --- a/src/test/java/net/kemitix/node/NodeItemTest.java +++ b/src/test/java/net/kemitix/node/NodeItemTest.java @@ -4,14 +4,15 @@ import lombok.val; import org.assertj.core.api.SoftAssertions; import org.junit.Rule; import org.junit.Test; +import org.junit.experimental.categories.Category; import org.junit.rules.ExpectedException; -import static org.assertj.core.api.Assertions.assertThat; - import java.util.Arrays; import java.util.Collections; import java.util.Optional; +import static org.assertj.core.api.Assertions.assertThat; + /** * Test for {@link NodeItem}. * @@ -309,6 +310,7 @@ public class NodeItemTest { * Test that we can walk a tree to the target node. */ @Test + @Category(NodeFindInPathTestsCategory.class) public void shouldWalkTreeToNode() { //given val grandparent = "grandparent"; @@ -334,6 +336,7 @@ public class NodeItemTest { * doesn't exist. */ @Test + @Category(NodeFindInPathTestsCategory.class) public void shouldNotFindNonExistentChildNode() { //given val parent = "parent"; @@ -352,6 +355,7 @@ public class NodeItemTest { * Test that when we pass null we get an exception. */ @Test + @Category(NodeFindInPathTestsCategory.class) public void shouldThrowNEWhenWalkTreeNull() { //given node = Nodes.unnamedRoot("subject"); @@ -366,6 +370,7 @@ public class NodeItemTest { * a result. */ @Test + @Category(NodeFindInPathTestsCategory.class) public void shouldReturnEmptyForEmptyWalkTreePath() { //given node = Nodes.unnamedRoot("subject"); From 91c57f098e3feca06c2ca91d4af6c35db5838166 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Sun, 4 Sep 2016 19:21:41 +0100 Subject: [PATCH 31/41] {Abstract}NodeItem: rewrite findInPath to avoid recursion --- .../java/net/kemitix/node/AbstractNodeItem.java | 15 +++++++++------ src/main/java/net/kemitix/node/NodeItem.java | 15 +++++++++------ 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/src/main/java/net/kemitix/node/AbstractNodeItem.java b/src/main/java/net/kemitix/node/AbstractNodeItem.java index 3097d4a..1fe6eab 100644 --- a/src/main/java/net/kemitix/node/AbstractNodeItem.java +++ b/src/main/java/net/kemitix/node/AbstractNodeItem.java @@ -105,14 +105,17 @@ abstract class AbstractNodeItem implements Node { if (path.isEmpty()) { return Optional.empty(); } - Optional> found = findChild(path.get(0)); - if (found.isPresent()) { - if (path.size() > 1) { - return found.get().findInPath(path.subList(1, path.size())); + Node current = this; + for (T item : path) { + final Optional> child = current.findChild(item); + if (child.isPresent()) { + current = child.get(); + } else { + current = null; + break; } - return found; } - return Optional.empty(); + return Optional.ofNullable(current); } @Override diff --git a/src/main/java/net/kemitix/node/NodeItem.java b/src/main/java/net/kemitix/node/NodeItem.java index 2ed9b02..519867f 100644 --- a/src/main/java/net/kemitix/node/NodeItem.java +++ b/src/main/java/net/kemitix/node/NodeItem.java @@ -263,14 +263,17 @@ class NodeItem implements Node { if (path.isEmpty()) { return Optional.empty(); } - Optional> found = findChild(path.get(0)); - if (found.isPresent()) { - if (path.size() > 1) { - return found.get().findInPath(path.subList(1, path.size())); + Node current = this; + for (T item : path) { + final Optional> child = current.findChild(item); + if (child.isPresent()) { + current = child.get(); + } else { + current = null; + break; } - return found; } - return Optional.empty(); + return Optional.ofNullable(current); } @Override From e28b140db82465965ae188ed3c5274a97847ee04 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Sun, 4 Sep 2016 19:28:20 +0100 Subject: [PATCH 32/41] NodeItem: simplify inserting child into path --- src/main/java/net/kemitix/node/NodeItem.java | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/main/java/net/kemitix/node/NodeItem.java b/src/main/java/net/kemitix/node/NodeItem.java index 519867f..067ef5e 100644 --- a/src/main/java/net/kemitix/node/NodeItem.java +++ b/src/main/java/net/kemitix/node/NodeItem.java @@ -301,15 +301,10 @@ class NodeItem implements Node { } return; } - String item = path[0]; - final Optional> childNamed = findChildByName(item); - Node child; - if (!childNamed.isPresent()) { - child = new NodeItem<>(null, item, this); - } else { - child = childNamed.get(); - } - child.insertInPath(nodeItem, Arrays.copyOfRange(path, 1, path.length)); + val item = path[0]; + findChildByName(item) + .orElseGet(() -> new NodeItem<>(null, item, this)) + .insertInPath(nodeItem, Arrays.copyOfRange(path, 1, path.length)); } @Override From 40f49fd832db73cf8fd04260e4b4d249b8334dd6 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Sun, 4 Sep 2016 19:40:08 +0100 Subject: [PATCH 33/41] NodeItem: refactored insertInPath to be easier to understand --- src/main/java/net/kemitix/node/NodeItem.java | 59 +++++++++++--------- 1 file changed, 34 insertions(+), 25 deletions(-) diff --git a/src/main/java/net/kemitix/node/NodeItem.java b/src/main/java/net/kemitix/node/NodeItem.java index 067ef5e..6c5f241 100644 --- a/src/main/java/net/kemitix/node/NodeItem.java +++ b/src/main/java/net/kemitix/node/NodeItem.java @@ -1,6 +1,7 @@ package net.kemitix.node; import lombok.NonNull; +import lombok.val; import java.util.*; import java.util.function.Function; @@ -279,32 +280,40 @@ class NodeItem implements Node { @Override public void insertInPath(final Node nodeItem, final String... path) { if (path.length == 0) { - if (!nodeItem.isNamed()) { - // nothing to conflict with - addChild(nodeItem); - return; - } - String nodeName = nodeItem.getName(); - final Optional> childNamed = findChildByName(nodeName); - 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 named '" + nodeName - + "' already exists here"); - } else { - nodeItem.getData().ifPresent(existing::setData); - } - return; + insertChild(nodeItem); + } else { + val item = path[0]; + findChildByName(item) + .orElseGet(() -> new NodeItem<>(null, item, this)) + .insertInPath(nodeItem, Arrays.copyOfRange(path, 1, path.length)); + } + } + + private void insertChild(final Node nodeItem) { + if (nodeItem.isNamed()) { + insertNamedChild(nodeItem); + } else { + // nothing to conflict with + addChild(nodeItem); + } + } + + private void insertNamedChild(final Node nodeItem) { + val childByName = findChildByName(nodeItem.getName()); + if (childByName.isPresent()) { + // we have an existing node with the same name + val existing = childByName.get(); + if (existing.isEmpty()) { + // place any data in the new node into the existing empty node + nodeItem.getData().ifPresent(existing::setData); + } else { + throw new NodeException("A non-empty node named '" + nodeItem.getName() + + "' already exists here"); + } + } else { + // nothing with the same name exists + addChild(nodeItem); } - val item = path[0]; - findChildByName(item) - .orElseGet(() -> new NodeItem<>(null, item, this)) - .insertInPath(nodeItem, Arrays.copyOfRange(path, 1, path.length)); } @Override From 1da9d44a8b70f879bb17b35c242b2d7d645c066d Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Sun, 4 Sep 2016 19:45:16 +0100 Subject: [PATCH 34/41] Node: add javadoc @deprecated to findOrCreateChild --- src/main/java/net/kemitix/node/Node.java | 3 +++ src/main/java/net/kemitix/node/NodeItem.java | 3 +++ 2 files changed, 6 insertions(+) diff --git a/src/main/java/net/kemitix/node/Node.java b/src/main/java/net/kemitix/node/Node.java index f1fa7ba..395ebb7 100644 --- a/src/main/java/net/kemitix/node/Node.java +++ b/src/main/java/net/kemitix/node/Node.java @@ -113,6 +113,9 @@ public interface Node { * @param child the child's data to search or create with * * @return the found or created child node + * + * @deprecated use node.findChild(child).orElseGet(() -> + * node.createChild(child)); */ @Deprecated Node findOrCreateChild(T child); diff --git a/src/main/java/net/kemitix/node/NodeItem.java b/src/main/java/net/kemitix/node/NodeItem.java index 6c5f241..c41a3d7 100644 --- a/src/main/java/net/kemitix/node/NodeItem.java +++ b/src/main/java/net/kemitix/node/NodeItem.java @@ -191,6 +191,9 @@ class NodeItem implements Node { * @param child the child's data to search or create with * * @return the found or created child node + * + * @deprecated use node.findChild(child).orElseGet(() -> node.createChild + * (child)); */ @Override @Deprecated From cd1afc6778b071fb3faba39d761f518d2ff66c20 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Sun, 4 Sep 2016 19:46:30 +0100 Subject: [PATCH 35/41] NodeItem: avoid import .* --- src/main/java/net/kemitix/node/NodeItem.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/net/kemitix/node/NodeItem.java b/src/main/java/net/kemitix/node/NodeItem.java index c41a3d7..7f0cd75 100644 --- a/src/main/java/net/kemitix/node/NodeItem.java +++ b/src/main/java/net/kemitix/node/NodeItem.java @@ -3,7 +3,11 @@ package net.kemitix.node; import lombok.NonNull; import lombok.val; -import java.util.*; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; import java.util.function.Function; /** From e4c4fdf4bc2bb12cfeeddc6002a1b992855ef5ab Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Sun, 4 Sep 2016 19:47:19 +0100 Subject: [PATCH 36/41] NodeItem,: use strange javadoc element ordering --- src/main/java/net/kemitix/node/NodeItem.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/net/kemitix/node/NodeItem.java b/src/main/java/net/kemitix/node/NodeItem.java index 7f0cd75..2ee8b97 100644 --- a/src/main/java/net/kemitix/node/NodeItem.java +++ b/src/main/java/net/kemitix/node/NodeItem.java @@ -13,9 +13,9 @@ import java.util.function.Function; /** * Represents a tree of nodes. * - * @param the type of data stored in each node - * * @author Paul Campbell + * + * @param the type of data stored in each node */ class NodeItem implements Node { From 9ec2668802d34aed90d84384e31a3256b57e5839 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Sun, 4 Sep 2016 19:49:13 +0100 Subject: [PATCH 37/41] NodeItem: wrap lines at 80 columns --- src/main/java/net/kemitix/node/NodeItem.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/net/kemitix/node/NodeItem.java b/src/main/java/net/kemitix/node/NodeItem.java index 2ee8b97..979fddd 100644 --- a/src/main/java/net/kemitix/node/NodeItem.java +++ b/src/main/java/net/kemitix/node/NodeItem.java @@ -292,7 +292,8 @@ class NodeItem implements Node { val item = path[0]; findChildByName(item) .orElseGet(() -> new NodeItem<>(null, item, this)) - .insertInPath(nodeItem, Arrays.copyOfRange(path, 1, path.length)); + .insertInPath(nodeItem, + Arrays.copyOfRange(path, 1, path.length)); } } @@ -314,8 +315,8 @@ class NodeItem implements Node { // place any data in the new node into the existing empty node nodeItem.getData().ifPresent(existing::setData); } else { - throw new NodeException("A non-empty node named '" + nodeItem.getName() - + "' already exists here"); + throw new NodeException("A non-empty node named '" + + nodeItem.getName() + "' already exists here"); } } else { // nothing with the same name exists From 69be86ba074ccc8408d87185bf3baeb05bee5d8c Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Sun, 4 Sep 2016 22:13:10 +0100 Subject: [PATCH 38/41] NodeItem: reduce complexity of addChild --- src/main/java/net/kemitix/node/NodeItem.java | 38 +++++++++++--------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/src/main/java/net/kemitix/node/NodeItem.java b/src/main/java/net/kemitix/node/NodeItem.java index 979fddd..f9ebf47 100644 --- a/src/main/java/net/kemitix/node/NodeItem.java +++ b/src/main/java/net/kemitix/node/NodeItem.java @@ -132,28 +132,34 @@ class NodeItem implements Node { */ @Override public void addChild(@NonNull final Node child) { - if (this.equals(child) || isDescendantOf(child)) { - throw new NodeException("Child is an ancestor"); - } - if (child.isNamed()) { - final Optional> existingChild = findChildByName( - child.getName()); - if (existingChild.isPresent() && existingChild.get() != child) { - throw new NodeException( - "Node with that name already exists here"); - } - } + verifyChildIsNotAnAncestor(child); + verifyChildWithSameNameDoesNotAlreadyExist(child); children.add(child); // update the child's parent if they don't have one or it is not this - Optional> childParent = child.getParent(); - boolean isOrphan = !childParent.isPresent(); - boolean hasDifferentParent = !isOrphan && !childParent.get() - .equals(this); - if (isOrphan || hasDifferentParent) { + val childParent = child.getParent(); + if (!childParent.isPresent() || !childParent.get().equals(this)) { child.setParent(this); } } + private void verifyChildWithSameNameDoesNotAlreadyExist( + final @NonNull Node child) { + if (child.isNamed()) { + findChildByName(child.getName()) + .filter(existingChild -> existingChild != child) + .ifPresent(existingChild -> { + throw new NodeException( + "Node with that name already exists here"); + }); + } + } + + private void verifyChildIsNotAnAncestor(final @NonNull Node child) { + if (this.equals(child) || isDescendantOf(child)) { + throw new NodeException("Child is an ancestor"); + } + } + /** * Creates a new node and adds it as a child of the current node. * From 29a5ceca827430896bab8952961100d1c9dd88d5 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Sun, 4 Sep 2016 22:19:22 +0100 Subject: [PATCH 39/41] NodeItem: remove support for dynamic names --- src/main/java/net/kemitix/node/NodeItem.java | 26 -------------------- 1 file changed, 26 deletions(-) diff --git a/src/main/java/net/kemitix/node/NodeItem.java b/src/main/java/net/kemitix/node/NodeItem.java index f9ebf47..12a7cef 100644 --- a/src/main/java/net/kemitix/node/NodeItem.java +++ b/src/main/java/net/kemitix/node/NodeItem.java @@ -8,7 +8,6 @@ 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. @@ -23,8 +22,6 @@ class NodeItem implements Node { private final Set> children = new HashSet<>(); - private Function, String> nameSupplier; - private Node parent; private String name; @@ -47,7 +44,6 @@ class NodeItem implements Node { */ NodeItem(final T data) { this.data = data; - this.nameSupplier = (n) -> null; } /** @@ -74,24 +70,8 @@ class NodeItem implements Node { 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() { - if (name == null) { - return generateName(); - } return name; } @@ -380,14 +360,8 @@ class NodeItem implements Node { public void removeParent() { if (parent != null) { Node oldParent = parent; - Function, String> supplier = getNameSupplier(); parent = null; oldParent.removeChild(this); - if (this.nameSupplier == null) { - // this is now a root node, so must provide a default name - // supplier - this.nameSupplier = supplier; - } } } From 4f4266fe3e87bf8226825afb01789664d6b094fc Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Tue, 13 Sep 2016 07:39:05 +0100 Subject: [PATCH 40/41] pom.xml: version set to 0.4.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index a502cc3..4835b9d 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 node - 0.4.0-SNAPSHOT + 0.4.0 jar Node From 4f713542831ceeaed81563c5c5abbbe3d794e1f4 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Tue, 13 Sep 2016 07:39:05 +0100 Subject: [PATCH 41/41] CHANGELOG --- CHANGELOG | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 66ec612..5ee231c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,13 @@ CHANGELOG ========= +0.4.0 +------ + +* Upgrade kemitix-parent to 2.0.0 +* Add ImmutableTree implementation +* Switch to static factory constructors + 0.3.0 ------