liquibase.util.DependencyUtil Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of liquibase-core Show documentation
Show all versions of liquibase-core Show documentation
Liquibase is a tool for managing and executing database changes.
package liquibase.util;
import liquibase.Scope;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Set;
public class DependencyUtil {
public static class DependencyGraph {
private final LinkedHashMap> nodes = new LinkedHashMap<>();
private final NodeValueListener listener;
private final List> evaluatedNodes = new ArrayList<>();
private Integer recursiveSizeCheck;
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()) {
int recursiveSizeDepth = recursiveSizeDepth(nextNodesToDisplay);
if (recursiveSizeDepth < 0 || (recursiveSizeCheck != null && recursiveSizeDepth >= 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();
}
}
if (nodeToRemove != null) {
Scope.getCurrentScope().getLog(getClass()).fine("Potential StackOverflowException. Pro-actively removing " +
nodeToRemove.value + " with " + nodeToRemoveLinks + " incoming nodes");
nextNodesToDisplay.remove(nodeToRemove);
}
}
if (recursiveSizeDepth >= 0) {
recursiveSizeCheck = recursiveSizeDepth;
}
computeDependencies(nextNodesToDisplay);
}
// here the recursive call ends
}
private int recursiveSizeDepth(List> nodes) {
if (nodes == null) {
return 0;
}
int sum = 0;
for (GraphNode node : nodes) {
int depth = recursiveSizeDepth(node, 0);
if (depth < 0) {
// safety counter was triggered. terminate
return -1;
}
sum += depth;
}
return sum;
}
private int recursiveSizeDepth(GraphNode node, int safetyCounter) {
if (safetyCounter > 1000) {
return -1;
}
if (isAlreadyEvaluated(node)) {
return 0;
} else if (node.getGoingOutNodes() == null || node.getGoingOutNodes().isEmpty()) { // leaf node
return 1;
}
int sum = 0;
safetyCounter++;
for (GraphNode n : node.getGoingOutNodes()) {
int depth = recursiveSizeDepth(n, safetyCounter);
if (depth < 0) {
return -1;
}
sum += depth;
}
return node.getGoingOutNodes().size() + sum;
}
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);
}
}