1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.eclipse.aether.util.graph.transformer;
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.HashSet;
26 import java.util.IdentityHashMap;
27 import java.util.LinkedHashMap;
28 import java.util.List;
29 import java.util.Map;
30
31 import org.eclipse.aether.RepositoryException;
32 import org.eclipse.aether.collection.DependencyGraphTransformationContext;
33 import org.eclipse.aether.collection.DependencyGraphTransformer;
34 import org.eclipse.aether.graph.DependencyNode;
35
36 import static java.util.Objects.requireNonNull;
37
38
39
40
41
42
43
44
45
46
47
48
49 public final class ConflictIdSorter implements DependencyGraphTransformer {
50
51 @SuppressWarnings("unchecked")
52 @Override
53 public DependencyNode transformGraph(DependencyNode node, DependencyGraphTransformationContext context)
54 throws RepositoryException {
55 requireNonNull(node, "node cannot be null");
56 requireNonNull(context, "context cannot be null");
57 Map<DependencyNode, String> conflictIds =
58 (Map<DependencyNode, String>) context.get(TransformationContextKeys.CONFLICT_IDS);
59 if (conflictIds == null) {
60 ConflictMarker marker = new ConflictMarker();
61 marker.transformGraph(node, context);
62
63 conflictIds = (Map<DependencyNode, String>) context.get(TransformationContextKeys.CONFLICT_IDS);
64 }
65
66 @SuppressWarnings("unchecked")
67 Map<String, Object> stats = (Map<String, Object>) context.get(TransformationContextKeys.STATS);
68 long time1 = System.nanoTime();
69
70 Map<String, ConflictId> ids = new LinkedHashMap<>(256);
71
72 ConflictId id = null;
73 String key = conflictIds.get(node);
74 if (key != null) {
75 id = new ConflictId(key, 0);
76 ids.put(key, id);
77 }
78
79 Map<DependencyNode, Boolean> visited = new IdentityHashMap<>(conflictIds.size());
80
81 buildConflictIdDAG(ids, node, id, 0, visited, conflictIds);
82
83 long time2 = System.nanoTime();
84
85 int cycles = topoSortConflictIds(ids.values(), context);
86
87 if (stats != null) {
88 long time3 = System.nanoTime();
89 stats.put("ConflictIdSorter.graphTime", time2 - time1);
90 stats.put("ConflictIdSorter.topsortTime", time3 - time2);
91 stats.put("ConflictIdSorter.conflictIdCount", ids.size());
92 stats.put("ConflictIdSorter.conflictIdCycleCount", cycles);
93 }
94
95 return node;
96 }
97
98 private void buildConflictIdDAG(
99 Map<String, ConflictId> ids,
100 DependencyNode node,
101 ConflictId id,
102 int depth,
103 Map<DependencyNode, Boolean> visited,
104 Map<DependencyNode, String> conflictIds) {
105 if (visited.put(node, Boolean.TRUE) != null) {
106 return;
107 }
108
109 depth++;
110
111 for (DependencyNode child : node.getChildren()) {
112 String key = conflictIds.get(child);
113 ConflictId childId = ids.get(key);
114 if (childId == null) {
115 childId = new ConflictId(key, depth);
116 ids.put(key, childId);
117 } else {
118 childId.pullup(depth);
119 }
120
121 if (id != null) {
122 id.add(childId);
123 }
124
125 buildConflictIdDAG(ids, child, childId, depth, visited, conflictIds);
126 }
127 }
128
129 private int topoSortConflictIds(Collection<ConflictId> conflictIds, DependencyGraphTransformationContext context) {
130 List<String> sorted = new ArrayList<>(conflictIds.size());
131
132 RootQueue roots = new RootQueue(conflictIds.size() / 2);
133 for (ConflictId id : conflictIds) {
134 if (id.inDegree <= 0) {
135 roots.add(id);
136 }
137 }
138
139 processRoots(sorted, roots);
140
141 boolean cycle = sorted.size() < conflictIds.size();
142
143 while (sorted.size() < conflictIds.size()) {
144
145
146 ConflictId nearest = null;
147 for (ConflictId id : conflictIds) {
148 if (id.inDegree <= 0) {
149 continue;
150 }
151 if (nearest == null
152 || id.minDepth < nearest.minDepth
153 || (id.minDepth == nearest.minDepth && id.inDegree < nearest.inDegree)) {
154 nearest = id;
155 }
156 }
157
158 nearest.inDegree = 0;
159 roots.add(nearest);
160
161 processRoots(sorted, roots);
162 }
163
164 Collection<Collection<String>> cycles = Collections.emptySet();
165 if (cycle) {
166 cycles = findCycles(conflictIds);
167 }
168
169 context.put(TransformationContextKeys.SORTED_CONFLICT_IDS, sorted);
170 context.put(TransformationContextKeys.CYCLIC_CONFLICT_IDS, cycles);
171
172 return cycles.size();
173 }
174
175 private void processRoots(List<String> sorted, RootQueue roots) {
176 while (!roots.isEmpty()) {
177 ConflictId root = roots.remove();
178
179 sorted.add(root.key);
180
181 for (ConflictId child : root.children) {
182 child.inDegree--;
183 if (child.inDegree == 0) {
184 roots.add(child);
185 }
186 }
187 }
188 }
189
190 private Collection<Collection<String>> findCycles(Collection<ConflictId> conflictIds) {
191 Collection<Collection<String>> cycles = new HashSet<>();
192
193 Map<String, Integer> stack = new HashMap<>(128);
194 Map<ConflictId, Boolean> visited = new IdentityHashMap<>(conflictIds.size());
195 for (ConflictId id : conflictIds) {
196 findCycles(id, visited, stack, cycles);
197 }
198
199 return cycles;
200 }
201
202 private void findCycles(
203 ConflictId id,
204 Map<ConflictId, Boolean> visited,
205 Map<String, Integer> stack,
206 Collection<Collection<String>> cycles) {
207 Integer depth = stack.put(id.key, stack.size());
208 if (depth != null) {
209 stack.put(id.key, depth);
210 Collection<String> cycle = new HashSet<>();
211 for (Map.Entry<String, Integer> entry : stack.entrySet()) {
212 if (entry.getValue() >= depth) {
213 cycle.add(entry.getKey());
214 }
215 }
216 cycles.add(cycle);
217 } else {
218 if (visited.put(id, Boolean.TRUE) == null) {
219 for (ConflictId childId : id.children) {
220 findCycles(childId, visited, stack, cycles);
221 }
222 }
223 stack.remove(id.key);
224 }
225 }
226
227 static final class ConflictId {
228
229 final String key;
230
231 Collection<ConflictId> children = Collections.emptySet();
232
233 int inDegree;
234
235 int minDepth;
236
237 ConflictId(String key, int depth) {
238 this.key = key;
239 this.minDepth = depth;
240 }
241
242 public void add(ConflictId child) {
243 if (children.isEmpty()) {
244 children = new HashSet<>();
245 }
246 if (children.add(child)) {
247 child.inDegree++;
248 }
249 }
250
251 public void pullup(int depth) {
252 if (depth < minDepth) {
253 minDepth = depth;
254 depth++;
255 for (ConflictId child : children) {
256 child.pullup(depth);
257 }
258 }
259 }
260
261 @Override
262 public boolean equals(Object obj) {
263 if (this == obj) {
264 return true;
265 } else if (!(obj instanceof ConflictId)) {
266 return false;
267 }
268 ConflictId that = (ConflictId) obj;
269 return this.key.equals(that.key);
270 }
271
272 @Override
273 public int hashCode() {
274 return key.hashCode();
275 }
276
277 @Override
278 public String toString() {
279 return key + " @ " + minDepth + " <" + inDegree;
280 }
281 }
282
283 static final class RootQueue {
284
285 private int nextOut;
286
287 private int nextIn;
288
289 private ConflictId[] ids;
290
291 RootQueue(int capacity) {
292 ids = new ConflictId[capacity + 16];
293 }
294
295 boolean isEmpty() {
296 return nextOut >= nextIn;
297 }
298
299 void add(ConflictId id) {
300 if (nextOut >= nextIn && nextOut > 0) {
301 nextIn -= nextOut;
302 nextOut = 0;
303 }
304 if (nextIn >= ids.length) {
305 ConflictId[] tmp = new ConflictId[ids.length + ids.length / 2 + 16];
306 System.arraycopy(ids, nextOut, tmp, 0, nextIn - nextOut);
307 ids = tmp;
308 nextIn -= nextOut;
309 nextOut = 0;
310 }
311 int i;
312 for (i = nextIn - 1; i >= nextOut && id.minDepth < ids[i].minDepth; i--) {
313 ids[i + 1] = ids[i];
314 }
315 ids[i + 1] = id;
316 nextIn++;
317 }
318
319 ConflictId remove() {
320 return ids[nextOut++];
321 }
322 }
323 }