TreeVisitor that avoids recursive calls and base implementation to support it

This commit is contained in:
Sergey Malenkov
2017-07-16 20:09:33 +03:00
parent e0c6e26d47
commit 6a67253546
3 changed files with 748 additions and 0 deletions
@@ -0,0 +1,208 @@
/*
* Copyright 2000-2017 JetBrains s.r.o.
*
* 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 com.intellij.ui.tree;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.concurrency.AsyncPromise;
import org.jetbrains.concurrency.Promise;
import javax.swing.tree.TreePath;
import java.util.ArrayDeque;
import java.util.Collection;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
abstract class AbstractTreeWalker<N> {
private enum State {STARTED, REQUESTED, PAUSED, FINISHED, FAILED}
private final AtomicReference<State> state = new AtomicReference<>();
private final AsyncPromise<TreePath> promise = new AsyncPromise<>();
private final ArrayDeque<ArrayDeque<N>> stack = new ArrayDeque<>();
private final Function<N, Object> converter;
private final TreeVisitor visitor;
private volatile TreePath current;
/**
* Creates a new tree walker with the specified tree visitor.
*
* @param visitor an object that controls visiting a tree structure
*/
public AbstractTreeWalker(@NotNull TreeVisitor visitor) {
this(visitor, node -> node);
}
/**
* Creates a new tree walker with the specified node converter,
* which allows to generate a tree path expected by the given tree visitor.
*
* @param visitor an object that controls visiting a tree structure
* @param converter a node converter for the path components
*/
public AbstractTreeWalker(@NotNull TreeVisitor visitor, Function<N, Object> converter) {
this.converter = converter;
this.visitor = visitor;
}
/**
* Returns a list of child nodes for the specified node.
* This method is called by the walker only if the visitor
* returned the {@link TreeVisitor.Action#CONTINUE CONTINUE} action.
* The walker will be paused if it returns {@code null}.
* To continue user should call the {@link #setChildren} method.
*
* @param node a node in a tree structure
* @return children for the specified node or {@code null} if children will be set later
*/
protected abstract Collection<N> getChildren(@NotNull N node);
/**
* Sets the children, awaited by the walker, and continues to traverse a tree structure.
*
* @param children a list of child nodes for the node specified in the {@link #getChildren} method
* @throws IllegalStateException if it is called in unexpected state
*/
public void setChildren(Collection<N> children) {
boolean paused = state.compareAndSet(State.PAUSED, State.STARTED);
if (!paused && !state.compareAndSet(State.REQUESTED, State.STARTED)) throw new IllegalStateException();
stack.push(children == null ? new ArrayDeque<>() : new ArrayDeque<>(children));
if (paused) processNextPath();
}
/**
* @return a promise that will be resolved when visiting is finished
*/
@NotNull
public Promise<TreePath> promise() {
return promise;
}
/**
* Stops visiting a tree structure by specifying a cause.
*/
public void setError(@NotNull Throwable error) {
state.set(State.FAILED);
promise.setError(error);
}
/**
* Starts visiting a tree structure from the specified root node.
*
* @param node a tree root or {@code null} if nothing to traverse
*/
public void start(N node) {
start(null, node);
}
/**
* Starts visiting a tree structure from the specified node.
*
* @param parent a parent tree path or {@code null} for a root node
* @param node a tree node or {@code null} if nothing to traverse
*/
public void start(TreePath parent, N node) {
TreePath result = null;
if (node != null) {
try {
Object component = converter.apply(node);
TreePath path = parent != null
? parent.pathByAddingChild(component)
: new TreePath(component);
switch (visitor.accept(path)) {
case CONTINUE:
update(null, State.REQUESTED);
if (processChildren(path, node)) processNextPath();
return;
case INTERRUPT:
result = path;
break;
case SKIP_CHILDREN:
break;
case SKIP_SIBLINGS:
break;
}
}
catch (Exception error) {
setError(error);
}
}
update(null, State.FINISHED);
promise.setResult(result);
}
/**
* @param path a path to the specified node
* @param node a node to get children to process
* @return {@code false} if the walker should be pause
*/
private boolean processChildren(@NotNull TreePath path, @NotNull N node) {
current = path;
Collection<N> children = getChildren(node);
if (children == null) return !state.compareAndSet(State.REQUESTED, State.PAUSED);
update(State.REQUESTED, State.STARTED);
stack.push(new ArrayDeque<>(children));
return true;
}
private void processNextPath() {
try {
while (State.STARTED == state.get()) {
ArrayDeque<N> siblings = stack.peek();
if (siblings == null) {
update(State.STARTED, State.FINISHED);
current = null;
promise.setResult(null);
return; // nothing to process
}
N node = siblings.poll();
if (node == null) {
TreePath path = current;
if (path == null) throw new IllegalStateException();
if (siblings != stack.poll()) throw new IllegalStateException();
current = path.getParentPath();
}
else {
Object component = converter.apply(node);
TreePath path = this.current.pathByAddingChild(component);
switch (visitor.accept(path)) {
case CONTINUE:
update(State.STARTED, State.REQUESTED);
if (processChildren(path, node)) break;
return; // stop processing and wait for setChildren
case INTERRUPT:
update(State.STARTED, State.FINISHED);
current = null;
stack.clear();
promise.setResult(path);
return; // path is found
case SKIP_SIBLINGS:
siblings.clear();
break;
case SKIP_CHILDREN:
break;
}
}
}
}
catch (Exception error) {
setError(error);
}
}
private void update(State expected, State replacement) {
if (!state.compareAndSet(expected, replacement)) throw new IllegalStateException();
}
}
@@ -0,0 +1,115 @@
/*
* Copyright 2000-2017 JetBrains s.r.o.
*
* 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 com.intellij.ui.tree;
import org.jetbrains.annotations.NotNull;
import javax.swing.tree.TreePath;
import java.util.function.Function;
public interface TreeVisitor {
/**
* @param path a currently visited path
* @return an action that controls visiting a tree
*/
@NotNull
Action accept(@NotNull TreePath path);
enum Action {
/**
* Interrupt visiting a tree structure.
*/
INTERRUPT,
/**
* Continue visiting the node children.
*/
CONTINUE,
/**
* Continue without visiting the node children.
*/
SKIP_CHILDREN,
/**
* Continue without visiting the node siblings.
*/
SKIP_SIBLINGS,
}
abstract class Finder implements TreeVisitor {
@NotNull
@Override
public Action accept(@NotNull TreePath path) {
return found(path) ? Action.INTERRUPT : contains(path) ? Action.CONTINUE : Action.SKIP_CHILDREN;
}
/**
* @param path a currently visited path
* @return {@code true} if the specified path is found and visiting can be interrupted
*/
protected abstract boolean found(@NotNull TreePath path);
/**
* @param path a currently visited path
* @return {@code true} if the specified path may contain a seeking path
*/
protected abstract boolean contains(@NotNull TreePath path);
}
class PathFinder implements TreeVisitor {
private final Function<Object, Object> converter;
private final TreePath path;
public PathFinder(@NotNull TreePath path) {
this(path, object -> object);
}
public PathFinder(@NotNull TreePath path, @NotNull Function<Object, Object> converter) {
this.converter = converter;
this.path = path;
}
@NotNull
@Override
public Action accept(@NotNull TreePath path) {
Object component = converter.apply(path.getLastPathComponent());
if (component == null) return Action.SKIP_CHILDREN;
int pathCount = path.getPathCount();
int thisCount = this.path.getPathCount();
if (thisCount < pathCount) return Action.SKIP_CHILDREN;
Action action = thisCount == pathCount ? Action.INTERRUPT : Action.CONTINUE;
TreePath value = this.path;
while (thisCount > pathCount) {
thisCount--;
value = value.getParentPath();
if (value == null) return Action.SKIP_CHILDREN;
}
return matches(component, value.getLastPathComponent()) ? action : Action.SKIP_CHILDREN;
}
/**
* @param pathComponent a last component of the current path
* @param thisComponent a component of the seeking path at the same level
* @return {@code true} if both components match each other
*/
protected boolean matches(@NotNull Object pathComponent, @NotNull Object thisComponent) {
return pathComponent.equals(thisComponent);
}
}
}
@@ -0,0 +1,425 @@
/*
* Copyright 2000-2017 JetBrains s.r.o.
*
* 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 com.intellij.ui.tree;
import org.jetbrains.annotations.NotNull;
import org.junit.Test;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.MutableTreeNode;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import java.util.ArrayList;
import java.util.Collection;
import static com.intellij.util.ReflectionUtil.getField;
import static org.junit.Assert.assertEquals;
public class AbstractTreeWalkerTest {
private final static boolean PRINT = false;
@Test
public void testInterrupt() {
Node root = createRoot();
test(root, 1, path -> TreeVisitor.Action.INTERRUPT, root);
test(null, 0, path -> TreeVisitor.Action.INTERRUPT);
}
@Test
public void testContinue() {
test(createRoot(), 21, path -> TreeVisitor.Action.CONTINUE);
test(null, 0, path -> TreeVisitor.Action.CONTINUE);
}
@Test
public void testSkipSiblings() {
test(createRoot(), 1, path -> TreeVisitor.Action.SKIP_SIBLINGS);
test(null, 0, path -> TreeVisitor.Action.SKIP_SIBLINGS);
}
@Test
public void testSkipChildren() {
test(createRoot(), 1, path -> TreeVisitor.Action.SKIP_CHILDREN);
test(null, 0, path -> TreeVisitor.Action.SKIP_CHILDREN);
}
@Test
public void testDeepVisit() {
testDeepVisit(1);
testDeepVisit(10);
testDeepVisit(100);
testDeepVisit(1000);
if (PRINT) return;
testDeepVisit(10000);
testDeepVisit(100000);
}
private static void testDeepVisit(int count) {
Node node = new Node(count);
for (int i = 1; i < count; i++) node = new Node(count - i, node);
test(node, count, path -> TreeVisitor.Action.CONTINUE);
}
@Test
public void testDoubleStart() {
/*
Walker walker = new Walker(path -> TreeVisitor.Action.CONTINUE, null, 0);
assertResult(walker);
*/
}
@Test
public void testFinder() {
TreeNode root = createRoot();
TreeNode color = root.getChildAt(0);
TreeNode digit = root.getChildAt(1);
TreeNode greek = root.getChildAt(2);
test(root, 1, createFinder(root), root);
test(root, 2, createFinder(color), root, color);
test(root, 3, createFinder(color.getChildAt(0)), root, color, color.getChildAt(0));
test(root, 4, createFinder(color.getChildAt(1)), root, color, color.getChildAt(1));
test(root, 5, createFinder(color.getChildAt(2)), root, color, color.getChildAt(2));
test(root, 3, createFinder(digit), root, digit);
test(root, 4, createFinder(digit.getChildAt(0)), root, digit, digit.getChildAt(0));
test(root, 5, createFinder(digit.getChildAt(1)), root, digit, digit.getChildAt(1));
test(root, 6, createFinder(digit.getChildAt(2)), root, digit, digit.getChildAt(2));
test(root, 7, createFinder(digit.getChildAt(3)), root, digit, digit.getChildAt(3));
test(root, 8, createFinder(digit.getChildAt(4)), root, digit, digit.getChildAt(4));
test(root, 9, createFinder(digit.getChildAt(5)), root, digit, digit.getChildAt(5));
test(root, 10, createFinder(digit.getChildAt(6)), root, digit, digit.getChildAt(6));
test(root, 11, createFinder(digit.getChildAt(7)), root, digit, digit.getChildAt(7));
test(root, 12, createFinder(digit.getChildAt(8)), root, digit, digit.getChildAt(8));
test(root, 4, createFinder(greek), root, greek);
test(root, 5, createFinder(greek.getChildAt(0)), root, greek, greek.getChildAt(0));
test(root, 6, createFinder(greek.getChildAt(1)), root, greek, greek.getChildAt(1));
test(root, 7, createFinder(greek.getChildAt(2)), root, greek, greek.getChildAt(2));
test(root, 8, createFinder(greek.getChildAt(3)), root, greek, greek.getChildAt(3));
test(root, 9, createFinder(greek.getChildAt(4)), root, greek, greek.getChildAt(4));
}
@Test
public void testColorFinder() {
Node color = createColorNode();
Node digit = createDigitNode();
Node greek = createGreekNode();
Node root = new Node("root", color, digit, greek);
TreePath parent = new TreePath(root);
test(parent, color, 1, createFinder(root));
test(parent, color, 1, createFinder(color), root, color);
test(parent, color, 2, createFinder(color.getChildAt(0)), root, color, color.getChildAt(0));
test(parent, color, 3, createFinder(color.getChildAt(1)), root, color, color.getChildAt(1));
test(parent, color, 4, createFinder(color.getChildAt(2)), root, color, color.getChildAt(2));
test(parent, color, 1, createFinder(digit));
test(parent, color, 1, createFinder(greek));
}
@Test
public void testDigitFinder() {
TreeNode root = createRoot();
TreeNode color = root.getChildAt(0);
TreeNode digit = root.getChildAt(1);
TreeNode greek = root.getChildAt(2);
TreePath parent = new TreePath(root);
test(parent, digit, 1, createFinder(root));
test(parent, digit, 1, createFinder(color));
test(parent, digit, 1, createFinder(digit), root, digit);
test(parent, digit, 2, createFinder(digit.getChildAt(0)), root, digit, digit.getChildAt(0));
test(parent, digit, 3, createFinder(digit.getChildAt(1)), root, digit, digit.getChildAt(1));
test(parent, digit, 4, createFinder(digit.getChildAt(2)), root, digit, digit.getChildAt(2));
test(parent, digit, 5, createFinder(digit.getChildAt(3)), root, digit, digit.getChildAt(3));
test(parent, digit, 6, createFinder(digit.getChildAt(4)), root, digit, digit.getChildAt(4));
test(parent, digit, 7, createFinder(digit.getChildAt(5)), root, digit, digit.getChildAt(5));
test(parent, digit, 8, createFinder(digit.getChildAt(6)), root, digit, digit.getChildAt(6));
test(parent, digit, 9, createFinder(digit.getChildAt(7)), root, digit, digit.getChildAt(7));
test(parent, digit, 10, createFinder(digit.getChildAt(8)), root, digit, digit.getChildAt(8));
test(parent, digit, 1, createFinder(greek));
}
@Test
public void testGreekFinder() {
TreeNode root = createRoot();
TreeNode color = root.getChildAt(0);
TreeNode digit = root.getChildAt(1);
TreeNode greek = root.getChildAt(2);
TreePath parent = new TreePath(root);
test(parent, greek, 1, createFinder(root));
test(parent, greek, 1, createFinder(color));
test(parent, greek, 1, createFinder(digit));
test(parent, greek, 1, createFinder(greek), root, greek);
test(parent, greek, 2, createFinder(greek.getChildAt(0)), root, greek, greek.getChildAt(0));
test(parent, greek, 3, createFinder(greek.getChildAt(1)), root, greek, greek.getChildAt(1));
test(parent, greek, 4, createFinder(greek.getChildAt(2)), root, greek, greek.getChildAt(2));
test(parent, greek, 5, createFinder(greek.getChildAt(3)), root, greek, greek.getChildAt(3));
test(parent, greek, 6, createFinder(greek.getChildAt(4)), root, greek, greek.getChildAt(4));
}
private static TreeVisitor createFinder(TreeNode node) {
return new TreeVisitor.Finder() {
@Override
protected boolean found(@NotNull TreePath path) {
return node.equals(path.getLastPathComponent());
}
@Override
protected boolean contains(@NotNull TreePath path) {
Object component = path.getLastPathComponent();
return component instanceof Node && node instanceof Node && ((Node)node).isNodeAncestor((Node)component);
}
};
}
@Test
public void testPathFinder() {
TreeNode root = createRoot();
TreeNode color = root.getChildAt(0);
TreeNode digit = root.getChildAt(1);
TreeNode greek = root.getChildAt(2);
test(root, 1, createPathFinder("root"), root);
test(root, 1, createPathFinder("toor")); // not found
test(root, 2, createPathFinder("root", "color"), root, color);
test(root, 4, createPathFinder("root", "roloc")); // not found
test(root, 3, createPathFinder("root", "color", "red"), root, color, color.getChildAt(0));
test(root, 7, createPathFinder("root", "color", "der")); // not found
test(root, 4, createPathFinder("root", "color", "green"), root, color, color.getChildAt(1));
test(root, 7, createPathFinder("root", "color", "neerg")); // not found
test(root, 5, createPathFinder("root", "color", "blue"), root, color, color.getChildAt(2));
test(root, 7, createPathFinder("root", "color", "eulb")); // not found
test(root, 3, createPathFinder("root", "digit"), root, digit);
test(root, 4, createPathFinder("root", "tigid")); // not found
test(root, 4, createPathFinder("root", "digit", "one"), root, digit, digit.getChildAt(0));
test(root, 13, createPathFinder("root", "digit", "eno")); // not found
test(root, 5, createPathFinder("root", "digit", "two"), root, digit, digit.getChildAt(1));
test(root, 13, createPathFinder("root", "digit", "owt")); // not found
test(root, 6, createPathFinder("root", "digit", "three"), root, digit, digit.getChildAt(2));
test(root, 13, createPathFinder("root", "digit", "eerht")); // not found
test(root, 7, createPathFinder("root", "digit", "four"), root, digit, digit.getChildAt(3));
test(root, 13, createPathFinder("root", "digit", "ruof")); // not found
test(root, 8, createPathFinder("root", "digit", "five"), root, digit, digit.getChildAt(4));
test(root, 13, createPathFinder("root", "digit", "evif")); // not found
test(root, 9, createPathFinder("root", "digit", "six"), root, digit, digit.getChildAt(5));
test(root, 13, createPathFinder("root", "digit", "xis")); // not found
test(root, 10, createPathFinder("root", "digit", "seven"), root, digit, digit.getChildAt(6));
test(root, 13, createPathFinder("root", "digit", "neves")); // not found
test(root, 11, createPathFinder("root", "digit", "eight"), root, digit, digit.getChildAt(7));
test(root, 13, createPathFinder("root", "digit", "thgie")); // not found
test(root, 12, createPathFinder("root", "digit", "nine"), root, digit, digit.getChildAt(8));
test(root, 13, createPathFinder("root", "digit", "enin")); // not found
test(root, 4, createPathFinder("root", "greek"), root, greek);
test(root, 4, createPathFinder("root", "keerg")); // not found
test(root, 5, createPathFinder("root", "greek", "alpha"), root, greek, greek.getChildAt(0));
test(root, 9, createPathFinder("root", "greek", "ahpla")); // not found
test(root, 6, createPathFinder("root", "greek", "beta"), root, greek, greek.getChildAt(1));
test(root, 9, createPathFinder("root", "greek", "ateb")); // not found
test(root, 7, createPathFinder("root", "greek", "gamma"), root, greek, greek.getChildAt(2));
test(root, 9, createPathFinder("root", "greek", "ammag")); // not found
test(root, 8, createPathFinder("root", "greek", "delta"), root, greek, greek.getChildAt(3));
test(root, 9, createPathFinder("root", "greek", "atled")); // not found
test(root, 9, createPathFinder("root", "greek", "epsilon"), root, greek, greek.getChildAt(4));
test(root, 9, createPathFinder("root", "greek", "nolispe")); // not found
}
@Test
public void testColorPathFinder() {
TreeNode root = createRoot();
TreeNode color = root.getChildAt(0);
TreePath parent = new TreePath(root);
test(parent, color, 1, createPathFinder("root"));
test(parent, color, 1, createPathFinder("toor")); // not found
test(parent, color, 1, createPathFinder("root", "color"), root, color);
test(parent, color, 1, createPathFinder("root", "roloc")); // not found
test(parent, color, 2, createPathFinder("root", "color", "red"), root, color, color.getChildAt(0));
test(parent, color, 4, createPathFinder("root", "color", "der")); // not found
test(parent, color, 3, createPathFinder("root", "color", "green"), root, color, color.getChildAt(1));
test(parent, color, 4, createPathFinder("root", "color", "neerg")); // not found
test(parent, color, 4, createPathFinder("root", "color", "blue"), root, color, color.getChildAt(2));
test(parent, color, 4, createPathFinder("root", "color", "eulb")); // not found
test(parent, color, 1, createPathFinder("root", "digit"));
test(parent, color, 1, createPathFinder("root", "tigid")); // not found
test(parent, color, 1, createPathFinder("root", "greek"));
test(parent, color, 1, createPathFinder("root", "keerg")); // not found
}
@Test
public void testDigitPathFinder() {
TreeNode root = createRoot();
TreeNode digit = root.getChildAt(1);
TreePath parent = new TreePath(root);
test(parent, digit, 1, createPathFinder("root"));
test(parent, digit, 1, createPathFinder("toor")); // not found
test(parent, digit, 1, createPathFinder("root", "color"));
test(parent, digit, 1, createPathFinder("root", "roloc")); // not found
test(parent, digit, 1, createPathFinder("root", "digit"), root, digit);
test(parent, digit, 1, createPathFinder("root", "tigid")); // not found
test(parent, digit, 2, createPathFinder("root", "digit", "one"), root, digit, digit.getChildAt(0));
test(parent, digit, 10, createPathFinder("root", "digit", "eno")); // not found
test(parent, digit, 3, createPathFinder("root", "digit", "two"), root, digit, digit.getChildAt(1));
test(parent, digit, 10, createPathFinder("root", "digit", "owt")); // not found
test(parent, digit, 4, createPathFinder("root", "digit", "three"), root, digit, digit.getChildAt(2));
test(parent, digit, 10, createPathFinder("root", "digit", "eerht")); // not found
test(parent, digit, 5, createPathFinder("root", "digit", "four"), root, digit, digit.getChildAt(3));
test(parent, digit, 10, createPathFinder("root", "digit", "ruof")); // not found
test(parent, digit, 6, createPathFinder("root", "digit", "five"), root, digit, digit.getChildAt(4));
test(parent, digit, 10, createPathFinder("root", "digit", "evif")); // not found
test(parent, digit, 7, createPathFinder("root", "digit", "six"), root, digit, digit.getChildAt(5));
test(parent, digit, 10, createPathFinder("root", "digit", "xis")); // not found
test(parent, digit, 8, createPathFinder("root", "digit", "seven"), root, digit, digit.getChildAt(6));
test(parent, digit, 10, createPathFinder("root", "digit", "neves")); // not found
test(parent, digit, 9, createPathFinder("root", "digit", "eight"), root, digit, digit.getChildAt(7));
test(parent, digit, 10, createPathFinder("root", "digit", "thgie")); // not found
test(parent, digit, 10, createPathFinder("root", "digit", "nine"), root, digit, digit.getChildAt(8));
test(parent, digit, 10, createPathFinder("root", "digit", "enin")); // not found
test(parent, digit, 1, createPathFinder("root", "greek"));
test(parent, digit, 1, createPathFinder("root", "keerg")); // not found
}
@Test
public void testGreekPathFinder() {
TreeNode root = createRoot();
TreeNode greek = root.getChildAt(2);
TreePath parent = new TreePath(root);
test(parent, greek, 1, createPathFinder("root"));
test(parent, greek, 1, createPathFinder("toor")); // not found
test(parent, greek, 1, createPathFinder("root", "color"));
test(parent, greek, 1, createPathFinder("root", "roloc")); // not found
test(parent, greek, 1, createPathFinder("root", "digit"));
test(parent, greek, 1, createPathFinder("root", "tigid")); // not found
test(parent, greek, 1, createPathFinder("root", "greek"), root, greek);
test(parent, greek, 1, createPathFinder("root", "keerg")); // not found
test(parent, greek, 2, createPathFinder("root", "greek", "alpha"), root, greek, greek.getChildAt(0));
test(parent, greek, 6, createPathFinder("root", "greek", "ahpla")); // not found
test(parent, greek, 3, createPathFinder("root", "greek", "beta"), root, greek, greek.getChildAt(1));
test(parent, greek, 6, createPathFinder("root", "greek", "ateb")); // not found
test(parent, greek, 4, createPathFinder("root", "greek", "gamma"), root, greek, greek.getChildAt(2));
test(parent, greek, 6, createPathFinder("root", "greek", "ammag")); // not found
test(parent, greek, 5, createPathFinder("root", "greek", "delta"), root, greek, greek.getChildAt(3));
test(parent, greek, 6, createPathFinder("root", "greek", "atled")); // not found
test(parent, greek, 6, createPathFinder("root", "greek", "epsilon"), root, greek, greek.getChildAt(4));
test(parent, greek, 6, createPathFinder("root", "greek", "nolispe")); // not found
}
private static TreeVisitor createPathFinder(String... names) {
return new TreeVisitor.PathFinder(new TreePath(names), Object::toString);
}
private static void test(TreeNode node, int count, @NotNull TreeVisitor visitor, Object... expected) {
test(null, node, count, visitor, expected);
}
private static void test(TreePath parent, TreeNode node, int count, TreeVisitor visitor, Object... expected) {
test(parent, node, count, false, new Walker(visitor), expected);
test(parent, node, count, false, new Walker(visitor) {
@Override
protected Collection<TreeNode> getChildren(@NotNull TreeNode node) {
setChildren(super.getChildren(node));
return null;
}
}, expected);
}
private static void test(TreePath parent, TreeNode node, int count, boolean error, Walker walker, Object... expected) {
walker.start(parent, node);
switch (walker.promise().getState()) {
case PENDING:
throw new IllegalStateException("not processed");
case FULFILLED:
if (!error) break;
throw new IllegalStateException("not rejected");
case REJECTED:
if (error) break;
throw new IllegalStateException("not fulfilled");
}
TreeVisitor wrapper = getField(AbstractTreeWalker.class, walker, TreeVisitor.class, "visitor");
assertEquals(Integer.valueOf(count), getField(Wrapper.class, wrapper, int.class, "count"));
assertResult(walker, expected);
}
private static void assertResult(Walker walker, Object... expected) {
assertResult(walker, expected.length == 0 ? null : new TreePath(expected));
}
private static void assertResult(Walker walker, TreePath expected) {
assertEquals("unexpected result", expected, walker.promise().blockingGet(1));
}
@NotNull
private static Node createRoot() {
return new Node("root", createColorNode(), createDigitNode(), createGreekNode());
}
@NotNull
private static Node createColorNode() {
return new Node("color", "red", "green", "blue");
}
@NotNull
private static Node createDigitNode() {
return new Node("digit", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine");
}
@NotNull
private static Node createGreekNode() {
return new Node("greek", "alpha", "beta", "gamma", "delta", "epsilon");
}
private static class Node extends DefaultMutableTreeNode {
private Node(Object content, Object... children) {
super(content);
for (Object child : children) {
add(child instanceof MutableTreeNode
? (MutableTreeNode)child
: new Node(child));
}
}
}
private static class Walker extends AbstractTreeWalker<TreeNode> {
private Walker(TreeVisitor visitor) {
super(new Wrapper(visitor));
}
@Override
protected Collection<TreeNode> getChildren(@NotNull TreeNode node) {
int count = node.getChildCount();
ArrayList<TreeNode> list = new ArrayList<>(count);
for (int i = 0; i < count; i++) list.add(node.getChildAt(i));
return list;
}
}
private static class Wrapper implements TreeVisitor {
@SuppressWarnings("unused")
private int count; // reflection
private final TreeVisitor visitor;
private Wrapper(TreeVisitor visitor) {
this.visitor = visitor;
if (PRINT) System.out.println("==============================");
}
@NotNull
@Override
public Action accept(@NotNull TreePath path) {
count++;
if (PRINT) System.out.println(path);
return visitor.accept(path);
}
}
}