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