View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.eclipse.aether.util.graph.transformer;
20  
21  import java.util.Collection;
22  import java.util.Collections;
23  import java.util.HashMap;
24  import java.util.HashSet;
25  import java.util.IdentityHashMap;
26  import java.util.Map;
27  import java.util.Set;
28  
29  import org.eclipse.aether.RepositoryException;
30  import org.eclipse.aether.artifact.Artifact;
31  import org.eclipse.aether.collection.DependencyGraphTransformationContext;
32  import org.eclipse.aether.collection.DependencyGraphTransformer;
33  import org.eclipse.aether.graph.Dependency;
34  import org.eclipse.aether.graph.DependencyNode;
35  
36  import static java.util.Objects.requireNonNull;
37  
38  /**
39   * A dependency graph transformer that identifies conflicting dependencies. When this transformer has executed, the
40   * transformation context holds a {@code Map<DependencyNode, Object>} where dependency nodes that belong to the same
41   * conflict group will have an equal conflict identifier. This map is stored using the key
42   * {@link TransformationContextKeys#CONFLICT_IDS}.
43   */
44  public final class ConflictMarker implements DependencyGraphTransformer {
45  
46      /**
47       * After the execution of this method, every DependencyNode with an attached dependency is member of one conflict
48       * group.
49       *
50       * @see DependencyGraphTransformer#transformGraph(DependencyNode, DependencyGraphTransformationContext)
51       */
52      public DependencyNode transformGraph(DependencyNode node, DependencyGraphTransformationContext context)
53              throws RepositoryException {
54          requireNonNull(node, "node cannot be null");
55          requireNonNull(context, "context cannot be null");
56          @SuppressWarnings("unchecked")
57          Map<String, Object> stats = (Map<String, Object>) context.get(TransformationContextKeys.STATS);
58          long time1 = System.nanoTime();
59  
60          Map<DependencyNode, Object> nodes = new IdentityHashMap<>(1024);
61          Map<Object, ConflictGroup> groups = new HashMap<>(1024);
62  
63          analyze(node, nodes, groups, new int[] {0});
64  
65          long time2 = System.nanoTime();
66  
67          Map<DependencyNode, Object> conflictIds = mark(nodes.keySet(), groups);
68  
69          context.put(TransformationContextKeys.CONFLICT_IDS, conflictIds);
70  
71          if (stats != null) {
72              long time3 = System.nanoTime();
73              stats.put("ConflictMarker.analyzeTime", time2 - time1);
74              stats.put("ConflictMarker.markTime", time3 - time2);
75              stats.put("ConflictMarker.nodeCount", nodes.size());
76          }
77  
78          return node;
79      }
80  
81      private void analyze(
82              DependencyNode node, Map<DependencyNode, Object> nodes, Map<Object, ConflictGroup> groups, int[] counter) {
83          if (nodes.put(node, Boolean.TRUE) != null) {
84              return;
85          }
86  
87          Set<Object> keys = getKeys(node);
88          if (!keys.isEmpty()) {
89              ConflictGroup group = null;
90              boolean fixMappings = false;
91  
92              for (Object key : keys) {
93                  ConflictGroup g = groups.get(key);
94  
95                  if (group != g) {
96                      if (group == null) {
97                          Set<Object> newKeys = merge(g.keys, keys);
98                          if (newKeys == g.keys) {
99                              group = g;
100                             break;
101                         } else {
102                             group = new ConflictGroup(newKeys, counter[0]++);
103                             fixMappings = true;
104                         }
105                     } else if (g == null) {
106                         fixMappings = true;
107                     } else {
108                         Set<Object> newKeys = merge(g.keys, group.keys);
109                         if (newKeys == g.keys) {
110                             group = g;
111                             fixMappings = false;
112                             break;
113                         } else if (newKeys != group.keys) {
114                             group = new ConflictGroup(newKeys, counter[0]++);
115                             fixMappings = true;
116                         }
117                     }
118                 }
119             }
120 
121             if (group == null) {
122                 group = new ConflictGroup(keys, counter[0]++);
123                 fixMappings = true;
124             }
125             if (fixMappings) {
126                 for (Object key : group.keys) {
127                     groups.put(key, group);
128                 }
129             }
130         }
131 
132         for (DependencyNode child : node.getChildren()) {
133             analyze(child, nodes, groups, counter);
134         }
135     }
136 
137     private Set<Object> merge(Set<Object> keys1, Set<Object> keys2) {
138         int size1 = keys1.size();
139         int size2 = keys2.size();
140 
141         if (size1 < size2) {
142             if (keys2.containsAll(keys1)) {
143                 return keys2;
144             }
145         } else {
146             if (keys1.containsAll(keys2)) {
147                 return keys1;
148             }
149         }
150 
151         Set<Object> keys = new HashSet<>();
152         keys.addAll(keys1);
153         keys.addAll(keys2);
154         return keys;
155     }
156 
157     private Set<Object> getKeys(DependencyNode node) {
158         Set<Object> keys;
159 
160         Dependency dependency = node.getDependency();
161 
162         if (dependency == null) {
163             keys = Collections.emptySet();
164         } else {
165             Object key = toKey(dependency.getArtifact());
166 
167             if (node.getRelocations().isEmpty() && node.getAliases().isEmpty()) {
168                 keys = Collections.singleton(key);
169             } else {
170                 keys = new HashSet<>();
171                 keys.add(key);
172 
173                 for (Artifact relocation : node.getRelocations()) {
174                     key = toKey(relocation);
175                     keys.add(key);
176                 }
177 
178                 for (Artifact alias : node.getAliases()) {
179                     key = toKey(alias);
180                     keys.add(key);
181                 }
182             }
183         }
184 
185         return keys;
186     }
187 
188     private Map<DependencyNode, Object> mark(Collection<DependencyNode> nodes, Map<Object, ConflictGroup> groups) {
189         Map<DependencyNode, Object> conflictIds = new IdentityHashMap<>(nodes.size() + 1);
190 
191         for (DependencyNode node : nodes) {
192             Dependency dependency = node.getDependency();
193             if (dependency != null) {
194                 Object key = toKey(dependency.getArtifact());
195                 conflictIds.put(node, groups.get(key).index);
196             }
197         }
198 
199         return conflictIds;
200     }
201 
202     private static Object toKey(Artifact artifact) {
203         return new Key(artifact);
204     }
205 
206     static class ConflictGroup {
207 
208         final Set<Object> keys;
209 
210         final int index;
211 
212         ConflictGroup(Set<Object> keys, int index) {
213             this.keys = keys;
214             this.index = index;
215         }
216 
217         @Override
218         public String toString() {
219             return String.valueOf(keys);
220         }
221     }
222 
223     static class Key {
224 
225         private final Artifact artifact;
226 
227         Key(Artifact artifact) {
228             this.artifact = artifact;
229         }
230 
231         @Override
232         public boolean equals(Object obj) {
233             if (obj == this) {
234                 return true;
235             } else if (!(obj instanceof Key)) {
236                 return false;
237             }
238             Key that = (Key) obj;
239             return artifact.getArtifactId().equals(that.artifact.getArtifactId())
240                     && artifact.getGroupId().equals(that.artifact.getGroupId())
241                     && artifact.getExtension().equals(that.artifact.getExtension())
242                     && artifact.getClassifier().equals(that.artifact.getClassifier());
243         }
244 
245         @Override
246         public int hashCode() {
247             int hash = 17;
248             hash = hash * 31 + artifact.getArtifactId().hashCode();
249             hash = hash * 31 + artifact.getGroupId().hashCode();
250             hash = hash * 31 + artifact.getClassifier().hashCode();
251             hash = hash * 31 + artifact.getExtension().hashCode();
252             return hash;
253         }
254 
255         @Override
256         public String toString() {
257             return artifact.getGroupId()
258                     + ':'
259                     + artifact.getArtifactId()
260                     + ':'
261                     + artifact.getClassifier()
262                     + ':'
263                     + artifact.getExtension();
264         }
265     }
266 }