1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.shared.artifact.filter;
20
21 import java.util.ArrayList;
22 import java.util.Arrays;
23 import java.util.Collection;
24 import java.util.EnumSet;
25 import java.util.HashMap;
26 import java.util.HashSet;
27 import java.util.LinkedHashSet;
28 import java.util.List;
29 import java.util.Set;
30
31 import org.apache.maven.artifact.Artifact;
32 import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
33 import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
34 import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
35 import org.apache.maven.artifact.versioning.VersionRange;
36 import org.slf4j.Logger;
37
38 import static java.util.Objects.requireNonNull;
39
40
41
42
43
44
45
46 public class PatternIncludesArtifactFilter implements ArtifactFilter, StatisticsReportingArtifactFilter {
47 private static final String SEP = System.lineSeparator();
48
49
50
51
52 private final Set<Pattern> patterns;
53
54
55
56
57 private final boolean actTransitively;
58
59
60
61
62 private final Set<Pattern> patternsTriggered = new HashSet<>();
63
64
65
66
67 private final List<Artifact> filteredArtifact = new ArrayList<>();
68
69
70
71
72
73
74 public PatternIncludesArtifactFilter(final Collection<String> patterns) {
75 this(patterns, false);
76 }
77
78
79
80
81
82
83
84 public PatternIncludesArtifactFilter(final Collection<String> patterns, final boolean actTransitively) {
85 this.actTransitively = actTransitively;
86 final Set<Pattern> pat = new LinkedHashSet<>();
87 if (patterns != null && !patterns.isEmpty()) {
88 for (String pattern : patterns) {
89 Pattern p = compile(pattern);
90 pat.add(p);
91 }
92 }
93 this.patterns = pat;
94 }
95
96 @Override
97 public boolean include(final Artifact artifact) {
98 final boolean shouldInclude = patternMatches(artifact);
99
100 if (!shouldInclude) {
101 addFilteredArtifact(artifact);
102 }
103
104 return shouldInclude;
105 }
106
107 protected boolean patternMatches(final Artifact artifact) {
108 Boolean match = match(adapt(artifact));
109 if (match != null) {
110 return match;
111 }
112
113 if (actTransitively) {
114 final List<String> depTrail = artifact.getDependencyTrail();
115
116 if (depTrail != null && depTrail.size() > 1) {
117 for (String trailItem : depTrail) {
118 Artifactoid artifactoid = adapt(trailItem);
119 match = match(artifactoid);
120 if (match != null) {
121 return match;
122 }
123 }
124 }
125 }
126
127 return false;
128 }
129
130 private Boolean match(Artifactoid artifactoid) {
131 for (Pattern pattern : patterns) {
132 if (pattern.matches(artifactoid)) {
133 patternsTriggered.add(pattern);
134 return !(pattern instanceof NegativePattern);
135 }
136 }
137
138 return null;
139 }
140
141
142
143
144
145
146 protected void addFilteredArtifact(final Artifact artifact) {
147 filteredArtifact.add(artifact);
148 }
149
150 @Override
151 public void reportMissedCriteria(final Logger logger) {
152
153 if (!patterns.isEmpty()) {
154 final List<Pattern> missed = new ArrayList<>(patterns);
155 missed.removeAll(patternsTriggered);
156
157 if (!missed.isEmpty() && logger.isWarnEnabled()) {
158 final StringBuilder buffer = new StringBuilder();
159
160 buffer.append("The following patterns were never triggered in this ");
161 buffer.append(getFilterDescription());
162 buffer.append(':');
163
164 for (Pattern pattern : missed) {
165 buffer.append(SEP).append("o '").append(pattern).append("'");
166 }
167
168 buffer.append(SEP);
169
170 logger.warn(buffer.toString());
171 }
172 }
173 }
174
175 @Override
176 public String toString() {
177 return "Includes filter:" + getPatternsAsString();
178 }
179
180 protected String getPatternsAsString() {
181 final StringBuilder buffer = new StringBuilder();
182 for (Pattern pattern : patterns) {
183 buffer.append(SEP).append("o '").append(pattern).append("'");
184 }
185
186 return buffer.toString();
187 }
188
189 protected String getFilterDescription() {
190 return "artifact inclusion filter";
191 }
192
193 @Override
194 public void reportFilteredArtifacts(final Logger logger) {
195 if (!filteredArtifact.isEmpty() && logger.isDebugEnabled()) {
196 final StringBuilder buffer =
197 new StringBuilder("The following artifacts were removed by this " + getFilterDescription() + ": ");
198
199 for (Artifact artifactId : filteredArtifact) {
200 buffer.append(SEP).append(artifactId.getId());
201 }
202
203 logger.debug(buffer.toString());
204 }
205 }
206
207 @Override
208 public boolean hasMissedCriteria() {
209
210 if (!patterns.isEmpty()) {
211 final List<Pattern> missed = new ArrayList<>(patterns);
212 missed.removeAll(patternsTriggered);
213 return !missed.isEmpty();
214 }
215
216 return false;
217 }
218
219 private enum Coordinate {
220 GROUP_ID,
221 ARTIFACT_ID,
222 TYPE,
223 CLASSIFIER,
224 BASE_VERSION
225 }
226
227 private interface Artifactoid {
228 String getCoordinate(Coordinate coordinate);
229 }
230
231 private static Artifactoid adapt(final Artifact artifact) {
232 requireNonNull(artifact);
233 return coordinate -> {
234 requireNonNull(coordinate);
235 switch (coordinate) {
236 case GROUP_ID:
237 return artifact.getGroupId();
238 case ARTIFACT_ID:
239 return artifact.getArtifactId();
240 case BASE_VERSION:
241 return artifact.getBaseVersion();
242 case CLASSIFIER:
243 return artifact.hasClassifier() ? artifact.getClassifier() : null;
244 case TYPE:
245 return artifact.getType();
246 default:
247 }
248 throw new IllegalArgumentException("unknown coordinate: " + coordinate);
249 };
250 }
251
252
253
254
255
256 private static Artifactoid adapt(final String depTrailString) {
257 requireNonNull(depTrailString);
258 String[] coordinates = depTrailString.split(":");
259 if (coordinates.length != 4 && coordinates.length != 5) {
260 throw new IllegalArgumentException("Bad dep trail string: " + depTrailString);
261 }
262 final HashMap<Coordinate, String> map = new HashMap<>();
263 map.put(Coordinate.GROUP_ID, coordinates[0]);
264 map.put(Coordinate.ARTIFACT_ID, coordinates[1]);
265 map.put(Coordinate.TYPE, coordinates[2]);
266 if (coordinates.length == 5) {
267 map.put(Coordinate.CLASSIFIER, coordinates[3]);
268 map.put(Coordinate.BASE_VERSION, coordinates[4]);
269 } else {
270 map.put(Coordinate.BASE_VERSION, coordinates[3]);
271 }
272
273 return coordinate -> {
274 requireNonNull(coordinate);
275 return map.get(coordinate);
276 };
277 }
278
279 private static final String ANY = "*";
280
281
282
283
284
285 private static String[] splitAndTokenize(String pattern) {
286 String[] stokens = pattern.split(":");
287 String[] tokens = new String[stokens.length];
288 for (int i = 0; i < stokens.length; i++) {
289 String str = stokens[i];
290 tokens[i] = str != null && !str.isEmpty() ? str : ANY;
291 }
292 return tokens;
293 }
294
295
296
297
298
299
300
301 private static Pattern compile(String pattern) {
302 if (pattern.startsWith("!")) {
303 return new NegativePattern(pattern, compile(pattern.substring(1)));
304 } else {
305 String[] tokens = splitAndTokenize(pattern);
306 if (tokens.length < 1 || tokens.length > 5) {
307 throw new IllegalArgumentException("Invalid pattern: " + pattern);
308 }
309
310 ArrayList<Pattern> patterns = new ArrayList<>(5);
311
312 if (tokens.length == 5) {
313
314 patterns.add(toPattern(tokens[0], Coordinate.GROUP_ID));
315 patterns.add(toPattern(tokens[1], Coordinate.ARTIFACT_ID));
316 patterns.add(toPattern(tokens[2], Coordinate.TYPE));
317 patterns.add(toPattern(tokens[3], Coordinate.CLASSIFIER));
318 patterns.add(toPattern(tokens[4], Coordinate.BASE_VERSION));
319 } else if (tokens.length == 4) {
320
321 patterns.add(toPattern(tokens[0], Coordinate.GROUP_ID));
322 patterns.add(toPattern(tokens[1], Coordinate.ARTIFACT_ID));
323 patterns.add(toPattern(tokens[2], Coordinate.TYPE));
324 patterns.add(toPattern(tokens[3], Coordinate.BASE_VERSION, Coordinate.CLASSIFIER));
325 } else if (tokens.length == 3) {
326
327
328
329
330
331
332
333
334
335
336 if (ANY.equals(tokens[0]) && ANY.equals(tokens[1]) && ANY.equals(tokens[2])) {
337 patterns.add(MATCH_ALL_PATTERN);
338 } else if (ANY.equals(tokens[0]) && ANY.equals(tokens[1])) {
339 patterns.add(toPattern(pattern, tokens[2], Coordinate.TYPE, Coordinate.CLASSIFIER));
340 } else if (ANY.equals(tokens[0]) && ANY.equals(tokens[2])) {
341 patterns.add(toPattern(pattern, tokens[1], Coordinate.ARTIFACT_ID, Coordinate.TYPE));
342 } else if (ANY.equals(tokens[0])) {
343 patterns.add(toPattern(pattern, tokens[1], Coordinate.GROUP_ID, Coordinate.ARTIFACT_ID));
344 patterns.add(toPattern(pattern, tokens[2], Coordinate.TYPE, Coordinate.CLASSIFIER));
345 } else if (ANY.equals(tokens[1]) && ANY.equals(tokens[2])) {
346 patterns.add(toPattern(pattern, tokens[0], Coordinate.GROUP_ID, Coordinate.ARTIFACT_ID));
347 } else if (ANY.equals(tokens[1])) {
348 patterns.add(toPattern(tokens[0], tokens[0], Coordinate.GROUP_ID));
349 patterns.add(toPattern(pattern, tokens[2], Coordinate.TYPE, Coordinate.CLASSIFIER));
350 } else if (ANY.equals(tokens[2])) {
351 patterns.add(toPattern(tokens[0], Coordinate.GROUP_ID));
352 patterns.add(toPattern(tokens[1], Coordinate.ARTIFACT_ID));
353 } else {
354 patterns.add(toPattern(tokens[0], Coordinate.GROUP_ID));
355 patterns.add(toPattern(tokens[1], Coordinate.ARTIFACT_ID));
356 patterns.add(toPattern(tokens[2], Coordinate.TYPE));
357 }
358
359 } else if (tokens.length == 2) {
360
361
362
363
364
365
366 if (ANY.equals(tokens[0]) && ANY.equals(tokens[1])) {
367 patterns.add(MATCH_ALL_PATTERN);
368 } else if (ANY.equals(tokens[0])) {
369 patterns.add(toPattern(
370 pattern,
371 tokens[1],
372 Coordinate.GROUP_ID,
373 Coordinate.ARTIFACT_ID,
374 Coordinate.TYPE,
375 Coordinate.BASE_VERSION));
376 } else if (ANY.equals(tokens[1])) {
377 patterns.add(toPattern(tokens[0], Coordinate.GROUP_ID));
378 } else {
379 patterns.add(toPattern(tokens[0], Coordinate.GROUP_ID));
380 patterns.add(toPattern(tokens[1], Coordinate.ARTIFACT_ID));
381 }
382 } else {
383
384 patterns.add(toPattern(tokens[0], Coordinate.GROUP_ID));
385 }
386
387
388 if (patterns.size() == 1) {
389 Pattern pat = patterns.get(0);
390 if (pat == MATCH_ALL_PATTERN) {
391 return new MatchAllPattern(pattern);
392 } else {
393 return pat;
394 }
395 } else {
396 return new AndPattern(pattern, patterns.toArray(new Pattern[0]));
397 }
398 }
399 }
400
401 private static Pattern toPattern(final String token, final Coordinate... coordinates) {
402 return toPattern(token, token, coordinates);
403 }
404
405 private static Pattern toPattern(final String pattern, final String token, final Coordinate... coordinates) {
406 if (ANY.equals(token)) {
407 return MATCH_ALL_PATTERN;
408 } else {
409 EnumSet<Coordinate> coordinateSet = EnumSet.noneOf(Coordinate.class);
410 coordinateSet.addAll(Arrays.asList(coordinates));
411 return new CoordinateMatchingPattern(pattern, token, coordinateSet);
412 }
413 }
414
415 private static final Pattern MATCH_ALL_PATTERN = new MatchAllPattern(ANY);
416
417 private abstract static class Pattern {
418 protected final String pattern;
419
420 private Pattern(String pattern) {
421 this.pattern = requireNonNull(pattern);
422 }
423
424 public abstract boolean matches(Artifactoid artifact);
425
426 @Override
427 public String toString() {
428 return pattern;
429 }
430 }
431
432 private static class AndPattern extends Pattern {
433 private final Pattern[] patterns;
434
435 private AndPattern(String pattern, Pattern[] patterns) {
436 super(pattern);
437 this.patterns = patterns;
438 }
439
440 @Override
441 public boolean matches(Artifactoid artifactoid) {
442 for (Pattern pattern : patterns) {
443 if (!pattern.matches(artifactoid)) {
444 return false;
445 }
446 }
447 return true;
448 }
449 }
450
451 private static class CoordinateMatchingPattern extends Pattern {
452 private final String token;
453
454 private final EnumSet<Coordinate> coordinates;
455
456 private final boolean containsWildcard;
457
458 private final boolean containsAsterisk;
459
460 private final VersionRange optionalVersionRange;
461
462 private CoordinateMatchingPattern(String pattern, String token, EnumSet<Coordinate> coordinates) {
463 super(pattern);
464 this.token = token;
465 this.coordinates = coordinates;
466 this.containsAsterisk = token.contains("*");
467 this.containsWildcard = this.containsAsterisk || token.contains("?");
468 if (!this.containsWildcard
469 && coordinates.equals(EnumSet.of(Coordinate.BASE_VERSION))
470 && (token.startsWith("[") || token.startsWith("("))) {
471 try {
472 this.optionalVersionRange = VersionRange.createFromVersionSpec(token);
473 } catch (InvalidVersionSpecificationException e) {
474 throw new IllegalArgumentException("Wrong version spec: " + token, e);
475 }
476 } else {
477 this.optionalVersionRange = null;
478 }
479 }
480
481 @Override
482 public boolean matches(Artifactoid artifactoid) {
483 for (Coordinate coordinate : coordinates) {
484 String value = artifactoid.getCoordinate(coordinate);
485 if (Coordinate.BASE_VERSION == coordinate && optionalVersionRange != null) {
486 if (optionalVersionRange.containsVersion(new DefaultArtifactVersion(value))) {
487 return true;
488 }
489 } else if (containsWildcard) {
490 if (match(token, containsAsterisk, value)) {
491 return true;
492 }
493 } else {
494 if (token.equals(value)) {
495 return true;
496 }
497 }
498 }
499 return false;
500 }
501 }
502
503
504
505
506 private static class MatchAllPattern extends Pattern {
507 private MatchAllPattern(String pattern) {
508 super(pattern);
509 }
510
511 @Override
512 public boolean matches(Artifactoid artifactoid) {
513 return true;
514 }
515 }
516
517
518
519
520 private static class NegativePattern extends Pattern {
521 private final Pattern inner;
522
523 private NegativePattern(String pattern, Pattern inner) {
524 super(pattern);
525 this.inner = inner;
526 }
527
528 @Override
529 public boolean matches(Artifactoid artifactoid) {
530 return inner.matches(artifactoid);
531 }
532 }
533
534
535
536 @SuppressWarnings("InnerAssignment")
537 private static boolean match(final String pattern, final boolean containsAsterisk, final String value) {
538 char[] patArr = pattern.toCharArray();
539 char[] strArr = value != null ? value.toCharArray() : new char[0];
540 int patIdxStart = 0;
541 int patIdxEnd = patArr.length - 1;
542 int strIdxStart = 0;
543 int strIdxEnd = strArr.length - 1;
544 char ch;
545
546 if (!containsAsterisk) {
547
548 if (patIdxEnd != strIdxEnd) {
549 return false;
550 }
551 for (int i = 0; i <= patIdxEnd; i++) {
552 ch = patArr[i];
553 if (ch != '?' && ch != strArr[i]) {
554 return false;
555 }
556 }
557 return true;
558 }
559
560 if (patIdxEnd == 0) {
561 return true;
562 }
563
564
565 while ((ch = patArr[patIdxStart]) != '*' && strIdxStart <= strIdxEnd) {
566 if (ch != '?' && ch != strArr[strIdxStart]) {
567 return false;
568 }
569 patIdxStart++;
570 strIdxStart++;
571 }
572 if (strIdxStart > strIdxEnd) {
573
574
575 for (int i = patIdxStart; i <= patIdxEnd; i++) {
576 if (patArr[i] != '*') {
577 return false;
578 }
579 }
580 return true;
581 }
582
583
584 while ((ch = patArr[patIdxEnd]) != '*' && strIdxStart <= strIdxEnd) {
585 if (ch != '?' && ch != strArr[strIdxEnd]) {
586 return false;
587 }
588 patIdxEnd--;
589 strIdxEnd--;
590 }
591 if (strIdxStart > strIdxEnd) {
592
593
594 for (int i = patIdxStart; i <= patIdxEnd; i++) {
595 if (patArr[i] != '*') {
596 return false;
597 }
598 }
599 return true;
600 }
601
602
603
604 while (patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd) {
605 int patIdxTmp = -1;
606 for (int i = patIdxStart + 1; i <= patIdxEnd; i++) {
607 if (patArr[i] == '*') {
608 patIdxTmp = i;
609 break;
610 }
611 }
612 if (patIdxTmp == patIdxStart + 1) {
613
614 patIdxStart++;
615 continue;
616 }
617
618
619 int patLength = (patIdxTmp - patIdxStart - 1);
620 int strLength = (strIdxEnd - strIdxStart + 1);
621 int foundIdx = -1;
622 strLoop:
623 for (int i = 0; i <= strLength - patLength; i++) {
624 for (int j = 0; j < patLength; j++) {
625 ch = patArr[patIdxStart + j + 1];
626 if (ch != '?' && ch != strArr[strIdxStart + i + j]) {
627 continue strLoop;
628 }
629 }
630
631 foundIdx = strIdxStart + i;
632 break;
633 }
634
635 if (foundIdx == -1) {
636 return false;
637 }
638
639 patIdxStart = patIdxTmp;
640 strIdxStart = foundIdx + patLength;
641 }
642
643
644
645 for (int i = patIdxStart; i <= patIdxEnd; i++) {
646 if (patArr[i] != '*') {
647 return false;
648 }
649 }
650 return true;
651 }
652 }