001/*
002 * Licensed under the Apache License, Version 2.0 (the "License");
003 * you may not use this file except in compliance with the License.
004 * You may obtain a copy of the License at
005 *
006 * http://www.apache.org/licenses/LICENSE-2.0
007 *
008 * Unless required by applicable law or agreed to in writing, software
009 * distributed under the License is distributed on an "AS IS" BASIS,
010 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
011 * See the License for the specific language governing permissions and
012 * limitations under the License.
013 */
014
015package org.basepom.mojo.dvc;
016
017import static com.google.common.base.Preconditions.checkNotNull;
018import static com.google.common.base.Preconditions.checkState;
019
020import java.util.Objects;
021import java.util.Optional;
022
023import com.google.common.annotations.VisibleForTesting;
024import com.google.common.base.Joiner;
025import com.google.common.base.MoreObjects;
026import com.google.common.collect.ImmutableList;
027import org.apache.maven.RepositoryUtils;
028import org.apache.maven.artifact.Artifact;
029import org.apache.maven.project.MavenProject;
030import org.eclipse.aether.graph.Dependency;
031import org.eclipse.aether.graph.DependencyNode;
032
033/**
034 * A qualified name for a dependency or artifact. This is everything but a version.
035 */
036public final class QualifiedName
037        implements Comparable<QualifiedName> {
038
039    private final String groupId;
040    private final String artifactId;
041    private final String type;
042    private final String classifier;
043
044    public static QualifiedName fromDependencyNode(final DependencyNode dependencyNode) {
045        checkNotNull(dependencyNode, "dependency is null");
046        return fromArtifact(RepositoryUtils.toArtifact(dependencyNode.getArtifact()));
047    }
048
049    public static QualifiedName fromDependency(final Dependency dependency) {
050        checkNotNull(dependency, "dependency is null");
051        return fromArtifact(RepositoryUtils.toArtifact(dependency.getArtifact()));
052    }
053
054    public static QualifiedName fromArtifact(final Artifact artifact) {
055        checkNotNull(artifact, "artifact is null");
056
057        return new QualifiedName(artifact.getGroupId(),
058                artifact.getArtifactId(),
059                artifact.getType(),
060                artifact.getClassifier());
061    }
062
063    public static QualifiedName fromProject(final MavenProject project) {
064        checkNotNull(project, "project is null");
065
066        return new QualifiedName(project.getGroupId(),
067                project.getArtifactId(),
068                null,
069                null);
070    }
071
072    @VisibleForTesting
073    QualifiedName(String groupId, String artifactId, String type, String classifier) {
074        this.groupId = checkNotNull(groupId, "groupId is null");
075        this.artifactId = checkNotNull(artifactId, "artifactId is null");
076        this.type = type;
077        this.classifier = classifier;
078
079        checkState(classifier == null || type != null, "Classifier must be null if type is null");
080    }
081
082    public String getGroupId() {
083        return groupId;
084    }
085
086    public String getArtifactId() {
087        return artifactId;
088    }
089
090    public Optional<String> getType() {
091        return Optional.ofNullable(type);
092    }
093
094    public Optional<String> getClassifier() {
095        return Optional.ofNullable(classifier);
096    }
097
098    /**
099     * @return True if this qualified name refers to a test artifact.
100     */
101    public boolean hasTests() {
102        return getType().map(t -> t.equals("test-jar")).orElse(false)
103                || (getClassifier().map(c -> c.equals("tests")).orElse(false) && getType().map(t -> t.equals("jar")).orElse(false));
104    }
105
106    /**
107     * @return The full name (group, artifact, type, classifier). Normalizes any test jar to be group:artifact:jar:tests.
108     */
109    public String getFullName() {
110        ImmutableList.Builder<String> builder = ImmutableList.builder();
111        builder.add(getGroupId()).add(getArtifactId());
112
113        getType().ifPresent(t -> builder.add(t));
114        getClassifier().ifPresent(t -> builder.add(t));
115        return Joiner.on(':').join(builder.build());
116    }
117
118    /**
119     * @return The short name (group, artifact, optional classifier). Skips absent classifiers. Normalizes test jars to `tests` classifier.
120     */
121    public String getShortName() {
122        String result = Joiner.on(':').skipNulls()
123                .join(getGroupId(),
124                        getArtifactId());
125
126        String classifier = hasTests() ? "tests" : getClassifier().orElse("");
127
128        if (!classifier.isEmpty()) {
129            result = result + " (" + classifier + ")";
130        }
131
132        return result;
133    }
134
135    public int length() {
136        return getShortName().length();
137    }
138
139    public String getMinimalName() {
140        return Joiner.on(':')
141                .join(getGroupId(), getArtifactId());
142    }
143
144    @Override
145    public boolean equals(final Object o) {
146        if (this == o) {
147            return true;
148        }
149        if (o == null || getClass() != o.getClass()) {
150            return false;
151        }
152        QualifiedName that = (QualifiedName) o;
153        if (Objects.equals(groupId, that.groupId)
154                && Objects.equals(artifactId, that.artifactId)) {
155
156            // Two test artifacts test equal
157            if (hasTests() && ((QualifiedName) o).hasTests()) {
158                return true;
159            }
160
161            return Objects.equals(getType().orElse("jar"), that.getType().orElse("jar"))
162                    && Objects.equals(getClassifier().orElse(""), that.getClassifier().orElse(""));
163        }
164
165        return false;
166    }
167
168    @Override
169    public int hashCode() {
170        if (hasTests()) {
171            return Objects.hash(groupId, artifactId, "test-jar", "tests");
172        } else {
173            return Objects.hash(groupId, artifactId, getType().orElse("jar"), getClassifier().orElse(""));
174        }
175    }
176
177    @Override
178    public String toString() {
179        return MoreObjects.toStringHelper(this)
180                .add("groupId", groupId)
181                .add("artifactId", artifactId)
182                .add("type", type)
183                .add("classifier", classifier)
184                .toString();
185    }
186
187    @Override
188    public int compareTo(final QualifiedName other) {
189        if (other == null) {
190            return 1;
191        } else if (other == this || equals(other)) {
192            return 0;
193        } else {
194            return getMinimalName().compareTo(other.getMinimalName());
195        }
196    }
197}