001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019package org.eclipse.aether.util.graph.transformer; 020 021import java.util.ArrayList; 022import java.util.Collection; 023import java.util.Collections; 024import java.util.HashMap; 025import java.util.HashSet; 026import java.util.IdentityHashMap; 027import java.util.LinkedHashMap; 028import java.util.List; 029import java.util.Map; 030 031import org.eclipse.aether.RepositoryException; 032import org.eclipse.aether.collection.DependencyGraphTransformationContext; 033import org.eclipse.aether.collection.DependencyGraphTransformer; 034import org.eclipse.aether.graph.DependencyNode; 035 036import static java.util.Objects.requireNonNull; 037 038/** 039 * A dependency graph transformer that creates a topological sorting of the conflict ids which have been assigned to the 040 * dependency nodes. Conflict ids are sorted according to the dependency relation induced by the dependency graph. This 041 * transformer will query the key {@link TransformationContextKeys#CONFLICT_IDS} in the transformation context for an 042 * existing mapping of nodes to their conflicts ids. In absence of this map, the transformer will automatically invoke 043 * the {@link ConflictMarker} to calculate the conflict ids. When this transformer has executed, the transformation 044 * context holds a {@code List<Object>} that denotes the topologically sorted conflict ids. The list will be stored 045 * using the key {@link TransformationContextKeys#SORTED_CONFLICT_IDS}. In addition, the transformer will store a 046 * {@code Collection<Collection<Object>>} using the key {@link TransformationContextKeys#CYCLIC_CONFLICT_IDS} that 047 * describes cycles among conflict ids. 048 */ 049public final class ConflictIdSorter implements DependencyGraphTransformer { 050 051 @Override 052 public DependencyNode transformGraph(DependencyNode node, DependencyGraphTransformationContext context) 053 throws RepositoryException { 054 requireNonNull(node, "node cannot be null"); 055 requireNonNull(context, "context cannot be null"); 056 Map<?, ?> conflictIds = (Map<?, ?>) context.get(TransformationContextKeys.CONFLICT_IDS); 057 if (conflictIds == null) { 058 ConflictMarker marker = new ConflictMarker(); 059 marker.transformGraph(node, context); 060 061 conflictIds = (Map<?, ?>) context.get(TransformationContextKeys.CONFLICT_IDS); 062 } 063 064 @SuppressWarnings("unchecked") 065 Map<String, Object> stats = (Map<String, Object>) context.get(TransformationContextKeys.STATS); 066 long time1 = System.nanoTime(); 067 068 Map<Object, ConflictId> ids = new LinkedHashMap<>(256); 069 070 ConflictId id = null; 071 Object key = conflictIds.get(node); 072 if (key != null) { 073 id = new ConflictId(key, 0); 074 ids.put(key, id); 075 } 076 077 Map<DependencyNode, Object> visited = new IdentityHashMap<>(conflictIds.size()); 078 079 buildConflitIdDAG(ids, node, id, 0, visited, conflictIds); 080 081 long time2 = System.nanoTime(); 082 083 int cycles = topsortConflictIds(ids.values(), context); 084 085 if (stats != null) { 086 long time3 = System.nanoTime(); 087 stats.put("ConflictIdSorter.graphTime", time2 - time1); 088 stats.put("ConflictIdSorter.topsortTime", time3 - time2); 089 stats.put("ConflictIdSorter.conflictIdCount", ids.size()); 090 stats.put("ConflictIdSorter.conflictIdCycleCount", cycles); 091 } 092 093 return node; 094 } 095 096 private void buildConflitIdDAG( 097 Map<Object, ConflictId> ids, 098 DependencyNode node, 099 ConflictId id, 100 int depth, 101 Map<DependencyNode, Object> visited, 102 Map<?, ?> conflictIds) { 103 if (visited.put(node, Boolean.TRUE) != null) { 104 return; 105 } 106 107 depth++; 108 109 for (DependencyNode child : node.getChildren()) { 110 Object key = conflictIds.get(child); 111 ConflictId childId = ids.get(key); 112 if (childId == null) { 113 childId = new ConflictId(key, depth); 114 ids.put(key, childId); 115 } else { 116 childId.pullup(depth); 117 } 118 119 if (id != null) { 120 id.add(childId); 121 } 122 123 buildConflitIdDAG(ids, child, childId, depth, visited, conflictIds); 124 } 125 } 126 127 private int topsortConflictIds(Collection<ConflictId> conflictIds, DependencyGraphTransformationContext context) { 128 List<Object> sorted = new ArrayList<>(conflictIds.size()); 129 130 RootQueue roots = new RootQueue(conflictIds.size() / 2); 131 for (ConflictId id : conflictIds) { 132 if (id.inDegree <= 0) { 133 roots.add(id); 134 } 135 } 136 137 processRoots(sorted, roots); 138 139 boolean cycle = sorted.size() < conflictIds.size(); 140 141 while (sorted.size() < conflictIds.size()) { 142 // cycle -> deal gracefully with nodes still having positive in-degree 143 144 ConflictId nearest = null; 145 for (ConflictId id : conflictIds) { 146 if (id.inDegree <= 0) { 147 continue; 148 } 149 if (nearest == null 150 || id.minDepth < nearest.minDepth 151 || (id.minDepth == nearest.minDepth && id.inDegree < nearest.inDegree)) { 152 nearest = id; 153 } 154 } 155 156 nearest.inDegree = 0; 157 roots.add(nearest); 158 159 processRoots(sorted, roots); 160 } 161 162 Collection<Collection<Object>> cycles = Collections.emptySet(); 163 if (cycle) { 164 cycles = findCycles(conflictIds); 165 } 166 167 context.put(TransformationContextKeys.SORTED_CONFLICT_IDS, sorted); 168 context.put(TransformationContextKeys.CYCLIC_CONFLICT_IDS, cycles); 169 170 return cycles.size(); 171 } 172 173 private void processRoots(List<Object> sorted, RootQueue roots) { 174 while (!roots.isEmpty()) { 175 ConflictId root = roots.remove(); 176 177 sorted.add(root.key); 178 179 for (ConflictId child : root.children) { 180 child.inDegree--; 181 if (child.inDegree == 0) { 182 roots.add(child); 183 } 184 } 185 } 186 } 187 188 private Collection<Collection<Object>> findCycles(Collection<ConflictId> conflictIds) { 189 Collection<Collection<Object>> cycles = new HashSet<>(); 190 191 Map<Object, Integer> stack = new HashMap<>(128); 192 Map<ConflictId, Object> visited = new IdentityHashMap<>(conflictIds.size()); 193 for (ConflictId id : conflictIds) { 194 findCycles(id, visited, stack, cycles); 195 } 196 197 return cycles; 198 } 199 200 private void findCycles( 201 ConflictId id, 202 Map<ConflictId, Object> visited, 203 Map<Object, Integer> stack, 204 Collection<Collection<Object>> cycles) { 205 Integer depth = stack.put(id.key, stack.size()); 206 if (depth != null) { 207 stack.put(id.key, depth); 208 Collection<Object> cycle = new HashSet<>(); 209 for (Map.Entry<Object, Integer> entry : stack.entrySet()) { 210 if (entry.getValue() >= depth) { 211 cycle.add(entry.getKey()); 212 } 213 } 214 cycles.add(cycle); 215 } else { 216 if (visited.put(id, Boolean.TRUE) == null) { 217 for (ConflictId childId : id.children) { 218 findCycles(childId, visited, stack, cycles); 219 } 220 } 221 stack.remove(id.key); 222 } 223 } 224 225 static final class ConflictId { 226 227 final Object key; 228 229 Collection<ConflictId> children = Collections.emptySet(); 230 231 int inDegree; 232 233 int minDepth; 234 235 ConflictId(Object key, int depth) { 236 this.key = key; 237 this.minDepth = depth; 238 } 239 240 public void add(ConflictId child) { 241 if (children.isEmpty()) { 242 children = new HashSet<>(); 243 } 244 if (children.add(child)) { 245 child.inDegree++; 246 } 247 } 248 249 public void pullup(int depth) { 250 if (depth < minDepth) { 251 minDepth = depth; 252 depth++; 253 for (ConflictId child : children) { 254 child.pullup(depth); 255 } 256 } 257 } 258 259 @Override 260 public boolean equals(Object obj) { 261 if (this == obj) { 262 return true; 263 } else if (!(obj instanceof ConflictId)) { 264 return false; 265 } 266 ConflictId that = (ConflictId) obj; 267 return this.key.equals(that.key); 268 } 269 270 @Override 271 public int hashCode() { 272 return key.hashCode(); 273 } 274 275 @Override 276 public String toString() { 277 return key + " @ " + minDepth + " <" + inDegree; 278 } 279 } 280 281 static final class RootQueue { 282 283 private int nextOut; 284 285 private int nextIn; 286 287 private ConflictId[] ids; 288 289 RootQueue(int capacity) { 290 ids = new ConflictId[capacity + 16]; 291 } 292 293 boolean isEmpty() { 294 return nextOut >= nextIn; 295 } 296 297 void add(ConflictId id) { 298 if (nextOut >= nextIn && nextOut > 0) { 299 nextIn -= nextOut; 300 nextOut = 0; 301 } 302 if (nextIn >= ids.length) { 303 ConflictId[] tmp = new ConflictId[ids.length + ids.length / 2 + 16]; 304 System.arraycopy(ids, nextOut, tmp, 0, nextIn - nextOut); 305 ids = tmp; 306 nextIn -= nextOut; 307 nextOut = 0; 308 } 309 int i; 310 for (i = nextIn - 1; i >= nextOut && id.minDepth < ids[i].minDepth; i--) { 311 ids[i + 1] = ids[i]; 312 } 313 ids[i + 1] = id; 314 nextIn++; 315 } 316 317 ConflictId remove() { 318 return ids[nextOut++]; 319 } 320 } 321}