Home | History | Annotate | Download | only in internal
      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 
     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