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.Objects;
28  import java.util.Set;
29  
30  import org.eclipse.aether.RepositoryException;
31  import org.eclipse.aether.artifact.Artifact;
32  import org.eclipse.aether.collection.DependencyGraphTransformationContext;
33  import org.eclipse.aether.collection.DependencyGraphTransformer;
34  import org.eclipse.aether.graph.Dependency;
35  import org.eclipse.aether.graph.DependencyNode;
36  
37  import static java.util.Objects.requireNonNull;
38  
39  /**
40   * A dependency graph transformer that identifies conflicting dependencies. When this transformer has executed, the
41   * transformation context holds a {@code Map<DependencyNode, String>} where dependency nodes that belong to the same
42   * conflict group will have an equal conflict identifier. This map is stored using the key
43   * {@link TransformationContextKeys#CONFLICT_IDS}.
44   */
45  public final class ConflictMarker implements DependencyGraphTransformer {
46  
47      /**
48       * After the execution of this method, every DependencyNode with an attached dependency is member of one conflict
49       * group.
50       *
51       * @see DependencyGraphTransformer#transformGraph(DependencyNode, DependencyGraphTransformationContext)
52       */
53      @Override
54      public DependencyNode transformGraph(DependencyNode node, DependencyGraphTransformationContext context)
55              throws RepositoryException {
56          requireNonNull(node, "node cannot be null");
57          requireNonNull(context, "context cannot be null");
58          @SuppressWarnings("unchecked")
59          Map<String, Object> stats = (Map<String, Object>) context.get(TransformationContextKeys.STATS);
60          long time1 = System.nanoTime();
61  
62          Map<DependencyNode, Boolean> nodes = new IdentityHashMap<>(1024);
63          Map<Key, ConflictGroup> groups = new HashMap<>(1024);
64  
65          analyze(node, nodes, groups, new int[] {0});
66  
67          long time2 = System.nanoTime();
68  
69          Map<DependencyNode, String> conflictIds = mark(nodes.keySet(), groups);
70  
71          context.put(TransformationContextKeys.CONFLICT_IDS, conflictIds);
72  
73          if (stats != null) {
74              long time3 = System.nanoTime();
75              stats.put("ConflictMarker.analyzeTime", time2 - time1);
76              stats.put("ConflictMarker.markTime", time3 - time2);
77              stats.put("ConflictMarker.nodeCount", nodes.size());
78          }
79  
80          return node;
81      }
82  
83      private void analyze(
84              DependencyNode node, Map<DependencyNode, Boolean> nodes, Map<Key, ConflictGroup> groups, int[] counter) {
85          if (nodes.put(node, Boolean.TRUE) != null) {
86              return;
87          }
88  
89          Set<Key> keys = getKeys(node);
90          if (!keys.isEmpty()) {
91              ConflictGroup group = null;
92              boolean fixMappings = false;
93  
94              for (Key key : keys) {
95                  ConflictGroup g = groups.get(key);
96  
97                  if (group != g) {
98                      if (group == null) {
99                          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 }