diff --git a/src/main/java/net/kemitix/node/EmptyNodeException.java b/src/main/java/net/kemitix/node/EmptyNodeException.java new file mode 100644 index 0000000..16e3dfe --- /dev/null +++ b/src/main/java/net/kemitix/node/EmptyNodeException.java @@ -0,0 +1,42 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 Paul Campbell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +package net.kemitix.node; + +/** + * Raised when an attempt is made to get the data from an empty node. + * + * @author Paul Campbell (pcampbell@kemitix.net) + */ +public class EmptyNodeException extends NodeException { + + /** + * Constructor with message. + * + * @param message the message + */ + public EmptyNodeException(final String message) { + super(message); + } +} diff --git a/src/main/java/net/kemitix/node/Node.java b/src/main/java/net/kemitix/node/Node.java index d5128d6..19351fa 100644 --- a/src/main/java/net/kemitix/node/Node.java +++ b/src/main/java/net/kemitix/node/Node.java @@ -59,7 +59,14 @@ public interface Node { * * @return an Optional containing the node's data, or empty if the node has none */ - Optional getData(); + Optional findData(); + + /** + * Fetch the data held within the node. + * + * @return the node's data, or throws an {@link EmptyNodeException} + */ + T getData(); /** * Set the data held within the node. @@ -80,7 +87,14 @@ public interface Node { * * @return an Optional contain the parent node, or empty if a root node */ - Optional> getParent(); + Optional> findParent(); + + /** + * Fetch the parent node. + * + * @return the parent node, or throws an {@link OrphanedNodeException} + */ + Node getParent(); /** * Make the current node a direct child of the parent. diff --git a/src/main/java/net/kemitix/node/NodeItem.java b/src/main/java/net/kemitix/node/NodeItem.java index 6bb2658..739df89 100644 --- a/src/main/java/net/kemitix/node/NodeItem.java +++ b/src/main/java/net/kemitix/node/NodeItem.java @@ -42,7 +42,8 @@ import java.util.stream.Stream; * * @author Paul Campbell (pcampbell@kemitix.net) */ -@ToString(exclude = "children") +@ToString(exclude = {"children", "data", "parent"}) +@SuppressWarnings("methodcount") class NodeItem implements Node { private final Set> children = new HashSet<>(); @@ -61,9 +62,7 @@ class NodeItem implements Node { * @param parent the parent of the node, or null for a root node * @param children the children of the node - must not be null */ - NodeItem( - final T data, final String name, final Node parent, @NonNull final Set> children - ) { + NodeItem(final T data, final String name, final Node parent, @NonNull final Set> children) { this.data = data; this.name = name; if (parent != null) { @@ -92,10 +91,18 @@ class NodeItem implements Node { } @Override - public Optional getData() { + public Optional findData() { return Optional.ofNullable(data); } + @Override + public T getData() { + if (isEmpty()) { + throw new EmptyNodeException(getName()); + } + return data; + } + @Override public void setData(final T data) { this.data = data; @@ -107,10 +114,18 @@ class NodeItem implements Node { } @Override - public Optional> getParent() { + public Optional> findParent() { return Optional.ofNullable(parent); } + @Override + public Node getParent() { + if (parent == null) { + throw new OrphanedNodeException(getName()); + } + return parent; + } + /** * Make the current node a direct child of the parent. * @@ -155,9 +170,9 @@ class NodeItem implements Node { } children.add(child); // update the child's parent if they don't have one or it is not this - val childParent = child.getParent(); - if (!childParent.isPresent() || !childParent.get() - .equals(this)) { + if (!child.findParent() + .filter(this::equals) + .isPresent()) { child.setParent(this); } } @@ -214,8 +229,8 @@ class NodeItem implements Node { @Override public Optional> findChild(@NonNull final T child) { return children.stream() - .filter(node -> child.equals(node.getData() - .orElseGet(() -> null))) + .filter(node -> child.equals(node.findData() + .orElse(null))) .findFirst(); } @@ -283,7 +298,7 @@ class NodeItem implements Node { val existing = childByName.get(); if (existing.isEmpty()) { // place any data in the new node into the existing empty node - nodeItem.getData() + nodeItem.findData() .ifPresent(existing::setData); } else { throw new NodeException("A non-empty node named '" + nodeItem.getName() + "' already exists here"); diff --git a/src/main/java/net/kemitix/node/Nodes.java b/src/main/java/net/kemitix/node/Nodes.java index ffa8f74..e650d73 100644 --- a/src/main/java/net/kemitix/node/Nodes.java +++ b/src/main/java/net/kemitix/node/Nodes.java @@ -101,12 +101,12 @@ public final class Nodes { * @return the immutable copy of the tree */ public static Node asImmutable(final Node root) { - if (root.getParent() + if (root.findParent() .isPresent()) { throw new IllegalArgumentException("source must be the root node"); } final Set> children = getImmutableChildren(root); - return ImmutableNodeItem.newRoot(root.getData() + return ImmutableNodeItem.newRoot(root.findData() .orElse(null), root.getName(), children); } @@ -120,11 +120,10 @@ public final class Nodes { private static Node asImmutableChild( final Node source ) { - return ImmutableNodeItem.newChild(source.getData() - .orElse(null), source.getName(), source.getParent() + return ImmutableNodeItem.newChild(source.findData() + .orElse(null), source.getName(), source.findParent() .orElse(null), getImmutableChildren(source) ); } - } diff --git a/src/main/java/net/kemitix/node/OrphanedNodeException.java b/src/main/java/net/kemitix/node/OrphanedNodeException.java new file mode 100644 index 0000000..4f8587e --- /dev/null +++ b/src/main/java/net/kemitix/node/OrphanedNodeException.java @@ -0,0 +1,42 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 Paul Campbell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +package net.kemitix.node; + +/** + * Raised when an attempt is made to access the parent of a root node. + * + * @author Paul Campbell (pcampbell@kemitix.net) + */ +public class OrphanedNodeException extends NodeException { + + /** + * Constructor with message. + * + * @param message the message + */ + public OrphanedNodeException(final String message) { + super(message); + } +} diff --git a/src/test/java/net/kemitix/node/EmptyNodeExceptionTest.java b/src/test/java/net/kemitix/node/EmptyNodeExceptionTest.java new file mode 100644 index 0000000..85b6122 --- /dev/null +++ b/src/test/java/net/kemitix/node/EmptyNodeExceptionTest.java @@ -0,0 +1,29 @@ +package net.kemitix.node; + +import lombok.val; +import org.junit.Test; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +/** + * Tests for {@link EmptyNodeException}. + * + * @author pcampbell + */ +public class EmptyNodeExceptionTest { + + /** + * Test that message provided to constructor is returned. + */ + @Test + public void shouldReturnConstructorMessage() { + //given + val message = "this is the message"; + //when + val nodeException = new EmptyNodeException(message); + //then + assertThat(nodeException.getMessage(), is(message)); + } + +} diff --git a/src/test/java/net/kemitix/node/ImmutableNodeItemTest.java b/src/test/java/net/kemitix/node/ImmutableNodeItemTest.java index 2957775..6f5491d 100644 --- a/src/test/java/net/kemitix/node/ImmutableNodeItemTest.java +++ b/src/test/java/net/kemitix/node/ImmutableNodeItemTest.java @@ -74,8 +74,8 @@ public class ImmutableNodeItemTest { //given immutableNode = Nodes.asImmutable(Nodes.unnamedRoot("data")); //then - assertThat(immutableNode.getParent()).as("immutableNode created without a parent has no parent") - .isEmpty(); + assertThat(immutableNode.findParent()).as("immutableNode created without a parent has no parent") + .isEmpty(); } @Test @@ -101,11 +101,11 @@ public class ImmutableNodeItemTest { //then // get the immutable node's child's parent val immutableChild = immutableNode.getChildByName("child"); - final Optional> optionalParent = immutableChild.getParent(); + final Optional> optionalParent = immutableChild.findParent(); if (optionalParent.isPresent()) { val p = optionalParent.get(); assertThat(p).hasFieldOrPropertyWithValue("name", "root") - .hasFieldOrPropertyWithValue("data", Optional.of("parent")); + .hasFieldOrPropertyWithValue("data", "parent"); } } diff --git a/src/test/java/net/kemitix/node/NodeItemTest.java b/src/test/java/net/kemitix/node/NodeItemTest.java index 5df0c55..f91deef 100644 --- a/src/test/java/net/kemitix/node/NodeItemTest.java +++ b/src/test/java/net/kemitix/node/NodeItemTest.java @@ -72,8 +72,8 @@ public class NodeItemTest { //given node = Nodes.unnamedRoot("data"); //then - assertThat(node.getParent()).as("node created without a parent has no parent") - .isEmpty(); + assertThat(node.findParent()).as("node created without a parent has no parent") + .isEmpty(); } /** @@ -86,8 +86,8 @@ public class NodeItemTest { //when node = Nodes.unnamedChild("subject", parent); //then - assertThat(node.getParent()).as("node created with a parent can return the parent") - .contains(parent); + assertThat(node.findParent()).as("node created with a parent can return the parent") + .contains(parent); } @Test @@ -144,8 +144,8 @@ public class NodeItemTest { //when node.setParent(parent); //then - assertThat(node.getParent()).as("when a node is assigned a new parent that parent can be " + "returned") - .contains(parent); + assertThat(node.findParent()).as("when a node is assigned a new parent that parent can be " + "returned") + .contains(parent); } /** @@ -188,8 +188,8 @@ public class NodeItemTest { //when child.setParent(newParent); //then - assertThat(child.getParent()).as("when a node is assigned a new parent, the old parent is " + "replaced") - .contains(newParent); + assertThat(child.findParent()).as("when a node is assigned a new parent, the old parent is " + "replaced") + .contains(newParent); assertThat(node.findChild("child") .isPresent()).as( "when a node is assigned a new parent, the old parent no " + "longer has the node among it's children") @@ -209,9 +209,9 @@ public class NodeItemTest { //when newParent.addChild(child); //then - assertThat(child.getParent()).as("when a node with an existing parent is added as a child " + - "to another node, then the old parent is replaced") - .contains(newParent); + assertThat(child.findParent()).as("when a node with an existing parent is added as a child " + + "to another node, then the old parent is replaced") + .contains(newParent); assertThat(node.findChild("child") .isPresent()).as("when a node with an existing parent is added as a child to " + "another node, then the old parent no longer has " + @@ -316,8 +316,8 @@ public class NodeItemTest { //when node.addChild(child); //then - assertThat(child.getParent()).as("when a node is added as a child, the child has the node as " + "its parent") - .contains(node); + assertThat(child.findParent()).as("when a node is added as a child, the child has the node as " + "its parent") + .contains(node); } /** @@ -338,10 +338,9 @@ public class NodeItemTest { //then assertThat(result.isPresent()).as("when we walk the tree to a node it is found") .isTrue(); - if (result.isPresent()) { - assertThat(result.get()).as("when we walk the tree to a node the correct node is found") - .isSameAs(node); - } + result.ifPresent( + stringNode -> assertThat(stringNode).as("when we walk the tree to a node the correct node is found") + .isSameAs(node)); } /** @@ -408,31 +407,27 @@ public class NodeItemTest { val alphaOptional = node.findChild(alphaData); assertThat(alphaOptional.isPresent()).as("when creating a descendant line, the first element is found") .isTrue(); - if (alphaOptional.isPresent()) { - val alpha = alphaOptional.get(); - assertThat(alpha.getParent()).as( - "when creating a descendant line, the first element has " + "the current node as its parent") - .contains(node); + alphaOptional.ifPresent(alpha -> { + assertThat(alpha.findParent()).as( + "when creating a descendant line, the first element has the current node as its parent") + .contains(node); val betaOptional = alpha.findChild(betaData); - assertThat(betaOptional.isPresent()).as("when creating a descendant line, the second element is " + "found") + assertThat(betaOptional.isPresent()).as("when creating a descendant line, the second element is found") .isTrue(); - if (betaOptional.isPresent()) { - val beta = betaOptional.get(); - assertThat(beta.getParent()).as( - "when creating a descendant line, the second element " + "has the first as its parent") - .contains(alpha); + betaOptional.ifPresent(beta -> { + assertThat(beta.findParent()).as( + "when creating a descendant line, the second element has the first as its parent") + .contains(alpha); val gammaOptional = beta.findChild(gammaData); - assertThat(gammaOptional.isPresent()).as( - "when creating a descendant line, the third element " + "is found") + assertThat(gammaOptional.isPresent()).as("when creating a descendant line, the third element is found") .isTrue(); - if (gammaOptional.isPresent()) { - val gamma = gammaOptional.get(); - assertThat(gamma.getParent()).as( - "when creating a descendant line, the third " + "element has the second as its parent") - .contains(beta); - } - } - } + gammaOptional.ifPresent(gamma -> { + assertThat(gamma.findParent()).as( + "when creating a descendant line, the third element has the second as its parent") + .contains(beta); + }); + }); + }); } /** @@ -478,10 +473,9 @@ public class NodeItemTest { //then assertThat(found.isPresent()).as("when retrieving a child by its data, it is found") .isTrue(); - if (found.isPresent()) { - assertThat(found.get()).as("when retrieving a child by its data, it is the expected " + "node") - .isSameAs(child); - } + found.ifPresent( + stringNode -> assertThat(stringNode).as("when retrieving a child by its data, it is the expected node") + .isSameAs(child)); } /** @@ -509,17 +503,14 @@ public class NodeItemTest { //when val child = node.createChild(childData); //then - assertThat(child.getParent()).as( - "when creating a child node, the child has the current node " + "as its parent") - .contains(node); + assertThat(child.findParent()).as("when creating a child node, the child has the current node as its parent") + .contains(node); val foundChild = node.findChild(childData); - assertThat(foundChild.isPresent()).as("when creating a child node, the child can be found by its " + "data") + assertThat(foundChild.isPresent()).as("when creating a child node, the child can be found by its data") .isTrue(); - if (foundChild.isPresent()) { - assertThat(foundChild.get()).as( - "when creating a child node, the correct child can be " + "found by its data") - .isSameAs(child); - } + foundChild.ifPresent(stringNode -> assertThat(stringNode).as( + "when creating a child node, the correct child can be found by its data") + .isSameAs(child)); } /** @@ -538,7 +529,7 @@ public class NodeItemTest { @Test public void canCreateRootNodeWithoutData() { node = Nodes.namedRoot(null, "empty"); - assertThat(node.getData()).isEmpty(); + assertThat(node.findData()).isEmpty(); } @Test @@ -590,19 +581,15 @@ public class NodeItemTest { //when node.insertInPath(four, "one", "two", "three"); //then - val three = four.getParent() - .get(); + val three = four.getParent(); assertThat(four.getParent()).as("add node to a tree") .isNotNull(); assertThat(three.getName()).isEqualTo("three"); - val two = three.getParent() - .get(); + val two = three.getParent(); assertThat(two.getName()).isEqualTo("two"); - val one = two.getParent() - .get(); + val one = two.getParent(); assertThat(one.getName()).isEqualTo("one"); - assertThat(one.getParent() - .get()).isSameAs(node); + assertThat(one.getParent()).isSameAs(node); assertThat(node.getChildByName("one") .getChildByName("two") .getChildByName("three") @@ -662,7 +649,7 @@ public class NodeItemTest { //when child.removeParent(); //then - assertThat(child.getParent()).isEmpty(); + assertThat(child.findParent()).isEmpty(); assertThat(node.getChildren()).isEmpty(); } @@ -703,7 +690,7 @@ public class NodeItemTest { node.addChild(child); child.addChild(target); val addMe = Nodes.namedRoot("I'm new", "target"); - assertThat(addMe.getParent()).isEmpty(); + assertThat(addMe.findParent()).isEmpty(); assertThat(child.getChildByName("target") .isEmpty()).as("target starts empty") .isTrue(); @@ -761,7 +748,7 @@ public class NodeItemTest { node.removeChild(child); //then assertThat(node.getChildren()).isEmpty(); - assertThat(child.getParent()).isEmpty(); + assertThat(child.findParent()).isEmpty(); } @Test @@ -803,7 +790,7 @@ public class NodeItemTest { Node child = node.createChild("child data", "child name"); //then assertThat(child.getName()).isEqualTo("child name"); - assertThat(child.getParent()).contains(node); + assertThat(child.findParent()).contains(node); assertThat(node.getChildren()).containsExactly(child); } @@ -836,7 +823,7 @@ public class NodeItemTest { //when val child = Nodes.unnamedChild("child data", node); //then - assertThat(child.getParent()).contains(node); + assertThat(child.findParent()).contains(node); assertThat(node.getChildren()).containsExactly(child); } diff --git a/src/test/java/net/kemitix/node/NodesTest.java b/src/test/java/net/kemitix/node/NodesTest.java index 6c9aee4..e8badef 100644 --- a/src/test/java/net/kemitix/node/NodesTest.java +++ b/src/test/java/net/kemitix/node/NodesTest.java @@ -44,7 +44,7 @@ public class NodesTest { SoftAssertions softly = new SoftAssertions(); softly.assertThat(node.getData()).contains("data"); softly.assertThat(node.getName()).isEmpty(); - softly.assertThat(node.getParent()).contains(parent); + softly.assertThat(node.findParent()).contains(parent); softly.assertAll(); } @@ -55,7 +55,7 @@ public class NodesTest { SoftAssertions softly = new SoftAssertions(); softly.assertThat(node.getData()).contains("data"); softly.assertThat(node.getName()).isEqualTo("child"); - softly.assertThat(node.getParent()).contains(parent); + softly.assertThat(node.findParent()).contains(parent); softly.assertAll(); } diff --git a/src/test/java/net/kemitix/node/OrphanedNodeExceptionTest.java b/src/test/java/net/kemitix/node/OrphanedNodeExceptionTest.java new file mode 100644 index 0000000..a942219 --- /dev/null +++ b/src/test/java/net/kemitix/node/OrphanedNodeExceptionTest.java @@ -0,0 +1,29 @@ +package net.kemitix.node; + +import lombok.val; +import org.junit.Test; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +/** + * Tests for {@link OrphanedNodeException}. + * + * @author pcampbell + */ +public class OrphanedNodeExceptionTest { + + /** + * Test that message provided to constructor is returned. + */ + @Test + public void shouldReturnConstructorMessage() { + //given + val message = "this is the message"; + //when + val nodeException = new OrphanedNodeException(message); + //then + assertThat(nodeException.getMessage(), is(message)); + } + +}