View Javadoc
1   /*
2    * Licensed under the Apache License, Version 2.0 (the "License");
3    * you may not use this file except in compliance with the License.
4    * You may obtain a copy of the License at
5    *
6    * http://www.apache.org/licenses/LICENSE-2.0
7    *
8    * Unless required by applicable law or agreed to in writing, software
9    * distributed under the License is distributed on an "AS IS" BASIS,
10   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11   * See the License for the specific language governing permissions and
12   * limitations under the License.
13   */
14  
15  package org.basepom.mojo.duplicatefinder.artifact;
16  
17  import static com.google.common.base.Preconditions.checkNotNull;
18  
19  import java.util.Objects;
20  import java.util.Optional;
21  
22  import com.google.common.base.Joiner;
23  import com.google.common.base.MoreObjects;
24  import com.google.common.collect.ImmutableList;
25  import org.apache.maven.artifact.Artifact;
26  import org.apache.maven.artifact.versioning.ArtifactVersion;
27  import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
28  import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
29  import org.apache.maven.artifact.versioning.OverConstrainedVersionException;
30  import org.apache.maven.artifact.versioning.VersionRange;
31  import org.apache.maven.model.Dependency;
32  
33  public class MavenCoordinates {
34  
35      private final String artifactId;
36      private final String groupId;
37      private final Optional<? extends ArtifactVersion> version;
38      private final Optional<VersionRange> versionRange;
39      private final String type;
40      private final Optional<String> classifier;
41  
42      public MavenCoordinates(final Dependency dependency) throws InvalidVersionSpecificationException {
43          checkNotNull(dependency, "dependency is null");
44  
45          this.artifactId = checkNotNull(dependency.getArtifactId(), "artifactId for dependency '%s' is null", dependency);
46          this.groupId = checkNotNull(dependency.getGroupId(), "groupId for dependency '%s' is null", dependency);
47  
48          final String version = dependency.getVersion();
49          this.version = Optional.ofNullable(version).map(DefaultArtifactVersion::new);
50  
51          if (this.version.isPresent()) {
52              this.versionRange = Optional.of(VersionRange.createFromVersionSpec(version));
53          } else {
54              this.versionRange = Optional.empty();
55          }
56  
57          final String type = dependency.getType();
58          final String classifier = dependency.getClassifier();
59          if ("test-jar".equals(type)) {
60              this.classifier = Optional.of(MoreObjects.firstNonNull(classifier, "tests"));
61              this.type = "jar";
62          } else {
63              this.type = MoreObjects.firstNonNull(type, "jar");
64              this.classifier = Optional.ofNullable(classifier);
65          }
66      }
67  
68      public MavenCoordinates(final Artifact artifact) throws OverConstrainedVersionException {
69          checkNotNull(artifact, "artifact is null");
70  
71          this.artifactId = checkNotNull(artifact.getArtifactId(), "artifactId for artifact '%s' is null", artifact);
72          this.groupId = checkNotNull(artifact.getGroupId(), "groupId for artifact '%s' is null", artifact);
73          this.versionRange = Optional.ofNullable(artifact.getVersionRange());
74  
75          if (this.versionRange.isPresent()) {
76              this.version = Optional.ofNullable(artifact.getSelectedVersion());
77          } else {
78              final String version = artifact.getBaseVersion();
79              this.version = Optional.ofNullable(version).map(DefaultArtifactVersion::new);
80          }
81  
82          final String type = artifact.getType();
83          final String classifier = artifact.getClassifier();
84          if ("test-jar".equals(type)) {
85              this.classifier = Optional.of(MoreObjects.firstNonNull(classifier, "tests"));
86              this.type = "jar";
87          } else {
88              this.type = MoreObjects.firstNonNull(type, "jar");
89              this.classifier = Optional.ofNullable(classifier);
90          }
91      }
92  
93      public String getArtifactId() {
94          return artifactId;
95      }
96  
97      public String getGroupId() {
98          return groupId;
99      }
100 
101     public Optional<? extends ArtifactVersion> getVersion() {
102         return version;
103     }
104 
105     public Optional<VersionRange> getVersionRange() {
106         return versionRange;
107     }
108 
109     public String getType() {
110         return type;
111     }
112 
113     public Optional<String> getClassifier() {
114         return classifier;
115     }
116 
117     public boolean matches(final Artifact artifact) throws OverConstrainedVersionException {
118         return matches(new MavenCoordinates(artifact));
119     }
120 
121     public boolean matches(final MavenCoordinates other) {
122         if (!(Objects.equals(getGroupId(), other.getGroupId())
123                 && Objects.equals(getArtifactId(), other.getArtifactId())
124                 && Objects.equals(getType(), other.getType()))) {
125             return false;
126         }
127 
128         // If a classifier is present, try to match the other classifier,
129         // otherwise, if no classifier is present, it matches all classifiers from the other MavenCoordinates.
130         if (getClassifier().isPresent()) {
131             if (!Objects.equals(getClassifier().get(), other.getClassifier().orElse(null))) {
132                 return false;
133             }
134         }
135 
136         // no version range and no version present, so any other version matches
137         if (!getVersionRange().isPresent() && !getVersion().isPresent()) {
138             return true;
139         }
140 
141         // starting here, either a version or a version range is present
142 
143         // other has no version. So there can be no match
144         if (!other.getVersion().isPresent()) {
145             return false;
146         }
147 
148         // version range local and version in other
149         if (getVersionRange().isPresent()) {
150             // is there a recommended version?
151             final ArtifactVersion recommendedVersion = getVersionRange().get().getRecommendedVersion();
152             if (recommendedVersion != null) {
153                 // Yes, then it must be matched.
154                 return Objects.equals(recommendedVersion, other.getVersion().orElse(null));
155             }
156 
157             // No, see if the other version is in the range
158             if (getVersionRange().get().containsVersion(other.getVersion().get())) {
159                 return true;
160             }
161         }
162 
163         // exact version match.
164         return Objects.equals(getVersion().orElse(null), other.getVersion().orElse(null));
165     }
166 
167     @Override
168     public int hashCode() {
169         return Objects.hash(groupId, artifactId, classifier, type);
170     }
171 
172     @Override
173     public boolean equals(final Object other) {
174         if (other == null || other.getClass() != this.getClass()) {
175             return false;
176         }
177         if (other == this) {
178             return true;
179         }
180 
181         MavenCoordinates that = (MavenCoordinates) other;
182 
183         return Objects.equals(this.groupId, that.groupId)
184                 && Objects.equals(this.artifactId, that.artifactId)
185                 && Objects.equals(this.classifier, that.classifier)
186                 && Objects.equals(this.type, that.type);
187     }
188 
189     @Override
190     public String toString() {
191         final ImmutableList.Builder<String> builder = ImmutableList.builder();
192 
193         builder.add(getGroupId());
194         builder.add(getArtifactId());
195 
196         if (getVersion().isPresent()) {
197             builder.add(getVersion().get().toString());
198         } else {
199             builder.add("<any>");
200         }
201 
202         builder.add(getType());
203         builder.add(getClassifier().orElse("<any>"));
204         return Joiner.on(':').join(builder.build());
205     }
206 }