in maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/transformer/ConflictResolver.java [299:387]
private static void removeLosers(State state) {
ConflictItem winner = state.conflictCtx.winner;
String winnerArtifactId = ArtifactIdUtils.toId(winner.node.getArtifact());
List<DependencyNode> previousParent = null;
ListIterator<DependencyNode> childIt = null;
HashSet<String> toRemoveIds = new HashSet<>();
for (ConflictItem item : state.items) {
if (item == winner) {
continue;
}
if (item.parent != previousParent) {
childIt = item.parent.listIterator();
previousParent = item.parent;
}
while (childIt.hasNext()) {
DependencyNode child = childIt.next();
if (child == item.node) {
// NONE: just remove it and done
if (Verbosity.NONE == state.verbosity) {
childIt.remove();
break;
}
// STANDARD: doing extra bookkeeping to select "which nodes to remove"
if (Verbosity.STANDARD == state.verbosity) {
String childArtifactId = ArtifactIdUtils.toId(child.getArtifact());
// if two IDs are equal, it means "there is nearest", not conflict per se.
// In that case we do NOT allow this child to be removed (but remove others)
// and this keeps us safe from iteration (and in general, version) ordering
// as we explicitly leave out ID that is "nearest found" state.
//
// This tackles version ranges mostly, where ranges are turned into list of
// several nodes in collector (as many were discovered, ie. from metadata), and
// old code would just "mark" the first hit as conflict, and remove the rest,
// even if rest could contain "more suitable" version, that is not conflicting/diverging.
// This resulted in verbose mode transformed tree, that was misrepresenting things
// for dependency convergence calculations: it represented state like parent node
// depends on "wrong" version (diverge), while "right" version was present (but removed)
// as well, as it was contained in parents version range.
if (!Objects.equals(winnerArtifactId, childArtifactId)) {
toRemoveIds.add(childArtifactId);
}
}
// FULL: just record the facts
DependencyNode loser = new DefaultDependencyNode(child);
loser.setData(NODE_DATA_WINNER, winner.node);
loser.setData(
NODE_DATA_ORIGINAL_SCOPE, loser.getDependency().getScope());
loser.setData(
NODE_DATA_ORIGINAL_OPTIONALITY,
loser.getDependency().isOptional());
loser.setScope(item.getScopes().iterator().next());
loser.setChildren(Collections.emptyList());
childIt.set(loser);
item.node = loser;
break;
}
}
}
// 2nd pass to apply "standard" verbosity: leaving only 1 loser, but with care
if (Verbosity.STANDARD == state.verbosity && !toRemoveIds.isEmpty()) {
previousParent = null;
for (ConflictItem item : state.items) {
if (item == winner) {
continue;
}
if (item.parent != previousParent) {
childIt = item.parent.listIterator();
previousParent = item.parent;
}
while (childIt.hasNext()) {
DependencyNode child = childIt.next();
if (child == item.node) {
String childArtifactId = ArtifactIdUtils.toId(child.getArtifact());
if (toRemoveIds.contains(childArtifactId)
&& relatedSiblingsCount(child.getArtifact(), item.parent) > 1) {
childIt.remove();
}
break;
}
}
}
}
// there might still be losers beneath the winner (e.g. in case of cycles)
// those will be nuked during future graph walks when we include the winner in the recursion
}