Clean up Node interface (#60)

* NoteItem Remove @NonNull from private method parameters

* When creating node with null children treat as no children

* NodeItem use Lombok Getter/Setter

* Extract NodeTreeDraw

* Remove getData()

* Remove getChild()

* Remove getParent()

* Remove getChildByName()

* Merge NodeTreeDraw into Nodes

* Remove methodcound suppression

* [changelog] Updated
This commit is contained in:
Paul Campbell 2020-03-22 20:53:40 +00:00 committed by GitHub
parent 04e0e83748
commit 82f0c25b1d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 207 additions and 357 deletions

View file

@ -9,16 +9,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
** Added ** Added
- Add kemitix-maven-tiles - Add kemitix-maven-tiles 2.4.1
** Changed ** Changed
- Replace Jenkins with Github Actions (#57) - Replace Jenkins with Github Actions (#57)
- [checkstyle] suppress npath complexity issues - [checkstyle] suppress npath complexity issues
- [coverage] lower requirements - [coverage] lower requirements
- Clean up changelog and readme, and remove external build dependencies (#38)
- Pinned pitest-junit5-plugin at 0.9 (#59)
- Moved: Node.drawTree to Nodes (#60)
** Removed
- Removed from Node: getChildByName, getParent, getChild, getData (#60
** Dependencies ** Dependencies
- Bump kemitix-checkstyle-ruleset from 4.0.1 to 5.4.0 (#59)
- Bump kemitix-parent from 5.2.0 to 5.3.0 (#56)
- Bump lombok from 1.18.10 to 1.18.12 (#55)
- Bump assertj-core from 3.13.2 to 3.15.0 (#54)
- Bump junit from 4.12 to 4.13 (#53)
- Bump tiles-maven-plugin from 2.15 to 2.16 (#52)
- Bump hamcrest-core from 2.1 to 2.2 (#50) - Bump hamcrest-core from 2.1 to 2.2 (#50)
- Bump lombok from 1.18.8 to 1.18.10 (#49) - Bump lombok from 1.18.8 to 1.18.10 (#49)
- Bump assertj-core from 3.12.2 to 3.13.2 (#48) - Bump assertj-core from 3.12.2 to 3.13.2 (#48)
@ -29,7 +42,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Bump lombok from 1.18.4 to 1.18.6 (#41) - Bump lombok from 1.18.4 to 1.18.6 (#41)
- Bump tiles-maven-plugin from 2.12 to 2.13 (#40) - Bump tiles-maven-plugin from 2.12 to 2.13 (#40)
- Bump hamcrest-core from 1.3 to 2.1 (#37) - Bump hamcrest-core from 1.3 to 2.1 (#37)
- Clean up changelog and readme, and remove external build dependencies (#38)
* [0.7.0] - 2017-02-18 * [0.7.0] - 2017-02-18

View file

@ -58,13 +58,6 @@ public interface Node<T> {
*/ */
Optional<T> findData(); Optional<T> 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. * Set the data held within the node.
* *
@ -93,13 +86,6 @@ public interface Node<T> {
*/ */
Optional<Node<T>> findParent(); Optional<Node<T>> findParent();
/**
* Fetch the parent node.
*
* @return the parent node, or throws an {@link OrphanedNodeException}
*/
Node<T> getParent();
/** /**
* Make the current node a direct child of the parent. * Make the current node a direct child of the parent.
* *
@ -157,15 +143,6 @@ public interface Node<T> {
*/ */
Optional<Node<T>> findChild(T child); Optional<Node<T>> findChild(T child);
/**
* Fetches the node for the child if present.
*
* @param child the child's data to search for
*
* @return the child node if found
*/
Node<T> getChild(T child);
/** /**
* Checks if the node is an ancestor. * Checks if the node is an ancestor.
* *
@ -202,25 +179,6 @@ public interface Node<T> {
*/ */
Optional<Node<T>> findChildByName(String name); Optional<Node<T>> findChildByName(String name);
/**
* Returns the child with the given name. If one can't be found a
* NodeException is thrown.
*
* @param name the name of the child
*
* @return the node
*/
Node<T> getChildByName(String name);
/**
* Draw a representation of the tree.
*
* @param depth current depth for recursion
*
* @return a representation of the tree
*/
String drawTree(int depth);
/** /**
* Returns true if the Node has a name. Where a name supplier is used, the * Returns true if the Node has a name. Where a name supplier is used, the
* generated name is used. * generated name is used.

View file

@ -21,7 +21,9 @@
package net.kemitix.node; package net.kemitix.node;
import lombok.Getter;
import lombok.NonNull; import lombok.NonNull;
import lombok.Setter;
import lombok.val; import lombok.val;
import java.util.*; import java.util.*;
@ -38,15 +40,17 @@ import static net.kemitix.node.HeadTail.tail;
* *
* @author Paul Campbell (pcampbell@kemitix.net) * @author Paul Campbell (pcampbell@kemitix.net)
*/ */
@SuppressWarnings("methodcount")
class NodeItem<T> implements Node<T> { class NodeItem<T> implements Node<T> {
private final Set<Node<T>> children = new HashSet<>(); private final Set<Node<T>> children = new HashSet<>();
@Setter
private T data; private T data;
private Node<T> parent; private Node<T> parent;
@Setter
@Getter
private String name; private String name;
/** /**
@ -57,13 +61,20 @@ class NodeItem<T> implements Node<T> {
* @param parent the parent of the node, or null for a root 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 * @param children the children of the node - must not be null
*/ */
NodeItem(final T data, final String name, final Node<T> parent, @NonNull final Set<Node<T>> children) { NodeItem(
final T data,
final String name,
final Node<T> parent,
final Set<Node<T>> children
) {
this.data = data; this.data = data;
this.name = name; this.name = name;
if (parent != null) { if (parent != null) {
doSetParent(parent); doSetParent(parent);
} }
this.children.addAll(children); if (children != null) {
this.children.addAll(children);
}
} }
/** /**
@ -75,34 +86,11 @@ class NodeItem<T> implements Node<T> {
this.parent = newParent; this.parent = newParent;
} }
@Override
public String getName() {
return name;
}
@Override
public void setName(final String name) {
this.name = name;
}
@Override @Override
public Optional<T> findData() { public Optional<T> findData() {
return Optional.ofNullable(data); 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;
}
@Override @Override
public boolean isEmpty() { public boolean isEmpty() {
return data == null; return data == null;
@ -118,14 +106,6 @@ class NodeItem<T> implements Node<T> {
return Optional.ofNullable(parent); return Optional.ofNullable(parent);
} }
@Override
public Node<T> getParent() {
if (parent == null) {
throw new OrphanedNodeException(getName());
}
return parent;
}
/** /**
* Make the current node a direct child of the parent. * Make the current node a direct child of the parent.
* *
@ -137,7 +117,7 @@ class NodeItem<T> implements Node<T> {
} }
@SuppressWarnings("npathcomplexity") @SuppressWarnings("npathcomplexity")
private void doSetParent(@NonNull final Node<T> newParent) { private void doSetParent(final Node<T> newParent) {
if (this.equals(newParent) || newParent.isDescendantOf(this)) { if (this.equals(newParent) || newParent.isDescendantOf(this)) {
throw new NodeException("Parent is a descendant"); throw new NodeException("Parent is a descendant");
} }
@ -178,7 +158,7 @@ class NodeItem<T> implements Node<T> {
} }
} }
private void verifyChildIsNotAnAncestor(final @NonNull Node<T> child) { private void verifyChildIsNotAnAncestor(final Node<T> child) {
if (this.equals(child) || isDescendantOf(child)) { if (this.equals(child) || isDescendantOf(child)) {
throw new NodeException("Child is an ancestor"); throw new NodeException("Child is an ancestor");
} }
@ -237,11 +217,6 @@ class NodeItem<T> implements Node<T> {
.findFirst(); .findFirst();
} }
@Override
public Node<T> getChild(final T child) {
return findChild(child).orElseThrow(() -> new NodeException("Child not found"));
}
/** /**
* Checks if the node is an ancestor. * Checks if the node is an ancestor.
* *
@ -323,33 +298,6 @@ class NodeItem<T> implements Node<T> {
.findAny(); .findAny();
} }
@Override
public Node<T> getChildByName(final String named) {
final Optional<Node<T>> optional = findChildByName(named);
if (optional.isPresent()) {
return optional.get();
}
throw new NodeException("Named child not found");
}
@Override
@SuppressWarnings("movevariableinsideif")
public String drawTree(final int depth) {
final StringBuilder sb = new StringBuilder();
final String unnamed = "(unnamed)";
if (isNamed()) {
sb.append(formatByDepth(name, depth));
} else if (!children.isEmpty()) {
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 @Override
public boolean isNamed() { public boolean isNamed() {
String currentName = getName(); String currentName = getName();

View file

@ -85,7 +85,7 @@ public final class Nodes {
*/ */
public static <T> Node<T> namedChild( public static <T> Node<T> namedChild(
final T data, final String name, final Node<T> parent final T data, final String name, final Node<T> parent
) { ) {
return new NodeItem<>(data, name, parent, new HashSet<>()); return new NodeItem<>(data, name, parent, new HashSet<>());
} }
@ -104,23 +104,49 @@ public final class Nodes {
} }
final Set<Node<T>> children = getImmutableChildren(root); final Set<Node<T>> children = getImmutableChildren(root);
return ImmutableNodeItem.newRoot(root.findData() return ImmutableNodeItem.newRoot(root.findData()
.orElse(null), root.getName(), children); .orElse(null), root.getName(), children);
} }
private static <T> Set<Node<T>> getImmutableChildren(final Node<T> source) { private static <T> Set<Node<T>> getImmutableChildren(final Node<T> source) {
return source.getChildren() return source.getChildren()
.stream() .stream()
.map(Nodes::asImmutableChild) .map(Nodes::asImmutableChild)
.collect(Collectors.toSet()); .collect(Collectors.toSet());
} }
private static <T> Node<T> asImmutableChild( private static <T> Node<T> asImmutableChild(
final Node<T> source final Node<T> source
) { ) {
return ImmutableNodeItem.newChild(source.findData() return ImmutableNodeItem.newChild(source.findData()
.orElse(null), source.getName(), source.findParent() .orElse(null), source.getName(), source.findParent()
.orElse(null), .orElse(null),
getImmutableChildren(source) getImmutableChildren(source)
); );
}
/**
* Draw a representation of the tree.
*
* @param depth current depth for recursion
* @return a representation of the tree
*/
@SuppressWarnings("movevariableinsideif")
public static <T> String drawTree(
final Node<T> node,
final int depth
) {
final StringBuilder sb = new StringBuilder();
final String unnamed = "(unnamed)";
if (node.isNamed()) {
sb.append(formatByDepth(node.getName(), depth));
} else if (!node.getChildren().isEmpty()) {
sb.append(formatByDepth(unnamed, depth));
}
node.getChildren().forEach(c -> sb.append(drawTree(c, depth + 1)));
return sb.toString();
}
private static String formatByDepth(final String value, final int depth) {
return String.format("[%1$" + (depth + value.length()) + "s]\n", value);
} }
} }

View file

@ -11,6 +11,7 @@ import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ -33,18 +34,6 @@ public class ImmutableNodeItemTest {
exception.expectMessage(IMMUTABLE_OBJECT); 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 @Test
public void canCreateAnEmptyAndUnnamedNode() { public void canCreateAnEmptyAndUnnamedNode() {
//when //when
@ -81,14 +70,15 @@ public class ImmutableNodeItemTest {
@Test @Test
public void shouldContainImmutableCopyOfChild() { public void shouldContainImmutableCopyOfChild() {
//given //given
val parent = Nodes.unnamedRoot("root"); Node<String> parent = Nodes.unnamedRoot("root");
val child = Nodes.namedChild("child", "child", parent); Node<String> child = Nodes.namedChild("child", "child", parent);
//when //when
immutableNode = Nodes.asImmutable(parent); immutableNode = Nodes.asImmutable(parent);
//then //then
val immutableChild = immutableNode.getChildByName("child"); Optional<Node<String>> immutableChild =
assertThat(immutableChild).isNotSameAs(child); immutableNode.findChildByName("child");
assertThat(immutableChild.getName()).isEqualTo("child"); assertThat(immutableChild).isNotEqualTo(Optional.of(child));
assertThat(immutableChild.map(Node::getName)).contains("child");
} }
@Test @Test
@ -100,13 +90,14 @@ public class ImmutableNodeItemTest {
immutableNode = Nodes.asImmutable(parent); immutableNode = Nodes.asImmutable(parent);
//then //then
// get the immutable node's child's parent // get the immutable node's child's parent
val immutableChild = immutableNode.getChildByName("child"); Optional<Node<String>> foundParent =
final Optional<Node<String>> optionalParent = immutableChild.findParent(); immutableNode.findChildByName("child")
if (optionalParent.isPresent()) { .flatMap(Node::findParent);
val p = optionalParent.get(); assertThat(foundParent).isNotEmpty();
assertThat(p).hasFieldOrPropertyWithValue("name", "root") foundParent.ifPresent(p ->
.hasFieldOrPropertyWithValue("data", "parent"); assertThat(p)
} .hasFieldOrPropertyWithValue("name", "root")
.hasFieldOrPropertyWithValue("data", "parent"));
} }
@Test @Test
@ -215,10 +206,9 @@ public class ImmutableNodeItemTest {
val result = immutableNode.findChild("child"); val result = immutableNode.findChild("child");
//then //then
assertThat(result.isPresent()).isTrue(); assertThat(result.isPresent()).isTrue();
if (result.isPresent()) { result.map(resultNode ->
assertThat(result.get() assertThat(resultNode.findData())
.getData()).contains("child"); .contains("child"));
}
} }
/** /**
@ -244,9 +234,10 @@ public class ImmutableNodeItemTest {
root.addChild(beta); root.addChild(beta);
immutableNode = Nodes.asImmutable(root); immutableNode = Nodes.asImmutable(root);
//when //when
val result = immutableNode.getChildByName("alpha"); Optional<Node<String>> result = immutableNode.findChildByName("alpha");
//then //then
assertThat(result.getName()).isEqualTo(alpha.getName()); assertThat(result.map(Node::getName))
.contains(alpha.getName());
} }
@Test @Test
@ -257,11 +248,10 @@ public class ImmutableNodeItemTest {
val beta = Nodes.namedRoot("beta data", "beta"); val beta = Nodes.namedRoot("beta data", "beta");
root.addChild(alpha); root.addChild(alpha);
root.addChild(beta); root.addChild(beta);
exception.expect(NodeException.class);
exception.expectMessage("Named child not found");
immutableNode = Nodes.asImmutable(root); immutableNode = Nodes.asImmutable(root);
//when //then
immutableNode.getChildByName("gamma"); assertThat(immutableNode.findChildByName("gamma"))
.isEmpty();
} }
@Test @Test
@ -316,27 +306,6 @@ public class ImmutableNodeItemTest {
immutableNode.removeChild(null); 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 @Test
public void setDataShouldThrowException() { public void setDataShouldThrowException() {
//given //given
@ -355,28 +324,6 @@ public class ImmutableNodeItemTest {
immutableNode.createChild("child data", "child name"); 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 @Test
public void canSafelyHandleFindChildWhenAChildHasNoData() { public void canSafelyHandleFindChildWhenAChildHasNoData() {
//given //given
@ -437,15 +384,16 @@ public class ImmutableNodeItemTest {
Nodes.namedChild("eight", "eight", n6); Nodes.namedChild("eight", "eight", n6);
val immutableRoot = Nodes.asImmutable(node); val immutableRoot = Nodes.asImmutable(node);
//when //when
val result = immutableRoot.stream() val result = immutableRoot.stream().collect(Collectors.toList());
.collect(Collectors.toList());
//then //then
assertThat(result).as("full tree") assertThat(result).as("full tree").hasSize(9);
.hasSize(9);
// and // and
assertThat(immutableRoot.getChild("one") assertThat(immutableRoot
.stream() .findChild("one")
.collect(Collectors.toList())).as("sub-tree") .map(Node::stream)
.hasSize(4); .map(Stream::count)
)
.as("sub-tree")
.contains(4L);
} }
} }

View file

@ -0,0 +1,4 @@
package net.kemitix.node;
public interface IsNamedCategory {
}

View file

@ -27,27 +27,6 @@ public class NodeItemTest {
private Node<String> node; private Node<String> node;
@Test
public void getDataReturnsData() {
//given
val data = "this node data";
//when
node = Nodes.unnamedRoot(data);
//then
assertThat(node.getData()).as("can get the data from a node")
.contains(data);
}
@Test
public void getDataWhenEmptyThrowsException() throws Exception {
//given
node = Nodes.unnamedRoot(null);
assertThat(node.isEmpty()).isTrue();
exception.expect(EmptyNodeException.class);
//when
node.getData();
}
@Test @Test
public void findDataWhenFullReturnsData() { public void findDataWhenFullReturnsData() {
//given //given
@ -69,26 +48,6 @@ public class NodeItemTest {
assertThat(result).isEmpty(); assertThat(result).isEmpty();
} }
@Test
public void getParentWhenRootThrowsException() {
//given
node = Nodes.unnamedRoot(null);
exception.expect(OrphanedNodeException.class);
//when
node.getParent();
}
@Test
public void getParentWhenChildReturnsRoot() {
//given
val root = Nodes.unnamedRoot("root");
node = Nodes.unnamedChild("child", root);
//when
val result = node.getParent();
//then
assertThat(result).isSameAs(root);
}
@Test @Test
public void findParentWhenRootReturnsEmptyOptional() { public void findParentWhenRootReturnsEmptyOptional() {
//given //given
@ -607,34 +566,6 @@ public class NodeItemTest {
assertThat(node.findData()).isEmpty(); assertThat(node.findData()).isEmpty();
} }
@Test
public void getChildNamedFindsChild() {
//given
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
val result = node.getChildByName("alpha");
//then
assertThat(result).isSameAs(alpha);
}
@Test
public void getChildNamedFindsNothing() {
//given
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);
exception.expectMessage("Named child not found");
//when
node.getChildByName("gamma");
}
@Test @Test
public void nodeNamesAreUniqueWithinAParent() { public void nodeNamesAreUniqueWithinAParent() {
//given //given
@ -656,23 +587,38 @@ public class NodeItemTest {
//when //when
node.insertInPath(four, "one", "two", "three"); node.insertInPath(four, "one", "two", "three");
//then //then
val three = four.getParent(); assertThat(four.findParent())
assertThat(four.getParent()).as("add node to a tree") .as("add node to a tree")
.isNotNull(); .isNotEmpty();
assertThat(three.getName()).isEqualTo("three");
val two = three.getParent(); val three = four.findParent();
assertThat(two.getName()).isEqualTo("two"); assertThat(three).isNotEmpty();
val one = two.getParent(); three.map(threeNode ->
assertThat(one.getName()).isEqualTo("one"); assertThat(threeNode.getName())
assertThat(one.getParent()).isSameAs(node); .isEqualTo("three"));
assertThat(node.getChildByName("one")
.getChildByName("two") val two = three.flatMap(Node::findParent);
.getChildByName("three") assertThat(two).isNotEmpty();
.getChildByName("four")).isSameAs(four); two.map(twoNode ->
assertThat(twoNode.getName())
.isEqualTo("two"));
val one = two.flatMap(Node::findParent);
assertThat(one).isNotEmpty();
one.ifPresent(oneNode ->
SoftAssertions.assertSoftly(softly -> {
assertThat(oneNode.getName()).isEqualTo("one");
assertThat(oneNode.findParent()).contains(node);
}));
Optional<Node<String>> fourNode = node.findChildByName("one")
.flatMap(oneChild -> oneChild.findChildByName("two"))
.flatMap(twoChild -> twoChild.findChildByName("three"))
.flatMap(threeChild -> threeChild.findChildByName("four"));
assertThat(fourNode).isNotEmpty();
assertThat(fourNode).contains(four);
} }
@Test @Test
@SuppressWarnings("unchecked")
public void canPlaceInTreeUnderExistingNode() { public void canPlaceInTreeUnderExistingNode() {
//given //given
node = Nodes.namedRoot(null, "root"); node = Nodes.namedRoot(null, "root");
@ -682,15 +628,17 @@ public class NodeItemTest {
node.insertInPath(child); // as root/child node.insertInPath(child); // as root/child
node.insertInPath(grandchild, "child"); // as root/child/grandchild node.insertInPath(grandchild, "child"); // as root/child/grandchild
//then //then
assertThat(node.getChildByName("child")).as("child") assertThat(node.findChildByName("child"))
.isSameAs(child); .as("child")
assertThat(node.getChildByName("child") .contains(child);
.getChildByName("grandchild")).as("grandchild") Optional<Node<String>> grandNode = node.findChildByName("child")
.isSameAs(grandchild); .flatMap(childNode -> childNode.findChildByName("grandchild"));
assertThat(grandNode)
.as("grandchild")
.contains(grandchild);
} }
@Test @Test
@SuppressWarnings("unchecked")
public void canPlaceInTreeAboveExistingNode() { public void canPlaceInTreeAboveExistingNode() {
//given //given
node = Nodes.namedRoot(null, "root"); node = Nodes.namedRoot(null, "root");
@ -700,12 +648,14 @@ public class NodeItemTest {
node.insertInPath(grandchild, "child"); node.insertInPath(grandchild, "child");
node.insertInPath(child); node.insertInPath(child);
//then //then
assertThat(node.getChildByName("child") assertThat(node.findChildByName("child").flatMap(Node::findData))
.getData()).as("data in tree") .as("data in tree")
.contains("child data"); .contains("child data");
assertThat(node.getChildByName("child") assertThat(
.getChildByName("grandchild")).as("grandchild") node.findChildByName("child").flatMap(childNode ->
.isSameAs(grandchild); childNode.findChildByName("grandchild")))
.as("grandchild")
.contains(grandchild);
} }
@Test @Test
@ -756,7 +706,6 @@ public class NodeItemTest {
} }
@Test @Test
@SuppressWarnings("unchecked")
public void placeNodeInTreeWhenEmptyChildWithTargetNameExists() { public void placeNodeInTreeWhenEmptyChildWithTargetNameExists() {
//given //given
node = Nodes.unnamedRoot(null); node = Nodes.unnamedRoot(null);
@ -764,18 +713,18 @@ public class NodeItemTest {
final Node<String> target = Nodes.namedRoot(null, "target"); final Node<String> target = Nodes.namedRoot(null, "target");
node.addChild(child); node.addChild(child);
child.addChild(target); child.addChild(target);
val addMe = Nodes.namedRoot("I'm new", "target"); Node<String> addMe = Nodes.namedRoot("I'm new", "target");
assertThat(addMe.findParent()).isEmpty(); assertThat(addMe.findParent()).isEmpty();
assertThat(child.getChildByName("target") assertThat(child.findChildByName("target").flatMap(Node::findData))
.isEmpty()).as("target starts empty") .as("target starts empty")
.isTrue(); .isEmpty();
//when //when
// addMe should replace target as the sole descendant of child // addMe should replace target as the sole descendant of child
node.insertInPath(addMe, "child"); node.insertInPath(addMe, "child");
//then //then
assertThat(child.getChildByName("target") assertThat(child.findChildByName("target").flatMap(Node::findData))
.getData()).as("target now contains data") .as("target now contains data")
.contains("I'm new"); .contains("I'm new");
} }
@Test @Test
@ -789,14 +738,16 @@ public class NodeItemTest {
} }
@Test @Test
@Category(IsNamedCategory.class)
public void isNamedNull() { public void isNamedNull() {
//given //given
node = Nodes.unnamedRoot(null); node = Nodes.namedRoot(null, null);
//then //then
assertThat(node.isNamed()).isFalse(); assertThat(node.isNamed()).isFalse();
} }
@Test @Test
@Category(IsNamedCategory.class)
public void isNamedEmpty() { public void isNamedEmpty() {
//given //given
node = Nodes.namedRoot(null, ""); node = Nodes.namedRoot(null, "");
@ -805,6 +756,7 @@ public class NodeItemTest {
} }
@Test @Test
@Category(IsNamedCategory.class)
public void isNamedNamed() { public void isNamedNamed() {
//given //given
node = Nodes.namedRoot(null, "named"); node = Nodes.namedRoot(null, "named");
@ -826,26 +778,6 @@ public class NodeItemTest {
assertThat(child.findParent()).isEmpty(); assertThat(child.findParent()).isEmpty();
} }
@Test
public void drawTreeIsCorrect() {
//given
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
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 @Test
public void canChangeNodeData() { public void canChangeNodeData() {
//given //given
@ -853,7 +785,7 @@ public class NodeItemTest {
//when //when
node.setData("updated"); node.setData("updated");
//then //then
assertThat(node.getData()).contains("updated"); assertThat(node.findData()).contains("updated");
} }
@Test @Test
@ -869,27 +801,6 @@ public class NodeItemTest {
assertThat(node.getChildren()).containsExactly(child); assertThat(node.getChildren()).containsExactly(child);
} }
@Test
public void canGetChildWhenFound() {
//given
node = Nodes.unnamedRoot("data");
val child = Nodes.namedChild("child data", "child name", node);
//when
val found = node.getChild("child data");
//then
assertThat(found).isSameAs(child);
}
@Test
public void canGetChildWhenNotFound() {
//given
exception.expect(NodeException.class);
exception.expectMessage("Child not found");
node = Nodes.unnamedRoot("data");
//when
node.getChild("child data");
}
@Test @Test
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public void constructorWithNameSupplierAndParentBeChildOfParent() { public void constructorWithNameSupplierAndParentBeChildOfParent() {
@ -969,4 +880,17 @@ public class NodeItemTest {
assertThat(resultChild1).containsExactlyInAnyOrder(root); assertThat(resultChild1).containsExactlyInAnyOrder(root);
assertThat(resultChild3).containsExactlyInAnyOrder(child2, root); assertThat(resultChild3).containsExactlyInAnyOrder(child2, root);
} }
@Test
public void whenNodeItemChildrenAreNullThenAsNoChildren() {
//when
NodeItem<String> nodeItem = new NodeItem<>(
"data",
"name",
null,
null);
//then
assertThat(nodeItem.getChildren())
.isEmpty();
}
} }

View file

@ -0,0 +1,30 @@
package net.kemitix.node;
import lombok.val;
import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
public class NodeTreeDrawTest {
@Test
public void drawTreeIsCorrect() {
//given
final Node<String> 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 = Nodes.drawTree(node, 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]");
}
}

View file

@ -23,7 +23,7 @@ public class NodesTest {
public void shouldCreateUnnamedRoot() throws Exception { public void shouldCreateUnnamedRoot() throws Exception {
val node = Nodes.unnamedRoot("data"); val node = Nodes.unnamedRoot("data");
SoftAssertions softly = new SoftAssertions(); SoftAssertions softly = new SoftAssertions();
softly.assertThat(node.getData()).contains("data"); softly.assertThat(node.findData()).contains("data");
softly.assertThat(node.getName()).isEmpty(); softly.assertThat(node.getName()).isEmpty();
softly.assertAll(); softly.assertAll();
} }
@ -32,7 +32,7 @@ public class NodesTest {
public void shouldCreateNamedRoot() throws Exception { public void shouldCreateNamedRoot() throws Exception {
val node = Nodes.namedRoot("data", "name"); val node = Nodes.namedRoot("data", "name");
SoftAssertions softly = new SoftAssertions(); SoftAssertions softly = new SoftAssertions();
softly.assertThat(node.getData()).contains("data"); softly.assertThat(node.findData()).contains("data");
softly.assertThat(node.getName()).isEqualTo("name"); softly.assertThat(node.getName()).isEqualTo("name");
softly.assertAll(); softly.assertAll();
} }
@ -42,7 +42,7 @@ public class NodesTest {
val parent = Nodes.unnamedRoot("root"); val parent = Nodes.unnamedRoot("root");
val node = Nodes.unnamedChild("data", parent); val node = Nodes.unnamedChild("data", parent);
SoftAssertions softly = new SoftAssertions(); SoftAssertions softly = new SoftAssertions();
softly.assertThat(node.getData()).contains("data"); softly.assertThat(node.findData()).contains("data");
softly.assertThat(node.getName()).isEmpty(); softly.assertThat(node.getName()).isEmpty();
softly.assertThat(node.findParent()).contains(parent); softly.assertThat(node.findParent()).contains(parent);
softly.assertAll(); softly.assertAll();
@ -53,7 +53,7 @@ public class NodesTest {
val parent = Nodes.unnamedRoot("root"); val parent = Nodes.unnamedRoot("root");
val node = Nodes.namedChild("data", "child", parent); val node = Nodes.namedChild("data", "child", parent);
SoftAssertions softly = new SoftAssertions(); SoftAssertions softly = new SoftAssertions();
softly.assertThat(node.getData()).contains("data"); softly.assertThat(node.findData()).contains("data");
softly.assertThat(node.getName()).isEqualTo("child"); softly.assertThat(node.getName()).isEqualTo("child");
softly.assertThat(node.findParent()).contains(parent); softly.assertThat(node.findParent()).contains(parent);
softly.assertAll(); softly.assertAll();