package org.testng.internal;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.testng.collections.ListMultiMap;
import org.testng.collections.Lists;
import org.testng.collections.Maps;
import org.testng.collections.Sets;
import org.testng.internal.collections.Pair;
/**
* Representation of the graph of methods.
*/
public class DynamicGraph<T> {
private final List<T> m_nodesReady = Lists.newArrayList();
private final List<T> m_nodesRunning = Lists.newArrayList();
private final List<T> m_nodesFinished = Lists.newArrayList();
private final ListMultiMap<T, Edge<T>> m_edges = Maps.newListMultiMap();
private final ListMultiMap<T, Edge<T>> m_allEdges = Maps.newListMultiMap();
public enum Status {
READY, RUNNING, FINISHED
}
private static class Edge<T> {
private final T from;
private final T to;
private final int weight;
private Edge(int weight, T from, T to) {
this.from = from;
this.to = to;
this.weight = weight;
}
}
/**
* Add a node to the graph.
*/
public void addNode(T node) {
m_nodesReady.add(node);
}
/**
*
* @param weight - Represents one of {@link org.testng.TestRunner.PriorityWeight} ordinals indicating
* the weightage of a particular node in the graph
* @param from - Represents the edge that depends on another edge.
* @param to - Represents the edge on which another edge depends upon.
*/
public void addEdge(int weight, T from, T to) {
addEdges(Collections.singletonList(new Edge<>(weight, from, to)));
}
/**
* Add an edge between two nodes.
*/
public void addEdge(int weight, T from, T... tos) {
addEdge(weight, from, Arrays.asList(tos));
}
/**
* Add an edge between two nodes.
*/
public void addEdge(int weight, T from, Iterable<T> tos) {
List<Edge<T>> edges = Lists.newArrayList();
for (T to : tos) {
edges.add(new Edge<>(weight, from, to));
}
addEdges(edges);
}
private void addEdges(List<Edge<T>> edges) {
for (Edge<T> edge : edges) {
Edge<T> existingEdge = getNode(m_edges, edge);
if (existingEdge != null && existingEdge.weight == edge.weight) {
throw new IllegalStateException("Circular dependency: " + edge.from + " <-> " + edge.to);
}
if (existingEdge == null) {
m_edges.put(edge.from, edge);
} else if (existingEdge.weight < edge.weight) {
m_edges.put(edge.from, edge);
}
// else: existingEdge.weight > edge.weight and ignore
m_allEdges.put(edge.from, edge);
}
}
private static <T> Edge<T> getNode(ListMultiMap<T, Edge<T>> edges, Edge<T> edge) {
for (Edge<T> e : edges.get(edge.to)) {
if (e.to.equals(edge.from)) {
return e;
}
}
return null;
}
/**
* @return a set of all the nodes that don't depend on any other nodes.
*/
public List<T> getFreeNodes() {
List<T> result = Lists.newArrayList();
for (T node : m_nodesReady) {
// A node is free if...
// - no other nodes depend on it
if (m_edges.get(node).isEmpty() || getUnfinishedNodes(node).isEmpty()) {
result.add(node);
}
}
// if all nodes have dependencies, then we can ignore the lowest one
if (result.isEmpty()) {
int lowestPriority = getLowestEdgePriority(m_nodesReady);
for (T node : m_nodesReady) {
// if a node has a dependency on a running node,
// then we can expect to have a free node when the node will finish
for (T unode : getUnfinishedNodes(node)) {
if (m_nodesRunning.contains(unode)) {
return Collections.emptyList();
}
}
List<Edge<T>> edges = m_edges.get(node);
if (hasAllEdgesWithLevel(edges, lowestPriority)) {
result.add(node);
}
}
}
// Filter result: remove node if the result contains all nodes from an edge
List<T> finalResult = Lists.newArrayList();
for (T node : result) {
List<Edge<T>> edges = m_edges.get(node);
boolean canAdd = true;
for (Edge<T> edge : edges) {
if (result.contains(edge.to)) {
canAdd = false;
}
}
if (canAdd) {
finalResult.add(node);
}
}
return finalResult;
}
private int getLowestEdgePriority(List<T> nodes) {
if (nodes.isEmpty()) {
return 0;
}
Integer lowerPriority = null;
for (T node : nodes) {
for (Edge<T> edge : m_edges.get(node)) {
if (lowerPriority == null) {
lowerPriority = edge.weight;
} else {
lowerPriority = lowerPriority < edge.weight ? lowerPriority : edge.weight;
}
}
}
return lowerPriority == null ? 0 : lowerPriority;
}
private static <T> boolean hasAllEdgesWithLevel(List<Edge<T>> edges, int level) {
for (Edge<?> edge : edges) {
if (edge.weight != level) {
return false;
}
}
return true;
}
/**
* @return a list of all the nodes that have a status other than FINISHED.
*/
private Collection<? extends T> getUnfinishedNodes(T node) {
Set<T> result = Sets.newHashSet();
for (Edge<T> edge : m_edges.get(node)) {
if (m_nodesReady.contains(edge.to) || m_nodesRunning.contains(edge.to)) {
result.add(edge.to);
}
}
return result;
}
/**
* Set the status for a set of nodes.
*/
public void setStatus(Collection<T> nodes, Status status) {
for (T n : nodes) {
setStatus(n, status);
}
}
/**
* Set the status for a node.
*/
public void setStatus(T node, Status status) {
switch(status) {
case RUNNING:
m_nodesReady.remove(node);
m_nodesRunning.add(node);
break;
case FINISHED:
m_nodesReady.remove(node);
m_nodesRunning.remove(node);
m_nodesFinished.add(node);
m_edges.removeAll(node);
List<Pair<T, Edge<T>>> edgesToRemove = Lists.newArrayList();
for (Map.Entry<T, List<Edge<T>>> entry : m_edges.entrySet()) {
for (Edge<T> edge : entry.getValue()) {
if (edge.to.equals(node)) {
edgesToRemove.add(new Pair<>(entry.getKey(), edge));
}
}
}
for (Pair<T, Edge<T>> pair : edgesToRemove) {
// Add virtual edge before removing intermediate node:
// ie: if a -> b and b -> c, then a -> c is added before the b deletion
for (Edge<T> edge : m_allEdges.get(node)) {
// If the edge doesn't exist yet
Edge<T> pedge = pair.second();
Edge<T> existingEdge = getNode(m_edges, new Edge<>(0, pedge.from, edge.to));
if ((existingEdge == null || existingEdge.weight != pedge.weight) &&
// Then we filter useless edge creation: the "to" must have to run later and,
// "from"/"to" must not be the same node
m_nodesReady.contains(edge.to) && !pedge.from.equals(edge.to)) {
if (edge.weight > pedge.weight) {
addEdge(edge.weight, pedge.from, edge.to);
} else {
addEdge(pedge.weight, pedge.from, edge.to);
}
}
}
m_edges.remove(pair.first(), pair.second());
}
break;
default:
throw new IllegalArgumentException("Unsupported status: " + status);
}
}
/**
* @return the number of nodes in this graph.
*/
public int getNodeCount() {
int result = m_nodesReady.size() + m_nodesRunning.size() + m_nodesFinished.size();
return result;
}
public int getNodeCountWithStatus(Status status) {
switch(status) {
case READY: return m_nodesReady.size();
case RUNNING: return m_nodesRunning.size();
case FINISHED: return m_nodesFinished.size();
default: throw new IllegalArgumentException();
}
}
@Override
public String toString() {
StringBuilder result = new StringBuilder("[DynamicGraph ");
result.append("\n Ready:" + m_nodesReady);
result.append("\n Running:" + m_nodesRunning);
result.append("\n Finished:" + m_nodesFinished);
result.append("\n Edges:\n");
for (Map.Entry<T, List<Edge<T>>> es : m_edges.entrySet()) {
result.append(" " + es.getKey() + "\n");
for (Edge<T> t : es.getValue()) {
result.append(" " + t.to + "\n");
}
}
result.append("]");
return result.toString();
}
private String getName(T t) {
String s = t.toString();
int n1 = s.lastIndexOf('.') + 1;
int n2 = s.indexOf('(');
return s.substring(n1, n2);
}
/**
* @return a .dot file (GraphViz) version of this graph.
*/
public String toDot() {
String FREE = "[style=filled color=yellow]";
String RUNNING = "[style=filled color=green]";
String FINISHED = "[style=filled color=grey]";
StringBuilder result = new StringBuilder("digraph g {\n");
List<T> freeNodes = getFreeNodes();
String color;
for (T n : m_nodesReady) {
color = freeNodes.contains(n) ? FREE : "";
result.append(" " + getName(n) + color + "\n");
}
for (T n : m_nodesRunning) {
color = freeNodes.contains(n) ? FREE : RUNNING;
result.append(" " + getName(n) + color + "\n");
}
for (T n : m_nodesFinished) {
result.append(" " + getName(n) + FINISHED+ "\n");
}
result.append("\n");
for (List<Edge<T>> edges : m_edges.values()) {
for (Edge<T> edge : edges) {
String dotted = m_nodesFinished.contains(edge.from) ? "style=dotted" : "";
result.append(" " + getName(edge.from) + " -> " + getName(edge.to) + " [dir=back " + dotted + "]\n");
}
}
result.append("}\n");
return result.toString();
}
public ListMultiMap<T, Edge<T>> getEdges() {
return m_edges;
}
}