KEYCLOAK-19562 Introduce generic trees
This commit is contained in:
parent
d6ae76d8f2
commit
53f02a50f6
3 changed files with 919 additions and 0 deletions
|
@ -0,0 +1,287 @@
|
|||
/*
|
||||
* Copyright 2021 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.models.map.storage.tree;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Deque;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Queue;
|
||||
import java.util.Stack;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* Generic implementation of a node in a tree.
|
||||
* <p>
|
||||
* Any method that is not purely on tree or nodes should go into a specialized subclass of this class!
|
||||
*
|
||||
* @author hmlnarik
|
||||
*/
|
||||
public class DefaultTreeNode<Self extends DefaultTreeNode<Self>> implements TreeNode<Self> {
|
||||
|
||||
private final Map<String, Object> edgeProperties = new HashMap<>();
|
||||
private final Map<String, Object> nodeProperties = new HashMap<>();
|
||||
private final Map<String, Object> treeProperties;
|
||||
private final LinkedList<Self> children = new LinkedList<>();
|
||||
private String id;
|
||||
private Self parent;
|
||||
|
||||
/**
|
||||
* @param treeProperties Reference to tree properties map. Tree properties are maintained outside of this node.
|
||||
*/
|
||||
protected DefaultTreeNode(Map<String, Object> treeProperties) {
|
||||
this.treeProperties = treeProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getEdgeProperties() {
|
||||
return this.edgeProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <V> Optional<V> getEdgeProperty(String key, Class<V> clazz) {
|
||||
final Object v = getEdgeProperties().get(key);
|
||||
return clazz.isInstance(v) ? Optional.of(clazz.cast(v)) : Optional.empty();
|
||||
}
|
||||
|
||||
public void setEdgeProperty(String property, Object value) {
|
||||
this.edgeProperties.put(property, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getNodeProperties() {
|
||||
return this.nodeProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <V> Optional<V> getNodeProperty(String key, Class<V> clazz) {
|
||||
final Object v = getNodeProperties().get(key);
|
||||
return clazz.isInstance(v) ? Optional.of(clazz.cast(v)) : Optional.empty();
|
||||
}
|
||||
|
||||
public void setNodeProperty(String property, Object value) {
|
||||
this.nodeProperties.put(property, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getTreeProperties() {
|
||||
return this.treeProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <V> Optional<V> getTreeProperty(String key, Class<V> clazz) {
|
||||
final Object v = getTreeProperties().get(key);
|
||||
return clazz.isInstance(v) ? Optional.of(clazz.cast(v)) : Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Self> findFirstDfs(Predicate<Self> visitor) {
|
||||
Deque<Self> stack = new LinkedList<>();
|
||||
stack.add((Self) this);
|
||||
while (! stack.isEmpty()) {
|
||||
Self node = stack.pop();
|
||||
if (visitor.test(node)) {
|
||||
return Optional.of(node);
|
||||
}
|
||||
List<Self> c = node.getChildren();
|
||||
for (ListIterator<Self> li = c.listIterator(c.size()); li.hasPrevious(); ) {
|
||||
stack.push(li.previous());
|
||||
}
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Self> findFirstBottommostDfs(Predicate<Self> visitor) {
|
||||
Deque<Self> stack = new LinkedList<>();
|
||||
stack.add((Self) this);
|
||||
while (! stack.isEmpty()) {
|
||||
Self node = stack.pop();
|
||||
if (visitor.test(node)) {
|
||||
// find the bottommost, i.e. inspect children first before returning this node
|
||||
for (Self child : node.getChildren()) {
|
||||
Optional<Self> childRes = child.findFirstBottommostDfs(visitor);
|
||||
if (childRes.isPresent()) {
|
||||
return childRes;
|
||||
}
|
||||
}
|
||||
return Optional.of(node);
|
||||
}
|
||||
List<Self> c = node.getChildren();
|
||||
for (ListIterator<Self> li = c.listIterator(c.size()); li.hasPrevious(); ) {
|
||||
stack.push(li.previous());
|
||||
}
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Self> findFirstBfs(Predicate<Self> visitor) {
|
||||
Queue<Self> queue = new LinkedList<>();
|
||||
queue.add((Self) this);
|
||||
while (! queue.isEmpty()) {
|
||||
Self node = queue.poll();
|
||||
if (visitor.test(node)) {
|
||||
return Optional.of(node);
|
||||
}
|
||||
queue.addAll(node.getChildren());
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Self> getPathToRoot(PathOrientation orientation) {
|
||||
LinkedList<Self> res = new LinkedList<>();
|
||||
Consumer<Self> addFunc = orientation == PathOrientation.BOTTOM_FIRST ? res::addLast : res::addFirst;
|
||||
Optional<Self> p = Optional.of((Self) this);
|
||||
while (p.isPresent()) {
|
||||
addFunc.accept(p.get());
|
||||
p = p.get().getParent();
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Self> getChildren() {
|
||||
return Collections.unmodifiableList(this.children);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addChild(Self node) {
|
||||
if (node == null) {
|
||||
return;
|
||||
}
|
||||
if (! this.children.contains(node)) {
|
||||
this.children.add(node);
|
||||
}
|
||||
node.setParent((Self) this);
|
||||
|
||||
// Prevent setting a parent of this node as a child of this node. In such a case, remove the parent of this node
|
||||
for (Optional<Self> p = getParent(); p.isPresent(); p = p.get().getParent()) {
|
||||
if (p.get() == node) {
|
||||
setParent(null);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addChild(int index, Self node) {
|
||||
if (node == null) {
|
||||
return;
|
||||
}
|
||||
if (! this.children.contains(node)) {
|
||||
this.children.add(index, node);
|
||||
}
|
||||
node.setParent((Self) this);
|
||||
|
||||
// Prevent setting a parent of this node as a child of this node. In such a case, remove the parent of this node
|
||||
for (Optional<Self> p = getParent(); p.isPresent(); p = p.get().getParent()) {
|
||||
if (p.get() == node) {
|
||||
setParent(null);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Self> getChild(String id) {
|
||||
for (Self c : children) {
|
||||
if (id.equals(c.getId())) {
|
||||
return Optional.of(c);
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int removeChild(Predicate<Self> shouldRemove) {
|
||||
if (shouldRemove == null) {
|
||||
return 0;
|
||||
}
|
||||
int res = 0;
|
||||
for (Iterator<Self> it = children.iterator(); it.hasNext();) {
|
||||
Self n = it.next();
|
||||
if (shouldRemove.test(n)) {
|
||||
it.remove();
|
||||
n.setParent(null);
|
||||
res++;
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Self> removeChild(Self node) {
|
||||
if (node == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
for (Iterator<Self> it = children.iterator(); it.hasNext();) {
|
||||
Self res = it.next();
|
||||
if (node.equals(res)) {
|
||||
it.remove();
|
||||
res.setParent(null);
|
||||
return Optional.of(res);
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Self> getParent() {
|
||||
return Optional.ofNullable(this.parent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setParent(Self parent) {
|
||||
if (this.parent == parent) {
|
||||
return;
|
||||
}
|
||||
if (parent == this) {
|
||||
setParent(null);
|
||||
}
|
||||
|
||||
if (this.parent != null) {
|
||||
Self previousParent = this.parent;
|
||||
this.parent = null;
|
||||
previousParent.removeChild((Self) this);
|
||||
}
|
||||
|
||||
if (parent != null) {
|
||||
this.parent = parent;
|
||||
parent.addChild((Self) this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,169 @@
|
|||
/*
|
||||
* Copyright 2021 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.models.map.storage.tree;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* Interface representing a node in a tree that has ID.
|
||||
* <p>
|
||||
* Think twice when adding a method here: if added method does not operate purely
|
||||
* on nodes or a generic tree, it does not belong here.
|
||||
* @author hmlnarik
|
||||
*/
|
||||
public interface TreeNode<Self extends TreeNode<? extends Self>> {
|
||||
|
||||
public enum PathOrientation { BOTTOM_FIRST, TOP_FIRST }
|
||||
|
||||
/**
|
||||
* Adds a node as a child of this node, and sets the parent of the {@code node} to this node.
|
||||
* @param node Future child node. If {@code null} or the node is already amongst the children list, no action is done.
|
||||
*/
|
||||
void addChild(Self node);
|
||||
|
||||
/**
|
||||
* Adds a node as a child of this node, and sets the parent of the {@code node} to this node.
|
||||
* @param index Index at which the specified element is to be inserted
|
||||
* @param node Future child node. If {@code null} or the node is already amongst the children list, no action is done.
|
||||
*
|
||||
*/
|
||||
void addChild(int index, Self node);
|
||||
|
||||
/**
|
||||
* Returns a node by ID. If there are more nodes with the same ID, any node from those may be returned.
|
||||
* @param id
|
||||
* @return
|
||||
*/
|
||||
Optional<Self> getChild(String id);
|
||||
|
||||
/**
|
||||
* Returns the children of the current node. Order does matter.
|
||||
* @return Read-only list of the children. Never returns {@code null}.
|
||||
*/
|
||||
List<Self> getChildren();
|
||||
|
||||
/**
|
||||
* Parent-to-this-node edge properties. For example, import/no-import mode or sync mode belongs here.
|
||||
* @return Returns properties of the edge from the parent to this node. Never returns {@code null}.
|
||||
*/
|
||||
Map<String, Object> getEdgeProperties();
|
||||
|
||||
/**
|
||||
* Convenience method for obtaining a single parent-to-this-node edge property.
|
||||
* @param <V>
|
||||
* @param key
|
||||
* @param clazz
|
||||
* @return {@code Optional} with a property value if it exists. Never returns {@code null}
|
||||
*/
|
||||
<V> Optional<V> getEdgeProperty(String key, Class<V> clazz);
|
||||
|
||||
/**
|
||||
* Returns ID of the node, which could match e.g. ID of the component with storage definition.
|
||||
* @return Node ID
|
||||
*/
|
||||
String getId();
|
||||
|
||||
/**
|
||||
* Properties of the this node. In storage context, properties of the single map storage represented by this
|
||||
* node, for example read-only/read-write flag.
|
||||
* @return Returns properties of the storage managed in this node. Never returns {@code null}.
|
||||
*/
|
||||
Map<String, Object> getNodeProperties();
|
||||
|
||||
/**
|
||||
* Convenience method for obtaining a single property of this node.
|
||||
* @param <V>
|
||||
* @param key
|
||||
* @param clazz
|
||||
* @return {@code Optional} with a property value if it exists. Never returns {@code null}
|
||||
*/
|
||||
<V> Optional<V> getNodeProperty(String key, Class<V> clazz);
|
||||
|
||||
/**
|
||||
* Properties of the whole tree. For example, kind of the stored objects, e.g. realms or clients.
|
||||
* @return Returns properties of the tree that contains in this node. Never returns {@code null}.
|
||||
*/
|
||||
Map<String, Object> getTreeProperties();
|
||||
|
||||
/**
|
||||
* Convenience method for obtaining a single property of tree that this node belongs to.
|
||||
* @param <V>
|
||||
* @param key
|
||||
* @param clazz
|
||||
* @return {@code Optional} with a property value if it exists. Never returns {@code null}
|
||||
*/
|
||||
<V> Optional<V> getTreeProperty(String key, Class<V> clazz);
|
||||
|
||||
/**
|
||||
* Removes the given child node.
|
||||
* @param node Node to remove
|
||||
* @return Removed node
|
||||
*/
|
||||
Optional<Self> removeChild(Self node);
|
||||
|
||||
/**
|
||||
* Removes child nodes satisfying the given predicate.
|
||||
* @param node Predicate on node returning {@code true} for each node that should be removed
|
||||
* @return Number of removed nodes
|
||||
*/
|
||||
int removeChild(Predicate<Self> shouldRemove);
|
||||
|
||||
/**
|
||||
* Returns parent node or an empty {@code Optional} if this node is a root node.
|
||||
* @return See description. Never returns {@code null}.
|
||||
*/
|
||||
Optional<Self> getParent();
|
||||
|
||||
/**
|
||||
* Sets the parent node to the given {@code parent}. If this node was a child of another node,
|
||||
* also removes this node from the children of the previous parent.
|
||||
* @param parent New parent node or {@code null} if this node should be parentless.
|
||||
*/
|
||||
void setParent(Self parent);
|
||||
|
||||
/**
|
||||
* Depth-first search for a node.
|
||||
* @param visitor Predicate on nodes, returns {@code true} when a search condition is satisfied which terminates the search.
|
||||
* @return Leftmost first node that matches the predicate, {@code null} when no node matches.
|
||||
*/
|
||||
Optional<Self> findFirstDfs(Predicate<Self> visitor);
|
||||
|
||||
/**
|
||||
* Depth-first search for a node that is bottommost from those matching DFS.
|
||||
* @param visitor Predicate on nodes, returns {@code true} when a search condition is satisfied which terminates the search.
|
||||
* @return Leftmost and bottommost node that matches the predicate, {@code null} when no node matches.
|
||||
*/
|
||||
Optional<Self> findFirstBottommostDfs(Predicate<Self> visitor);
|
||||
|
||||
/**
|
||||
* Breadth-first search for a node.
|
||||
* @param visitor Predicate on nodes, returns {@code true} when a search condition is satisfied which terminates the search.
|
||||
* @return First node that matches the predicate, {@code null} when no node matches.
|
||||
*/
|
||||
Optional<Self> findFirstBfs(Predicate<Self> visitor);
|
||||
|
||||
/**
|
||||
* Returns the path (list of nodes) from this node to root node.
|
||||
* @param orientation Determines order of the nodes in the returned list - either this node is first and the root node
|
||||
* is last, ({@link PathOrientation#BOTTOM_FIRST}) or vice versa ({@link PathOrientation#TOP_FIRST}).
|
||||
* @return
|
||||
*/
|
||||
List<Self> getPathToRoot(PathOrientation orientation);
|
||||
}
|
|
@ -0,0 +1,463 @@
|
|||
/*
|
||||
* Copyright 2021 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.models.map.storage.tree;
|
||||
|
||||
import org.keycloak.models.map.storage.tree.TreeNode.PathOrientation;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import org.junit.Test;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.hamcrest.Matchers.containsInAnyOrder;
|
||||
import static org.hamcrest.Matchers.empty;
|
||||
import static org.hamcrest.Matchers.hasKey;
|
||||
import static org.hamcrest.Matchers.hasSize;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.hamcrest.Matchers.notNullValue;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author hmlnarik
|
||||
*/
|
||||
public class DefaultTreeNodeTest {
|
||||
|
||||
private class Node extends DefaultTreeNode<Node> {
|
||||
public Node() {
|
||||
super(treeProperties);
|
||||
}
|
||||
public Node(String id) {
|
||||
super(treeProperties);
|
||||
setId(id);
|
||||
}
|
||||
public Node(Node parent, String id) {
|
||||
super(treeProperties);
|
||||
setId(id);
|
||||
setParent(parent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.getId() == null ? "Node:" + System.identityHashCode(this) : this.getId();
|
||||
}
|
||||
}
|
||||
|
||||
private static final String KEY_1 = "key1";
|
||||
private static final String VALUE_1 = "value";
|
||||
private static final String KEY_2 = "key2";
|
||||
private static final Date VALUE_2 = new Date();
|
||||
private static final String KEY_3 = "key3";
|
||||
private static final Integer VALUE_3 = 12345;
|
||||
|
||||
public Map<String, Object> treeProperties = new HashMap<>();
|
||||
{
|
||||
treeProperties.put(KEY_1, VALUE_1);
|
||||
treeProperties.put(KEY_2, VALUE_2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSingleNodeTree() {
|
||||
Node root = new Node();
|
||||
root.setNodeProperty(KEY_1, VALUE_1);
|
||||
root.setEdgeProperty(KEY_2, VALUE_2);
|
||||
|
||||
assertThat(root.getParent(), is(Optional.empty()));
|
||||
assertThat(root.getChildren(), empty());
|
||||
|
||||
assertNodeProperty(root, KEY_1, VALUE_1);
|
||||
assertNodeProperty(root, KEY_2, null);
|
||||
assertEdgeProperty(root, KEY_1, null);
|
||||
assertEdgeProperty(root, KEY_2, VALUE_2);
|
||||
|
||||
assertTreeProperties(root);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSimpleTwoNodeTree() {
|
||||
Node root = new Node();
|
||||
Node child = new Node();
|
||||
root.setNodeProperty(KEY_1, VALUE_1);
|
||||
|
||||
child.setParent(root);
|
||||
child.setId("my-id");
|
||||
child.setEdgeProperty(KEY_2, VALUE_2);
|
||||
|
||||
// check parent-child relationships
|
||||
assertThat(root.getParent(), is(Optional.empty()));
|
||||
assertThat(root.getChildren(), hasSize(1));
|
||||
|
||||
assertThat(child.getParent(), is(Optional.of(root)));
|
||||
assertThat(child.getChildren(), empty());
|
||||
|
||||
// check properties
|
||||
assertThat(root.getNodeProperties().keySet(), hasSize(1));
|
||||
assertThat(root.getEdgeProperties().keySet(), empty());
|
||||
assertThat(child.getNodeProperties().keySet(), empty());
|
||||
assertThat(child.getEdgeProperties().keySet(), hasSize(1));
|
||||
assertTreeProperties(root);
|
||||
assertTreeProperties(child);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSimpleTwoNodeTreeSwapped() {
|
||||
Node root = new Node();
|
||||
Node child = new Node();
|
||||
child.setParent(root);
|
||||
child.setId("my-id");
|
||||
|
||||
// Now swap the roles
|
||||
root.setParent(child);
|
||||
|
||||
// check parent-child relationships
|
||||
assertThat(child.getParent(), is(Optional.empty()));
|
||||
assertThat(child.getChildren(), hasSize(1));
|
||||
|
||||
assertThat(root.getParent(), is(Optional.of(child)));
|
||||
assertThat(root.getChildren(), empty());
|
||||
|
||||
// check properties have not changed
|
||||
root.setNodeProperty(KEY_1, VALUE_1);
|
||||
child.setEdgeProperty(KEY_2, VALUE_2);
|
||||
assertThat(root.getNodeProperties().keySet(), hasSize(1));
|
||||
assertThat(root.getEdgeProperties().keySet(), empty());
|
||||
assertThat(child.getNodeProperties().keySet(), empty());
|
||||
assertThat(child.getEdgeProperties().keySet(), hasSize(1));
|
||||
assertTreeProperties(root);
|
||||
assertTreeProperties(child);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStructureLinearThreeNodeSwapped() {
|
||||
Node level1 = new Node();
|
||||
Node level2 = new Node();
|
||||
Node level3 = new Node();
|
||||
|
||||
level2.setParent(level1);
|
||||
level3.setParent(level2);
|
||||
|
||||
// check parent-child relationships
|
||||
assertThat(level1.getParent(), is(Optional.empty()));
|
||||
assertThat(level1.getChildren(), containsInAnyOrder(level2));
|
||||
assertThat(level2.getParent(), is(Optional.of(level1)));
|
||||
assertThat(level2.getChildren(), containsInAnyOrder(level3));
|
||||
assertThat(level3.getParent(), is(Optional.of(level2)));
|
||||
assertThat(level3.getChildren(), empty());
|
||||
|
||||
// Swap nodes
|
||||
level1.setParent(level3);
|
||||
|
||||
// check parent-child relationships
|
||||
assertThat(level3.getParent(), is(Optional.empty()));
|
||||
assertThat(level3.getChildren(), containsInAnyOrder(level1));
|
||||
assertThat(level1.getParent(), is(Optional.of(level3)));
|
||||
assertThat(level1.getChildren(), containsInAnyOrder(level2));
|
||||
assertThat(level2.getParent(), is(Optional.of(level1)));
|
||||
assertThat(level2.getChildren(), empty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStructureAThreeNodeSwapped() {
|
||||
Node level1 = new Node();
|
||||
Node level21 = new Node();
|
||||
Node level22 = new Node();
|
||||
Node level23 = new Node();
|
||||
|
||||
level21.setParent(level1);
|
||||
level22.setParent(level1);
|
||||
level23.setParent(level1);
|
||||
|
||||
// check parent-child relationships
|
||||
assertThat(level1.getParent(), is(Optional.empty()));
|
||||
assertThat(level1.getChildren(), containsInAnyOrder(level21, level22, level23));
|
||||
assertThat(level21.getParent(), is(Optional.of(level1)));
|
||||
assertThat(level21.getChildren(), empty());
|
||||
assertThat(level22.getParent(), is(Optional.of(level1)));
|
||||
assertThat(level22.getChildren(), empty());
|
||||
assertThat(level23.getParent(), is(Optional.of(level1)));
|
||||
assertThat(level23.getChildren(), empty());
|
||||
|
||||
// Change parents
|
||||
level1.setParent(level22);
|
||||
|
||||
// check parent-child relationships
|
||||
assertThat(level22.getParent(), is(Optional.empty()));
|
||||
assertThat(level22.getChildren(), containsInAnyOrder(level1));
|
||||
assertThat(level1.getParent(), is(Optional.of(level22)));
|
||||
assertThat(level1.getChildren(), containsInAnyOrder(level21, level23));
|
||||
assertThat(level21.getParent(), is(Optional.of(level1)));
|
||||
assertThat(level21.getChildren(), empty());
|
||||
assertThat(level23.getParent(), is(Optional.of(level1)));
|
||||
assertThat(level23.getChildren(), empty());
|
||||
|
||||
// Change parents
|
||||
level21.setParent(level22);
|
||||
|
||||
// check parent-child relationships
|
||||
assertThat(level22.getParent(), is(Optional.empty()));
|
||||
assertThat(level22.getChildren(), containsInAnyOrder(level1, level21));
|
||||
assertThat(level1.getParent(), is(Optional.of(level22)));
|
||||
assertThat(level1.getChildren(), containsInAnyOrder(level23));
|
||||
assertThat(level21.getParent(), is(Optional.of(level22)));
|
||||
assertThat(level21.getChildren(), empty());
|
||||
assertThat(level23.getParent(), is(Optional.of(level1)));
|
||||
assertThat(level23.getChildren(), empty());
|
||||
|
||||
// Change parents
|
||||
level21.setParent(null);
|
||||
|
||||
// check parent-child relationships
|
||||
assertThat(level22.getParent(), is(Optional.empty()));
|
||||
assertThat(level22.getChildren(), containsInAnyOrder(level1));
|
||||
assertThat(level1.getParent(), is(Optional.of(level22)));
|
||||
assertThat(level1.getChildren(), containsInAnyOrder(level23));
|
||||
assertThat(level21.getParent(), is(Optional.empty()));
|
||||
assertThat(level21.getChildren(), empty());
|
||||
assertThat(level23.getParent(), is(Optional.of(level1)));
|
||||
assertThat(level23.getChildren(), empty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChangeId() {
|
||||
Node root = new Node();
|
||||
Node child1 = new Node();
|
||||
child1.setParent(root);
|
||||
child1.setId("my-id1");
|
||||
Node child2 = new Node();
|
||||
child2.setParent(root);
|
||||
child2.setId("my-id2");
|
||||
|
||||
// check parent-child relationships
|
||||
assertThat(root.getChild("my-id1"), is(Optional.of(child1)));
|
||||
assertThat(root.getChild("my-id2"), is(Optional.of(child2)));
|
||||
|
||||
child1.setId("my-id3");
|
||||
assertThat(root.getChild("my-id1"), is(Optional.empty()));
|
||||
assertThat(root.getChild("my-id3"), is(Optional.of(child1)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveChildDirectly() {
|
||||
Node root = new Node();
|
||||
Node child1 = new Node();
|
||||
child1.setParent(root);
|
||||
child1.setId("my-id1");
|
||||
Node child2 = new Node();
|
||||
child2.setParent(root);
|
||||
child2.setId("my-id2");
|
||||
|
||||
// check parent-child relationships
|
||||
assertThat(root.getChild("my-id1"), is(Optional.of(child1)));
|
||||
assertThat(root.getChild("my-id2"), is(Optional.of(child2)));
|
||||
|
||||
assertThat(root.removeChild(child1), is(Optional.of(child1)));
|
||||
|
||||
assertThat(root.getChildren(), containsInAnyOrder(child2));
|
||||
assertThat(child1.getParent(), is(Optional.empty()));
|
||||
|
||||
assertThat(root.removeChild(child1), is(Optional.empty()));
|
||||
|
||||
// try to remove it once again
|
||||
assertThat(root.getChildren(), containsInAnyOrder(child2));
|
||||
assertThat(child1.getParent(), is(Optional.empty()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveChildViaPredicate() {
|
||||
Node root = new Node();
|
||||
Node child1 = new Node();
|
||||
child1.setParent(root);
|
||||
child1.setId("my-id1");
|
||||
Node child2 = new Node();
|
||||
child2.setParent(root);
|
||||
child2.setId("my-id2");
|
||||
Node child3 = new Node();
|
||||
child3.setParent(root);
|
||||
child3.setId("my-id3");
|
||||
|
||||
// check removals
|
||||
assertThat(root.removeChild(node -> "my-id1".equals(node.getId())), is(1));
|
||||
|
||||
assertThat(root.getChildren(), containsInAnyOrder(child2, child3));
|
||||
assertThat(child1.getParent(), is(Optional.empty()));
|
||||
assertThat(child2.getParent(), is(Optional.of(root)));
|
||||
assertThat(child3.getParent(), is(Optional.of(root)));
|
||||
|
||||
assertThat(root.removeChild(node -> true), is(2));
|
||||
|
||||
assertThat(root.getChildren(), empty());
|
||||
assertThat(child1.getParent(), is(Optional.empty()));
|
||||
assertThat(child2.getParent(), is(Optional.empty()));
|
||||
assertThat(child3.getParent(), is(Optional.empty()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveChild() {
|
||||
Node root = new Node();
|
||||
Node child1 = new Node();
|
||||
child1.setParent(root);
|
||||
child1.setId("my-id1");
|
||||
Node child2 = new Node();
|
||||
child2.setParent(root);
|
||||
child2.setId("my-id2");
|
||||
|
||||
// check parent-child relationships
|
||||
assertThat(root.getChild("my-id1"), is(Optional.of(child1)));
|
||||
assertThat(root.getChild("my-id2"), is(Optional.of(child2)));
|
||||
|
||||
root.removeChild(child1);
|
||||
|
||||
assertThat(root.getChildren(), containsInAnyOrder(child2));
|
||||
assertThat(child1.getParent(), is(Optional.empty()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDfs() {
|
||||
Node root = new Node("1");
|
||||
Node child11 = new Node(root, "1.1");
|
||||
Node child12 = new Node(root, "1.2");
|
||||
Node child111 = new Node(child11, "1.1.1");
|
||||
Node child112 = new Node(child11, "1.1.2");
|
||||
Node child121 = new Node(child12, "1.2.1");
|
||||
Node child122 = new Node(child12, "1.2.2");
|
||||
Node child123 = new Node(child12, "1.2.3");
|
||||
Node child1121 = new Node(child112, "1.1.2.1");
|
||||
|
||||
List<Node> res = new LinkedList<>();
|
||||
assertThat(root.findFirstDfs(n -> { res.add(n); return false; }), is(Optional.empty()));
|
||||
assertThat(res, contains(root, child11, child111, child112, child1121, child12, child121, child122, child123));
|
||||
|
||||
res.clear();
|
||||
assertThat(root.findFirstDfs(n -> { res.add(n); return n == child12; }), is(Optional.of(child12)));
|
||||
assertThat(res, contains(root, child11, child111, child112, child1121, child12));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDfsBottommost() {
|
||||
Node root = new Node("1");
|
||||
Node child11 = new Node(root, "1.1");
|
||||
Node child12 = new Node(root, "1.2");
|
||||
Node child13 = new Node(root, "1.3");
|
||||
Node child111 = new Node(child11, "1.1.1");
|
||||
Node child112 = new Node(child11, "1.1.2");
|
||||
Node child121 = new Node(child12, "1.2.1");
|
||||
Node child122 = new Node(child12, "1.2.2");
|
||||
Node child123 = new Node(child12, "1.2.3");
|
||||
Node child1121 = new Node(child112, "1.1.2.1");
|
||||
Node child131 = new Node(child13, "1.3.1");
|
||||
Node child132 = new Node(child13, "1.3.2");
|
||||
|
||||
List<Node> res = new LinkedList<>();
|
||||
assertThat(root.findFirstBottommostDfs(n -> { res.add(n); return false; }), is(Optional.empty()));
|
||||
assertThat(res, contains(root, child11, child111, child112, child1121, child12, child121, child122, child123, child13, child131, child132));
|
||||
|
||||
res.clear();
|
||||
assertThat(root.findFirstBottommostDfs(n -> { res.add(n); return n == child12; }), is(Optional.of(child12)));
|
||||
assertThat(res, contains(root, child11, child111, child112, child1121, child12, child121, child122, child123));
|
||||
|
||||
res.clear();
|
||||
assertThat(root.findFirstBottommostDfs(n -> { res.add(n); return n.getId().startsWith("1.1.2"); }), is(Optional.of(child1121)));
|
||||
assertThat(res, contains(root, child11, child111, child112, child1121));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBfs() {
|
||||
Node root = new Node("1");
|
||||
Node child11 = new Node(root, "1.1");
|
||||
Node child12 = new Node(root, "1.2");
|
||||
Node child111 = new Node(child11, "1.1.1");
|
||||
Node child112 = new Node(child11, "1.1.2");
|
||||
Node child121 = new Node(child12, "1.2.1");
|
||||
Node child122 = new Node(child12, "1.2.2");
|
||||
Node child123 = new Node(child12, "1.2.3");
|
||||
Node child1121 = new Node(child112, "1.1.2.1");
|
||||
|
||||
List<Node> res = new LinkedList<>();
|
||||
assertThat(root.findFirstBfs(n -> { res.add(n); return false; }), is(Optional.empty()));
|
||||
assertThat(res, contains(root, child11, child12, child111, child112, child121, child122, child123, child1121));
|
||||
|
||||
res.clear();
|
||||
assertThat(root.findFirstBfs(n -> { res.add(n); return n == child12; }), is(Optional.of(child12)));
|
||||
assertThat(res, contains(root, child11, child12));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPathToRoot() {
|
||||
Node root = new Node("1");
|
||||
Node child11 = new Node(root, "1.1");
|
||||
Node child12 = new Node(root, "1.2");
|
||||
Node child111 = new Node(child11, "1.1.1");
|
||||
Node child112 = new Node(child11, "1.1.2");
|
||||
Node child121 = new Node(child12, "1.2.1");
|
||||
Node child122 = new Node(child12, "1.2.2");
|
||||
Node child123 = new Node(child12, "1.2.3");
|
||||
Node child1121 = new Node(child112, "1.1.2.1");
|
||||
|
||||
assertThat(child1121.getPathToRoot(PathOrientation.TOP_FIRST), contains(root, child11, child112, child1121));
|
||||
assertThat(child123.getPathToRoot(PathOrientation.TOP_FIRST), contains(root, child12, child123));
|
||||
assertThat(root.getPathToRoot(PathOrientation.TOP_FIRST), contains(root));
|
||||
|
||||
assertThat(child1121.getPathToRoot(PathOrientation.BOTTOM_FIRST), contains(child1121, child112, child11, root));
|
||||
assertThat(child123.getPathToRoot(PathOrientation.BOTTOM_FIRST), contains(child123, child12, root));
|
||||
assertThat(root.getPathToRoot(PathOrientation.BOTTOM_FIRST), contains(root));
|
||||
}
|
||||
|
||||
private void assertTreeProperties(Node node) {
|
||||
assertThat(node.getTreeProperty(KEY_1, String.class), notNullValue());
|
||||
assertThat(node.getTreeProperty(KEY_1, Date.class), notNullValue());
|
||||
|
||||
assertThat(node.getTreeProperty(KEY_1, String.class), is(Optional.of(VALUE_1)));
|
||||
assertThat(node.getTreeProperty(KEY_1, Date.class), is(Optional.empty()));
|
||||
|
||||
assertThat(node.getTreeProperty(KEY_2, String.class), is(Optional.empty()));
|
||||
assertThat(node.getTreeProperty(KEY_2, Date.class), is(Optional.of(VALUE_2)));
|
||||
|
||||
assertThat(node.getTreeProperties().size(), is(2));
|
||||
|
||||
treeProperties.put(KEY_3, VALUE_3);
|
||||
assertThat(node.getTreeProperties().size(), is(3));
|
||||
assertThat(node.getTreeProperty(KEY_3, String.class), is(Optional.empty()));
|
||||
assertThat(node.getTreeProperty(KEY_3, Integer.class), is(Optional.of(VALUE_3)));
|
||||
|
||||
treeProperties.remove(KEY_3);
|
||||
assertThat(node.getTreeProperties().size(), is(2));
|
||||
assertThat(node.getTreeProperties(), not(hasKey(KEY_3)));
|
||||
}
|
||||
|
||||
private void assertNodeProperty(Node node, String key, Object value) {
|
||||
if (value != null) {
|
||||
assertThat(node.getNodeProperty(key, value.getClass()), is(Optional.of(value)));
|
||||
assertThat(node.getNodeProperty(key, Object.class), is(Optional.of(value)));
|
||||
assertThat(node.getNodeProperty(key, Throwable.class), is(Optional.empty()));
|
||||
} else {
|
||||
assertThat(node.getNodeProperty(key, Object.class), is(Optional.empty()));
|
||||
}
|
||||
}
|
||||
|
||||
private void assertEdgeProperty(Node node, String key, Object value) {
|
||||
if (value != null) {
|
||||
assertThat(node.getEdgeProperty(key, value.getClass()), is(Optional.of(value)));
|
||||
assertThat(node.getEdgeProperty(key, Object.class), is(Optional.of(value)));
|
||||
assertThat(node.getEdgeProperty(key, Throwable.class), is(Optional.empty()));
|
||||
} else {
|
||||
assertThat(node.getEdgeProperty(key, Object.class), is(Optional.empty()));
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue