-
Notifications
You must be signed in to change notification settings - Fork 2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1176 from jianghaolu/master
Move graph related code to fluent core
- Loading branch information
Showing
10 changed files
with
1,077 additions
and
0 deletions.
There are no files selected for viewing
126 changes: 126 additions & 0 deletions
126
...ources/src/main/java/com/microsoft/azure/management/resources/fluentcore/dag/DAGNode.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
/** | ||
* | ||
* Copyright (c) Microsoft Corporation. All rights reserved. | ||
* Licensed under the MIT License. See License.txt in the project root for license information. | ||
* | ||
*/ | ||
|
||
package com.microsoft.azure.management.resources.fluentcore.dag; | ||
|
||
import java.util.ArrayList; | ||
import java.util.Collections; | ||
import java.util.List; | ||
import java.util.concurrent.locks.ReentrantLock; | ||
|
||
/** | ||
* 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; | ||
private boolean isPreparer; | ||
private ReentrantLock lock; | ||
|
||
/** | ||
* 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<>(); | ||
lock = new ReentrantLock(); | ||
} | ||
|
||
/** | ||
* @return the lock to be used while performing thread safe operation on this node. | ||
*/ | ||
public ReentrantLock lock() { | ||
return this.lock; | ||
} | ||
|
||
/** | ||
* @return a list of keys of nodes in {@link DAGraph} those are dependents on this node | ||
*/ | ||
List<String> dependentKeys() { | ||
return Collections.unmodifiableList(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) { | ||
super.addChild(dependencyKey); | ||
} | ||
|
||
/** | ||
* @return <tt>true</tt> if this node has any dependency | ||
*/ | ||
public boolean hasDependencies() { | ||
return this.hasChildren(); | ||
} | ||
|
||
/** | ||
* Mark or un-mark this node as preparer. | ||
* | ||
* @param isPreparer <tt>true</tt> if this node needs to be marked as preparer, <tt>false</tt> otherwise. | ||
*/ | ||
public void setPreparer(boolean isPreparer) { | ||
this.isPreparer = isPreparer; | ||
} | ||
|
||
/** | ||
* @return <tt>true</tt> if this node is marked as preparer | ||
*/ | ||
public boolean isPreparer() { | ||
return isPreparer; | ||
} | ||
|
||
/** | ||
* Initialize the node so that traversal can be performed on the parent DAG. | ||
*/ | ||
public void initialize() { | ||
this.toBeResolved = this.dependencyKeys().size(); | ||
this.dependentKeys.clear(); | ||
} | ||
|
||
/** | ||
* @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--; | ||
} | ||
} |
193 changes: 193 additions & 0 deletions
193
...ources/src/main/java/com/microsoft/azure/management/resources/fluentcore/dag/DAGraph.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,193 @@ | ||
/** | ||
* | ||
* Copyright (c) Microsoft Corporation. All rights reserved. | ||
* Licensed under the MIT License. See License.txt in the project root for license information. | ||
* | ||
*/ | ||
|
||
package com.microsoft.azure.management.resources.fluentcore.dag; | ||
|
||
import java.util.Map; | ||
import java.util.concurrent.ConcurrentLinkedQueue; | ||
|
||
/** | ||
* 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 ConcurrentLinkedQueue<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 ConcurrentLinkedQueue<>(); | ||
this.rootNode.setPreparer(true); | ||
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; | ||
} | ||
|
||
/** | ||
* @return <tt>true</tt> if this dag is the preparer responsible for | ||
* preparing the DAG for traversal. | ||
*/ | ||
public boolean isPreparer() { | ||
return this.rootNode.isPreparer(); | ||
} | ||
|
||
/** | ||
* 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()); | ||
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() { | ||
if (isPreparer()) { | ||
for (U node : graph.values()) { | ||
// Prepare each node for traversal | ||
node.initialize(); | ||
if (!this.isRootNode(node)) { | ||
// Mark other sub-DAGs as non-preparer | ||
node.setPreparer(false); | ||
} | ||
} | ||
initializeDependentKeys(); | ||
initializeQueue(); | ||
} | ||
} | ||
|
||
/** | ||
* Gets next node in the DAG which has no dependency or all of it's dependencies are resolved and | ||
* ready to be consumed. | ||
* | ||
* @return next node or null if all the nodes have been explored or no node is available at this moment. | ||
*/ | ||
public U getNext() { | ||
String nextItemKey = queue.poll(); | ||
if (nextItemKey == null) { | ||
return null; | ||
} | ||
return graph.get(nextItemKey); | ||
} | ||
|
||
/** | ||
* 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) { | ||
completed.setPreparer(true); | ||
String dependency = completed.key(); | ||
for (String dependentKey : graph.get(dependency).dependentKeys()) { | ||
DAGNode<T> dependent = graph.get(dependentKey); | ||
dependent.lock().lock(); | ||
try { | ||
dependent.reportResolved(dependency); | ||
if (dependent.hasAllResolved()) { | ||
queue.add(dependent.key()); | ||
} | ||
} finally { | ||
dependent.lock().unlock(); | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Initializes 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. | ||
*/ | ||
private void initializeDependentKeys() { | ||
visit(new Visitor<U>() { | ||
@Override | ||
public void visitNode(U node) { | ||
if (node.dependencyKeys().isEmpty()) { | ||
return; | ||
} | ||
|
||
String dependentKey = node.key(); | ||
for (String dependencyKey : node.dependencyKeys()) { | ||
graph.get(dependencyKey) | ||
.addDependent(dependentKey); | ||
} | ||
} | ||
|
||
@Override | ||
public void visitEdge(String fromKey, String toKey, EdgeType edgeType) { | ||
if (edgeType == EdgeType.BACK) { | ||
throw new IllegalStateException("Detected circular dependency: " + findPath(fromKey, toKey)); | ||
} | ||
} | ||
}); | ||
} | ||
|
||
/** | ||
* 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()); | ||
} | ||
} | ||
if (queue.isEmpty()) { | ||
throw new RuntimeException("Found circular dependency"); | ||
} | ||
} | ||
} |
Oops, something went wrong.