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.collection;
020
021import java.util.Collection;
022import java.util.Collections;
023import java.util.Iterator;
024import java.util.LinkedHashSet;
025import java.util.List;
026
027import org.eclipse.aether.RepositoryException;
028import org.eclipse.aether.artifact.Artifact;
029import org.eclipse.aether.graph.DependencyNode;
030import org.eclipse.aether.version.VersionConstraint;
031
032import static java.util.Objects.requireNonNull;
033
034/**
035 * Thrown in case of an unsolvable conflict between different version constraints for a dependency.
036 */
037public class UnsolvableVersionConflictException extends RepositoryException {
038
039    private final transient Collection<String> versions;
040
041    private final transient Collection<? extends List<? extends DependencyNode>> paths;
042
043    /**
044     * Creates a new exception with the specified paths to conflicting nodes in the dependency graph.
045     *
046     * @param paths The paths to the dependency nodes that participate in the version conflict, may be {@code null}.
047     * @deprecated Use {@link #UnsolvableVersionConflictException(String, Collection)} instead.
048     */
049    @Deprecated
050    public UnsolvableVersionConflictException(Collection<? extends List<? extends DependencyNode>> paths) {
051        this("Unsolvable hard constraint combination", paths);
052    }
053
054    /**
055     * Creates a new exception with the specified paths to conflicting nodes in the dependency graph.
056     *
057     * @param message The strategy that throw the bucket in, must not be {@code null}. Should provide concise message
058     *                why this exception was thrown.
059     * @param paths The paths to the dependency nodes that participate in the version conflict, may be {@code null}.
060     *
061     * @since 2.0.0
062     */
063    public UnsolvableVersionConflictException(
064            String message, Collection<? extends List<? extends DependencyNode>> paths) {
065        super(requireNonNull(message, "message") + "; Could not resolve version conflict among " + toPaths(paths));
066        if (paths == null) {
067            this.paths = Collections.emptyList();
068            this.versions = Collections.emptyList();
069        } else {
070            this.paths = paths;
071            this.versions = new LinkedHashSet<>();
072            for (List<? extends DependencyNode> path : paths) {
073                VersionConstraint constraint = path.get(path.size() - 1).getVersionConstraint();
074                if (constraint != null && constraint.getRange() != null) {
075                    versions.add(constraint.toString());
076                }
077            }
078        }
079    }
080
081    private static String toPaths(Collection<? extends List<? extends DependencyNode>> paths) {
082        String result = "";
083
084        if (paths != null) {
085            Collection<String> strings = new LinkedHashSet<>();
086
087            for (List<? extends DependencyNode> path : paths) {
088                strings.add(toPath(path));
089            }
090
091            result = strings.toString();
092        }
093
094        return result;
095    }
096
097    private static String toPath(List<? extends DependencyNode> path) {
098        StringBuilder buffer = new StringBuilder(256);
099
100        for (Iterator<? extends DependencyNode> it = path.iterator(); it.hasNext(); ) {
101            DependencyNode node = it.next();
102            if (node.getDependency() == null) {
103                continue;
104            }
105
106            Artifact artifact = node.getDependency().getArtifact();
107            buffer.append(artifact.getGroupId());
108            buffer.append(':').append(artifact.getArtifactId());
109            buffer.append(':').append(artifact.getExtension());
110            if (!artifact.getClassifier().isEmpty()) {
111                buffer.append(':').append(artifact.getClassifier());
112            }
113            buffer.append(':').append(node.getVersionConstraint());
114
115            if (it.hasNext()) {
116                buffer.append(" -> ");
117            }
118        }
119
120        return buffer.toString();
121    }
122
123    /**
124     * Gets the paths leading to the conflicting dependencies.
125     *
126     * @return The (read-only) paths leading to the conflicting dependencies, never {@code null}.
127     */
128    public Collection<? extends List<? extends DependencyNode>> getPaths() {
129        return paths;
130    }
131
132    /**
133     * Gets the conflicting version constraints of the dependency.
134     *
135     * @return The (read-only) conflicting version constraints, never {@code null}.
136     */
137    public Collection<String> getVersions() {
138        return versions;
139    }
140}