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