1package org.testng.internal;
2
3import org.testng.TestNGException;
4import org.testng.collections.Lists;
5import org.testng.collections.Maps;
6
7import java.util.Collection;
8import java.util.Collections;
9import java.util.HashSet;
10import java.util.LinkedList;
11import java.util.List;
12import java.util.Map;
13import 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 */
20public 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
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
36  public Set<T> getPredecessors(T node) {
37    return findNode(node).getPredecessors().keySet();
38  }
39
40  public boolean isIndependent(T object) {
41    return m_independentNodes.containsKey(object);
42  }
43
44  private Node<T> findNode(T object) {
45    return m_nodes.get(object);
46  }
47
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
64  private void addNeighbor(T tm, T predecessor) {
65    findNode(tm).addNeighbor(findNode(predecessor));
66  }
67
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
77  private Collection<Node<T>> getNodes() {
78    return m_nodes.values();
79  }
80
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   */
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   */
96  public List<T> getStrictlySortedNodes() {
97    return m_strictlySortedNodes;
98  }
99
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
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
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   */
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
190  private static void ppp(String s) {
191    if (m_verbose) {
192      System.out.println("[Graph] " + s);
193    }
194  }
195
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   */
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
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
262    public Node(T tm) {
263      m_object = tm;
264    }
265
266    private Set<Node<T>> m_neighbors = new HashSet<>();
267    public void addNeighbor(Node<T> neighbor) {
268      m_neighbors.add(neighbor);
269    }
270
271    public Set<Node<T>> getNeighbors() {
272      return m_neighbors;
273    }
274
275    @Override
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
284    public T getObject() {
285      return m_object;
286    }
287
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     */
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
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
325    public void addPredecessor(T tm) {
326      ppp("  ADDING PREDECESSOR FOR " + m_object + " ==> " + tm);
327      m_predecessors.put(tm, tm);
328    }
329
330    public boolean hasPredecessors() {
331      return m_predecessors.size() > 0;
332    }
333
334    public boolean hasPredecessor(T m) {
335      return m_predecessors.containsKey(m);
336    }
337
338    @Override
339    public int compareTo(Node<T> o) {
340      return getObject().toString().compareTo(o.getObject().toString());
341    }
342  }
343}
344