1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.enforcer.rules.utils;
20
21 import java.util.Collection;
22 import java.util.HashSet;
23 import java.util.Objects;
24 import java.util.function.Function;
25 import java.util.function.Predicate;
26
27 import org.apache.maven.artifact.Artifact;
28 import org.apache.maven.artifact.versioning.ArtifactVersion;
29 import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
30 import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
31 import org.apache.maven.artifact.versioning.VersionRange;
32 import org.apache.maven.model.Dependency;
33 import org.codehaus.plexus.util.StringUtils;
34
35 import static java.util.Optional.ofNullable;
36
37
38
39
40
41
42 public final class ArtifactMatcher {
43
44
45
46
47 public static class Pattern {
48 private final String pattern;
49
50 private final String[] parts;
51 private final Predicate<String>[] partsRegex;
52
53 public Pattern(String pattern) {
54 if (pattern == null) {
55 throw new NullPointerException("pattern");
56 }
57
58 this.pattern = pattern;
59
60 parts = pattern.split(":", 7);
61
62 if (parts.length == 7) {
63 throw new IllegalArgumentException("Pattern contains too many delimiters.");
64 }
65
66 for (String part : parts) {
67 if ("".equals(part)) {
68 throw new IllegalArgumentException("Pattern or its part is empty.");
69 }
70 }
71 partsRegex = new Predicate[parts.length];
72 }
73
74 public boolean match(Artifact artifact) {
75 Objects.requireNonNull(artifact, "artifact must not be null");
76 try {
77 return match(
78 artifact.getGroupId(),
79 artifact.getArtifactId(),
80 artifact.getVersion(),
81 artifact.getType(),
82 artifact.getScope(),
83 artifact.getClassifier());
84 } catch (InvalidVersionSpecificationException e) {
85 throw new IllegalArgumentException(e);
86 }
87 }
88
89 public boolean match(Dependency dependency) {
90 Objects.requireNonNull(dependency, "dependency must not be null");
91 try {
92 return match(
93 dependency.getGroupId(),
94 dependency.getArtifactId(),
95 dependency.getVersion(),
96 dependency.getType(),
97 dependency.getScope(),
98 dependency.getClassifier());
99 } catch (InvalidVersionSpecificationException e) {
100 throw new IllegalArgumentException(e);
101 }
102 }
103
104 private boolean match(
105 String groupId, String artifactId, String version, String type, String scope, String classifier)
106 throws InvalidVersionSpecificationException {
107 switch (parts.length) {
108 case 6:
109 if (!matches(5, classifier)) {
110 return false;
111 }
112 case 5:
113 if (scope == null || scope.isEmpty()) {
114 scope = Artifact.SCOPE_COMPILE;
115 }
116
117 if (!matches(4, scope)) {
118 return false;
119 }
120 case 4:
121 if (type == null || type.isEmpty()) {
122 type = "jar";
123 }
124
125 if (!matches(3, type)) {
126 return false;
127 }
128
129 case 3:
130 if (!matches(2, version)) {
131 if (!containsVersion(
132 VersionRange.createFromVersionSpec(parts[2]), new DefaultArtifactVersion(version))) {
133 return false;
134 }
135 }
136
137 case 2:
138 if (!matches(1, artifactId)) {
139 return false;
140 }
141 case 1:
142 return matches(0, groupId);
143 default:
144 throw new AssertionError();
145 }
146 }
147
148 private boolean matches(int index, String input) {
149
150 if (input == null) {
151 input = "";
152 }
153 if (partsRegex[index] == null) {
154 String regex = parts[index]
155 .replace(".", "\\.")
156 .replace("*", ".*")
157 .replace(":", "\\:")
158 .replace('?', '.')
159 .replace("[", "\\[")
160 .replace("]", "\\]")
161 .replace("(", "\\(")
162 .replace(")", "\\)");
163
164 if (".*".equals(regex)) {
165 partsRegex[index] = test -> true;
166 } else {
167 partsRegex[index] = test ->
168 java.util.regex.Pattern.compile(regex).matcher(test).matches();
169 }
170 }
171 return partsRegex[index].test(input);
172 }
173
174 @Override
175 public String toString() {
176 return pattern;
177 }
178 }
179
180 private final Collection<Pattern> excludePatterns = new HashSet<>();
181
182 private final Collection<Pattern> includePatterns = new HashSet<>();
183
184
185
186
187
188
189
190
191 public ArtifactMatcher(final Collection<String> excludeStrings, final Collection<String> includeStrings) {
192 ofNullable(excludeStrings).ifPresent(excludes -> excludes.stream()
193 .filter(StringUtils::isNotEmpty)
194 .map(Pattern::new)
195 .forEach(excludePatterns::add));
196 ofNullable(includeStrings).ifPresent(includes -> includes.stream()
197 .filter(StringUtils::isNotEmpty)
198 .map(Pattern::new)
199 .forEach(includePatterns::add));
200 }
201
202 private boolean match(Function<Pattern, Boolean> matcher) {
203 return excludePatterns.stream().anyMatch(matcher::apply)
204 && includePatterns.stream().noneMatch(matcher::apply);
205 }
206
207
208
209
210
211
212
213
214 public boolean match(Artifact artifact) {
215 return match(p -> p.match(artifact));
216 }
217
218
219
220
221
222
223
224
225 public boolean match(Dependency dependency) {
226 return match(p -> p.match(dependency));
227 }
228
229
230
231
232
233
234
235
236
237
238 public static boolean containsVersion(VersionRange allowedRange, ArtifactVersion version) {
239 ArtifactVersion recommendedVersion = allowedRange.getRecommendedVersion();
240 if (recommendedVersion == null) {
241 return allowedRange.containsVersion(version);
242 } else {
243
244 int compareTo = recommendedVersion.compareTo(version);
245 return compareTo <= 0;
246 }
247 }
248
249
250
251
252 public static class MatchingArtifact {
253 String artifactString;
254
255 public MatchingArtifact(Artifact artifact) {
256 artifactString = new StringBuilder()
257 .append(artifact.getGroupId())
258 .append(":")
259 .append(artifact.getArtifactId())
260 .append(":")
261 .append(artifact.getVersion())
262 .append(":")
263 .append(artifact.getType())
264 .append(":")
265 .append(artifact.getScope())
266 .append(":")
267 .append(artifact.getClassifier())
268 .toString();
269 }
270
271 @Override
272 public int hashCode() {
273 return artifactString.hashCode();
274 }
275
276 @Override
277 public boolean equals(Object obj) {
278 if (this == obj) {
279 return true;
280 }
281 if (obj == null) {
282 return false;
283 }
284 if (getClass() != obj.getClass()) {
285 return false;
286 }
287 MatchingArtifact other = (MatchingArtifact) obj;
288 return Objects.equals(artifactString, other.artifactString);
289 }
290
291 @Override
292 public String toString() {
293 return artifactString;
294 }
295 }
296 }