1   
2   
3   
4   
5   
6   
7   
8   
9   
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  package org.eclipse.aether.internal.impl.collect.bf;
20  
21  import java.io.Closeable;
22  import java.util.HashMap;
23  import java.util.LinkedHashMap;
24  import java.util.List;
25  import java.util.Map;
26  import java.util.concurrent.atomic.AtomicInteger;
27  import java.util.function.Function;
28  
29  import org.eclipse.aether.artifact.Artifact;
30  import org.eclipse.aether.graph.DependencyNode;
31  import org.eclipse.aether.util.artifact.ArtifactIdUtils;
32  import org.slf4j.Logger;
33  import org.slf4j.LoggerFactory;
34  
35  import static java.util.Objects.requireNonNull;
36  
37  
38  
39  
40  
41  
42  
43  abstract class DependencyResolutionSkipper implements Closeable {
44      
45  
46  
47  
48  
49  
50  
51  
52      abstract boolean skipResolution(DependencyNode node, List<DependencyNode> parents);
53  
54      
55  
56  
57  
58  
59  
60      abstract void cache(DependencyNode node, List<DependencyNode> parents);
61  
62      
63  
64  
65      @Override
66      public abstract void close();
67  
68      
69  
70  
71  
72  
73      public static DefaultDependencyResolutionSkipper defaultGACESkipper() {
74          return new DefaultDependencyResolutionSkipper(ArtifactIdUtils::toVersionlessId);
75      }
76  
77      
78  
79  
80  
81  
82      public static DefaultDependencyResolutionSkipper defaultGACEVSkipper() {
83          return new DefaultDependencyResolutionSkipper(ArtifactIdUtils::toId);
84      }
85  
86      
87  
88  
89      public static DependencyResolutionSkipper neverSkipper() {
90          return NeverDependencyResolutionSkipper.INSTANCE;
91      }
92  
93      
94  
95  
96      private static final class NeverDependencyResolutionSkipper extends DependencyResolutionSkipper {
97          private static final DependencyResolutionSkipper INSTANCE = new NeverDependencyResolutionSkipper();
98  
99          @Override
100         public boolean skipResolution(DependencyNode node, List<DependencyNode> parents) {
101             return false;
102         }
103 
104         @Override
105         public void cache(DependencyNode node, List<DependencyNode> parents) {}
106 
107         @Override
108         public void close() {}
109     }
110 
111     
112 
113 
114     static final class DefaultDependencyResolutionSkipper extends DependencyResolutionSkipper {
115         private static final Logger LOGGER = LoggerFactory.getLogger(DependencyResolutionSkipper.class);
116 
117         private final Map<DependencyNode, DependencyResolutionResult> results;
118         private final CacheManager cacheManager;
119         private final CoordinateManager coordinateManager;
120 
121         private DefaultDependencyResolutionSkipper(Function<Artifact, String> keyFunction) {
122             this.results = new LinkedHashMap<>(256);
123             this.cacheManager = new CacheManager(keyFunction);
124             this.coordinateManager = new CoordinateManager();
125         }
126 
127         @Override
128         public boolean skipResolution(DependencyNode node, List<DependencyNode> parents) {
129             DependencyResolutionResult result = new DependencyResolutionResult(node);
130             results.put(node, result);
131 
132             int depth = parents.size() + 1;
133             coordinateManager.createCoordinate(node, depth);
134 
135             if (cacheManager.isVersionConflict(node)) {
136                 
137 
138 
139                 result.skippedAsVersionConflict = true;
140                 if (LOGGER.isTraceEnabled()) {
141                     LOGGER.trace(
142                             "Skipped resolving node: {} as version conflict", ArtifactIdUtils.toId(node.getArtifact()));
143                 }
144             } else if (cacheManager.isDuplicate(node)) {
145                 if (coordinateManager.isLeftmost(node, parents)) {
146                     
147 
148 
149 
150 
151                     result.forceResolution = true;
152                     if (LOGGER.isTraceEnabled()) {
153                         LOGGER.trace(
154                                 "Force resolving node: {} for scope selection",
155                                 ArtifactIdUtils.toId(node.getArtifact()));
156                     }
157                 } else {
158                     
159 
160 
161 
162                     result.skippedAsDuplicate = true;
163                     if (LOGGER.isTraceEnabled()) {
164                         LOGGER.trace(
165                                 "Skipped resolving node: {} as duplicate", ArtifactIdUtils.toId(node.getArtifact()));
166                     }
167                 }
168             } else {
169                 result.resolve = true;
170                 if (LOGGER.isTraceEnabled()) {
171                     LOGGER.trace("Resolving node: {}", ArtifactIdUtils.toId(node.getArtifact()));
172                 }
173             }
174 
175             if (result.toResolve()) {
176                 coordinateManager.updateLeftmost(node);
177                 return false;
178             }
179 
180             return true;
181         }
182 
183         @Override
184         public void cache(DependencyNode node, List<DependencyNode> parents) {
185             boolean parentForceResolution =
186                     parents.stream().anyMatch(n -> results.containsKey(n) && results.get(n).forceResolution);
187             if (parentForceResolution) {
188                 if (LOGGER.isTraceEnabled()) {
189                     LOGGER.trace(
190                             "Won't cache as node: {} inherits from a force-resolved node "
191                                     + "and will be omitted for duplicate",
192                             ArtifactIdUtils.toId(node.getArtifact()));
193                 }
194             } else {
195                 cacheManager.cacheWinner(node);
196             }
197         }
198 
199         @Override
200         public void close() {
201             if (LOGGER.isTraceEnabled()) {
202                 LOGGER.trace(
203                         "Skipped {} nodes as duplicate",
204                         results.entrySet().stream()
205                                 .filter(n -> n.getValue().skippedAsDuplicate)
206                                 .count());
207                 LOGGER.trace(
208                         "Skipped {} nodes as having version conflict",
209                         results.entrySet().stream()
210                                 .filter(n -> n.getValue().skippedAsVersionConflict)
211                                 .count());
212                 LOGGER.trace(
213                         "Resolved {} nodes",
214                         results.entrySet().stream()
215                                 .filter(n -> n.getValue().resolve)
216                                 .count());
217                 LOGGER.trace(
218                         "Forced resolving {} nodes for scope selection",
219                         results.entrySet().stream()
220                                 .filter(n -> n.getValue().forceResolution)
221                                 .count());
222             }
223         }
224 
225         public Map<DependencyNode, DependencyResolutionResult> getResults() {
226             return results;
227         }
228 
229         private static final class CacheManager {
230 
231             
232 
233 
234             private final Map<Artifact, DependencyNode> winners;
235 
236             
237 
238 
239             private final Map<String, Artifact> winnerGAs;
240 
241             
242 
243 
244             private final Function<Artifact, String> keyFunction;
245 
246             private CacheManager(Function<Artifact, String> keyFunction) {
247                 this.winners = new HashMap<>(256);
248                 this.winnerGAs = new HashMap<>(256);
249                 this.keyFunction = requireNonNull(keyFunction);
250             }
251 
252             boolean isVersionConflict(DependencyNode node) {
253                 String ga = keyFunction.apply(node.getArtifact());
254                 if (winnerGAs.containsKey(ga)) {
255                     Artifact result = winnerGAs.get(ga);
256                     return !node.getArtifact().getVersion().equals(result.getVersion());
257                 }
258 
259                 return false;
260             }
261 
262             void cacheWinner(DependencyNode node) {
263                 winners.put(node.getArtifact(), node);
264                 winnerGAs.put(keyFunction.apply(node.getArtifact()), node.getArtifact());
265             }
266 
267             boolean isDuplicate(DependencyNode node) {
268                 return winners.containsKey(node.getArtifact());
269             }
270         }
271 
272         private static final class CoordinateManager {
273             private final Map<Integer, AtomicInteger> sequenceGen = new HashMap<>(256);
274 
275             
276 
277 
278             private final Map<DependencyNode, Coordinate> coordinateMap = new HashMap<>(256);
279 
280             
281 
282 
283             private final Map<Artifact, Coordinate> leftmostCoordinates = new HashMap<>(256);
284 
285             Coordinate getCoordinate(DependencyNode node) {
286                 return coordinateMap.get(node);
287             }
288 
289             Coordinate createCoordinate(DependencyNode node, int depth) {
290                 int seq = sequenceGen
291                         .computeIfAbsent(depth, k -> new AtomicInteger())
292                         .incrementAndGet();
293                 Coordinate coordinate = new Coordinate(depth, seq);
294                 coordinateMap.put(node, coordinate);
295                 return coordinate;
296             }
297 
298             void updateLeftmost(DependencyNode current) {
299                 leftmostCoordinates.put(current.getArtifact(), getCoordinate(current));
300             }
301 
302             boolean isLeftmost(DependencyNode node, List<DependencyNode> parents) {
303                 Coordinate leftmost = leftmostCoordinates.get(node.getArtifact());
304                 if (leftmost != null && leftmost.depth <= parents.size()) {
305                     DependencyNode sameLevelNode = parents.get(leftmost.depth - 1);
306                     return getCoordinate(sameLevelNode).sequence < leftmost.sequence;
307                 }
308 
309                 return false;
310             }
311         }
312 
313         private static final class Coordinate {
314             int depth;
315             int sequence;
316 
317             Coordinate(int depth, int sequence) {
318                 this.depth = depth;
319                 this.sequence = sequence;
320             }
321 
322             @Override
323             public String toString() {
324                 return "{" + "depth=" + depth + ", sequence=" + sequence + '}';
325             }
326         }
327     }
328 
329     
330 
331 
332     static final class DependencyResolutionResult {
333         DependencyNode current;
334         boolean skippedAsVersionConflict; 
335         boolean skippedAsDuplicate; 
336         boolean resolve; 
337         boolean forceResolution; 
338 
339         DependencyResolutionResult(DependencyNode current) {
340             this.current = current;
341         }
342 
343         boolean toResolve() {
344             return resolve || forceResolution;
345         }
346     }
347 }