1   
2   
3   
4   
5   
6   
7   
8   
9   
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  package org.apache.maven.model.building;
20  
21  import java.util.*;
22  
23  class Graph {
24  
25      final Map<String, Set<String>> graph = new LinkedHashMap<>();
26  
27      synchronized void addEdge(String from, String to) throws CycleDetectedException {
28          if (graph.computeIfAbsent(from, l -> new HashSet<>()).add(to)) {
29              List<String> cycle = visitCycle(graph, Collections.singleton(to), new HashMap<>(), new LinkedList<>());
30              if (cycle != null) {
31                  
32                  throw new CycleDetectedException(
33                          "Edge between '" + from + "' and '" + to + "' introduces to cycle in the graph", cycle);
34              }
35          }
36      }
37  
38      private enum DfsState {
39          VISITING,
40          VISITED
41      }
42  
43      private static List<String> visitCycle(
44              Map<String, Set<String>> graph,
45              Collection<String> children,
46              Map<String, DfsState> stateMap,
47              LinkedList<String> cycle) {
48          if (children != null) {
49              for (String v : children) {
50                  DfsState state = stateMap.putIfAbsent(v, DfsState.VISITING);
51                  if (state == null) {
52                      cycle.addLast(v);
53                      List<String> ret = visitCycle(graph, graph.get(v), stateMap, cycle);
54                      if (ret != null) {
55                          return ret;
56                      }
57                      cycle.removeLast();
58                      stateMap.put(v, DfsState.VISITED);
59                  } else if (state == DfsState.VISITING) {
60                      
61                      int pos = cycle.lastIndexOf(v);
62                      List<String> ret = cycle.subList(pos, cycle.size());
63                      ret.add(v);
64                      return ret;
65                  }
66              }
67          }
68          return null;
69      }
70  
71      public static class CycleDetectedException extends Exception {
72          private final List<String> cycle;
73  
74          CycleDetectedException(String message, List<String> cycle) {
75              super(message);
76              this.cycle = cycle;
77          }
78  
79          public List<String> getCycle() {
80              return cycle;
81          }
82  
83          @Override
84          public String getMessage() {
85              return super.getMessage() + " " + String.join(" --> ", cycle);
86          }
87      }
88  }