liquibase.util.DependencyUtil Maven / Gradle / Ivy
package liquibase.util;
import liquibase.logging.LogService;
import liquibase.logging.LogType;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
public class DependencyUtil {
public static class DependencyGraph {
private HashMap> nodes = new HashMap<>();
private NodeValueListener listener;
private List> evaluatedNodes = new ArrayList<>();
private int recursiveSizeCheck = -1;
public DependencyGraph(NodeValueListener listener) {
this.listener = listener;
}
public void add(T evalFirstValue, T evalAfterValue) {
GraphNode firstNode = null;
GraphNode afterNode = null;
if (nodes.containsKey(evalFirstValue)) {
firstNode = nodes.get(evalFirstValue);
} else {
firstNode = createNode(evalFirstValue);
nodes.put(evalFirstValue, firstNode);
}
if (nodes.containsKey(evalAfterValue)) {
afterNode = nodes.get(evalAfterValue);
} else {
afterNode = createNode(evalAfterValue);
nodes.put(evalAfterValue, afterNode);
}
firstNode.addGoingOutNode(afterNode);
afterNode.addComingInNode(firstNode);
}
private GraphNode createNode(T value) {
GraphNode node = new GraphNode<>();
node.value = value;
return node;
}
public void computeDependencies() {
List> orphanNodes = getOrphanNodes();
List> nextNodesToDisplay = new ArrayList<>();
if (orphanNodes != null) {
for (GraphNode node : orphanNodes) {
listener.evaluating(node.value);
evaluatedNodes.add(node);
nextNodesToDisplay.addAll(node.getGoingOutNodes());
}
computeDependencies(nextNodesToDisplay);
}
}
private void computeDependencies(List> nodes) {
List> nextNodesToDisplay = null;
for (GraphNode node : nodes) {
if (!isAlreadyEvaluated(node)) {
List> comingInNodes = node.getComingInNodes();
if (areAlreadyEvaluated(comingInNodes)) {
listener.evaluating(node.value);
evaluatedNodes.add(node);
List> goingOutNodes = node.getGoingOutNodes();
if (goingOutNodes != null) {
if (nextNodesToDisplay == null)
nextNodesToDisplay = new ArrayList<>();
// add these too, so they get a chance to be displayed
// as well
nextNodesToDisplay.addAll(goingOutNodes);
}
} else {
if (nextNodesToDisplay == null)
nextNodesToDisplay = new ArrayList<>();
// the checked node should be carried
nextNodesToDisplay.add(node);
}
}
}
if ((nextNodesToDisplay != null) && !nextNodesToDisplay.isEmpty()) {
if (nextNodesToDisplay.size() == recursiveSizeCheck) {
//Recursion is not making progress, heading to a stack overflow exception.
//Probably some cycles in there somewhere, so pull out a node and re-try
GraphNode nodeToRemove = null;
int nodeToRemoveLinks = Integer.MAX_VALUE;
for (GraphNode node : nextNodesToDisplay) {
List links = node.getComingInNodes();
if ((links != null) && (links.size() < nodeToRemoveLinks)) {
nodeToRemove = node;
nodeToRemoveLinks = links.size();
}
}
LogService.getLog(getClass()).debug(LogType.LOG, "Potential StackOverflowException. Pro-actively removing "+nodeToRemove.value+" with "+nodeToRemoveLinks+" incoming nodes");
nextNodesToDisplay.remove(nodeToRemove);
}
recursiveSizeCheck = nextNodesToDisplay.size();
computeDependencies(nextNodesToDisplay);
}
// here the recursive call ends
}
private boolean isAlreadyEvaluated(GraphNode node) {
return evaluatedNodes.contains(node);
}
private boolean areAlreadyEvaluated(List> nodes) {
return evaluatedNodes.containsAll(nodes);
}
private List> getOrphanNodes() {
List> orphanNodes = null;
Set keys = nodes.keySet();
for (T key : keys) {
GraphNode node = nodes.get(key);
if (node.getComingInNodes() == null) {
if (orphanNodes == null)
orphanNodes = new ArrayList<>();
orphanNodes.add(node);
}
}
return orphanNodes;
}
}
private static class GraphNode {
public T value;
private List> comingInNodes;
private List> goingOutNodes;
public void addComingInNode(GraphNode node) {
if (comingInNodes == null)
comingInNodes = new ArrayList<>();
comingInNodes.add(node);
}
public void addGoingOutNode(GraphNode node) {
if (goingOutNodes == null)
goingOutNodes = new ArrayList<>();
goingOutNodes.add(node);
}
public List> getComingInNodes() {
return comingInNodes;
}
public List> getGoingOutNodes() {
return goingOutNodes;
}
}
public interface NodeValueListener {
void evaluating(T nodeValue);
}
}