All Downloads are FREE. Search and download functionalities are using the official Maven repository.

graphql.util.NodeMultiZipper Maven / Gradle / Ivy

There is a newer version: 230521-nf-execution
Show newest version
package graphql.util;

import com.google.common.collect.ImmutableList;
import graphql.PublicApi;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

import static graphql.Assert.assertNotEmpty;
import static graphql.Assert.assertNotNull;
import static graphql.Assert.assertTrue;
import static graphql.util.NodeZipper.ModificationType.REPLACE;

@PublicApi
public class NodeMultiZipper {

    private final T commonRoot;
    private final ImmutableList> zippers;
    private final NodeAdapter nodeAdapter;

    public NodeMultiZipper(T commonRoot, List> zippers, NodeAdapter nodeAdapter) {
        this.commonRoot = assertNotNull(commonRoot);
        this.zippers = ImmutableList.copyOf(zippers);
        this.nodeAdapter = nodeAdapter;
    }

    /*
     * constructor without defensive copy of the zippers
     */
    private NodeMultiZipper(T commonRoot, List> zippers, NodeAdapter nodeAdapter, Object dummy) {
        this.commonRoot = assertNotNull(commonRoot);
        this.zippers = ImmutableList.copyOf(zippers);
        this.nodeAdapter = nodeAdapter;
    }

    /*
     * special factory method which doesn't copy the zippers list and trusts that the zippers list is not modified outside
     */
    public static  NodeMultiZipper newNodeMultiZipperTrusted(T commonRoot, List> zippers, NodeAdapter nodeAdapter) {
        return new NodeMultiZipper<>(commonRoot, zippers, nodeAdapter, null);
    }

    /**
     * @return can be null if the root node is marked as deleted
     */
    public T toRootNode() {
        if (zippers.size() == 0) {
            return commonRoot;
        }

        // we want to preserve the order here
        Set> curZippers = new LinkedHashSet<>(zippers);
        while (curZippers.size() > 1) {

            List> deepestZippers = getDeepestZippers(curZippers);
            Map>> sameParent = zipperWithSameParent(deepestZippers);

            List> newZippers = new ArrayList<>();
            Map> zipperByNode = FpKit.groupingByUniqueKey(curZippers, NodeZipper::getCurNode);
            for (Map.Entry>> entry : sameParent.entrySet()) {
                NodeZipper newZipper = moveUp(entry.getKey(), entry.getValue());
                Optional> zipperToBeReplaced = Optional.ofNullable(zipperByNode.get(entry.getKey()));
                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.iterator().next().toRoot();
    }

    public T getCommonRoot() {
        return commonRoot;
    }

    public List> getZippers() {
        return zippers;
    }

    public int size() {
        return zippers.size();
    }

    public NodeZipper getZipperForNode(T node) {
        return FpKit.findOneOrNull(zippers, zipper -> zipper.getCurNode() == node);
    }

    public NodeMultiZipper withReplacedZippers(List> zippers) {
        return new NodeMultiZipper<>(commonRoot, zippers, this.nodeAdapter);
    }


    public NodeMultiZipper withNewZipper(NodeZipper newZipper) {
        List> newZippers = new ArrayList<>(zippers);
        newZippers.add(newZipper);
        return new NodeMultiZipper<>(commonRoot, newZippers, this.nodeAdapter);
    }

    public NodeMultiZipper withReplacedZipper(NodeZipper oldZipper, NodeZipper newZipper) {
        int index = zippers.indexOf(oldZipper);
        assertTrue(index >= 0, () -> "oldZipper not found");
        List> newZippers = new ArrayList<>(zippers);
        newZippers.set(index, newZipper);
        return new NodeMultiZipper<>(commonRoot, newZippers, this.nodeAdapter);
    }

    public NodeMultiZipper withReplacedZipperForNode(T currentNode, T newNode) {
        int index = FpKit.findIndex(zippers, zipper -> zipper.getCurNode() == currentNode);
        assertTrue(index >= 0, () -> "No current zipper found for provided node");
        NodeZipper newZipper = zippers.get(index).withNewNode(newNode);
        List> newZippers = new ArrayList<>(zippers);
        newZippers.set(index, newZipper);
        return new NodeMultiZipper<>(commonRoot, newZippers, this.nodeAdapter);
    }


    private List> getDeepestZippers(Set> zippers) {
        Map>> grouped = FpKit.groupingBy(zippers, astZipper -> astZipper.getBreadcrumbs().size());

        Integer maxLevel = Collections.max(grouped.keySet());
        return grouped.get(maxLevel);
    }

    private NodeZipper moveUp(T parent, List> sameParent) {
        assertNotEmpty(sameParent, () -> "expected at least one zipper");

        Map> childrenMap = new HashMap<>(nodeAdapter.getNamedChildren(parent));
        Map indexCorrection = new HashMap<>();

        sameParent = new ArrayList<>(sameParent);
        sameParent.sort((zipper1, zipper2) -> {
            int index1 = zipper1.getBreadcrumbs().get(0).getLocation().getIndex();
            int index2 = zipper2.getBreadcrumbs().get(0).getLocation().getIndex();
            if (index1 != index2) {
                return Integer.compare(index1, index2);
            }
            NodeZipper.ModificationType modificationType1 = zipper1.getModificationType();
            NodeZipper.ModificationType modificationType2 = zipper2.getModificationType();

            // same index can never be deleted and changed at the same time

            if (modificationType1 == modificationType2) {
                return 0;
            }

            // always first replacing the node
            if (modificationType1 == REPLACE) {
                return -1;
            }
            // and then INSERT_BEFORE before INSERT_AFTER
            return modificationType1 == NodeZipper.ModificationType.INSERT_BEFORE ? -1 : 1;

        });

        for (NodeZipper zipper : sameParent) {
            NodeLocation location = zipper.getBreadcrumbs().get(0).getLocation();
            Integer ixDiff = indexCorrection.getOrDefault(location.getName(), 0);
            int ix = location.getIndex() + ixDiff;
            String name = location.getName();
            List childList = new ArrayList<>(childrenMap.get(name));
            switch (zipper.getModificationType()) {
                case REPLACE:
                    childList.set(ix, zipper.getCurNode());
                    break;
                case DELETE:
                    childList.remove(ix);
                    indexCorrection.put(name, ixDiff - 1);
                    break;
                case INSERT_BEFORE:
                    childList.add(ix, zipper.getCurNode());
                    indexCorrection.put(name, ixDiff + 1);
                    break;
                case INSERT_AFTER:
                    childList.add(ix + 1, zipper.getCurNode());
                    indexCorrection.put(name, ixDiff + 1);
                    break;
            }
            childrenMap.put(name, childList);
        }

        T newNode = nodeAdapter.withNewChildren(parent, childrenMap);
        List> newBreadcrumbs = sameParent.get(0).getBreadcrumbs().subList(1, sameParent.get(0).getBreadcrumbs().size());
        return new NodeZipper<>(newNode, newBreadcrumbs, this.nodeAdapter);
    }

    private Map>> zipperWithSameParent(List> zippers) {
        return FpKit.groupingBy(zippers, NodeZipper::getParent);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy