KEYCLOAK-19562 Introduce generic trees

This commit is contained in:
Hynek Mlnarik 2021-10-15 14:10:01 +02:00 committed by Hynek Mlnařík
parent d6ae76d8f2
commit 53f02a50f6
3 changed files with 919 additions and 0 deletions

View file

@ -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);
}
}
}

View file

@ -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);
}

View file

@ -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()));
}
}
}