Skip to content

Commit 2c4d341

Browse files
gkeslerandimarek
authored andcommitted
- incorporating comments from @andimarek regarding complexity of QueryTraversal
* moved from anonymous to the outer level utility classes QueryTraversalNotifier, QueryTraversalContext and SelectionProvider * moved from anonymous to the inner level main visitor class QueryTraversalDelegate
1 parent 4fe4e85 commit 2c4d341

4 files changed

Lines changed: 208 additions & 146 deletions

File tree

Lines changed: 95 additions & 146 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package graphql.analysis;
22

3-
import graphql.Assert;
43
import graphql.Internal;
54
import graphql.execution.ConditionalNodes;
65
import graphql.execution.ValuesResolver;
@@ -21,7 +20,6 @@
2120
import graphql.schema.GraphQLUnmodifiedType;
2221
import graphql.schema.SchemaUtil;
2322

24-
import java.util.LinkedHashMap;
2523
import java.util.Map;
2624

2725
import static graphql.Assert.assertNotNull;
@@ -36,34 +34,31 @@
3634
import java.util.ArrayDeque;
3735
import java.util.Collections;
3836
import java.util.Deque;
39-
import java.util.List;
40-
import java.util.Objects;
41-
import java.util.function.Consumer;
4237

4338
@Internal
4439
public class QueryTraversal {
4540

46-
4741
private final OperationDefinition operationDefinition;
4842
private final GraphQLSchema schema;
49-
private Map<String, FragmentDefinition> fragmentsByName = new LinkedHashMap<>();
43+
private Map<String, FragmentDefinition> fragmentsByName;
5044
private final Map<String, Object> variables;
45+
private final SelectionProvider selectionProvider;
5146

5247
private final ConditionalNodes conditionalNodes = new ConditionalNodes();
53-
5448
private final ValuesResolver valuesResolver = new ValuesResolver();
5549
private final SchemaUtil schemaUtil = new SchemaUtil();
5650

57-
5851
public QueryTraversal(GraphQLSchema schema,
5952
Document document,
6053
String operation,
6154
Map<String, Object> variables) {
6255
NodeUtil.GetOperationResult getOperationResult = NodeUtil.getOperation(document, operation);
56+
6357
this.operationDefinition = getOperationResult.operationDefinition;
6458
this.fragmentsByName = getOperationResult.fragmentsByName;
6559
this.schema = schema;
6660
this.variables = variables;
61+
this.selectionProvider = new SelectionProvider(fragmentsByName);
6762
}
6863

6964
public void visitPostOrder(QueryVisitor visitor) {
@@ -102,160 +97,114 @@ public <T> T reducePreOrder(QueryReducer<T> queryReducer, T initialValue) {
10297
visitPreOrder((env) -> acc[0] = queryReducer.reduceField(env, (T) acc[0]));
10398
return (T) acc[0];
10499
}
105-
106-
private List<Selection> childrenOf (Selection n) {
107-
return (List<Selection>)n.accept(null, new NodeVisitorStub<List<Selection>>() {
108-
@Override
109-
public Object visit(InlineFragment node, List<Selection> data) {
110-
return getChildren(node.getSelectionSet());
111-
}
112-
113-
@Override
114-
public Object visit(FragmentSpread fragmentSpread, List<Selection> data) {
115-
return getChildren(fragmentsByName.get(fragmentSpread.getName()).getSelectionSet());
116-
}
117-
118-
@Override
119-
public Object visit(Field node, List<Selection> data) {
120-
return getChildren(node.getSelectionSet());
121-
}
122-
123-
@Override
124-
public Object visit(SelectionSet node, List<Selection> data) {
125-
return node.getSelections();
126-
}
127-
128-
private List<Selection> getChildren (SelectionSet node) {
129-
return (node == null)
130-
? Collections.emptyList()
131-
: node.getSelections();
132-
}
133-
});
134-
}
135-
100+
136101
private void visitImpl(QueryVisitor visitor, SelectionSet selectionSet, GraphQLCompositeType type, QueryVisitorEnvironment parent, boolean preOrder) {
137-
new Traverser<Selection>(this::childrenOf)
138-
.traverse(selectionSet.getSelections(), null, new NodeVisitorStub<TraverserContext<Selection>>() {
139-
@Override
140-
public Object visit(InlineFragment inlineFragment, TraverserContext<Selection> context) {
141-
if (!conditionalNodes.shouldInclude(variables, inlineFragment.getDirectives()))
142-
return TraverserMarkers.ABORT; // stop recursing down
143-
144-
// inline fragments are allowed not have type conditions, if so the parent type counts
145-
Frame top = frames.peek();
146-
147-
GraphQLCompositeType fragmentCondition;
148-
if (inlineFragment.getTypeCondition() != null) {
149-
TypeName typeCondition = inlineFragment.getTypeCondition();
150-
fragmentCondition = (GraphQLCompositeType) schema.getType(typeCondition.getName());
151-
} else {
152-
fragmentCondition = top.type;
153-
}
102+
QueryTraversalNotifier visitorNotifier = preOrder
103+
? new QueryTraversalNotifier(visitor::visitField, env -> {})
104+
: new QueryTraversalNotifier(env -> {}, visitor::visitField);
105+
106+
new Traverser<Selection>(selectionProvider::childrenOf)
107+
.traverse(selectionSet.getSelections(), null, new QueryTraversalDelegate(visitorNotifier, new QueryTraversalContext(type, parent)));
108+
}
109+
110+
private class QueryTraversalDelegate extends NodeVisitorStub<TraverserContext<Selection>> {
111+
112+
final Deque<QueryTraversalContext> contextStack;
113+
final QueryTraversalNotifier visitorNotifier;
114+
115+
QueryTraversalDelegate (QueryTraversalNotifier visitorNotifier, QueryTraversalContext root) {
116+
this.contextStack = new ArrayDeque<>(Collections.singleton(root));
117+
this.visitorNotifier = visitorNotifier;
118+
}
119+
120+
@Override
121+
public Object visit(InlineFragment inlineFragment, TraverserContext<Selection> context) {
122+
if (!conditionalNodes.shouldInclude(variables, inlineFragment.getDirectives()))
123+
return TraverserMarkers.ABORT; // stop recursing down
124+
125+
// inline fragments are allowed not have type conditions, if so the parent type counts
126+
QueryTraversalContext top = contextStack.peek();
127+
128+
GraphQLCompositeType fragmentCondition;
129+
if (inlineFragment.getTypeCondition() != null) {
130+
TypeName typeCondition = inlineFragment.getTypeCondition();
131+
fragmentCondition = (GraphQLCompositeType) schema.getType(typeCondition.getName());
132+
} else {
133+
fragmentCondition = top.getType();
134+
}
154135

155-
// for unions we only have other fragments inside
156-
frames.push(new Frame(fragmentCondition, top.environment));
157-
return context;
158-
}
136+
// for unions we only have other fragments inside
137+
contextStack.push(new QueryTraversalContext(fragmentCondition, top.getEnvironment()));
138+
return context;
139+
}
159140

160-
@Override
161-
public Object visit(FragmentSpread fragmentSpread, TraverserContext<Selection> context) {
162-
if (!conditionalNodes.shouldInclude(variables, fragmentSpread.getDirectives()))
163-
return TraverserMarkers.ABORT; // stop recursion
141+
@Override
142+
public Object visit(FragmentSpread fragmentSpread, TraverserContext<Selection> context) {
143+
if (!conditionalNodes.shouldInclude(variables, fragmentSpread.getDirectives()))
144+
return TraverserMarkers.ABORT; // stop recursion
164145

165-
FragmentDefinition fragmentDefinition = fragmentsByName.get(fragmentSpread.getName());
166-
if (!conditionalNodes.shouldInclude(variables, fragmentDefinition.getDirectives()))
167-
return TraverserMarkers.ABORT; // stop recursion
146+
FragmentDefinition fragmentDefinition = fragmentsByName.get(fragmentSpread.getName());
147+
if (!conditionalNodes.shouldInclude(variables, fragmentDefinition.getDirectives()))
148+
return TraverserMarkers.ABORT; // stop recursion
168149

169-
Frame top = frames.peek();
150+
QueryTraversalContext top = contextStack.peek();
170151

171-
GraphQLCompositeType typeCondition = (GraphQLCompositeType) schema.getType(fragmentDefinition.getTypeCondition().getName());
152+
GraphQLCompositeType typeCondition = (GraphQLCompositeType) schema.getType(fragmentDefinition.getTypeCondition().getName());
172153

173-
frames.push(new Frame(typeCondition, top.environment));
174-
return context;
175-
}
154+
contextStack.push(new QueryTraversalContext(typeCondition, top.getEnvironment()));
155+
return context;
156+
}
176157

177-
@Override
178-
public Object visit(Field field, TraverserContext<Selection> context) {
179-
if (!conditionalNodes.shouldInclude(variables, field.getDirectives()))
180-
return TraverserMarkers.ABORT; // stop recursion
158+
@Override
159+
public Object visit(Field field, TraverserContext<Selection> context) {
160+
if (!conditionalNodes.shouldInclude(variables, field.getDirectives()))
161+
return TraverserMarkers.ABORT; // stop recursion
181162

182-
Frame top = frames.peek();
163+
QueryTraversalContext top = contextStack.peek();
183164

184-
GraphQLFieldDefinition fieldDefinition = Introspection.getFieldDef(schema, top.type, field.getName());
185-
Map<String, Object> argumentValues = valuesResolver.getArgumentValues(schema.getFieldVisibility(), fieldDefinition.getArguments(), field.getArguments(), variables);
165+
GraphQLFieldDefinition fieldDefinition = Introspection.getFieldDef(schema, top.getType(), field.getName());
166+
Map<String, Object> argumentValues = valuesResolver.getArgumentValues(schema.getFieldVisibility(), fieldDefinition.getArguments(), field.getArguments(), variables);
186167

187-
QueryVisitorEnvironment environment = new QueryVisitorEnvironment(field, fieldDefinition, top.type, top.environment, argumentValues);
188-
visitorNotifier.notifyPreOrder(environment);
168+
QueryVisitorEnvironment environment = new QueryVisitorEnvironment(field, fieldDefinition, top.getType(), top.getEnvironment(), argumentValues);
169+
visitorNotifier.notifyPreOrder(environment);
189170

190-
GraphQLUnmodifiedType unmodifiedType = schemaUtil.getUnmodifiedType(fieldDefinition.getType());
191-
Frame frame = (unmodifiedType instanceof GraphQLCompositeType)
192-
? new Frame((GraphQLCompositeType)unmodifiedType, environment)
193-
: new Frame(null, environment);// Terminal (scalar) node, EMPTY FRAME
171+
GraphQLUnmodifiedType unmodifiedType = schemaUtil.getUnmodifiedType(fieldDefinition.getType());
172+
QueryTraversalContext frame = (unmodifiedType instanceof GraphQLCompositeType)
173+
? new QueryTraversalContext((GraphQLCompositeType)unmodifiedType, environment)
174+
: new QueryTraversalContext(null, environment);// Terminal (scalar) node, EMPTY FRAME
194175

195-
frames.push(frame);
196-
return context;
197-
}
176+
contextStack.push(frame);
177+
return context;
178+
}
198179

199-
@Override
200-
public Object enter(TraverserContext<Node> context, TraverserContext<Selection> data) {
201-
return context
202-
.thisNode()
203-
.accept(context, this);
204-
}
180+
@Override
181+
public Object enter(TraverserContext<Node> context, TraverserContext<Selection> data) {
182+
return context
183+
.thisNode()
184+
.accept(context, this);
185+
}
205186

206-
@Override
207-
public Object leave(TraverserContext<Node> context, TraverserContext<Selection> data) {
208-
return context
209-
.thisNode()
210-
.accept(context, postOrderVisitor);
211-
}
212-
213-
final NodeVisitor<TraverserContext<Selection>> postOrderVisitor = new NodeVisitorStub<TraverserContext<Selection>>() {
214-
@Override
215-
public Object visit(Field field, TraverserContext<Selection> context) {
216-
Frame top = frames.pop();
217-
visitorNotifier.notifyPostOrder(top.environment);
218-
219-
return context;
220-
}
187+
@Override
188+
public Object leave(TraverserContext<Node> context, TraverserContext<Selection> data) {
189+
return context
190+
.thisNode()
191+
.accept(context, postOrderVisitor);
192+
}
221193

222-
@Override
223-
protected Object visitSelection(Selection<?> node, TraverserContext<Selection> context) {
224-
frames.pop();
225-
return context;
226-
}
227-
};
194+
final NodeVisitor<TraverserContext<Selection>> postOrderVisitor = new NodeVisitorStub<TraverserContext<Selection>>() {
195+
@Override
196+
public Object visit(Field field, TraverserContext<Selection> context) {
197+
QueryTraversalContext top = contextStack.pop();
198+
visitorNotifier.notifyPostOrder(top.getEnvironment());
228199

229-
class Frame {
230-
Frame (GraphQLCompositeType type, QueryVisitorEnvironment environment) {
231-
this.type = type;
232-
this.environment = environment;
233-
}
200+
return context;
201+
}
234202

235-
GraphQLCompositeType type;
236-
QueryVisitorEnvironment environment;
237-
}
238-
239-
class QueryVisitorNotifier {
240-
QueryVisitorNotifier (Consumer<QueryVisitorEnvironment> preOrder, Consumer<QueryVisitorEnvironment> postOrder) {
241-
this.preOrder = Assert.assertNotNull(preOrder);
242-
this.postOrder = Assert.assertNotNull(postOrder);
243-
}
244-
245-
void notifyPreOrder (QueryVisitorEnvironment env) {
246-
preOrder.accept(env);
247-
}
248-
void notifyPostOrder (QueryVisitorEnvironment env) {
249-
postOrder.accept(env);
250-
}
251-
252-
final Consumer<QueryVisitorEnvironment> preOrder, postOrder;
253-
}
254-
255-
final Deque<Frame> frames = new ArrayDeque<>(Collections.singleton(new Frame(type, parent)));
256-
final QueryVisitorNotifier visitorNotifier = preOrder
257-
? new QueryVisitorNotifier(visitor::visitField, env -> {})
258-
: new QueryVisitorNotifier(env -> {}, visitor::visitField);
259-
});
203+
@Override
204+
protected Object visitSelection(Selection<?> node, TraverserContext<Selection> context) {
205+
contextStack.pop();
206+
return context;
207+
}
208+
};
260209
}
261210
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package graphql.analysis;
2+
3+
import graphql.Internal;
4+
import graphql.schema.GraphQLCompositeType;
5+
6+
/**
7+
* QueryTraversal helper class that maintains traversal context as
8+
* the query traversal algorithm traverses down the Selection AST
9+
*/
10+
@Internal
11+
class QueryTraversalContext {
12+
13+
private final GraphQLCompositeType type;
14+
private final QueryVisitorEnvironment environment;
15+
16+
QueryTraversalContext(GraphQLCompositeType type, QueryVisitorEnvironment environment) {
17+
this.type = type;
18+
this.environment = environment;
19+
}
20+
21+
public GraphQLCompositeType getType() {
22+
return type;
23+
}
24+
25+
public QueryVisitorEnvironment getEnvironment() {
26+
return environment;
27+
}
28+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package graphql.analysis;
2+
3+
import graphql.Assert;
4+
import graphql.Internal;
5+
import java.util.function.Consumer;
6+
7+
/**
8+
* QueryTraversal helper class responsible for notifications to the
9+
* provided preOrder and postOrder consumers
10+
*/
11+
@Internal
12+
class QueryTraversalNotifier {
13+
14+
private final Consumer<QueryVisitorEnvironment> preOrder;
15+
private final Consumer<QueryVisitorEnvironment> postOrder;
16+
17+
QueryTraversalNotifier(Consumer<QueryVisitorEnvironment> preOrder, Consumer<QueryVisitorEnvironment> postOrder) {
18+
this.preOrder = Assert.assertNotNull(preOrder);
19+
this.postOrder = Assert.assertNotNull(postOrder);
20+
}
21+
22+
void notifyPreOrder(QueryVisitorEnvironment env) {
23+
preOrder.accept(env);
24+
}
25+
26+
void notifyPostOrder(QueryVisitorEnvironment env) {
27+
postOrder.accept(env);
28+
}
29+
}

0 commit comments

Comments
 (0)