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