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.Collection; 022import java.util.Collections; 023import java.util.HashMap; 024import java.util.HashSet; 025import java.util.IdentityHashMap; 026import java.util.Map; 027import java.util.Objects; 028import java.util.Set; 029 030import org.eclipse.aether.RepositoryException; 031import org.eclipse.aether.artifact.Artifact; 032import org.eclipse.aether.collection.DependencyGraphTransformationContext; 033import org.eclipse.aether.collection.DependencyGraphTransformer; 034import org.eclipse.aether.graph.Dependency; 035import org.eclipse.aether.graph.DependencyNode; 036 037import static java.util.Objects.requireNonNull; 038 039/** 040 * A dependency graph transformer that identifies conflicting dependencies. When this transformer has executed, the 041 * transformation context holds a {@code Map<DependencyNode, String>} where dependency nodes that belong to the same 042 * conflict group will have an equal conflict identifier. This map is stored using the key 043 * {@link TransformationContextKeys#CONFLICT_IDS}. 044 */ 045public final class ConflictMarker implements DependencyGraphTransformer { 046 047 /** 048 * After the execution of this method, every DependencyNode with an attached dependency is member of one conflict 049 * group. 050 * 051 * @see DependencyGraphTransformer#transformGraph(DependencyNode, DependencyGraphTransformationContext) 052 */ 053 @Override 054 public DependencyNode transformGraph(DependencyNode node, DependencyGraphTransformationContext context) 055 throws RepositoryException { 056 requireNonNull(node, "node cannot be null"); 057 requireNonNull(context, "context cannot be null"); 058 @SuppressWarnings("unchecked") 059 Map<String, Object> stats = (Map<String, Object>) context.get(TransformationContextKeys.STATS); 060 long time1 = System.nanoTime(); 061 062 Map<DependencyNode, Boolean> nodes = new IdentityHashMap<>(1024); 063 Map<Key, ConflictGroup> groups = new HashMap<>(1024); 064 065 analyze(node, nodes, groups, new int[] {0}); 066 067 long time2 = System.nanoTime(); 068 069 Map<DependencyNode, String> conflictIds = mark(nodes.keySet(), groups); 070 071 context.put(TransformationContextKeys.CONFLICT_IDS, conflictIds); 072 073 if (stats != null) { 074 long time3 = System.nanoTime(); 075 stats.put("ConflictMarker.analyzeTime", time2 - time1); 076 stats.put("ConflictMarker.markTime", time3 - time2); 077 stats.put("ConflictMarker.nodeCount", nodes.size()); 078 } 079 080 return node; 081 } 082 083 private void analyze( 084 DependencyNode node, Map<DependencyNode, Boolean> nodes, Map<Key, ConflictGroup> groups, int[] counter) { 085 if (nodes.put(node, Boolean.TRUE) != null) { 086 return; 087 } 088 089 Set<Key> keys = getKeys(node); 090 if (!keys.isEmpty()) { 091 ConflictGroup group = null; 092 boolean fixMappings = false; 093 094 for (Key key : keys) { 095 ConflictGroup g = groups.get(key); 096 097 if (group != g) { 098 if (group == null) { 099 Set<Key> newKeys = merge(g.keys, keys); 100 if (newKeys == g.keys) { 101 group = g; 102 break; 103 } else { 104 group = new ConflictGroup(newKeys, counter[0]++); 105 fixMappings = true; 106 } 107 } else if (g == null) { 108 fixMappings = true; 109 } else { 110 Set<Key> newKeys = merge(g.keys, group.keys); 111 if (newKeys == g.keys) { 112 group = g; 113 fixMappings = false; 114 break; 115 } else if (newKeys != group.keys) { 116 group = new ConflictGroup(newKeys, counter[0]++); 117 fixMappings = true; 118 } 119 } 120 } 121 } 122 123 if (group == null) { 124 group = new ConflictGroup(keys, counter[0]++); 125 fixMappings = true; 126 } 127 if (fixMappings) { 128 for (Key key : group.keys) { 129 groups.put(key, group); 130 } 131 } 132 } 133 134 for (DependencyNode child : node.getChildren()) { 135 analyze(child, nodes, groups, counter); 136 } 137 } 138 139 private Set<Key> merge(Set<Key> keys1, Set<Key> keys2) { 140 int size1 = keys1.size(); 141 int size2 = keys2.size(); 142 143 if (size1 < size2) { 144 if (keys2.containsAll(keys1)) { 145 return keys2; 146 } 147 } else { 148 if (keys1.containsAll(keys2)) { 149 return keys1; 150 } 151 } 152 153 Set<Key> keys = new HashSet<>(); 154 keys.addAll(keys1); 155 keys.addAll(keys2); 156 return keys; 157 } 158 159 private Set<Key> getKeys(DependencyNode node) { 160 Set<Key> keys; 161 162 Dependency dependency = node.getDependency(); 163 164 if (dependency == null) { 165 keys = Collections.emptySet(); 166 } else { 167 Key key = toKey(dependency.getArtifact()); 168 169 if (node.getRelocations().isEmpty() && node.getAliases().isEmpty()) { 170 keys = Collections.singleton(key); 171 } else { 172 keys = new HashSet<>(); 173 keys.add(key); 174 175 for (Artifact relocation : node.getRelocations()) { 176 key = toKey(relocation); 177 keys.add(key); 178 } 179 180 for (Artifact alias : node.getAliases()) { 181 key = toKey(alias); 182 keys.add(key); 183 } 184 } 185 } 186 187 return keys; 188 } 189 190 private Map<DependencyNode, String> mark(Collection<DependencyNode> nodes, Map<Key, ConflictGroup> groups) { 191 Map<DependencyNode, String> conflictIds = new IdentityHashMap<>(nodes.size() + 1); 192 193 for (DependencyNode node : nodes) { 194 Dependency dependency = node.getDependency(); 195 if (dependency != null) { 196 Key key = toKey(dependency.getArtifact()); 197 conflictIds.put( 198 node, String.valueOf(groups.get(key).index).intern()); // interning it as is expected so in UT 199 } 200 } 201 202 return conflictIds; 203 } 204 205 private static Key toKey(Artifact artifact) { 206 return new Key(artifact); 207 } 208 209 static class ConflictGroup { 210 211 final Set<Key> keys; 212 213 final int index; 214 215 ConflictGroup(Set<Key> keys, int index) { 216 this.keys = keys; 217 this.index = index; 218 } 219 220 @Override 221 public String toString() { 222 return String.valueOf(keys); 223 } 224 } 225 226 static class Key { 227 228 private final Artifact artifact; 229 private final int hashCode; 230 231 Key(Artifact artifact) { 232 this.artifact = artifact; 233 this.hashCode = Objects.hash( 234 artifact.getArtifactId(), artifact.getGroupId(), artifact.getExtension(), artifact.getClassifier()); 235 } 236 237 @Override 238 public boolean equals(Object obj) { 239 if (obj == this) { 240 return true; 241 } else if (!(obj instanceof Key)) { 242 return false; 243 } 244 Key that = (Key) obj; 245 return artifact.getArtifactId().equals(that.artifact.getArtifactId()) 246 && artifact.getGroupId().equals(that.artifact.getGroupId()) 247 && artifact.getExtension().equals(that.artifact.getExtension()) 248 && artifact.getClassifier().equals(that.artifact.getClassifier()); 249 } 250 251 @Override 252 public int hashCode() { 253 return hashCode; 254 } 255 256 @Override 257 public String toString() { 258 return artifact.getGroupId() 259 + ':' 260 + artifact.getArtifactId() 261 + ':' 262 + artifact.getClassifier() 263 + ':' 264 + artifact.getExtension(); 265 } 266 } 267}