ai.vespa.schemals.index.InheritanceGraph Maven / Gradle / Ivy
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