-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Make all Nodes immutable but still easy to "modify" #1335
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
1f67b2c
05fb8cc
5c575f0
9a145f4
1cc1222
40a4f7b
5f11a73
7a9f6cf
7b86cee
8000977
e7639a8
ee8c62d
cb72a31
5558e3d
f228109
f92364c
65a34a8
7952846
3a345de
49525f4
8436b61
ce19b7c
3833d72
0e8b4f6
1f29524
84fdbc1
e801153
007567d
7215491
85da93c
2ae091b
0d35906
cca5172
a0463cd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,11 +8,16 @@ | |
| import java.util.LinkedHashMap; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
| import java.util.function.Function; | ||
| import java.util.stream.Collectors; | ||
|
|
||
| import static java.util.stream.Collectors.mapping; | ||
| import static java.util.stream.Collectors.toList; | ||
|
|
||
| @Internal | ||
| public class ExecutionResultMultiZipper { | ||
|
|
||
|
|
||
| private final ExecutionResultNode commonRoot; | ||
| private final List<ExecutionResultZipper> zippers; | ||
|
|
||
|
|
@@ -68,7 +73,7 @@ public ExecutionResultMultiZipper withReplacedZipper(ExecutionResultZipper oldZi | |
|
|
||
| private List<ExecutionResultZipper> getDeepestZippers(List<ExecutionResultZipper> zippers) { | ||
| Map<Integer, List<ExecutionResultZipper>> grouped = | ||
| zippers.stream().collect(Collectors.groupingBy(executionResultZipper -> executionResultZipper.getBreadcrumbList().size())); | ||
| zippers.stream().collect(Collectors.groupingBy(executionResultZipper -> executionResultZipper.getBreadcrumbList().size(), LinkedHashMap::new, mapping(Function.identity(), toList()))); | ||
| Integer maxLevel = Collections.max(grouped.keySet()); | ||
| return grouped.get(maxLevel); | ||
| } | ||
|
|
@@ -85,7 +90,7 @@ private ExecutionResultZipper moveUp(ExecutionResultNode parent, List<ExecutionR | |
| } | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. TODO ^
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
|
|
||
| private Map<ExecutionResultNode, List<ExecutionResultZipper>> zipperWithSameParent(List<ExecutionResultZipper> zippers) { | ||
| return zippers.stream().collect(Collectors.groupingBy(ExecutionResultZipper::getParent)); | ||
| return zippers.stream().collect(Collectors.groupingBy(ExecutionResultZipper::getParent, LinkedHashMap::new, mapping(Function.identity(), toList()))); | ||
| } | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. TODO ^ I think you put in a supplier of the Map
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah I reckon create its own helper method |
||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,11 +10,15 @@ | |
| import java.util.List; | ||
| import java.util.function.Consumer; | ||
|
|
||
| import static graphql.language.NodeChildrenContainer.newNodeChildrenContainer; | ||
|
|
||
| @PublicApi | ||
| public class Argument extends AbstractNode<Argument> implements NamedNode<Argument> { | ||
|
|
||
| private String name; | ||
| private Value value; | ||
| private final String name; | ||
| private final Value value; | ||
|
|
||
| public static final String CHILD_VALUE = "value"; | ||
|
|
||
| @Internal | ||
| protected Argument(String name, Value value, SourceLocation sourceLocation, List<Comment> comments, IgnoredChars ignoredChars) { | ||
|
|
@@ -42,21 +46,26 @@ public Value getValue() { | |
| return value; | ||
| } | ||
|
|
||
| public void setName(String name) { | ||
| this.name = name; | ||
| } | ||
|
|
||
| public void setValue(Value value) { | ||
| this.value = value; | ||
| } | ||
|
|
||
| @Override | ||
| public List<Node> getChildren() { | ||
| List<Node> result = new ArrayList<>(); | ||
| result.add(value); | ||
| return result; | ||
| } | ||
|
|
||
| @Override | ||
| public NodeChildrenContainer getNamedChildren() { | ||
| return newNodeChildrenContainer() | ||
| .child(CHILD_VALUE, value) | ||
| .build(); | ||
| } | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would do static import in all the places to reduce the boiler plate
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
|
|
||
| @Override | ||
| public Argument withNewChildren(NodeChildrenContainer newChildren) { | ||
| return transform(builder -> builder | ||
| .value(newChildren.getChildOrNull(CHILD_VALUE)) | ||
| ); | ||
| } | ||
|
|
||
| @Override | ||
| public boolean isEqualTo(Node o) { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| package graphql.language; | ||
|
|
||
| import graphql.PublicApi; | ||
|
|
||
| import java.util.Objects; | ||
|
|
||
| /** | ||
| * Used by {@link AstZipper} to identify the position of the current node inside the tree: | ||
| * the breadcrumbs lead you to the root node. | ||
| */ | ||
| @PublicApi | ||
| public class AstBreadcrumb { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I woukld still love to see JavaDoc at the top to outline the class.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. added |
||
|
|
||
| private final Node node; | ||
| private final NodeLocation location; | ||
|
|
||
| public AstBreadcrumb(Node node, NodeLocation location) { | ||
| this.node = node; | ||
| this.location = location; | ||
| } | ||
|
|
||
| public Node getNode() { | ||
| return node; | ||
| } | ||
|
|
||
| public NodeLocation getLocation() { | ||
| return location; | ||
| } | ||
|
|
||
|
|
||
| @Override | ||
| public String toString() { | ||
| return "AstBreadcrumb{" + | ||
| "node=" + node + | ||
| ", location=" + location + | ||
| '}'; | ||
| } | ||
|
|
||
| @Override | ||
| public boolean equals(Object o) { | ||
| if (this == o) { | ||
| return true; | ||
| } | ||
| if (o == null || getClass() != o.getClass()) { | ||
| return false; | ||
| } | ||
| AstBreadcrumb that = (AstBreadcrumb) o; | ||
| return Objects.equals(node, that.node) && | ||
| Objects.equals(location, that.location); | ||
| } | ||
|
|
||
| @Override | ||
| public int hashCode() { | ||
| return Objects.hash(node, location); | ||
| } | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,127 @@ | ||
| package graphql.language; | ||
|
|
||
| import graphql.PublicApi; | ||
|
|
||
| import java.util.ArrayList; | ||
| import java.util.Collections; | ||
| import java.util.LinkedHashMap; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
| import java.util.Optional; | ||
| import java.util.function.Function; | ||
| import java.util.stream.Collectors; | ||
|
|
||
| import static graphql.Assert.assertNotEmpty; | ||
| import static graphql.Assert.assertTrue; | ||
| import static graphql.language.NodeChildrenContainer.newNodeChildrenContainer; | ||
| import static java.util.stream.Collectors.groupingBy; | ||
| import static java.util.stream.Collectors.mapping; | ||
| import static java.util.stream.Collectors.toList; | ||
|
|
||
| /** | ||
| * A collection of {@link AstZipper} sharing all the same root node. | ||
| * It is used to track multiple changes at once, while {@link AstZipper} focus on one Node. | ||
| */ | ||
| @PublicApi | ||
| public class AstMultiZipper { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. JavaDoc
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. added |
||
| private final Node commonRoot; | ||
| private final List<AstZipper> zippers; | ||
|
|
||
| public AstMultiZipper(Node commonRoot, List<AstZipper> zippers) { | ||
| this.commonRoot = commonRoot; | ||
| this.zippers = new ArrayList<>(zippers); | ||
| } | ||
|
|
||
| public Node toRootNode() { | ||
| if (zippers.size() == 0) { | ||
| return commonRoot; | ||
| } | ||
|
|
||
| List<AstZipper> curZippers = new ArrayList<>(zippers); | ||
| while (curZippers.size() > 1) { | ||
|
|
||
| List<AstZipper> deepestZippers = getDeepestZippers(curZippers); | ||
| Map<Node, List<AstZipper>> sameParent = zipperWithSameParent(deepestZippers); | ||
|
|
||
| List<AstZipper> newZippers = new ArrayList<>(); | ||
| for (Map.Entry<Node, List<AstZipper>> entry : sameParent.entrySet()) { | ||
| AstZipper newZipper = moveUp(entry.getKey(), entry.getValue()); | ||
| Optional<AstZipper> zipperToBeReplaced = curZippers.stream().filter(zipper -> zipper.getCurNode() == entry.getKey()).findFirst(); | ||
| zipperToBeReplaced.ifPresent(curZippers::remove); | ||
| newZippers.add(newZipper); | ||
| } | ||
| curZippers.removeAll(deepestZippers); | ||
| curZippers.addAll(newZippers); | ||
| } | ||
| assertTrue(curZippers.size() == 1, "unexpected state: all zippers must share the same root node"); | ||
| return curZippers.get(0).toRoot(); | ||
| } | ||
|
|
||
| public Node getCommonRoot() { | ||
| return commonRoot; | ||
| } | ||
|
|
||
| public List<AstZipper> getZippers() { | ||
| return new ArrayList<>(zippers); | ||
| } | ||
|
|
||
| public AstMultiZipper withReplacedZippers(List<AstZipper> zippers) { | ||
| return new AstMultiZipper(commonRoot, zippers); | ||
| } | ||
|
|
||
| public AstMultiZipper withNewZipper(AstZipper newZipper) { | ||
| List<AstZipper> newZippers = getZippers(); | ||
| newZippers.add(newZipper); | ||
| return new AstMultiZipper(commonRoot, newZippers); | ||
| } | ||
|
|
||
| public AstMultiZipper withReplacedZipper(AstZipper oldZipper, AstZipper newZipper) { | ||
| int index = zippers.indexOf(oldZipper); | ||
| assertTrue(index >= 0, "oldZipper not found"); | ||
| List<AstZipper> newZippers = new ArrayList<>(zippers); | ||
| newZippers.set(index, newZipper); | ||
| return new AstMultiZipper(commonRoot, newZippers); | ||
| } | ||
|
|
||
|
|
||
| private List<AstZipper> getDeepestZippers(List<AstZipper> zippers) { | ||
| Map<Integer, List<AstZipper>> grouped = zippers | ||
| .stream() | ||
| .collect(groupingBy(astZipper -> astZipper.getBreadcrumbs().size(), LinkedHashMap::new, mapping(Function.identity(), toList()))); | ||
|
|
||
| Integer maxLevel = Collections.max(grouped.keySet()); | ||
| return grouped.get(maxLevel); | ||
| } | ||
|
|
||
| private AstZipper moveUp(Node parent, List<AstZipper> sameParent) { | ||
| assertNotEmpty(sameParent, "expected at least one zipper"); | ||
| Map<String, List<Node>> childrenMap = parent.getNamedChildren().getChildren(); | ||
|
|
||
| for (AstZipper zipper : sameParent) { | ||
| NodeLocation location = zipper.getBreadcrumbs().get(0).getLocation(); | ||
| childrenMap.computeIfAbsent(location.getName(), (key) -> new ArrayList<>()); | ||
| List<Node> childrenList = childrenMap.get(location.getName()); | ||
| if (childrenList.size() > location.getIndex()) { | ||
| childrenList.set(location.getIndex(), zipper.getCurNode()); | ||
| } else { | ||
| childrenList.add(zipper.getCurNode()); | ||
| } | ||
| } | ||
| Node newNode = parent.withNewChildren(newNodeChildrenContainer(childrenMap).build()); | ||
| List<AstBreadcrumb> newBreadcrumbs = sameParent.get(0).getBreadcrumbs().subList(1, sameParent.get(0).getBreadcrumbs().size()); | ||
| return new AstZipper(newNode, newBreadcrumbs); | ||
| } | ||
|
|
||
| private Map<Node, List<AstZipper>> zipperWithSameParent(List<AstZipper> zippers) { | ||
| return zippers.stream().collect(groupingBy(AstZipper::getParent, LinkedHashMap::new, | ||
| mapping(Function.identity(), Collectors.toList()))); | ||
| } | ||
|
|
||
| @Override | ||
| public String toString() { | ||
| return "AstMultiZipper{" + | ||
| "commonRoot=" + commonRoot.getClass() + | ||
| ", zippersCount=" + zippers.size() + | ||
| '}'; | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Almost deserves it is on
Collectorbased puirely on how much boilerplate. Perhaps its own methodzippers.stream().collect(Collectors.groupingBy(thisGrouping(executionResultZipper -> executionResultZipper.getBreadcrumbList().size()));