commit
f0b2ccbbb4
5 changed files with 744 additions and 2 deletions
157
src/main/java/net/kemitix/node/AbstractNodeItem.java
Normal file
157
src/main/java/net/kemitix/node/AbstractNodeItem.java
Normal file
|
@ -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 <T> the type of data stored in each node
|
||||
*
|
||||
* @author pcampbell
|
||||
*/
|
||||
abstract class AbstractNodeItem<T> implements Node<T> {
|
||||
|
||||
private T data;
|
||||
|
||||
private String name;
|
||||
|
||||
private Node<T> parent;
|
||||
|
||||
private final Set<Node<T>> children;
|
||||
|
||||
protected AbstractNodeItem(
|
||||
final T data, final String name, final Node<T> parent,
|
||||
final Set<Node<T>> children) {
|
||||
this.data = data;
|
||||
this.name = name;
|
||||
this.parent = parent;
|
||||
this.children = children;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<T> getData() {
|
||||
return Optional.ofNullable(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return data == null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Node<T>> getParent() {
|
||||
return Optional.ofNullable(parent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Node<T>> 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<Node<T>> findChild(final T child) {
|
||||
if (child == null) {
|
||||
throw new NullPointerException("child");
|
||||
}
|
||||
return children.stream().filter(node -> {
|
||||
final Optional<T> d = node.getData();
|
||||
return d.isPresent() && d.get().equals(child);
|
||||
}).findAny();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node<T> 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<T> 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<Node<T>> findInPath(final List<T> path) {
|
||||
if (path == null) {
|
||||
throw new NullPointerException("path");
|
||||
}
|
||||
if (path.size() > 0) {
|
||||
Optional<Node<T>> 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<Node<T>> findChildByName(final String named) {
|
||||
if (named == null) {
|
||||
throw new NullPointerException("name");
|
||||
}
|
||||
return children.stream()
|
||||
.filter(n -> n.getName().equals(named))
|
||||
.findAny();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node<T> 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;
|
||||
}
|
||||
}
|
95
src/main/java/net/kemitix/node/ImmutableNodeItem.java
Normal file
95
src/main/java/net/kemitix/node/ImmutableNodeItem.java
Normal file
|
@ -0,0 +1,95 @@
|
|||
package net.kemitix.node;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Represents an immutable tree of nodes.
|
||||
*
|
||||
* <p>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.</p>
|
||||
*
|
||||
* @param <T> the type of data stored in each node
|
||||
*
|
||||
* @author pcampbell
|
||||
*/
|
||||
final class ImmutableNodeItem<T> extends AbstractNodeItem<T> {
|
||||
|
||||
private static final String IMMUTABLE_OBJECT = "Immutable object";
|
||||
|
||||
private ImmutableNodeItem(
|
||||
final T data, final String name, final Node<T> parent,
|
||||
final Set<Node<T>> children) {
|
||||
super(data, name, parent, children);
|
||||
}
|
||||
|
||||
static <T> ImmutableNodeItem<T> newRoot(
|
||||
final T data, final String name, final Set<Node<T>> children) {
|
||||
return new ImmutableNodeItem<>(data, name, null, children);
|
||||
}
|
||||
|
||||
static <T> ImmutableNodeItem<T> newChild(
|
||||
final T data, final String name, final Node<T> parent,
|
||||
final Set<Node<T>> 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<T> parent) {
|
||||
throw new UnsupportedOperationException(IMMUTABLE_OBJECT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addChild(final Node<T> child) {
|
||||
throw new UnsupportedOperationException(IMMUTABLE_OBJECT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node<T> createChild(final T child) {
|
||||
throw new UnsupportedOperationException(IMMUTABLE_OBJECT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node<T> createChild(final T child, final String name) {
|
||||
throw new UnsupportedOperationException(IMMUTABLE_OBJECT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createDescendantLine(final List<T> descendants) {
|
||||
throw new UnsupportedOperationException(IMMUTABLE_OBJECT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node<T> findOrCreateChild(final T child) {
|
||||
return findChild(child).orElseThrow(
|
||||
() -> new UnsupportedOperationException(IMMUTABLE_OBJECT));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void insertInPath(final Node<T> node, final String... path) {
|
||||
throw new UnsupportedOperationException(IMMUTABLE_OBJECT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeChild(final Node<T> node) {
|
||||
throw new UnsupportedOperationException(IMMUTABLE_OBJECT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeParent() {
|
||||
throw new UnsupportedOperationException(IMMUTABLE_OBJECT);
|
||||
}
|
||||
|
||||
}
|
|
@ -14,7 +14,7 @@ import java.util.function.Function;
|
|||
*
|
||||
* @author pcampbell
|
||||
*/
|
||||
public class NodeItem<T> implements Node<T> {
|
||||
class NodeItem<T> implements Node<T> {
|
||||
|
||||
private T data;
|
||||
|
||||
|
|
|
@ -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 <T> 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 <T> the type of the data
|
||||
*
|
||||
* @return the immutable copy of the tree
|
||||
*/
|
||||
public static <T> Node<T> asImmutable(final Node<T> root) {
|
||||
if (root.getParent().isPresent()) {
|
||||
throw new IllegalArgumentException("source must be the root node");
|
||||
}
|
||||
final Set<Node<T>> children = getImmutableChildren(root);
|
||||
return ImmutableNodeItem.newRoot(root.getData().orElse(null),
|
||||
root.getName(), children);
|
||||
}
|
||||
|
||||
private static <T> Set<Node<T>> getImmutableChildren(final Node<T> source) {
|
||||
return source.getChildren()
|
||||
.stream()
|
||||
.map(Nodes::asImmutableChild)
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
private static <T> Node<T> asImmutableChild(
|
||||
final Node<T> source) {
|
||||
final Optional<Node<T>> 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");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
449
src/test/java/net/kemitix/node/ImmutableNodeItemTest.java
Normal file
449
src/test/java/net/kemitix/node/ImmutableNodeItemTest.java
Normal file
|
@ -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<String> 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<Node<String>> 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");
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue