1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.eclipse.aether.util.graph.transformer;
20
21 import java.util.ArrayList;
22 import java.util.Collection;
23 import java.util.Collections;
24 import java.util.HashMap;
25 import java.util.HashSet;
26 import java.util.List;
27 import java.util.Map;
28 import java.util.Objects;
29 import java.util.stream.Collectors;
30
31 import org.eclipse.aether.RepositoryException;
32 import org.eclipse.aether.artifact.Artifact;
33 import org.eclipse.aether.collection.DependencyGraphTransformationContext;
34 import org.eclipse.aether.graph.DefaultDependencyNode;
35 import org.eclipse.aether.graph.Dependency;
36 import org.eclipse.aether.graph.DependencyNode;
37 import org.eclipse.aether.util.artifact.ArtifactIdUtils;
38
39 import static java.util.Objects.requireNonNull;
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92 public final class PathConflictResolver extends ConflictResolver {
93 private final ConflictResolver.VersionSelector versionSelector;
94 private final ConflictResolver.ScopeSelector scopeSelector;
95 private final ConflictResolver.ScopeDeriver scopeDeriver;
96 private final ConflictResolver.OptionalitySelector optionalitySelector;
97
98
99
100
101
102
103
104
105
106 public PathConflictResolver(
107 ConflictResolver.VersionSelector versionSelector,
108 ConflictResolver.ScopeSelector scopeSelector,
109 ConflictResolver.OptionalitySelector optionalitySelector,
110 ConflictResolver.ScopeDeriver scopeDeriver) {
111 this.versionSelector = requireNonNull(versionSelector, "version selector cannot be null");
112 this.scopeSelector = requireNonNull(scopeSelector, "scope selector cannot be null");
113 this.optionalitySelector = requireNonNull(optionalitySelector, "optionality selector cannot be null");
114 this.scopeDeriver = requireNonNull(scopeDeriver, "scope deriver cannot be null");
115 }
116
117 @SuppressWarnings("unchecked")
118 @Override
119 public DependencyNode transformGraph(DependencyNode node, DependencyGraphTransformationContext context)
120 throws RepositoryException {
121 requireNonNull(node, "node cannot be null");
122 requireNonNull(context, "context cannot be null");
123 List<String> sortedConflictIds = (List<String>) context.get(TransformationContextKeys.SORTED_CONFLICT_IDS);
124 if (sortedConflictIds == null) {
125 ConflictIdSorter sorter = new ConflictIdSorter();
126 sorter.transformGraph(node, context);
127
128 sortedConflictIds = (List<String>) context.get(TransformationContextKeys.SORTED_CONFLICT_IDS);
129 }
130
131 @SuppressWarnings("unchecked")
132 Map<String, Object> stats = (Map<String, Object>) context.get(TransformationContextKeys.STATS);
133 long time1 = System.nanoTime();
134
135 @SuppressWarnings("unchecked")
136 Collection<Collection<String>> conflictIdCycles =
137 (Collection<Collection<String>>) context.get(TransformationContextKeys.CYCLIC_CONFLICT_IDS);
138 if (conflictIdCycles == null) {
139 throw new RepositoryException("conflict id cycles have not been identified");
140 }
141
142 Map<DependencyNode, String> conflictIds =
143 (Map<DependencyNode, String>) context.get(TransformationContextKeys.CONFLICT_IDS);
144 if (conflictIds == null) {
145 throw new RepositoryException("conflict groups have not been identified");
146 }
147
148 Map<String, Collection<String>> cyclicPredecessors = new HashMap<>();
149 for (Collection<String> cycle : conflictIdCycles) {
150 for (String conflictId : cycle) {
151 Collection<String> predecessors = cyclicPredecessors.computeIfAbsent(conflictId, k -> new HashSet<>());
152 predecessors.addAll(cycle);
153 }
154 }
155
156 State state = new State(
157 ConflictResolver.getVerbosity(context.getSession()),
158 versionSelector.getInstance(node, context),
159 scopeSelector.getInstance(node, context),
160 scopeDeriver.getInstance(node, context),
161 optionalitySelector.getInstance(node, context),
162 sortedConflictIds,
163 conflictIds,
164 cyclicPredecessors);
165
166 state.build(node);
167
168
169 for (String conflictId : state.sortedConflictIds) {
170
171 List<Path> paths = state.partitions.get(conflictId);
172 if (paths.isEmpty()) {
173
174 continue;
175 }
176
177
178 ConflictContext ctx = new ConflictContext(
179 node,
180 state.conflictIds,
181 paths.stream().map(ConflictItem::new).collect(Collectors.toList()),
182 conflictId);
183
184
185 state.versionSelector.selectVersion(ctx);
186 if (ctx.winner == null) {
187 throw new RepositoryException("conflict resolver did not select winner among " + ctx.items);
188 }
189
190 state.scopeSelector.selectScope(ctx);
191
192 state.optionalitySelector.selectOptionality(ctx);
193
194
195 Path winnerPath = ctx.winner.path;
196
197
198 if (state.resolvedIds.containsKey(conflictId)) {
199 throw new RepositoryException("conflict resolver already have winner for conflictId=" + conflictId
200 + ": " + state.resolvedIds);
201 }
202 state.resolvedIds.put(conflictId, winnerPath);
203
204
205 for (Path path : new ArrayList<>(paths)) {
206
207 path.scope = ctx.scope;
208 path.optional = ctx.optional;
209
210
211 path.children.forEach(c -> c.pull(0));
212
213 path.derive(1, path == winnerPath);
214
215 path.push(0);
216 }
217 }
218
219 if (stats != null) {
220 long time2 = System.nanoTime();
221 stats.put("ConflictResolver.totalTime", time2 - time1);
222 stats.put(
223 "ConflictResolver.conflictItemCount",
224 state.partitions.values().stream().map(List::size).reduce(0, Integer::sum));
225 }
226
227 return node;
228 }
229
230
231
232
233 private static class State {
234
235
236
237 private final ConflictResolver.Verbosity verbosity;
238
239
240
241
242 private final ConflictResolver.VersionSelector versionSelector;
243
244
245
246
247 private final ConflictResolver.ScopeSelector scopeSelector;
248
249
250
251
252 private final ConflictResolver.ScopeDeriver scopeDeriver;
253
254
255
256
257 private final ConflictResolver.OptionalitySelector optionalitySelector;
258
259
260
261
262 private final List<String> sortedConflictIds;
263
264
265
266
267 private final Map<DependencyNode, String> conflictIds;
268
269
270
271
272
273
274 private final Map<String, Collection<String>> cyclicPredecessors;
275
276
277
278
279
280 private final Map<String, List<Path>> partitions;
281
282
283
284
285 private final Map<String, Path> resolvedIds;
286
287 @SuppressWarnings("checkstyle:ParameterNumber")
288 private State(
289 ConflictResolver.Verbosity verbosity,
290 ConflictResolver.VersionSelector versionSelector,
291 ConflictResolver.ScopeSelector scopeSelector,
292 ConflictResolver.ScopeDeriver scopeDeriver,
293 ConflictResolver.OptionalitySelector optionalitySelector,
294 List<String> sortedConflictIds,
295 Map<DependencyNode, String> conflictIds,
296 Map<String, Collection<String>> cyclicPredecessors) {
297 this.verbosity = verbosity;
298 this.versionSelector = versionSelector;
299 this.scopeSelector = scopeSelector;
300 this.scopeDeriver = scopeDeriver;
301 this.optionalitySelector = optionalitySelector;
302 this.sortedConflictIds = sortedConflictIds;
303 this.conflictIds = conflictIds;
304 this.cyclicPredecessors = cyclicPredecessors;
305 this.partitions = new HashMap<>();
306 this.resolvedIds = new HashMap<>();
307 }
308
309
310
311
312
313 private Path build(DependencyNode node) throws RepositoryException {
314 Path root = new Path(this, node, null);
315 gatherCRNodes(root);
316 return root;
317 }
318
319
320
321
322 private void gatherCRNodes(Path node) throws RepositoryException {
323 List<DependencyNode> children = node.dn.getChildren();
324 if (!children.isEmpty()) {
325
326 List<Path> added = node.addChildren(children);
327 for (Path child : added) {
328 gatherCRNodes(child);
329 }
330 }
331 }
332 }
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362 private static class Path {
363 private final State state;
364 private DependencyNode dn;
365 private final String conflictId;
366 private final Path parent;
367 private final int depth;
368 private final List<Path> children;
369 private String scope;
370 private boolean optional;
371
372 private Path(State state, DependencyNode dn, Path parent) {
373 this.state = state;
374 this.dn = dn;
375 this.conflictId = state.conflictIds.get(dn);
376 this.parent = parent;
377 this.depth = parent != null ? parent.depth + 1 : 0;
378 this.children = new ArrayList<>();
379 pull(0);
380
381 this.state
382 .partitions
383 .computeIfAbsent(this.conflictId, k -> new ArrayList<>())
384 .add(this);
385 }
386
387
388
389
390
391 private void pull(int levels) {
392 Dependency d = dn.getDependency();
393 if (d != null) {
394 this.scope = d.getScope();
395 this.optional = d.isOptional();
396 } else {
397 this.scope = "";
398 this.optional = false;
399 }
400 int newLevels = levels - 1;
401 if (newLevels >= 0) {
402 for (Path child : this.children) {
403 child.pull(newLevels);
404 }
405 }
406 }
407
408
409
410
411
412 private void derive(int levels, boolean winner) throws RepositoryException {
413 if (!winner) {
414 if (this.parent != null) {
415 if ((dn.getManagedBits() & DependencyNode.MANAGED_SCOPE) == 0) {
416 ScopeContext context = new ScopeContext(this.parent.scope, this.scope);
417 state.scopeDeriver.deriveScope(context);
418 this.scope = context.derivedScope;
419 }
420 if ((dn.getManagedBits() & DependencyNode.MANAGED_OPTIONAL) == 0) {
421 if (!this.optional && this.parent.optional) {
422 this.optional = true;
423 }
424 }
425 } else {
426 this.scope = "";
427 this.optional = false;
428 }
429 }
430 int newLevels = levels - 1;
431 if (newLevels >= 0) {
432 for (Path child : children) {
433 child.derive(newLevels, false);
434 }
435 }
436 }
437
438
439
440
441
442
443
444 private void push(int levels) {
445 if (this.parent != null) {
446 Path winner = this.state.resolvedIds.get(this.conflictId);
447 if (winner == null) {
448 throw new IllegalStateException(
449 "Winner selection did not happen for conflictId=" + this.conflictId);
450 }
451 if (!Objects.equals(winner.conflictId, this.conflictId)) {
452 throw new IllegalStateException(
453 "ConflictId mix-up: this=" + this.conflictId + " winner=" + winner.conflictId);
454 }
455
456 if (winner == this) {
457
458 if (this.dn.getDependency() != null) {
459 this.dn.setData(
460 ConflictResolver.NODE_DATA_ORIGINAL_SCOPE,
461 this.dn.getDependency().getScope());
462 this.dn.setData(
463 ConflictResolver.NODE_DATA_ORIGINAL_OPTIONALITY,
464 this.dn.getDependency().getOptional());
465 this.dn.setScope(this.scope);
466 this.dn.setOptional(this.optional);
467 }
468 } else {
469
470 moveOutOfScope();
471 boolean markLoser = false;
472 switch (state.verbosity) {
473 case NONE:
474
475 this.parent.children.remove(this);
476 this.parent.dn.setChildren(new ArrayList<>(this.parent.dn.getChildren()));
477 this.parent.dn.getChildren().remove(this.dn);
478 this.children.clear();
479 break;
480 case STANDARD:
481
482 String artifactId = ArtifactIdUtils.toId(this.dn.getArtifact());
483 String winnerArtifactId = ArtifactIdUtils.toId(winner.dn.getArtifact());
484 if (!Objects.equals(artifactId, winnerArtifactId)
485 && relatedSiblingsCount(this.dn.getArtifact(), this.parent) > 1) {
486 this.parent.children.remove(this);
487 this.parent.dn.setChildren(new ArrayList<>(this.parent.dn.getChildren()));
488 this.parent.dn.getChildren().remove(this.dn);
489 this.children.clear();
490 } else {
491 this.children.clear();
492 this.dn.setChildren(Collections.emptyList());
493 markLoser = true;
494 }
495 break;
496 case FULL:
497
498 markLoser = true;
499 break;
500 default:
501 throw new IllegalArgumentException("Unknown " + state.verbosity);
502 }
503 if (markLoser) {
504
505 DependencyNode dnCopy = new DefaultDependencyNode(this.dn);
506 dnCopy.setData(ConflictResolver.NODE_DATA_WINNER, winner.dn);
507 dnCopy.setData(
508 ConflictResolver.NODE_DATA_ORIGINAL_SCOPE,
509 this.dn.getDependency().getScope());
510 dnCopy.setData(
511 ConflictResolver.NODE_DATA_ORIGINAL_OPTIONALITY,
512 this.dn.getDependency().getOptional());
513 dnCopy.setScope(this.scope);
514 dnCopy.setOptional(this.optional);
515 if (ConflictResolver.Verbosity.FULL != state.verbosity) {
516 dnCopy.getChildren().clear();
517 }
518
519
520 this.parent
521 .dn
522 .getChildren()
523 .set(this.parent.dn.getChildren().indexOf(this.dn), dnCopy);
524 this.dn = dnCopy;
525 }
526 }
527 }
528 if (!this.children.isEmpty()) {
529 int newLevels = levels - 1;
530 if (newLevels >= 0) {
531
532 for (Path child : new ArrayList<>(children)) {
533 child.push(newLevels);
534 }
535 }
536 } else if (!this.dn.getChildren().isEmpty()) {
537 this.dn.setChildren(Collections.emptyList());
538 }
539 }
540
541
542
543
544
545
546 private int relatedSiblingsCount(Artifact artifact, Path parent) {
547 String ga = artifact.getGroupId() + ":" + artifact.getArtifactId();
548 return Math.toIntExact(parent.children.stream()
549 .map(n -> n.dn.getArtifact())
550 .filter(a -> ga.equals(a.getGroupId() + ":" + a.getArtifactId()))
551 .count());
552 }
553
554
555
556
557
558 private void moveOutOfScope() {
559 this.state.partitions.get(this.conflictId).remove(this);
560 for (Path child : this.children) {
561 child.moveOutOfScope();
562 }
563 }
564
565
566
567
568
569 private List<Path> addChildren(List<DependencyNode> children) throws RepositoryException {
570 Collection<String> thisCyclicPredecessors =
571 this.state.cyclicPredecessors.getOrDefault(this.conflictId, Collections.emptySet());
572
573 ArrayList<Path> added = new ArrayList<>(children.size());
574 for (DependencyNode child : children) {
575 String childConflictId = this.state.conflictIds.get(child);
576 if (!this.state.partitions.containsKey(childConflictId)
577 || !thisCyclicPredecessors.contains(childConflictId)) {
578 Path c = new Path(this.state, child, this);
579 this.children.add(c);
580 c.derive(0, false);
581 added.add(c);
582 }
583 }
584 return added;
585 }
586
587
588
589
590 private void dump(String padding) {
591 System.out.println(padding + this.dn + ": " + this.scope + "/" + this.optional);
592 for (Path child : children) {
593 child.dump(padding + " ");
594 }
595 }
596
597
598
599
600 @Override
601 public String toString() {
602 return this.dn.toString();
603 }
604 }
605
606
607
608
609
610
611
612
613 private static final class ScopeContext extends ConflictResolver.ScopeContext {
614 private final String parentScope;
615 private final String childScope;
616 private String derivedScope;
617
618
619
620
621
622
623
624
625
626 private ScopeContext(String parentScope, String childScope) {
627 this.parentScope = (parentScope != null) ? parentScope : "";
628 this.derivedScope = (childScope != null) ? childScope : "";
629 this.childScope = (childScope != null) ? childScope : "";
630 }
631
632
633
634
635
636
637
638 public String getParentScope() {
639 return parentScope;
640 }
641
642
643
644
645
646
647
648 public String getChildScope() {
649 return childScope;
650 }
651
652
653
654
655
656
657
658 public String getDerivedScope() {
659 return derivedScope;
660 }
661
662
663
664
665
666
667 public void setDerivedScope(String derivedScope) {
668 this.derivedScope = (derivedScope != null) ? derivedScope : "";
669 }
670 }
671
672
673
674
675
676
677
678 private static final class ConflictItem extends ConflictResolver.ConflictItem {
679 private final Path path;
680 private final List<DependencyNode> parent;
681 private final Artifact artifact;
682 private final DependencyNode node;
683 private final int depth;
684 private final String scope;
685 private final int optionalities;
686
687
688
689
690 public static final int OPTIONAL_FALSE = 0x01;
691
692
693
694
695 public static final int OPTIONAL_TRUE = 0x02;
696
697 private ConflictItem(Path path) {
698 this.path = path;
699 if (path.parent != null) {
700 DependencyNode parent = path.parent.dn;
701 this.parent = parent.getChildren();
702 this.artifact = parent.getArtifact();
703 } else {
704 this.parent = null;
705 this.artifact = null;
706 }
707 this.node = path.dn;
708 this.depth = path.depth;
709 this.scope = path.scope;
710 this.optionalities = path.optional ? OPTIONAL_TRUE : OPTIONAL_FALSE;
711 }
712
713
714
715
716
717
718
719 @Override
720 public boolean isSibling(ConflictResolver.ConflictItem item) {
721 return parent == ((ConflictItem) item).parent;
722 }
723
724
725
726
727
728
729 @Override
730 public DependencyNode getNode() {
731 return node;
732 }
733
734
735
736
737
738
739 @Override
740 public Dependency getDependency() {
741 return node.getDependency();
742 }
743
744
745
746
747
748
749
750
751 @Override
752 public int getDepth() {
753 return depth;
754 }
755
756
757
758
759
760
761
762
763 @Override
764 public Collection<String> getScopes() {
765 return Collections.singleton(scope);
766 }
767
768
769
770
771
772
773
774
775
776 @Override
777 public int getOptionalities() {
778 return optionalities;
779 }
780
781 @Override
782 public String toString() {
783 return node + " @ " + depth + " < " + artifact;
784 }
785 }
786
787
788
789
790
791
792
793
794
795 private static final class ConflictContext extends ConflictResolver.ConflictContext {
796 private final DependencyNode root;
797 private final Map<DependencyNode, String> conflictIds;
798 private final Collection<ConflictResolver.ConflictItem> items;
799 private final String conflictId;
800
801
802 private ConflictItem winner;
803 private String scope;
804 private Boolean optional;
805
806 private ConflictContext(
807 DependencyNode root,
808 Map<DependencyNode, String> conflictIds,
809 Collection<ConflictItem> items,
810 String conflictId) {
811 this.root = root;
812 this.conflictIds = conflictIds;
813 this.items = Collections.unmodifiableCollection(items);
814 this.conflictId = conflictId;
815 }
816
817
818
819
820
821
822 @Override
823 public DependencyNode getRoot() {
824 return root;
825 }
826
827
828
829
830
831
832
833 @Override
834 public boolean isIncluded(DependencyNode node) {
835 return conflictId.equals(conflictIds.get(node));
836 }
837
838
839
840
841
842
843 @Override
844 public Collection<ConflictResolver.ConflictItem> getItems() {
845 return items;
846 }
847
848
849
850
851
852
853 @Override
854 public ConflictResolver.ConflictItem getWinner() {
855 return winner;
856 }
857
858
859
860
861
862
863 @Override
864 public void setWinner(ConflictResolver.ConflictItem winner) {
865 this.winner = (ConflictItem) winner;
866 }
867
868
869
870
871
872
873 @Override
874 public String getScope() {
875 return scope;
876 }
877
878
879
880
881
882
883 @Override
884 public void setScope(String scope) {
885 this.scope = scope;
886 }
887
888
889
890
891
892
893 @Override
894 public Boolean getOptional() {
895 return optional;
896 }
897
898
899
900
901
902
903 @Override
904 public void setOptional(Boolean optional) {
905 this.optional = optional;
906 }
907
908 @Override
909 public String toString() {
910 return winner + " @ " + scope + " < " + items;
911 }
912 }
913 }