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 }