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.internal.impl.scope;
020
021import java.util.Collection;
022import java.util.Collections;
023import java.util.Comparator;
024import java.util.HashSet;
025import java.util.List;
026import java.util.Set;
027import java.util.stream.Collectors;
028
029import org.eclipse.aether.impl.scope.InternalScopeManager;
030import org.eclipse.aether.scope.DependencyScope;
031import org.eclipse.aether.scope.ScopeManager;
032import org.eclipse.aether.util.graph.transformer.ConflictResolver;
033import org.eclipse.aether.util.graph.transformer.ConflictResolver.ConflictContext;
034import org.eclipse.aether.util.graph.transformer.ConflictResolver.ConflictItem;
035import org.eclipse.aether.util.graph.transformer.ConflictResolver.ScopeSelector;
036
037import static java.util.Objects.requireNonNull;
038
039/**
040 * A scope selector for use with {@link ConflictResolver} that supports the scopes from {@link ScopeManager}.
041 * In general, this selector picks the widest scope present among conflicting dependencies where e.g. "compile" is
042 * wider than "runtime" which is wider than "test". If however a direct dependency is involved, its scope is selected.
043 * <p>
044 * This class also "bridges" between {@link DependencyScope} and Resolver that uses plain string labels for scopes.
045 */
046public final class ManagedScopeSelector extends ScopeSelector {
047    private final DependencyScope systemScope;
048    private final List<DependencyScope> dependencyScopesByWidthDescending;
049
050    public ManagedScopeSelector(InternalScopeManager scopeManager) {
051        requireNonNull(scopeManager, "scopeManager");
052        this.systemScope = scopeManager.getSystemDependencyScope().orElse(null);
053        this.dependencyScopesByWidthDescending =
054                Collections.unmodifiableList(scopeManager.getDependencyScopeUniverse().stream()
055                        .sorted(Comparator.comparing(scopeManager::getDependencyScopeWidth)
056                                .reversed())
057                        .collect(Collectors.toList()));
058    }
059
060    @Override
061    public void selectScope(ConflictContext context) {
062        String scope = context.getWinner().getDependency().getScope();
063        if (systemScope == null || !systemScope.getId().equals(scope)) {
064            scope = chooseEffectiveScope(context.getItems());
065        }
066        context.setScope(scope);
067    }
068
069    private String chooseEffectiveScope(Collection<ConflictItem> items) {
070        Set<String> scopes = new HashSet<>();
071        for (ConflictItem item : items) {
072            if (item.getDepth() <= 1) {
073                return item.getDependency().getScope();
074            }
075            scopes.addAll(item.getScopes());
076        }
077        return chooseEffectiveScope(scopes);
078    }
079
080    /**
081     * Visible for testing. It chooses "widest" scope out of provided ones, unless system scope is present, in which
082     * case system scope is selected.
083     */
084    public String chooseEffectiveScope(Set<String> scopes) {
085        if (scopes.size() > 1 && systemScope != null) {
086            scopes.remove(systemScope.getId());
087        }
088
089        String effectiveScope = "";
090
091        if (scopes.size() == 1) {
092            effectiveScope = scopes.iterator().next();
093        } else {
094            for (DependencyScope dependencyScope : dependencyScopesByWidthDescending) {
095                if (scopes.contains(dependencyScope.getId())) {
096                    effectiveScope = dependencyScope.getId();
097                    break;
098                }
099            }
100        }
101
102        return effectiveScope;
103    }
104}