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 }