Skip to content

Commit 3be8ed4

Browse files
committed
add delete node functionality in Traverser and AstTransformer
1 parent e7b431a commit 3be8ed4

File tree

13 files changed

+278
-29
lines changed

13 files changed

+278
-29
lines changed

src/main/java/graphql/Assert.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,20 @@ public static <T> T assertNotNull(T object) {
2222
throw new AssertException("Object required to be not null");
2323
}
2424

25+
public static <T> void assertNull(T object, String format, Object... args) {
26+
if (object == null) {
27+
return;
28+
}
29+
throw new AssertException(format(format, args));
30+
}
31+
32+
public static <T> void assertNull(T object) {
33+
if (object == null) {
34+
return;
35+
}
36+
throw new AssertException("Object required to be null");
37+
}
38+
2539
public static <T> T assertNeverCalled() {
2640
throw new AssertException("Should never been called");
2741
}
@@ -55,6 +69,13 @@ public static void assertTrue(boolean condition, String format, Object... args)
5569
throw new AssertException(format(format, args));
5670
}
5771

72+
public static void assertFalse(boolean condition, String format, Object... args) {
73+
if (!condition) {
74+
return;
75+
}
76+
throw new AssertException(format(format, args));
77+
}
78+
5879
private static final String invalidNameErrorMessage = "Name must be non-null, non-empty and match [_A-Za-z][_0-9A-Za-z]* - was '%s'";
5980

6081
/**

src/main/java/graphql/language/AstMultiZipper.java

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

33
import graphql.PublicApi;
4+
import graphql.util.FpKit;
45

56
import java.util.ArrayList;
67
import java.util.Collections;
@@ -65,6 +66,10 @@ public List<AstZipper> getZippers() {
6566
return new ArrayList<>(zippers);
6667
}
6768

69+
public AstZipper getZipperForNode(Node node) {
70+
return FpKit.findOne(zippers, zipper -> zipper.getCurNode() == node);
71+
}
72+
6873
public AstMultiZipper withReplacedZippers(List<AstZipper> zippers) {
6974
return new AstMultiZipper(commonRoot, zippers);
7075
}

src/main/java/graphql/language/AstTransformerUtil.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
import graphql.util.TraversalControl;
55
import graphql.util.TraverserContext;
66

7+
import static graphql.Assert.assertTrue;
8+
79
@PublicApi
810
public class AstTransformerUtil {
911

@@ -25,4 +27,34 @@ public static TraversalControl changeNode(TraverserContext<Node> context, Node c
2527
context.changeNode(changedNode);
2628
return TraversalControl.CONTINUE;
2729
}
30+
31+
public static TraversalControl deleteNode(TraverserContext<Node> context) {
32+
assertTrue(context.getParentNode() != null, "can't delete root node");
33+
AstMultiZipper multiZipper = context.getCurrentAccumulate();
34+
AstZipper curZipper = context.getVar(AstZipper.class);
35+
AstZipper zipperForParent = multiZipper.getZipperForNode(context.getParentContext().thisNode());
36+
boolean zipperForParentAlreadyExisted = true;
37+
if (zipperForParent == null) {
38+
zipperForParent = curZipper.moveUp();
39+
zipperForParentAlreadyExisted = false;
40+
}
41+
42+
Node parentNode = zipperForParent.getCurNode();
43+
NodeLocation nodeLocation = curZipper.getBreadcrumbs().get(0).getLocation();
44+
Node newParent = NodeUtil.removeChild(parentNode, nodeLocation);
45+
AstZipper newZipperForParent = zipperForParent.withNewNode(newParent);
46+
47+
AstMultiZipper newMultiZipper;
48+
if (zipperForParentAlreadyExisted) {
49+
newMultiZipper = multiZipper.withReplacedZipper(zipperForParent, newZipperForParent);
50+
} else {
51+
newMultiZipper = multiZipper.withNewZipper(newZipperForParent);
52+
}
53+
54+
context.setAccumulate(newMultiZipper);
55+
context.deleteNode();
56+
return TraversalControl.CONTINUE;
57+
}
58+
59+
2860
}

src/main/java/graphql/language/AstZipper.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,12 @@ public AstZipper withNewNode(Node newNode) {
5353
return new AstZipper(newNode, breadcrumbs);
5454
}
5555

56+
public AstZipper moveUp() {
57+
Node node = getParent();
58+
List<AstBreadcrumb> newBreadcrumbs = breadcrumbs.subList(1, breadcrumbs.size());
59+
return new AstZipper(node, newBreadcrumbs);
60+
}
61+
5662
public Node toRoot() {
5763
Node curNode = this.curNode;
5864
for (AstBreadcrumb breadcrumb : breadcrumbs) {

src/main/java/graphql/language/NodeChildrenContainer.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,11 @@ public Builder replaceChild(String key, int index, Node newChild) {
9999
return this;
100100
}
101101

102+
public Builder removeChild(String key, int index) {
103+
this.children.get(key).remove(index);
104+
return this;
105+
}
106+
102107
public NodeChildrenContainer build() {
103108
return new NodeChildrenContainer(this.children);
104109

src/main/java/graphql/language/NodeUtil.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,4 +94,10 @@ public static void assertNewChildrenAreEmpty(NodeChildrenContainer newChildren)
9494
throw new IllegalArgumentException("Cannot pass non-empty newChildren to Node that doesn't hold children");
9595
}
9696
}
97+
98+
public static Node removeChild(Node node, NodeLocation childLocationToRemove) {
99+
NodeChildrenContainer namedChildren = node.getNamedChildren();
100+
NodeChildrenContainer newChildren = namedChildren.transform(builder -> builder.removeChild(childLocationToRemove.getName(), childLocationToRemove.getIndex()));
101+
return node.withNewChildren(newChildren);
102+
}
97103
}

src/main/java/graphql/util/DefaultTraverserContext.java

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,18 @@
77
import java.util.Map;
88
import java.util.Set;
99

10+
import static graphql.Assert.assertFalse;
1011
import static graphql.Assert.assertNotNull;
12+
import static graphql.Assert.assertNull;
1113
import static graphql.Assert.assertTrue;
1214

1315
@Internal
1416
public class DefaultTraverserContext<T> implements TraverserContext<T> {
1517

1618
private final T curNode;
1719
private T newNode;
20+
private boolean nodeDeleted;
21+
1822
private final TraverserContext<T> parent;
1923
private final Set<T> visited;
2024
private final Map<Class<?>, Object> vars;
@@ -53,20 +57,36 @@ public static <T> DefaultTraverserContext<T> simple(T node) {
5357

5458
@Override
5559
public T thisNode() {
60+
assertFalse(this.nodeDeleted, "node is deleted");
5661
if (newNode != null) {
5762
return newNode;
5863
}
5964
return curNode;
6065
}
6166

67+
@Override
68+
public T originalThisNode() {
69+
return curNode;
70+
}
6271

6372
@Override
6473
public void changeNode(T newNode) {
6574
assertNotNull(newNode);
66-
assertTrue(this.newNode == null, "node can only be changed once");
75+
assertTrue(this.newNode == null && !this.nodeDeleted, "node can only be changed or deleted once");
6776
this.newNode = newNode;
6877
}
6978

79+
@Override
80+
public void deleteNode() {
81+
assertNull(this.newNode, "node can only be changed or deleted once");
82+
assertFalse(this.nodeDeleted, "node can only be changed or deleted once");
83+
this.nodeDeleted = true;
84+
}
85+
86+
@Override
87+
public boolean isDeleted() {
88+
return this.nodeDeleted;
89+
}
7090

7191
@Override
7292
public TraverserContext<T> getParentContext() {
@@ -84,6 +104,14 @@ public List<T> getParentNodes() {
84104
return result;
85105
}
86106

107+
@Override
108+
public T getParentNode() {
109+
if (parent == null) {
110+
return null;
111+
}
112+
return parent.thisNode();
113+
}
114+
87115
@Override
88116
public Set<T> visitedNodes() {
89117
return visited;

src/main/java/graphql/util/FpKit.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import java.util.function.BiFunction;
1515
import java.util.function.BinaryOperator;
1616
import java.util.function.Function;
17+
import java.util.function.Predicate;
1718
import java.util.stream.Collectors;
1819
import java.util.stream.IntStream;
1920

@@ -151,4 +152,12 @@ public static <T> List<T> flatList(List<List<T>> listLists) {
151152
.collect(Collectors.toList());
152153
}
153154

155+
public static <T> T findOne(List<T> list, Predicate<T> filter) {
156+
return list
157+
.stream()
158+
.filter(filter)
159+
.findFirst()
160+
.orElse(null);
161+
}
162+
154163
}

src/main/java/graphql/util/Traverser.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,10 +140,11 @@ public TraverserResult traverse(Collection<? extends T> roots, TraverserVisitor<
140140
}
141141
} else {
142142
currentContext.setCurAccValue(currentAccValue);
143+
Object nodeBeforeEnter = currentContext.thisNode();
143144
TraversalControl traversalControl = visitor.enter(currentContext);
144145
currentAccValue = currentContext.getNewAccumulate();
145146
assertNotNull(traversalControl, "result of enter must not be null");
146-
this.traverserState.addVisited((T) currentContext.thisNode());
147+
this.traverserState.addVisited((T) nodeBeforeEnter);
147148
switch (traversalControl) {
148149
case QUIT:
149150
break traverseLoop;

src/main/java/graphql/util/TraverserContext.java

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,24 @@
1919
public interface TraverserContext<T> {
2020

2121
/**
22-
* Returns current node being visited
22+
* Returns current node being visited.
23+
* Special cases:
24+
* It is null for the root context and it is the changed node after {@link #changeNode(Object)} is called.
25+
* Throws Exception if the node is deleted.
2326
*
24-
* @return current node traverser is visiting. Is null for the root context
27+
* @return current node traverser is visiting.
28+
*
29+
* @throws graphql.AssertException if the current node is deleted
2530
*/
2631
T thisNode();
2732

33+
/**
34+
* Returns the original, unchanged, not deleted Node.
35+
*
36+
* @return the original node
37+
*/
38+
T originalThisNode();
39+
2840
/**
2941
* Change the current node to the provided node. Only applicable in enter.
3042
*
@@ -36,6 +48,16 @@ public interface TraverserContext<T> {
3648
*/
3749
void changeNode(T newNode);
3850

51+
/**
52+
* Deletes the current node.
53+
*/
54+
void deleteNode();
55+
56+
/**
57+
* @return true if the current node is deleted
58+
*/
59+
boolean isDeleted();
60+
3961
/**
4062
* Returns parent context.
4163
* Effectively organizes Context objects in a linked list so
@@ -54,6 +76,13 @@ public interface TraverserContext<T> {
5476
*/
5577
List<T> getParentNodes();
5678

79+
/**
80+
* The parent node.
81+
*
82+
* @return The parent node.
83+
*/
84+
T getParentNode();
85+
5786
/**
5887
* The position of the current node regarding to the parent node.
5988
*
@@ -156,7 +185,6 @@ public interface TraverserContext<T> {
156185
/**
157186
* In case of leave returns the children contexts, which have already been visited.
158187
*
159-
*
160188
* @return the children contexts. If the childs are a simple list the key is null.
161189
*/
162190
Map<String, List<TraverserContext<T>>> getChildrenContexts();

0 commit comments

Comments
 (0)