Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 97 additions & 6 deletions src/main/java/graphql/language/NodeTraverser.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package graphql.language;

import graphql.PublicApi;
import graphql.util.SimpleTraverserContext;
import graphql.util.TraversalControl;
import graphql.util.Traverser;
Expand All @@ -12,8 +13,16 @@
import java.util.Map;
import java.util.function.Function;

/**
* Lets you traverse a {@link Node} tree.
*/
@PublicApi
public class NodeTraverser {


/**
* Used by depthFirst to indicate via {@link TraverserContext#getVar(Class)} if the visit happens inside the ENTER or LEAVE phase.
*/
public enum LeaveOrEnter {
LEAVE,
ENTER
Expand All @@ -32,31 +41,113 @@ public NodeTraverser() {
}


/**
* depthFirst traversal with a enter/leave phase.
*
* @param nodeVisitor
* @param root
*/
public void depthFirst(NodeVisitor nodeVisitor, Node root) {
doTraverse(nodeVisitor, Collections.singleton(root));
depthFirst(nodeVisitor, Collections.singleton(root));
}

/**
* depthFirst traversal with a enter/leave phase.
*
* @param nodeVisitor
* @param roots
*/
public void depthFirst(NodeVisitor nodeVisitor, Collection<? extends Node> roots) {
doTraverse(nodeVisitor, roots);
TraverserVisitor<Node> nodeTraverserVisitor = new TraverserVisitor<Node>() {

@Override
public TraversalControl enter(TraverserContext<Node> context) {
context.setVar(LeaveOrEnter.class, LeaveOrEnter.ENTER);
return context.thisNode().accept(context, nodeVisitor);
}

@Override
public TraversalControl leave(TraverserContext<Node> context) {
context.setVar(LeaveOrEnter.class, LeaveOrEnter.LEAVE);
return context.thisNode().accept(context, nodeVisitor);
}

};
doTraverse(roots, nodeTraverserVisitor);
}

private void doTraverse(NodeVisitor nodeVisitor, Collection<? extends Node> roots) {
Traverser<Node> nodeTraverser = Traverser.depthFirst(this.getChildren);
nodeTraverser.rootVars(rootVars);
TraverserVisitor<Node> traverserVisitor = new TraverserVisitor<Node>() {
/**
* Version of {@link #preOrder(NodeVisitor, Collection)} with one root.
*
* @param nodeVisitor
* @param root
*/
public void preOrder(NodeVisitor nodeVisitor, Node root) {
preOrder(nodeVisitor, Collections.singleton(root));
}

/**
* Pre-Order traversal: This is a specialized version of depthFirst with only the enter phase.
*
* @param nodeVisitor
* @param roots
*/
public void preOrder(NodeVisitor nodeVisitor, Collection<? extends Node> roots) {
TraverserVisitor<Node> nodeTraverserVisitor = new TraverserVisitor<Node>() {

@Override
public TraversalControl enter(TraverserContext<Node> context) {
context.setVar(LeaveOrEnter.class, LeaveOrEnter.ENTER);
return context.thisNode().accept(context, nodeVisitor);
}

@Override
public TraversalControl leave(TraverserContext<Node> context) {
return TraversalControl.CONTINUE;
}

};
doTraverse(roots, nodeTraverserVisitor);

}

/**
* Version of {@link #postOrder(NodeVisitor, Collection)} with one root.
*
* @param nodeVisitor
* @param root
*/
public void postOrder(NodeVisitor nodeVisitor, Node root) {
postOrder(nodeVisitor, Collections.singleton(root));
}

/**
* Post-Order traversal: This is a specialized version of depthFirst with only the leave phase.
*
* @param nodeVisitor
* @param roots
*/
public void postOrder(NodeVisitor nodeVisitor, Collection<? extends Node> roots) {
TraverserVisitor<Node> nodeTraverserVisitor = new TraverserVisitor<Node>() {

@Override
public TraversalControl enter(TraverserContext<Node> context) {
return TraversalControl.CONTINUE;
}

@Override
public TraversalControl leave(TraverserContext<Node> context) {
context.setVar(LeaveOrEnter.class, LeaveOrEnter.LEAVE);
return context.thisNode().accept(context, nodeVisitor);
}

};
doTraverse(roots, nodeTraverserVisitor);
}

private void doTraverse(Collection<? extends Node> roots, TraverserVisitor traverserVisitor) {
Traverser<Node> nodeTraverser = Traverser.depthFirst(this.getChildren);
nodeTraverser.rootVars(rootVars);
nodeTraverser.traverse(roots, traverserVisitor);
}

Expand Down
7 changes: 5 additions & 2 deletions src/main/java/graphql/language/NodeVisitor.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package graphql.language;

import graphql.Internal;
import graphql.PublicApi;
import graphql.util.TraversalControl;
import graphql.util.TraverserContext;

@Internal
/**
* Used by {@link NodeTraverser} to visit {@link Node}.
*/
@PublicApi
public interface NodeVisitor {
TraversalControl visitArgument(Argument node, TraverserContext<Node> data);

Expand Down
10 changes: 6 additions & 4 deletions src/main/java/graphql/language/NodeVisitorStub.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package graphql.language;

import graphql.Internal;
import graphql.PublicApi;
import graphql.util.TraversalControl;
import graphql.util.TraverserContext;

@Internal
public class NodeVisitorStub
implements NodeVisitor {
/**
* Convenient implementation of {@link NodeVisitor} for easy subclassing methods handling different types of Nodes in one method.
*/
@PublicApi
public class NodeVisitorStub implements NodeVisitor {
@Override
public TraversalControl visitArgument(Argument node, TraverserContext<Node> context) {
return visitNode(node, context);
Expand Down
8 changes: 5 additions & 3 deletions src/main/java/graphql/util/TraversalControl.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
package graphql.util;

import graphql.Internal;
import graphql.PublicApi;

/**
* Special traversal control values
*/
@Internal
@PublicApi
public enum TraversalControl {

/**
* When returned the traversal will continue as planned.
*/
CONTINUE,
/**
* When returned from a Visitor's method, indicates exiting the traversal
Expand All @@ -17,7 +20,6 @@ public enum TraversalControl {
* When returned from a Visitor's method, indicates skipping traversal of a subtree.
*
* Not allowed to be returned from 'leave' or 'backRef' because it doesn't make sense.
*
*/
ABORT
}
27 changes: 22 additions & 5 deletions src/main/java/graphql/util/TraverserContext.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package graphql.util;

import graphql.Internal;
import graphql.PublicApi;

import java.util.Set;

Expand All @@ -9,7 +9,7 @@
*
* @param <T> type of tree node
*/
@Internal
@PublicApi
public interface TraverserContext<T> {
/**
* Returns current node being visited
Expand All @@ -25,13 +25,15 @@ public interface TraverserContext<T> {
* the current path as well as the variables {@link #getVar(java.lang.Class) }
* stored in every parent context.
*
* Useful when it is difficult to organize a local Visitor's stack, when performing
* breadth-first or parallel traversal
*
* @return context associated with the node parent
*/
TraverserContext<T> getParentContext();

/**
* The result of the {@link #getParentContext()}.
*
* @return
*/
Object getParentResult();

/**
Expand Down Expand Up @@ -71,10 +73,25 @@ public interface TraverserContext<T> {
<S> TraverserContext<T> setVar(Class<? super S> key, S value);


/**
* Set the result for this TraverserContext.
*
* @param result
*/
void setResult(Object result);

/**
* The result of this TraverserContext..
*
* @return
*/
Object getResult();

/**
* Used to share something across all TraverserContext.
*
* @return
*/
Object getInitialData();

}
44 changes: 44 additions & 0 deletions src/test/groovy/graphql/language/NodeTraverserTest.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,50 @@ class NodeTraverserTest extends Specification {
0 * nodeVisitor._
}

def "traverse nodes in pre-order"() {
given:
Field leaf = new Field("leaf")
SelectionSet rootSelectionSet = new SelectionSet(Arrays.asList(leaf))
Field root = new Field("root")
root.setSelectionSet(rootSelectionSet)

NodeTraverser nodeTraverser = new NodeTraverser()
NodeVisitor nodeVisitor = Mock(NodeVisitor)
when:
nodeTraverser.preOrder(nodeVisitor, root)

then:
1 * nodeVisitor.visitField(root, { isEnter(it) }) >> TraversalControl.CONTINUE
then:
1 * nodeVisitor.visitSelectionSet(rootSelectionSet, { isEnter(it) }) >> TraversalControl.CONTINUE
then:
1 * nodeVisitor.visitField(leaf, { isEnter(it) }) >> TraversalControl.CONTINUE
then:
0 * nodeVisitor._
}

def "traverse nodes in post-order"() {
given:
Field leaf = new Field("leaf")
SelectionSet rootSelectionSet = new SelectionSet(Arrays.asList(leaf))
Field root = new Field("root")
root.setSelectionSet(rootSelectionSet)

NodeTraverser nodeTraverser = new NodeTraverser()
NodeVisitor nodeVisitor = Mock(NodeVisitor)
when:
nodeTraverser.postOrder(nodeVisitor, root)

then:
1 * nodeVisitor.visitField(leaf, { isLeave(it) }) >> TraversalControl.CONTINUE
then:
1 * nodeVisitor.visitSelectionSet(rootSelectionSet, { isLeave(it) }) >> TraversalControl.CONTINUE
then:
1 * nodeVisitor.visitField(root, { isLeave(it) }) >> TraversalControl.CONTINUE
then:
0 * nodeVisitor._
}

def "uses root vars"() {
given:
Field root = new Field("root")
Expand Down