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