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

ai.vespa.schemals.index.InheritanceGraph Maven / Gradle / Ivy

There is a newer version: 8.441.21
Show newest version
package ai.vespa.schemals.index;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

import ai.vespa.schemals.common.ClientLogger;


/**
 * This class is responsible for managing inheritance relationships among generic constructs.
 * Each node is identified by a NodeType which should implement a good hashCode and .equals.
 *
 * @author Mangern
 */
public class InheritanceGraph {

    protected Map> parentsOfNode = new HashMap<>();
    protected Map> childrenOfNode = new HashMap<>();

    public class SearchResult {
        public NodeType node = null;
        public ResultType result = null;

        public SearchResult(NodeType node, ResultType result) {
            this.node = node;
            this.result = result;
        }
    }

    public InheritanceGraph() { }

    // Note: does not remove childrenOfNode
    //       because if this node is added back we 
    //       lazily remember the children, and access valid children through
    //       getValidChildren
    public void clearInheritsList(NodeType node) {
        if (!nodeExists(node)) return;

        // Remove myself from my parents children
        for (NodeType parent : parentsOfNode.get(node)) {
            if (childrenOfNode.containsKey(parent)) childrenOfNode.get(parent).remove(node);
        }

        parentsOfNode.remove(node);

    }

    public void createNodeIfNotExists(NodeType node) {
        if (!parentsOfNode.containsKey(node)) {
            parentsOfNode.put(node, new ArrayList<>());
        }

        if (!childrenOfNode.containsKey(node)) {
            childrenOfNode.put(node, new ArrayList<>());
        }
    }

    /**
     * Try to register a new inheritance relationship such that
     * childNode inherits from parentNode
     *
     * @return boolean indicating success. Inheritance is unsuccessful if the relationship would create a cycle 
     * in the inheritance graph.
     */
    public boolean addInherits(NodeType childNode, NodeType parentNode) {
        createNodeIfNotExists(childNode);
        createNodeIfNotExists(parentNode);

        List existingAncestors = getAllAncestors(parentNode);

        if (existingAncestors.contains(childNode)) {
            // childNode cannot inherit from parentNode if parentNode directly or indirectly inherits from childNode (cycle)
            return false;
        }

        List parentList = parentsOfNode.get(childNode);
        if (!parentList.contains(parentNode)) {
            parentList.add(parentNode);
        }

        List parentChildren = childrenOfNode.get(parentNode);

        if (!parentChildren.contains(childNode)) {
            parentChildren.add(childNode);
        }

        return true;
    }

    public List getAllParents(NodeType node) {
        createNodeIfNotExists(node);

        return parentsOfNode.get(node);
    }

    /**
     * Gets a list of all direct or indirect inheritance ancestors.
     * The returned list is in a topological order, i.e. the older ancestors
     * come earler in the list.
     * List includes the queried node (will be last)
     */
    public List getAllAncestors(NodeType node) {
        createNodeIfNotExists(node);

        List result = new ArrayList<>();
        Set visited = new HashSet<>();
        getAllAncestorsImpl(node, result, visited);
        return result;
    }

    /**
     * Gets a list of all direct or indirect inheritance ancestors.
     * The returned list is in a topological order, i.e. the older descendants
     * come earler in the list.
     * List includes the queried node (will be first)
     */
    public List getAllDescendants(NodeType node) {
        createNodeIfNotExists(node);

        List result = new ArrayList<>();
        Set visited = new HashSet<>();
        getAllDescendantsImpl(node, result, visited);
        return result;
    }

    /**
     * @return a list of all registered documents in topological order
     */
    public List getTopoOrdering() {
        Set visited = new HashSet<>();

        List result = new LinkedList<>();
        for (NodeType node : parentsOfNode.keySet()) {
            if (visited.contains(node)) continue;

            getAllAncestorsImpl(node, result, visited);
        }

        return result;
    }

    /**
     * Searches upwards for nodes where predicate(node) returns non null.
     * If a match is found, parents of that node are not checked (unless they are reachable by some other path without matches).
     * Return values store both the node and the result of running predicate on them
     */
    public  List> findFirstMatches(NodeType node, Function predicate) {
        createNodeIfNotExists(node);

        Set visited = new HashSet<>();
        List> result = new ArrayList<>();
        findFirstMatchesImpl(node, result, visited, predicate);
        return result;
    }

    private  void findFirstMatchesImpl(NodeType node, List> result, Set visited, Function predicate) {
        if (!parentsOfNode.containsKey(node)) return;
        if (visited.contains(node)) return;

        visited.add(node);

        T match = predicate.apply(node);

        if (match != null) {
            result.add(new SearchResult<>(node, match));
            return;
        }

        for (NodeType parent : parentsOfNode.get(node)) {
            findFirstMatchesImpl(parent, result, visited, predicate);
        }
    }
    
    /**
     * Recursive search upwards through the inheritance graph to
     * retreive all ancestors of the given node.
     */
    private void getAllAncestorsImpl(NodeType node, List result, Set visited) {
        if (!parentsOfNode.containsKey(node)) return;
        if (visited.contains(node)) return;

        visited.add(node);

        for (NodeType parentNode : parentsOfNode.get(node)) {
            getAllAncestorsImpl(parentNode, result, visited);
        }
        result.add(node);
    }

    /**
     * Recursive search downwards through the inheritance graph to 
     * retrieve all who directly or indirectly inherits the given node
     */
    private void getAllDescendantsImpl(NodeType node, List result, Set visited) {
        if (!childrenOfNode.containsKey(node)) return;
        if (visited.contains(node)) return;

        visited.add(node);

        result.add(node);

        for (NodeType childNode : getValidChildren(node)) {
            getAllDescendantsImpl(childNode, result, visited);
        }

    }

    private boolean nodeExists(NodeType node) {
        return parentsOfNode.containsKey(node) && childrenOfNode.containsKey(node);
    }

    private List getValidChildren(NodeType node) {
        return childrenOfNode.getOrDefault(node, new ArrayList<>())
                               .stream()
                               .filter(childNode -> parentsOfNode.get(childNode).contains(node))
                               .collect(Collectors.toList());
    }

    public void dumpAllEdges(ClientLogger logger) {
        for (Map.Entry> entry : parentsOfNode.entrySet()) {
            NodeType node = entry.getKey();
            logger.info(node.toString() + " inherits from:");
            for (NodeType parent : entry.getValue()) {
                logger.info("    " + parent.toString());
            }
        }

        for (NodeType parentURI : childrenOfNode.keySet()) {
            logger.info(parentURI.toString() + " has children:");
            for (NodeType childURI : getValidChildren(parentURI)) {
                logger.info("    " + childURI.toString());
            }
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy