001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019package org.eclipse.aether.util.graph.transformer; 020 021import java.util.ArrayList; 022import java.util.Collection; 023import java.util.HashSet; 024import java.util.Iterator; 025 026import org.eclipse.aether.RepositoryException; 027import org.eclipse.aether.collection.UnsolvableVersionConflictException; 028import org.eclipse.aether.graph.DependencyFilter; 029import org.eclipse.aether.graph.DependencyNode; 030import org.eclipse.aether.util.graph.transformer.ConflictResolver.ConflictContext; 031import org.eclipse.aether.util.graph.transformer.ConflictResolver.ConflictItem; 032import org.eclipse.aether.util.graph.transformer.ConflictResolver.VersionSelector; 033import org.eclipse.aether.util.graph.visitor.PathRecordingDependencyVisitor; 034import org.eclipse.aether.util.graph.visitor.TreeDependencyVisitor; 035import org.eclipse.aether.version.Version; 036import org.eclipse.aether.version.VersionConstraint; 037 038import static java.util.Objects.requireNonNull; 039 040/** 041 * A version selector for use with {@link ConflictResolver} that resolves version conflicts using a nearest-wins 042 * strategy. If there is no single node that satisfies all encountered version ranges, the selector will fail. 043 */ 044public final class NearestVersionSelector extends VersionSelector { 045 046 /** 047 * Creates a new instance of this version selector. 048 */ 049 public NearestVersionSelector() {} 050 051 @Override 052 public void selectVersion(ConflictContext context) throws RepositoryException { 053 ConflictGroup group = new ConflictGroup(); 054 for (ConflictItem item : context.getItems()) { 055 DependencyNode node = item.getNode(); 056 VersionConstraint constraint = node.getVersionConstraint(); 057 058 boolean backtrack = false; 059 boolean hardConstraint = constraint.getRange() != null; 060 061 if (hardConstraint) { 062 if (group.constraints.add(constraint)) { 063 if (group.winner != null 064 && !constraint.containsVersion( 065 group.winner.getNode().getVersion())) { 066 backtrack = true; 067 } 068 } 069 } 070 071 if (isAcceptable(group, node.getVersion())) { 072 group.candidates.add(item); 073 074 if (backtrack) { 075 backtrack(group, context); 076 } else if (group.winner == null || isNearer(item, group.winner)) { 077 group.winner = item; 078 } 079 } else if (backtrack) { 080 backtrack(group, context); 081 } 082 } 083 context.setWinner(group.winner); 084 } 085 086 private void backtrack(ConflictGroup group, ConflictContext context) throws UnsolvableVersionConflictException { 087 group.winner = null; 088 089 for (Iterator<ConflictItem> it = group.candidates.iterator(); it.hasNext(); ) { 090 ConflictItem candidate = it.next(); 091 092 if (!isAcceptable(group, candidate.getNode().getVersion())) { 093 it.remove(); 094 } else if (group.winner == null || isNearer(candidate, group.winner)) { 095 group.winner = candidate; 096 } 097 } 098 099 if (group.winner == null) { 100 throw newFailure(context); 101 } 102 } 103 104 private boolean isAcceptable(ConflictGroup group, Version version) { 105 for (VersionConstraint constraint : group.constraints) { 106 if (!constraint.containsVersion(version)) { 107 return false; 108 } 109 } 110 return true; 111 } 112 113 private boolean isNearer(ConflictItem item1, ConflictItem item2) { 114 if (item1.isSibling(item2)) { 115 return item1.getNode().getVersion().compareTo(item2.getNode().getVersion()) > 0; 116 } else { 117 return item1.getDepth() < item2.getDepth(); 118 } 119 } 120 121 private UnsolvableVersionConflictException newFailure(final ConflictContext context) { 122 DependencyFilter filter = (node, parents) -> { 123 requireNonNull(node, "node cannot be null"); 124 requireNonNull(parents, "parents cannot be null"); 125 return context.isIncluded(node); 126 }; 127 PathRecordingDependencyVisitor visitor = new PathRecordingDependencyVisitor(filter); 128 context.getRoot().accept(new TreeDependencyVisitor(visitor)); 129 return new UnsolvableVersionConflictException(visitor.getPaths()); 130 } 131 132 static final class ConflictGroup { 133 134 final Collection<VersionConstraint> constraints; 135 136 final Collection<ConflictItem> candidates; 137 138 ConflictItem winner; 139 140 ConflictGroup() { 141 constraints = new HashSet<>(); 142 candidates = new ArrayList<>(64); 143 } 144 145 @Override 146 public String toString() { 147 return String.valueOf(winner); 148 } 149 } 150}