com.espertech.esper.util.GraphUtil Maven / Gradle / Ivy
Show all versions of esper Show documentation
/*
***************************************************************************************
* Copyright (C) 2006 EsperTech, Inc. All rights reserved. *
* http://www.espertech.com/esper *
* http://www.espertech.com *
* ---------------------------------------------------------------------------------- *
* The software in this package is published under the terms of the GPL license *
* a copy of which has been included with this distribution in the license.txt file. *
***************************************************************************************
*/
package com.espertech.esper.util;
import java.util.*;
/**
* Utility for working with acyclic graph: determines cyclic dependency and dependency-satisfying processing order.
*/
public class GraphUtil {
/**
* Deep-merge a map into another map returning a result map.
*
* Copies all values present in the original map to a new map,
* adding additional value present in the second map passed in,
* ignoring same-key values in the second map that are present in the original.
*
* If the value is a Map itself, repeats the operation on the Map value.
*
* @param original nestable Map of entries to retain and not overwrite
* @param additional nestable Map of entries to add to the original
* @return merge of original and additional nestable map
*/
public static Map mergeNestableMap(Map original, Map additional) {
Map result = new LinkedHashMap(original);
for (Map.Entry additionalEntry : additional.entrySet()) {
String name = additionalEntry.getKey();
Object additionalValue = additionalEntry.getValue();
Object originalValue = original.get(name);
Object newValue;
if ((originalValue instanceof Map) &&
(additionalValue instanceof Map)) {
Map innerAdditional = (Map) additionalValue;
Map innerOriginal = (Map) originalValue;
newValue = mergeNestableMap(innerOriginal, innerAdditional);
result.put(name, newValue);
continue;
}
if (original.containsKey(name)) {
continue;
}
result.put(name, additionalValue);
}
return result;
}
/**
* Check cyclic dependency and determine processing order for the given graph.
*
* @param graph is represented as child nodes that have one or more parent nodes that they are dependent on
* @return set of parent and child nodes in order such that no node's dependency is not satisfied
* by a prior nodein the set
* @throws GraphCircularDependencyException if a dependency has been detected
*/
public static Set getTopDownOrder(Map> graph) throws GraphCircularDependencyException {
Stack circularDependency = getFirstCircularDependency(graph);
if (circularDependency != null) {
throw new GraphCircularDependencyException("Circular dependency detected between " + circularDependency);
}
Map> reversedGraph = new HashMap>();
// Reverse the graph - build a list of children per parent
for (Map.Entry> entry : graph.entrySet()) {
Set parents = entry.getValue();
String child = entry.getKey();
for (String parent : parents) {
Set childList = reversedGraph.get(parent);
if (childList == null) {
childList = new LinkedHashSet();
reversedGraph.put(parent, childList);
}
childList.add(child);
}
}
// Determine all root nodes, which are those without parent
TreeSet roots = new TreeSet();
for (Set parents : graph.values()) {
if (parents == null) {
continue;
}
for (String parent : parents) {
// node not itself a child
if (!graph.containsKey(parent)) {
roots.add(parent);
}
}
}
// for each root, recursively add its child nodes, this becomes the default order
Set graphFlattened = new LinkedHashSet();
for (String root : roots) {
recusiveAdd(graphFlattened, root, reversedGraph);
}
// now walk down the default order and for each node ensure all parents are created
Set created = new LinkedHashSet();
Set removeList = new HashSet();
while (!graphFlattened.isEmpty()) {
removeList.clear();
for (String node : graphFlattened) {
if (!recursiveParentsCreated(node, created, graph)) {
continue;
}
created.add(node);
removeList.add(node);
}
graphFlattened.removeAll(removeList);
}
return created;
}
// Determine if all the node's parents and their parents have been added to the created set
private static boolean recursiveParentsCreated(String node, Set created, Map> graph) {
Set parents = graph.get(node);
if (parents == null) {
return true;
}
for (String parent : parents) {
if (!created.contains(parent)) {
return false;
}
boolean allParentsCreated = recursiveParentsCreated(parent, created, graph);
if (!allParentsCreated) {
return false;
}
}
return true;
}
private static void recusiveAdd(Set graphFlattened, String root, Map> reversedGraph) {
graphFlattened.add(root);
Set childNodes = reversedGraph.get(root);
if (childNodes == null) {
return;
}
for (String child : childNodes) {
recusiveAdd(graphFlattened, child, reversedGraph);
}
}
/**
* Returns any circular dependency as a stack of stream numbers, or null if none exist.
*
* @param graph the dependency graph
* @return circular dependency stack
*/
private static Stack getFirstCircularDependency(Map> graph) {
for (String child : graph.keySet()) {
Stack deepDependencies = new Stack();
deepDependencies.push(child);
boolean isCircular = recursiveDeepDepends(deepDependencies, child, graph);
if (isCircular) {
return deepDependencies;
}
}
return null;
}
private static boolean recursiveDeepDepends(Stack deepDependencies, String currentChild, Map> graph) {
Set required = graph.get(currentChild);
if (required == null) {
return false;
}
for (String parent : required) {
if (deepDependencies.contains(parent)) {
return true;
}
deepDependencies.push(parent);
boolean isDeep = recursiveDeepDepends(deepDependencies, parent, graph);
if (isDeep) {
return true;
}
deepDependencies.pop();
}
return false;
}
}