Skip to content

Commit

Permalink
Framework for Creatable composition, managing dependency between them…
Browse files Browse the repository at this point in the history
… and creating resources based on topotogical order
  • Loading branch information
anuchandy committed May 31, 2016
1 parent 9b363b4 commit b859906
Show file tree
Hide file tree
Showing 8 changed files with 670 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package com.microsoft.azure;

import java.util.ArrayList;
import java.util.List;

/**
* The type representing node in a {@link DAGraph}.
*
* @param <T> the type of the data stored in the node
*/
public class DAGNode<T> extends Node<T> {
private List<String> dependentKeys;
private int toBeResolved;

/**
* Creates a DAG node.
*
* @param key unique id of the node
* @param data data to be stored in the node
*/
public DAGNode(String key, T data) {
super(key, data);
dependentKeys = new ArrayList<>();
}

/**
* @return a list of keys of nodes in {@link DAGraph} those are dependents on this node
*/
List<String> dependentKeys() {
return this.dependentKeys;
}

/**
* mark the node identified by the given key as dependent of this node.
*
* @param key the id of the dependent node
*/
public void addDependent(String key) {
this.dependentKeys.add(key);
}

/**
* @return a list of keys of nodes in {@link DAGraph} that this node depends on
*/
public List<String> dependencyKeys() {
return this.children();
}

/**
* mark the node identified by the given key as this node's dependency.
*
* @param dependencyKey the id of the dependency node
*/
public void addDependency(String dependencyKey) {
toBeResolved++;
super.addChild(dependencyKey);
}

/**
* @return <tt>true</tt> if this node has any dependency
*/
public boolean hasDependencies() {
return this.hasChildren();
}

/**
* @return <tt>true</tt> if all dependencies of this node are ready to be consumed
*/
boolean hasAllResolved() {
return toBeResolved == 0;
}

/**
* Reports that one of this node's dependency has been resolved and ready to be consumed.
*
* @param dependencyKey the id of the dependency node
*/
void reportResolved(String dependencyKey) {
if (toBeResolved == 0) {
throw new RuntimeException("invalid state - " + this.key() + ": The dependency '" + dependencyKey + "' is already reported or there is no such dependencyKey");
}
toBeResolved--;
}
}
156 changes: 156 additions & 0 deletions azure-client-runtime/src/main/java/com/microsoft/azure/DAGraph.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
package com.microsoft.azure;

import java.util.ArrayDeque;
import java.util.Map;
import java.util.Queue;

/**
* Type representing a DAG (directed acyclic graph).
* <p>
* each node in a DAG is represented by {@link DAGNode}
*
* @param <T> the type of the data stored in the graph nodes
* @param <U> the type of the nodes in the graph
*/
public class DAGraph<T, U extends DAGNode<T>> extends Graph<T, U> {
private Queue<String> queue;
private boolean hasParent;
private U rootNode;

/**
* Creates a new DAG.
*
* @param rootNode the root node of this DAG
*/
public DAGraph(U rootNode) {
this.rootNode = rootNode;
this.queue = new ArrayDeque<>();
this.addNode(rootNode);
}

/**
* @return <tt>true</tt> if this DAG is merged with another DAG and hence has a parent
*/
public boolean hasParent() {
return hasParent;
}

/**
* Checks whether the given node is root node of this DAG.
*
* @param node the node {@link DAGNode} to be checked
* @return <tt>true</tt> if the given node is root node
*/
public boolean isRootNode(U node) {
return this.rootNode == node;
}

/**
* Merge this DAG with another DAG.
* <p>
* this will mark this DAG as a child DAG, the dependencies of nodes in this DAG will be merged
* with (copied to) the parent DAG
*
* @param parent the parent DAG
*/
public void merge(DAGraph<T, U> parent) {
this.hasParent = true;
parent.rootNode.addDependency(this.rootNode.key());
this.rootNode.addDependent(parent.rootNode.key());
for (Map.Entry<String, U> entry: graph.entrySet()) {
String key = entry.getKey();
if (!parent.graph.containsKey(key)) {
parent.graph.put(key, entry.getValue());
}
}
}

/**
* Prepares this DAG for traversal using getNext method, each call to getNext returns next node
* in the DAG with no dependencies.
*/
public void prepare() {
initializeQueue();
if (queue.isEmpty()) {
throw new RuntimeException("Found circular dependency");
}
}

/**
* Gets next node in the DAG which has no dependency or all of it's dependencies are resolved and
* ready to be consumed.
* <p>
* null will be returned when all the nodes are explored
*
* @return next node
*/
public U getNext() {
return graph.get(queue.poll());
}

/**
* Gets the data stored in a graph node with a given key.
*
* @param key the key of the node
* @return the value stored in the node
*/
public T getNodeData(String key) {
return graph.get(key).data();
}

/**
* Reports that a node is resolved hence other nodes depends on it can consume it.
*
* @param completed the node ready to be consumed
*/
public void reportedCompleted(U completed) {
String dependency = completed.key();
for (String dependentKey : graph.get(dependency).dependentKeys()) {
DAGNode<T> dependent = graph.get(dependentKey);
dependent.reportResolved(dependency);
if (dependent.hasAllResolved()) {
queue.add(dependent.key());
}
}
}

/**
* populate dependents of all nodes.
* <p>
* the DAG will be explored in DFS order and all node's dependents will be identified,
* this prepares the DAG for traversal using getNext method, each call to getNext returns next node
* in the DAG with no dependencies.
*/
public void populateDependentKeys() {
this.queue.clear();
visit(new Visitor<U>() {
@Override
public void visit(U node) {
if (node.dependencyKeys().isEmpty()) {
queue.add(node.key());
return;
}

String dependentKey = node.key();
for (String dependencyKey : node.dependencyKeys()) {
graph.get(dependencyKey)
.dependentKeys()
.add(dependentKey);
}
}
});
}

/**
* Initializes the queue that tracks the next set of nodes with no dependencies or
* whose dependencies are resolved.
*/
private void initializeQueue() {
this.queue.clear();
for (Map.Entry<String, U> entry: graph.entrySet()) {
if (!entry.getValue().hasDependencies()) {
this.queue.add(entry.getKey());
}
}
}
}
78 changes: 78 additions & 0 deletions azure-client-runtime/src/main/java/com/microsoft/azure/Graph.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package com.microsoft.azure;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
* Type representing a directed graph data structure.
* <p>
* each node in a graph is represented by {@link Node}
*
* @param <T> the type of the data stored in the graph's nodes
* @param <U> the type of the nodes in the graph
*/
public class Graph<T, U extends Node<T>> {
protected Map<String, U> graph;
private Set<String> visited;

/**
* Creates a directed graph.
*/
public Graph() {
this.graph = new HashMap<>();
this.visited = new HashSet<>();
}

/**
* Adds a node to this graph.
*
* @param node the node
*/
public void addNode(U node) {
graph.put(node.key(), node);
}

/**
* Represents a visitor to be implemented by the consumer who want to visit the
* graph's nodes in DFS order.
*
* @param <U> the type of the node
*/
interface Visitor<U> {
/**
* visit a node.
*
* @param node the node to visited
*/
void visit(U node);
}

/**
* Perform DFS visit in this graph.
* <p>
* The directed graph will be traversed in DFS order and the visitor will be notified as
* search explores each node
*
* @param visitor the graph visitor
*/
public void visit(Visitor visitor) {
for (Map.Entry<String, ? extends Node<T>> item : graph.entrySet()) {
if (!visited.contains(item.getKey())) {
this.dfs(visitor, item.getValue());
}
}
visited.clear();
}

private void dfs(Visitor visitor, Node<T> node) {
visitor.visit(node);
visited.add(node.key());
for (String childKey : node.children()) {
if (!visited.contains(childKey)) {
this.dfs(visitor, this.graph.get(childKey));
}
}
}
}
62 changes: 62 additions & 0 deletions azure-client-runtime/src/main/java/com/microsoft/azure/Node.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package com.microsoft.azure;

import java.util.ArrayList;
import java.util.List;

/**
* Type represents a node in a {@link Graph}.
*
* @param <T> the type of the data stored in the node
*/
public class Node<T> {
private String key;
private T data;
private List<String> children;

/**
* Creates a graph node.
*
* @param key unique id of the node
* @param data data to be stored in the node
*/
public Node(String key, T data) {
this.key = key;
this.data = data;
this.children = new ArrayList<>();
}

/**
* @return this node's unique id
*/
public String key() {
return this.key;
}

/**
* @return data stored in this node
*/
public T data() {
return data;
}

/**
* @return <tt>true</tt> if this node has any children
*/
public boolean hasChildren() {
return !this.children.isEmpty();
}

/**
* @return children (neighbours) of this node
*/
public List<String> children() {
return this.children;
}

/**
* @param childKey add a child (neighbour) of this node
*/
public void addChild(String childKey) {
this.children.add(childKey);
}
}
Loading

0 comments on commit b859906

Please sign in to comment.