1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.internal.impl;
20
21 import java.util.ArrayList;
22 import java.util.Collection;
23 import java.util.Collections;
24 import java.util.HashMap;
25 import java.util.LinkedHashMap;
26 import java.util.LinkedList;
27 import java.util.List;
28 import java.util.Map;
29
30 class Graph {
31 private enum DfsState {
32 VISITING,
33 VISITED
34 }
35
36 final Map<String, Vertex> vertices = new LinkedHashMap<>();
37
38 public Vertex getVertex(String id) {
39 return vertices.get(id);
40 }
41
42 public Collection<Vertex> getVertices() {
43 return vertices.values();
44 }
45
46 Vertex addVertex(String label) {
47 return vertices.computeIfAbsent(label, Vertex::new);
48 }
49
50 void addEdge(Vertex from, Vertex to) throws CycleDetectedException {
51 from.children.add(to);
52 to.parents.add(from);
53 List<String> cycle = findCycle(to);
54 if (cycle != null) {
55
56 removeEdge(from, to);
57 throw new CycleDetectedException(
58 "Edge between '" + from.label + "' and '" + to.label + "' introduces to cycle in the graph", cycle);
59 }
60 }
61
62 void removeEdge(Vertex from, Vertex to) {
63 from.children.remove(to);
64 to.parents.remove(from);
65 }
66
67 List<String> visitAll() {
68 return visitAll(vertices.values(), new HashMap<>(), new ArrayList<>());
69 }
70
71 List<String> findCycle(Vertex vertex) {
72 return visitCycle(Collections.singleton(vertex), new HashMap<>(), new LinkedList<>());
73 }
74
75 private static List<String> visitAll(
76 Collection<Vertex> children, Map<Vertex, DfsState> stateMap, List<String> list) {
77 for (Vertex v : children) {
78 DfsState state = stateMap.putIfAbsent(v, DfsState.VISITING);
79 if (state == null) {
80 visitAll(v.children, stateMap, list);
81 stateMap.put(v, DfsState.VISITED);
82 list.add(v.label);
83 }
84 }
85 return list;
86 }
87
88 private static List<String> visitCycle(
89 Collection<Vertex> children, Map<Vertex, DfsState> stateMap, LinkedList<String> cycle) {
90 for (Vertex v : children) {
91 DfsState state = stateMap.putIfAbsent(v, DfsState.VISITING);
92 if (state == null) {
93 cycle.addLast(v.label);
94 List<String> ret = visitCycle(v.children, stateMap, cycle);
95 if (ret != null) {
96 return ret;
97 }
98 cycle.removeLast();
99 stateMap.put(v, DfsState.VISITED);
100 } else if (state == DfsState.VISITING) {
101
102 int pos = cycle.lastIndexOf(v.label);
103 List<String> ret = cycle.subList(pos, cycle.size());
104 ret.add(v.label);
105 return ret;
106 }
107 }
108 return null;
109 }
110
111 static class Vertex {
112 final String label;
113 final List<Vertex> children = new ArrayList<>();
114 final List<Vertex> parents = new ArrayList<>();
115
116 Vertex(String label) {
117 this.label = label;
118 }
119
120 String getLabel() {
121 return label;
122 }
123
124 List<Vertex> getChildren() {
125 return children;
126 }
127
128 List<Vertex> getParents() {
129 return parents;
130 }
131 }
132
133 static class CycleDetectedException extends RuntimeException {
134 private final List<String> cycle;
135
136 CycleDetectedException(String message, List<String> cycle) {
137 super(message);
138 this.cycle = cycle;
139 }
140
141 public List<String> getCycle() {
142 return cycle;
143 }
144
145 @Override
146 public String getMessage() {
147 return super.getMessage() + " " + String.join(" --> ", cycle);
148 }
149 }
150 }