1 package org.testng.internal;
2 
3 import org.testng.TestNGException;
4 import org.testng.collections.Lists;
5 import org.testng.collections.Maps;
6 
7 import java.util.Collection;
8 import java.util.Collections;
9 import java.util.HashSet;
10 import java.util.LinkedList;
11 import java.util.List;
12 import java.util.Map;
13 import java.util.Set;
14 /**
15  * Simple graph class to implement topological sort (used to sort methods based on what groups
16  * they depend on).
17  *
18  * @author Cedric Beust, Aug 19, 2004
19  */
20 public class Graph<T> {
21   private static boolean m_verbose = false;
22   private Map<T, Node<T>> m_nodes = Maps.newLinkedHashMap();
23   private List<T> m_strictlySortedNodes = null;
24 
25   //  A map of nodes that are not the predecessors of any node
26   // (not needed for the algorithm but convenient to calculate
27   // the parallel/sequential lists in TestNG).
28   private Map<T, Node<T>> m_independentNodes = null;
29 
addNode(T tm)30   public void addNode(T tm) {
31     ppp("ADDING NODE " + tm + " " + tm.hashCode());
32     m_nodes.put(tm, new Node<>(tm));
33     // Initially, all the nodes are put in the independent list as well
34   }
35 
getPredecessors(T node)36   public Set<T> getPredecessors(T node) {
37     return findNode(node).getPredecessors().keySet();
38   }
39 
isIndependent(T object)40   public boolean isIndependent(T object) {
41     return m_independentNodes.containsKey(object);
42   }
43 
findNode(T object)44   private Node<T> findNode(T object) {
45     return m_nodes.get(object);
46   }
47 
addPredecessor(T tm, T predecessor)48   public void addPredecessor(T tm, T predecessor) {
49     Node<T> node = findNode(tm);
50     if (null == node) {
51       throw new TestNGException("Non-existing node: " + tm);
52     }
53     else {
54       node.addPredecessor(predecessor);
55       addNeighbor(tm, predecessor);
56       // Remove these two nodes from the independent list
57       initializeIndependentNodes();
58       m_independentNodes.remove(predecessor);
59       m_independentNodes.remove(tm);
60       ppp("  REMOVED " + predecessor + " FROM INDEPENDENT OBJECTS");
61     }
62   }
63 
addNeighbor(T tm, T predecessor)64   private void addNeighbor(T tm, T predecessor) {
65     findNode(tm).addNeighbor(findNode(predecessor));
66   }
67 
getNeighbors(T t)68   public Set<T> getNeighbors(T t) {
69     Set<T> result = new HashSet<>();
70     for (Node<T> n : findNode(t).getNeighbors()) {
71       result.add(n.getObject());
72     }
73 
74     return result;
75   }
76 
getNodes()77   private Collection<Node<T>> getNodes() {
78     return m_nodes.values();
79   }
80 
getNodeValues()81   public Collection<T> getNodeValues() {
82     return m_nodes.keySet();
83   }
84 
85   /**
86    * @return All the nodes that don't have any order with each other.
87    */
getIndependentNodes()88   public Set<T> getIndependentNodes() {
89     return m_independentNodes.keySet();
90   }
91 
92   /**
93    * @return All the nodes that have an order with each other, sorted
94    * in one of the valid sorts.
95    */
getStrictlySortedNodes()96   public List<T> getStrictlySortedNodes() {
97     return m_strictlySortedNodes;
98   }
99 
topologicalSort()100   public void topologicalSort() {
101     ppp("================ SORTING");
102     m_strictlySortedNodes = Lists.newArrayList();
103     initializeIndependentNodes();
104 
105     //
106     // Clone the list of nodes but only keep those that are
107     // not independent.
108     //
109     List<Node<T>> nodes2 = Lists.newArrayList();
110     for (Node<T> n : getNodes()) {
111       if (! isIndependent(n.getObject())) {
112         ppp("ADDING FOR SORT: " + n.getObject());
113         nodes2.add(n.clone());
114       }
115       else {
116         ppp("SKIPPING INDEPENDENT NODE " + n);
117       }
118     }
119 
120     //
121     // Sort the nodes alphabetically to make sure that methods of the same class
122     // get run close to each other as much as possible
123     //
124     Collections.sort(nodes2);
125 
126     //
127     // Sort
128     //
129     while (! nodes2.isEmpty()) {
130 
131       //
132       // Find all the nodes that don't have any predecessors, add
133       // them to the result and mark them for removal
134       //
135       Node<T> node = findNodeWithNoPredecessors(nodes2);
136       if (null == node) {
137         List<T> cycle = new Tarjan<>(this, nodes2.get(0).getObject()).getCycle();
138         StringBuilder sb = new StringBuilder();
139         sb.append("The following methods have cyclic dependencies:\n");
140         for (T m : cycle) {
141           sb.append(m).append("\n");
142         }
143         throw new TestNGException(sb.toString());
144       }
145       else {
146         m_strictlySortedNodes.add(node.getObject());
147         removeFromNodes(nodes2, node);
148       }
149     }
150 
151     ppp("=============== DONE SORTING");
152     if (m_verbose) {
153       dumpSortedNodes();
154     }
155   }
156 
initializeIndependentNodes()157   private void initializeIndependentNodes() {
158     if (null == m_independentNodes) {
159       List<Node<T>> list = Lists.newArrayList(m_nodes.values());
160       // Ideally, we should not have to sort this. However, due to a bug where it treats all the methods as
161       // dependent nodes. Therefore, all the nodes were mostly sorted alphabetically. So we need to keep
162       // the behavior for now.
163       Collections.sort(list);
164       m_independentNodes = Maps.newLinkedHashMap();
165       for (Node<T> node : list) {
166         m_independentNodes.put(node.getObject(), node);
167       }
168     }
169   }
170 
dumpSortedNodes()171   private void dumpSortedNodes() {
172     System.out.println("====== SORTED NODES");
173     for (T n : m_strictlySortedNodes) {
174       System.out.println("              " + n);
175     }
176     System.out.println("====== END SORTED NODES");
177   }
178 
179   /**
180    * Remove a node from a list of nodes and update the list of predecessors
181    * for all the remaining nodes.
182    */
removeFromNodes(List<Node<T>> nodes, Node<T> node)183   private void removeFromNodes(List<Node<T>> nodes, Node<T> node) {
184     nodes.remove(node);
185     for (Node<T> n : nodes) {
186       n.removePredecessor(node.getObject());
187     }
188   }
189 
ppp(String s)190   private static void ppp(String s) {
191     if (m_verbose) {
192       System.out.println("[Graph] " + s);
193     }
194   }
195 
findNodeWithNoPredecessors(List<Node<T>> nodes)196   private Node<T> findNodeWithNoPredecessors(List<Node<T>> nodes) {
197     for (Node<T> n : nodes) {
198       if (! n.hasPredecessors()) {
199         return n;
200       }
201     }
202 
203     return null;
204   }
205 
206   /**
207    * @param o
208    * @return A list of all the predecessors for o
209    */
findPredecessors(T o)210   public List<T> findPredecessors(T o) {
211     // Locate the node
212     Node<T> node = findNode(o);
213     if (null == node) {
214       // This can happen if an interceptor returned new methods
215       return Lists.newArrayList();
216     }
217 
218     // If we found the node, use breadth first search to find all
219     // all of the predecessors of o.  "result" is the growing list
220     // of all predecessors.  "visited" is the set of items we've
221     // already encountered.  "queue" is the queue of items whose
222     // predecessors we haven't yet explored.
223 
224     LinkedList<T> result = new LinkedList<>();
225     Set<T> visited = new HashSet<>();
226     LinkedList<T> queue = new LinkedList<>();
227     visited.add(o);
228     queue.addLast(o);
229 
230     while (! queue.isEmpty()) {
231       for (T obj : getPredecessors(queue.removeFirst())) {
232         if (! visited.contains(obj)) {
233           visited.add(obj);
234           queue.addLast(obj);
235           result.addFirst(obj);
236         }
237       }
238     }
239 
240     return result;
241   }
242 
243   @Override
toString()244   public String toString() {
245     StringBuilder result = new StringBuilder("[Graph ");
246     for (T node : m_nodes.keySet()) {
247       result.append(findNode(node)).append(" ");
248     }
249     result.append("]");
250 
251     return result.toString();
252   }
253 
254 
255   /////
256   // class Node
257   //
258   public static class Node<T> implements Comparable<Node<T>> {
259     private T m_object = null;
260     private Map<T, T> m_predecessors = Maps.newHashMap();
261 
Node(T tm)262     public Node(T tm) {
263       m_object = tm;
264     }
265 
266     private Set<Node<T>> m_neighbors = new HashSet<>();
addNeighbor(Node<T> neighbor)267     public void addNeighbor(Node<T> neighbor) {
268       m_neighbors.add(neighbor);
269     }
270 
getNeighbors()271     public Set<Node<T>> getNeighbors() {
272       return m_neighbors;
273     }
274 
275     @Override
clone()276     public Node<T> clone() {
277       Node<T> result = new Node<>(m_object);
278       for (T pred : m_predecessors.values()) {
279         result.addPredecessor(pred);
280       }
281       return result;
282     }
283 
getObject()284     public T getObject() {
285       return m_object;
286     }
287 
getPredecessors()288     public Map<T, T> getPredecessors() {
289       return m_predecessors;
290     }
291 
292     /**
293      *
294      * @return true if this predecessor was found and removed
295      */
removePredecessor(T o)296     public boolean removePredecessor(T o) {
297       boolean result = false;
298 
299       T pred = m_predecessors.get(o);
300       if (null != pred) {
301         result = null != m_predecessors.remove(o);
302         if (result) {
303           ppp("  REMOVED PRED " + o + " FROM NODE " + m_object);
304         }
305         else {
306           ppp("  FAILED TO REMOVE PRED " + o + " FROM NODE " + m_object);
307         }
308       }
309 
310       return result;
311     }
312 
313     @Override
toString()314     public String toString() {
315       StringBuilder sb = new StringBuilder("[Node:" + m_object);
316       sb.append("  pred:");
317       for (T o : m_predecessors.values()) {
318         sb.append(" ").append(o);
319       }
320       sb.append("]");
321       String result = sb.toString();
322       return result;
323     }
324 
addPredecessor(T tm)325     public void addPredecessor(T tm) {
326       ppp("  ADDING PREDECESSOR FOR " + m_object + " ==> " + tm);
327       m_predecessors.put(tm, tm);
328     }
329 
hasPredecessors()330     public boolean hasPredecessors() {
331       return m_predecessors.size() > 0;
332     }
333 
hasPredecessor(T m)334     public boolean hasPredecessor(T m) {
335       return m_predecessors.containsKey(m);
336     }
337 
338     @Override
compareTo(Node<T> o)339     public int compareTo(Node<T> o) {
340       return getObject().toString().compareTo(o.getObject().toString());
341     }
342   }
343 }
344