graphql.language.AstMultiZipper Maven / Gradle / Ivy
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 {
private final Node commonRoot;
private final List zippers;
public AstMultiZipper(Node commonRoot, List zippers) {
this.commonRoot = commonRoot;
this.zippers = new ArrayList<>(zippers);
}
public Node toRootNode() {
if (zippers.size() == 0) {
return commonRoot;
}
List curZippers = new ArrayList<>(zippers);
while (curZippers.size() > 1) {
List deepestZippers = getDeepestZippers(curZippers);
Map> sameParent = zipperWithSameParent(deepestZippers);
List newZippers = new ArrayList<>();
for (Map.Entry> entry : sameParent.entrySet()) {
AstZipper newZipper = moveUp(entry.getKey(), entry.getValue());
Optional 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 getZippers() {
return new ArrayList<>(zippers);
}
public AstMultiZipper withReplacedZippers(List zippers) {
return new AstMultiZipper(commonRoot, zippers);
}
public AstMultiZipper withNewZipper(AstZipper newZipper) {
List 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 newZippers = new ArrayList<>(zippers);
newZippers.set(index, newZipper);
return new AstMultiZipper(commonRoot, newZippers);
}
private List getDeepestZippers(List zippers) {
Map> 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 sameParent) {
assertNotEmpty(sameParent, "expected at least one zipper");
Map> childrenMap = parent.getNamedChildren().getChildren();
for (AstZipper zipper : sameParent) {
NodeLocation location = zipper.getBreadcrumbs().get(0).getLocation();
childrenMap.computeIfAbsent(location.getName(), (key) -> new ArrayList<>());
List 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 newBreadcrumbs = sameParent.get(0).getBreadcrumbs().subList(1, sameParent.get(0).getBreadcrumbs().size());
return new AstZipper(newNode, newBreadcrumbs);
}
private Map> zipperWithSameParent(List 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() +
'}';
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy