diff --git a/src/main/java/graphql/language/NodeTraverser.java b/src/main/java/graphql/language/NodeTraverser.java index 8fac8080c4..7c6b768c09 100644 --- a/src/main/java/graphql/language/NodeTraverser.java +++ b/src/main/java/graphql/language/NodeTraverser.java @@ -1,5 +1,6 @@ package graphql.language; +import graphql.PublicApi; import graphql.util.SimpleTraverserContext; import graphql.util.TraversalControl; import graphql.util.Traverser; @@ -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 @@ -32,18 +41,59 @@ 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 roots) { - doTraverse(nodeVisitor, roots); + TraverserVisitor nodeTraverserVisitor = new TraverserVisitor() { + + @Override + public TraversalControl enter(TraverserContext context) { + context.setVar(LeaveOrEnter.class, LeaveOrEnter.ENTER); + return context.thisNode().accept(context, nodeVisitor); + } + + @Override + public TraversalControl leave(TraverserContext context) { + context.setVar(LeaveOrEnter.class, LeaveOrEnter.LEAVE); + return context.thisNode().accept(context, nodeVisitor); + } + + }; + doTraverse(roots, nodeTraverserVisitor); } - private void doTraverse(NodeVisitor nodeVisitor, Collection roots) { - Traverser nodeTraverser = Traverser.depthFirst(this.getChildren); - nodeTraverser.rootVars(rootVars); - TraverserVisitor traverserVisitor = new TraverserVisitor() { + /** + * 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 roots) { + TraverserVisitor nodeTraverserVisitor = new TraverserVisitor() { @Override public TraversalControl enter(TraverserContext context) { @@ -51,12 +101,53 @@ public TraversalControl enter(TraverserContext context) { return context.thisNode().accept(context, nodeVisitor); } + @Override + public TraversalControl leave(TraverserContext 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 roots) { + TraverserVisitor nodeTraverserVisitor = new TraverserVisitor() { + + @Override + public TraversalControl enter(TraverserContext context) { + return TraversalControl.CONTINUE; + } + @Override public TraversalControl leave(TraverserContext context) { context.setVar(LeaveOrEnter.class, LeaveOrEnter.LEAVE); return context.thisNode().accept(context, nodeVisitor); } + }; + doTraverse(roots, nodeTraverserVisitor); + } + + private void doTraverse(Collection roots, TraverserVisitor traverserVisitor) { + Traverser nodeTraverser = Traverser.depthFirst(this.getChildren); + nodeTraverser.rootVars(rootVars); nodeTraverser.traverse(roots, traverserVisitor); } diff --git a/src/main/java/graphql/language/NodeVisitor.java b/src/main/java/graphql/language/NodeVisitor.java index 762580b9bc..2ed79570b3 100644 --- a/src/main/java/graphql/language/NodeVisitor.java +++ b/src/main/java/graphql/language/NodeVisitor.java @@ -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 data); diff --git a/src/main/java/graphql/language/NodeVisitorStub.java b/src/main/java/graphql/language/NodeVisitorStub.java index bdbfeac9f6..f0fcd6b3fd 100644 --- a/src/main/java/graphql/language/NodeVisitorStub.java +++ b/src/main/java/graphql/language/NodeVisitorStub.java @@ -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 context) { return visitNode(node, context); diff --git a/src/main/java/graphql/util/TraversalControl.java b/src/main/java/graphql/util/TraversalControl.java index a9956d80e9..3f68ede32d 100644 --- a/src/main/java/graphql/util/TraversalControl.java +++ b/src/main/java/graphql/util/TraversalControl.java @@ -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 @@ -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 } diff --git a/src/main/java/graphql/util/TraverserContext.java b/src/main/java/graphql/util/TraverserContext.java index 9cd15bf558..e6ca0efd8c 100644 --- a/src/main/java/graphql/util/TraverserContext.java +++ b/src/main/java/graphql/util/TraverserContext.java @@ -1,6 +1,6 @@ package graphql.util; -import graphql.Internal; +import graphql.PublicApi; import java.util.Set; @@ -9,7 +9,7 @@ * * @param type of tree node */ -@Internal +@PublicApi public interface TraverserContext { /** * Returns current node being visited @@ -25,13 +25,15 @@ public interface TraverserContext { * 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 getParentContext(); + /** + * The result of the {@link #getParentContext()}. + * + * @return + */ Object getParentResult(); /** @@ -71,10 +73,25 @@ public interface TraverserContext { TraverserContext setVar(Class 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(); } diff --git a/src/test/groovy/graphql/language/NodeTraverserTest.groovy b/src/test/groovy/graphql/language/NodeTraverserTest.groovy index ee796aa77c..3b29b3df00 100644 --- a/src/test/groovy/graphql/language/NodeTraverserTest.groovy +++ b/src/test/groovy/graphql/language/NodeTraverserTest.groovy @@ -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")