org.testng.internal.Graph Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of testng Show documentation
Show all versions of testng Show documentation
Testing framework for Java
package org.testng.internal;
import org.testng.TestNGException;
import org.testng.collections.Lists;
import org.testng.collections.Maps;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Simple graph class to implement topological sort (used to sort methods based on what groups
* they depend on).
*
* @author Cedric Beust, Aug 19, 2004
*/
public class Graph {
private static boolean m_verbose = false;
private Map> m_nodes = Maps.newLinkedHashMap();
private List m_strictlySortedNodes = null;
// A map of nodes that are not the predecessors of any node
// (not needed for the algorithm but convenient to calculate
// the parallel/sequential lists in TestNG).
private Map> m_independentNodes = null;
public void addNode(T tm) {
ppp("ADDING NODE " + tm + " " + tm.hashCode());
m_nodes.put(tm, new Node<>(tm));
// Initially, all the nodes are put in the independent list as well
}
public Set getPredecessors(T node) {
return findNode(node).getPredecessors().keySet();
}
public boolean isIndependent(T object) {
return m_independentNodes.containsKey(object);
}
private Node findNode(T object) {
return m_nodes.get(object);
}
public void addPredecessor(T tm, T predecessor) {
Node node = findNode(tm);
if (null == node) {
throw new TestNGException("Non-existing node: " + tm);
}
else {
node.addPredecessor(predecessor);
addNeighbor(tm, predecessor);
// Remove these two nodes from the independent list
initializeIndependentNodes();
m_independentNodes.remove(predecessor);
m_independentNodes.remove(tm);
ppp(" REMOVED " + predecessor + " FROM INDEPENDENT OBJECTS");
}
}
private void addNeighbor(T tm, T predecessor) {
findNode(tm).addNeighbor(findNode(predecessor));
}
public Set getNeighbors(T t) {
Set result = new HashSet<>();
for (Node n : findNode(t).getNeighbors()) {
result.add(n.getObject());
}
return result;
}
private Collection> getNodes() {
return m_nodes.values();
}
public Collection getNodeValues() {
return m_nodes.keySet();
}
/**
* @return All the nodes that don't have any order with each other.
*/
public Set getIndependentNodes() {
return m_independentNodes.keySet();
}
/**
* @return All the nodes that have an order with each other, sorted
* in one of the valid sorts.
*/
public List getStrictlySortedNodes() {
return m_strictlySortedNodes;
}
public void topologicalSort() {
ppp("================ SORTING");
m_strictlySortedNodes = Lists.newArrayList();
initializeIndependentNodes();
//
// Clone the list of nodes but only keep those that are
// not independent.
//
List> nodes2 = Lists.newArrayList();
for (Node n : getNodes()) {
if (! isIndependent(n.getObject())) {
ppp("ADDING FOR SORT: " + n.getObject());
nodes2.add(n.clone());
}
else {
ppp("SKIPPING INDEPENDENT NODE " + n);
}
}
//
// Sort the nodes alphabetically to make sure that methods of the same class
// get run close to each other as much as possible
//
Collections.sort(nodes2);
//
// Sort
//
while (! nodes2.isEmpty()) {
//
// Find all the nodes that don't have any predecessors, add
// them to the result and mark them for removal
//
Node node = findNodeWithNoPredecessors(nodes2);
if (null == node) {
List cycle = new Tarjan<>(this, nodes2.get(0).getObject()).getCycle();
StringBuilder sb = new StringBuilder();
sb.append("The following methods have cyclic dependencies:\n");
for (T m : cycle) {
sb.append(m).append("\n");
}
throw new TestNGException(sb.toString());
}
else {
m_strictlySortedNodes.add(node.getObject());
removeFromNodes(nodes2, node);
}
}
ppp("=============== DONE SORTING");
if (m_verbose) {
dumpSortedNodes();
}
}
private void initializeIndependentNodes() {
if (null == m_independentNodes) {
List> list = Lists.newArrayList(m_nodes.values());
// Ideally, we should not have to sort this. However, due to a bug where it treats all the methods as
// dependent nodes. Therefore, all the nodes were mostly sorted alphabetically. So we need to keep
// the behavior for now.
Collections.sort(list);
m_independentNodes = Maps.newLinkedHashMap();
for (Node node : list) {
m_independentNodes.put(node.getObject(), node);
}
}
}
private void dumpSortedNodes() {
System.out.println("====== SORTED NODES");
for (T n : m_strictlySortedNodes) {
System.out.println(" " + n);
}
System.out.println("====== END SORTED NODES");
}
/**
* Remove a node from a list of nodes and update the list of predecessors
* for all the remaining nodes.
*/
private void removeFromNodes(List> nodes, Node node) {
nodes.remove(node);
for (Node n : nodes) {
n.removePredecessor(node.getObject());
}
}
private static void ppp(String s) {
if (m_verbose) {
System.out.println("[Graph] " + s);
}
}
private Node findNodeWithNoPredecessors(List> nodes) {
for (Node n : nodes) {
if (! n.hasPredecessors()) {
return n;
}
}
return null;
}
/**
* @param o
* @return A list of all the predecessors for o
*/
public List findPredecessors(T o) {
// Locate the node
Node node = findNode(o);
if (null == node) {
// This can happen if an interceptor returned new methods
return Lists.newArrayList();
}
// If we found the node, use breadth first search to find all
// all of the predecessors of o. "result" is the growing list
// of all predecessors. "visited" is the set of items we've
// already encountered. "queue" is the queue of items whose
// predecessors we haven't yet explored.
LinkedList result = new LinkedList<>();
Set visited = new HashSet<>();
LinkedList queue = new LinkedList<>();
visited.add(o);
queue.addLast(o);
while (! queue.isEmpty()) {
for (T obj : getPredecessors(queue.removeFirst())) {
if (! visited.contains(obj)) {
visited.add(obj);
queue.addLast(obj);
result.addFirst(obj);
}
}
}
return result;
}
@Override
public String toString() {
StringBuilder result = new StringBuilder("[Graph ");
for (T node : m_nodes.keySet()) {
result.append(findNode(node)).append(" ");
}
result.append("]");
return result.toString();
}
/////
// class Node
//
public static class Node implements Comparable> {
private T m_object = null;
private Map m_predecessors = Maps.newHashMap();
public Node(T tm) {
m_object = tm;
}
private Set> m_neighbors = new HashSet<>();
public void addNeighbor(Node neighbor) {
m_neighbors.add(neighbor);
}
public Set> getNeighbors() {
return m_neighbors;
}
@Override
public Node clone() {
Node result = new Node<>(m_object);
for (T pred : m_predecessors.values()) {
result.addPredecessor(pred);
}
return result;
}
public T getObject() {
return m_object;
}
public Map getPredecessors() {
return m_predecessors;
}
/**
*
* @return true if this predecessor was found and removed
*/
public boolean removePredecessor(T o) {
boolean result = false;
T pred = m_predecessors.get(o);
if (null != pred) {
result = null != m_predecessors.remove(o);
if (result) {
ppp(" REMOVED PRED " + o + " FROM NODE " + m_object);
}
else {
ppp(" FAILED TO REMOVE PRED " + o + " FROM NODE " + m_object);
}
}
return result;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder("[Node:" + m_object);
sb.append(" pred:");
for (T o : m_predecessors.values()) {
sb.append(" ").append(o);
}
sb.append("]");
String result = sb.toString();
return result;
}
public void addPredecessor(T tm) {
ppp(" ADDING PREDECESSOR FOR " + m_object + " ==> " + tm);
m_predecessors.put(tm, tm);
}
public boolean hasPredecessors() {
return m_predecessors.size() > 0;
}
public boolean hasPredecessor(T m) {
return m_predecessors.containsKey(m);
}
@Override
public int compareTo(Node o) {
return getObject().toString().compareTo(o.getObject().toString());
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy