/*
* Copyright 2015 S. Webber
*
* 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.oakgp.node.walk;
import org.oakgp.Arguments;
import org.oakgp.node.FunctionNode;
import org.oakgp.node.Node;
import org.oakgp.node.NodeType;
/** Provides a mechanism for recursively visiting nodes in a tree structure while keeping track of the depth of each node. */
public final class DepthWalk {
/** Private constructor as all methods are static. */
private DepthWalk() {
// do nothing
}
/**
* Returns the total number of nodes contained in the tree-structure represented by the given {@code Node} that match the specified predicate.
*
* @param treeWalkerStrategy
* the predicate used to determine if a {@code Node} should be included in the count
*/
public static int getNodeCount(Node tree, DepthWalkStrategy treeWalkerStrategy) {
return getNodeCount(tree, treeWalkerStrategy, 1);
}
private static int getNodeCount(Node node, DepthWalkStrategy treeWalkerStrategy, int currentDepth) {
if (NodeType.isFunction(node)) {
int total = treeWalkerStrategy.test(node, currentDepth) ? 1 : 0;
FunctionNode functionNode = (FunctionNode) node;
Arguments arguments = functionNode.getArguments();
for (int i = 0; i < arguments.getArgCount(); i++) {
total += getNodeCount(arguments.getArg(i), treeWalkerStrategy, currentDepth + 1);
}
return total;
} else {
return treeWalkerStrategy.test(node, currentDepth) ? 1 : 0;
}
}
/** Returns a {@code Node} from the tree structure represented by the given {@code Node} that matches the specified predicate. */
public static Node getAt(Node current, int index, DepthWalkStrategy treeWalkerStrategy) {
return getAt(current, index, treeWalkerStrategy, 1);
}
private static Node getAt(Node current, int index, DepthWalkStrategy treeWalkerStrategy, int currentDepth) {
if (NodeType.isFunction(current)) {
int total = 0;
FunctionNode functionNode = (FunctionNode) current;
Arguments arguments = functionNode.getArguments();
for (int i = 0; i < arguments.getArgCount(); i++) {
Node child = arguments.getArg(i);
int c = getNodeCount(child, treeWalkerStrategy, currentDepth + 1);
if (total + c > index) {
return getAt(child, index - total, treeWalkerStrategy, currentDepth + 1);
} else {
total += c;
}
}
if (!treeWalkerStrategy.test(current, currentDepth)) {
throw new IllegalStateException();
}
}
return current;
}
/**
* Returns a new {@code Node} resulting from replacing the {@code Node} at position {@code index} of the given {@code Node} with the result of
* {@code replacement}.
*
* @param index
* the index of the {@code Node}, in the tree structure represented by this object, that needs to be replaced
* @param replacement
* the function to apply to the {@code Node} at {@code index} to determine the {@code Node} that should replace it
* @return a new {@code Node} derived from replacing the {@code Node} at {@code index} with the result of {@code replacement}
*/
public static Node replaceAt(Node current, int index, DepthWalkReplacement replacement) {
return replaceAt(current, index, replacement, 1);
}
private static Node replaceAt(Node current, int index, DepthWalkReplacement replacement, int currentDepth) {
if (NodeType.isFunction(current)) {
int total = 0;
FunctionNode functionNode = (FunctionNode) current;
Arguments arguments = functionNode.getArguments();
for (int i = 0; i < arguments.getArgCount(); i++) {
Node child = arguments.getArg(i);
int c = child.getNodeCount();
if (total + c > index) {
return new FunctionNode(functionNode.getFunction(), arguments.replaceAt(i, replaceAt(child, index - total, replacement, currentDepth + 1)));
} else {
total += c;
}
}
}
return replacement.apply(current, currentDepth);
}
/** Defines a strategy for determining which nodes should be considered when walking a tree structure. */
@FunctionalInterface
public interface DepthWalkStrategy {
/**
* @param node
* a node that has been encountered while walking a tree structure
* @param depth
* the depth (distance from the root) of the specified {@code node} within the tree structure being walked
* @return {@code true} if this node should be considered, else {@code false}
*/
boolean test(Node node, int depth);
}
/** Defines a strategy for determining a replacement for a node encountered when walking a tree structure. */
@FunctionalInterface
public interface DepthWalkReplacement {
/**
* @param node
* a node that has been encountered while walking a tree structure
* @param depth
* the depth (distance from the root) of the specified {@code node} within the tree structure being walked
* @return a node that should replace the specified {@code node} within the tree structure being walked
*/
Node apply(Node node, int depth);
}
}