/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package se.kth.karamel.backend.dag;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import org.apache.log4j.Logger;
import se.kth.karamel.common.exception.DagConstructionException;
/**
*
* @author kamal
*/
public class Dag {
private static final Logger logger = Logger.getLogger(Dag.class);
private final Map<String, DagNode> allNodes = new HashMap<>();
public void addNode(String nodeId) {
if (!allNodes.containsKey(nodeId)) {
allNodes.put(nodeId, new DagNode(nodeId));
}
}
public void addTask(DagTask task) throws DagConstructionException {
logger.debug("Adding task: " + task.dagNodeId());
DagNode node = null;
if (!allNodes.containsKey(task.dagNodeId())) {
node = new DagNode(task.dagNodeId(), task);
allNodes.put(task.dagNodeId(), node);
} else {
node = allNodes.get(task.dagNodeId());
if (node.getTask() != null) {
throw new DagConstructionException(String.format("Task '%s' already exist.", task.dagNodeId()));
}
node.setTask(task);
}
for (String first : task.dagDependencies()) {
addDependency(first, task.dagNodeId());
}
}
public boolean addDependency(String first, String next) throws DagConstructionException {
if (first == null || first.isEmpty() || next == null || next.isEmpty()) {
throw new DagConstructionException(String.format("Dependencies cannot be null or empty: %s -> %s", first, next));
}
if (first.equals(next)) {
throw new DagConstructionException(String.format("Cyrcular dependency is not allowed: %s -> %s", first, next));
}
logger.debug("Adding dependency: " + first + " -> " + next);
DagNode firstNode;
if (allNodes.containsKey(first)) {
firstNode = allNodes.get(first);
} else {
firstNode = new DagNode(first);
allNodes.put(first, firstNode);
}
DagNode nextNode;
if (allNodes.containsKey(next)) {
nextNode = allNodes.get(next);
} else {
nextNode = new DagNode(next);
allNodes.put(next, nextNode);
}
return firstNode.addSuccessor(nextNode);
}
public void updateLabel(String nodeId, String label) throws DagConstructionException {
DagNode node = allNodes.get(nodeId);
if (node != null) {
node.setLabel(label);
} else {
throw new DagConstructionException(
String.format("Node '%s' does not exist in the dag to update its label to '%s'", nodeId, label));
}
}
public void start() throws DagConstructionException {
validate();
logger.debug("Dag is starting: \n" + print());
String prob = UUID.randomUUID().toString();
for (DagNode node : findRootNodes()) {
node.prepareToStart(prob);
}
for (DagNode node : findRootNodes()) {
node.start();
}
}
public void termiante() {
for (DagNode node : findRootNodes()) {
node.terminate();
}
}
public boolean isFailed() {
for (DagNode node : allNodes.values()) {
if (node.getStatus() == DagNode.Status.FAILED) {
return true;
}
}
return false;
}
public boolean isDone() {
for (DagNode node : allNodes.values()) {
if (node.getStatus() != DagNode.Status.DONE && node.getStatus() != DagNode.Status.EXIST) {
return false;
}
}
return true;
}
public void validate() throws DagConstructionException {
detectCycles();
for (DagNode node : allNodes.values()) {
if (node.getTask() == null) {
throw new DagConstructionException(String.format("No task assigned to '%s' while it appreard in dependencies.. "
+ "predecessors: %s successors: %s", node.getId(), node.getPredecessors().toString(),
node.getSuccessors().toString()));
}
}
}
public void detectCycles() throws DagConstructionException {
String probe = UUID.randomUUID().toString();
for (DagNode node : allNodes.values()) {
node.detectCylcles(probe, Collections.EMPTY_LIST);
}
}
public Set<DagNode> findRootNodes() {
HashSet<DagNode> roots = new HashSet<>();
for (DagNode s : allNodes.values()) {
if (s.getPredecessors().isEmpty()) {
roots.add(s);
}
}
return roots;
}
public String print() throws DagConstructionException {
detectCycles();
for (DagNode node : findRootNodes()) {
node.findMaxIndentionLevel(2);
}
String prob = UUID.randomUUID().toString();
StringBuilder builder = new StringBuilder();
for (DagNode node : findRootNodes()) {
builder.append("\n").append(node.printBfs(prob, "1|", 2));
}
return builder.toString();
}
public boolean isRoot(String nodeId) {
if (allNodes.containsKey(nodeId)) {
DagNode node = allNodes.get(nodeId);
return (node.getPredecessors().isEmpty());
}
return false;
}
public boolean hasDependency(String first, String next) {
if (allNodes.containsKey(first) && allNodes.containsKey(next)) {
DagNode firstNode = allNodes.get(first);
DagNode nextNode = allNodes.get(next);
if (firstNode.getSuccessors().contains(nextNode) && nextNode.getPredecessors().contains(firstNode)) {
return true;
}
}
return false;
}
public String asJson() {
StringBuilder builder = new StringBuilder();
builder.append("[");
Collection<DagNode> values = allNodes.values();
int i = 0;
for (DagNode dagNode : values) {
i++;
builder.append(dagNode.toJson());
if (i != allNodes.size()) {
builder.append(",");
}
}
builder.append("]");
return builder.toString();
}
}