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.HashSet;
25 import java.util.LinkedHashSet;
26 import java.util.List;
27 import java.util.Objects;
28 import java.util.Set;
29
30 import org.apache.maven.artifact.Artifact;
31 import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
32 import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
33 import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
34 import org.apache.maven.artifact.versioning.VersionRange;
35 import org.slf4j.Logger;
36
37
38
39
40
41
42
43 public class GNPatternIncludesArtifactFilter implements ArtifactFilter, StatisticsReportingArtifactFilter {
44
45 private final Set<Pattern> patterns;
46
47
48 private final boolean actTransitively;
49
50
51 private final Set<Pattern> patternsTriggered = new HashSet<>();
52
53
54 private final List<Artifact> filteredArtifact = new ArrayList<>();
55
56
57
58
59
60
61 public GNPatternIncludesArtifactFilter(final Collection<String> patterns) {
62 this(patterns, false);
63 }
64
65
66
67
68
69
70
71 public GNPatternIncludesArtifactFilter(final Collection<String> patterns, final boolean actTransitively) {
72 this.actTransitively = actTransitively;
73 final Set<Pattern> pat = new LinkedHashSet<>();
74 if (patterns != null && !patterns.isEmpty()) {
75 for (String pattern : patterns) {
76
77 Pattern p = compile(pattern);
78 pat.add(p);
79 }
80 }
81 this.patterns = pat;
82 }
83
84
85 public boolean include(final Artifact artifact) {
86 final boolean shouldInclude = patternMatches(artifact);
87
88 if (!shouldInclude) {
89 addFilteredArtifact(artifact);
90 }
91
92 return shouldInclude;
93 }
94
95
96
97
98
99
100
101 protected boolean patternMatches(final Artifact artifact) {
102
103 char[][] artifactGatvCharArray = new char[][] {
104 emptyOrChars(artifact.getGroupId()),
105 emptyOrChars(artifact.getArtifactId()),
106 emptyOrChars(artifact.getType()),
107 emptyOrChars(artifact.getClassifier()),
108 emptyOrChars(artifact.getBaseVersion())
109 };
110 Boolean match = match(artifactGatvCharArray);
111 if (match != null) {
112 return match;
113 }
114
115 if (actTransitively) {
116 final List<String> depTrail = artifact.getDependencyTrail();
117
118 if (depTrail != null && depTrail.size() > 1) {
119 for (String trailItem : depTrail) {
120 char[][] depGatvCharArray = tokenizeAndSplit(trailItem);
121 match = match(depGatvCharArray);
122 if (match != null) {
123 return match;
124 }
125 }
126 }
127 }
128
129 return false;
130 }
131
132 private Boolean match(char[][] gatvCharArray) {
133 for (Pattern pattern : patterns) {
134 if (pattern.matches(gatvCharArray)) {
135 patternsTriggered.add(pattern);
136 return !(pattern instanceof NegativePattern);
137 }
138 }
139
140 return null;
141 }
142
143
144
145
146
147
148 protected void addFilteredArtifact(final Artifact artifact) {
149 filteredArtifact.add(artifact);
150 }
151
152
153 public void reportMissedCriteria(final Logger logger) {
154
155 if (!patterns.isEmpty()) {
156 final List<Pattern> missed = new ArrayList<>(patterns);
157 missed.removeAll(patternsTriggered);
158
159 if (!missed.isEmpty() && logger.isWarnEnabled()) {
160 final StringBuilder buffer = new StringBuilder();
161
162 buffer.append("The following patterns were never triggered in this ");
163 buffer.append(getFilterDescription());
164 buffer.append(':');
165
166 for (Pattern pattern : missed) {
167 buffer.append("\no '").append(pattern).append("'");
168 }
169
170 buffer.append("\n");
171
172 logger.warn(buffer.toString());
173 }
174 }
175 }
176
177
178 @Override
179 public String toString() {
180 return "Includes filter:" + getPatternsAsString();
181 }
182
183
184
185
186
187
188 protected String getPatternsAsString() {
189 final StringBuilder buffer = new StringBuilder();
190 for (Pattern pattern : patterns) {
191 buffer.append("\no '").append(pattern).append("'");
192 }
193
194 return buffer.toString();
195 }
196
197
198
199
200
201
202 protected String getFilterDescription() {
203 return "artifact inclusion filter";
204 }
205
206
207 public void reportFilteredArtifacts(final Logger logger) {
208 if (!filteredArtifact.isEmpty() && logger.isDebugEnabled()) {
209 final StringBuilder buffer =
210 new StringBuilder("The following artifacts were removed by this " + getFilterDescription() + ": ");
211
212 for (Artifact artifactId : filteredArtifact) {
213 buffer.append('\n').append(artifactId.getId());
214 }
215
216 logger.debug(buffer.toString());
217 }
218 }
219
220
221
222
223
224
225 public boolean hasMissedCriteria() {
226
227 if (!patterns.isEmpty()) {
228 final List<Pattern> missed = new ArrayList<>(patterns);
229 missed.removeAll(patternsTriggered);
230
231 return !missed.isEmpty();
232 }
233
234 return false;
235 }
236
237 private static final char[] EMPTY = new char[0];
238
239 private static final char[] ANY = new char[] {'*'};
240
241 static char[] emptyOrChars(String str) {
242 return str != null && str.length() > 0 ? str.toCharArray() : EMPTY;
243 }
244
245 static char[] anyOrChars(char[] str) {
246 return str.length > 1 || (str.length == 1 && str[0] != '*') ? str : ANY;
247 }
248
249 static char[][] tokenizeAndSplit(String pattern) {
250 String[] stokens = pattern.split(":");
251 char[][] tokens = new char[stokens.length][];
252 for (int i = 0; i < stokens.length; i++) {
253 tokens[i] = emptyOrChars(stokens[i]);
254 }
255 return tokens;
256 }
257
258 @SuppressWarnings("InnerAssignment")
259 static boolean match(char[] patArr, char[] strArr, boolean isVersion) {
260 int patIdxStart = 0;
261 int patIdxEnd = patArr.length - 1;
262 int strIdxStart = 0;
263 int strIdxEnd = strArr.length - 1;
264 char ch;
265
266 boolean containsStar = false;
267 for (char aPatArr : patArr) {
268 if (aPatArr == '*') {
269 containsStar = true;
270 break;
271 }
272 }
273
274 if (!containsStar) {
275 if (isVersion && (patArr[0] == '[' || patArr[0] == '(')) {
276 return isVersionIncludedInRange(String.valueOf(strArr), String.valueOf(patArr));
277 }
278
279 if (patIdxEnd != strIdxEnd) {
280 return false;
281 }
282 for (int i = 0; i <= patIdxEnd; i++) {
283 ch = patArr[i];
284 if (ch != '?' && ch != strArr[i]) {
285 return false;
286 }
287 }
288 return true;
289 }
290
291 if (patIdxEnd == 0) {
292 return true;
293 }
294
295
296 while ((ch = patArr[patIdxStart]) != '*' && strIdxStart <= strIdxEnd) {
297 if (ch != '?' && ch != strArr[strIdxStart]) {
298 return false;
299 }
300 patIdxStart++;
301 strIdxStart++;
302 }
303 if (strIdxStart > strIdxEnd) {
304
305
306 for (int i = patIdxStart; i <= patIdxEnd; i++) {
307 if (patArr[i] != '*') {
308 return false;
309 }
310 }
311 return true;
312 }
313
314
315 while ((ch = patArr[patIdxEnd]) != '*' && strIdxStart <= strIdxEnd) {
316 if (ch != '?' && ch != strArr[strIdxEnd]) {
317 return false;
318 }
319 patIdxEnd--;
320 strIdxEnd--;
321 }
322 if (strIdxStart > strIdxEnd) {
323
324
325 for (int i = patIdxStart; i <= patIdxEnd; i++) {
326 if (patArr[i] != '*') {
327 return false;
328 }
329 }
330 return true;
331 }
332
333
334
335 while (patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd) {
336 int patIdxTmp = -1;
337 for (int i = patIdxStart + 1; i <= patIdxEnd; i++) {
338 if (patArr[i] == '*') {
339 patIdxTmp = i;
340 break;
341 }
342 }
343 if (patIdxTmp == patIdxStart + 1) {
344
345 patIdxStart++;
346 continue;
347 }
348
349
350 int patLength = (patIdxTmp - patIdxStart - 1);
351 int strLength = (strIdxEnd - strIdxStart + 1);
352 int foundIdx = -1;
353 strLoop:
354 for (int i = 0; i <= strLength - patLength; i++) {
355 for (int j = 0; j < patLength; j++) {
356 ch = patArr[patIdxStart + j + 1];
357 if (ch != '?' && ch != strArr[strIdxStart + i + j]) {
358 continue strLoop;
359 }
360 }
361
362 foundIdx = strIdxStart + i;
363 break;
364 }
365
366 if (foundIdx == -1) {
367 return false;
368 }
369
370 patIdxStart = patIdxTmp;
371 strIdxStart = foundIdx + patLength;
372 }
373
374
375
376 for (int i = patIdxStart; i <= patIdxEnd; i++) {
377 if (patArr[i] != '*') {
378 return false;
379 }
380 }
381 return true;
382 }
383
384 static boolean isVersionIncludedInRange(final String version, final String range) {
385 try {
386 return VersionRange.createFromVersionSpec(range).containsVersion(new DefaultArtifactVersion(version));
387 } catch (final InvalidVersionSpecificationException e) {
388 return false;
389 }
390 }
391
392 static Pattern compile(String pattern) {
393 if (pattern.startsWith("!")) {
394 return new NegativePattern(pattern, compile(pattern.substring(1)));
395 } else {
396 char[][] stokens = tokenizeAndSplit(pattern);
397 char[][] tokens = new char[stokens.length][];
398 for (int i = 0; i < stokens.length; i++) {
399 tokens[i] = anyOrChars(stokens[i]);
400 }
401 if (tokens.length > 5) {
402 throw new IllegalArgumentException("Invalid pattern: " + pattern);
403 }
404
405
406
407
408
409 if (tokens.length == 1) {
410 if (tokens[0] == ANY) {
411
412 return all(pattern);
413 } else {
414
415 return match(pattern, tokens[0], 0);
416 }
417 }
418 if (tokens.length == 2) {
419 if (tokens[0] == ANY) {
420 if (tokens[1] == ANY) {
421
422 return all(pattern);
423 } else {
424
425 return match(pattern, tokens[1], 0, 3);
426 }
427 } else {
428 if (tokens[1] == ANY) {
429
430 return match(pattern, tokens[0], 0);
431 } else {
432
433 Pattern m00 = match(tokens[0], 0);
434 Pattern m11 = match(tokens[1], 1);
435 return and(pattern, m00, m11);
436 }
437 }
438 }
439 if (tokens.length == 3) {
440 if (tokens[0] == ANY) {
441 if (tokens[1] == ANY) {
442 if (tokens[2] == ANY) {
443
444 return all(pattern);
445 } else {
446
447 return match(pattern, tokens[2], 2, 3);
448 }
449 } else {
450 if (tokens[2] == ANY) {
451
452 return match(pattern, tokens[1], 1, 2);
453 } else {
454
455 Pattern m11 = match(tokens[1], 1);
456 Pattern m12 = match(tokens[1], 2);
457 Pattern m22 = match(tokens[2], 2);
458 Pattern m23 = match(tokens[2], 3);
459 return or(pattern, and(m11, m22), and(m12, m23));
460 }
461 }
462 } else {
463 if (tokens[1] == ANY) {
464 if (tokens[2] == ANY) {
465
466 return match(pattern, tokens[0], 0, 1);
467 } else {
468
469 Pattern m00 = match(tokens[0], 0);
470 Pattern m223 = match(tokens[2], 2, 3);
471 return and(pattern, m00, m223);
472 }
473 } else {
474 if (tokens[2] == ANY) {
475
476 Pattern m00 = match(tokens[0], 0);
477 Pattern m11 = match(tokens[1], 1);
478 return and(pattern, m00, m11);
479 } else {
480
481 Pattern m00 = match(tokens[0], 0);
482 Pattern m11 = match(tokens[1], 1);
483 Pattern m22 = match(tokens[2], 2);
484 return and(pattern, m00, m11, m22);
485 }
486 }
487 }
488 }
489 if (tokens.length >= 4) {
490 List<Pattern> patterns = new ArrayList<>();
491 for (int i = 0; i < tokens.length; i++) {
492 if (tokens[i] != ANY) {
493 patterns.add(match(tokens[i], i));
494 }
495 }
496 return and(pattern, patterns.toArray(new Pattern[0]));
497 }
498 throw new IllegalStateException();
499 }
500 }
501
502
503 private static Pattern match(String pattern, char[] token, int posVal) {
504 return match(pattern, token, posVal, posVal);
505 }
506
507
508 private static Pattern match(char[] token, int posVal) {
509 return match("", token, posVal, posVal);
510 }
511
512
513 private static Pattern match(String pattern, char[] token, int posMin, int posMax) {
514 boolean hasWildcard = false;
515 for (char ch : token) {
516 if (ch == '*' || ch == '?') {
517 hasWildcard = true;
518 break;
519 }
520 }
521 if (hasWildcard || posMax == 4) {
522 return new PosPattern(pattern, token, posMin, posMax);
523 } else {
524 return new EqPattern(pattern, token, posMin, posMax);
525 }
526 }
527
528
529 private static Pattern match(char[] token, int posMin, int posMax) {
530 return new PosPattern("", token, posMin, posMax);
531 }
532
533
534 private static Pattern and(String pattern, Pattern... patterns) {
535 return new AndPattern(pattern, patterns);
536 }
537
538
539 private static Pattern and(Pattern... patterns) {
540 return and("", patterns);
541 }
542
543
544 private static Pattern or(String pattern, Pattern... patterns) {
545 return new OrPattern(pattern, patterns);
546 }
547
548
549 private static Pattern or(Pattern... patterns) {
550 return or("", patterns);
551 }
552
553
554 private static Pattern all(String pattern) {
555 return new MatchAllPattern(pattern);
556 }
557
558
559
560
561 abstract static class Pattern {
562 private final String pattern;
563
564 Pattern(String pattern) {
565 this.pattern = Objects.requireNonNull(pattern);
566 }
567
568 public abstract boolean matches(char[][] parts);
569
570
571
572
573
574 public String translateEquals() {
575 return null;
576 }
577
578
579
580
581 protected String translateEquals(int pos) {
582 return null;
583 }
584
585 @Override
586 public String toString() {
587 return pattern;
588 }
589 }
590
591
592
593
594 static class AndPattern extends Pattern {
595 private final Pattern[] patterns;
596
597 AndPattern(String pattern, Pattern[] patterns) {
598 super(pattern);
599 this.patterns = patterns;
600 }
601
602 @Override
603 public boolean matches(char[][] parts) {
604 for (Pattern pattern : patterns) {
605 if (!pattern.matches(parts)) {
606 return false;
607 }
608 }
609 return true;
610 }
611
612 @Override
613 public String translateEquals() {
614 String[] strings = new String[patterns.length];
615 for (int i = 0; i < patterns.length; i++) {
616 strings[i] = patterns[i].translateEquals(i);
617 if (strings[i] == null) {
618 return null;
619 }
620 }
621 StringBuilder sb = new StringBuilder();
622 for (int i = 0; i < strings.length; i++) {
623 if (i > 0) {
624 sb.append(":");
625 }
626 sb.append(strings[i]);
627 }
628 return sb.toString();
629 }
630 }
631
632
633
634
635 static class OrPattern extends Pattern {
636 private final Pattern[] patterns;
637
638 OrPattern(String pattern, Pattern[] patterns) {
639 super(pattern);
640 this.patterns = patterns;
641 }
642
643 @Override
644 public boolean matches(char[][] parts) {
645 for (Pattern pattern : patterns) {
646 if (pattern.matches(parts)) {
647 return true;
648 }
649 }
650 return false;
651 }
652 }
653
654
655
656
657
658
659 static class PosPattern extends Pattern {
660 private final char[] patternCharArray;
661 private final int posMin;
662 private final int posMax;
663
664 PosPattern(String pattern, char[] patternCharArray, int posMin, int posMax) {
665 super(pattern);
666 this.patternCharArray = patternCharArray;
667 this.posMin = posMin;
668 this.posMax = posMax;
669 }
670
671 @Override
672 public boolean matches(char[][] parts) {
673 for (int i = posMin; i <= posMax; i++) {
674 if (match(patternCharArray, parts[i], i == 4)) {
675 return true;
676 }
677 }
678 return false;
679 }
680 }
681
682
683
684
685
686 static class EqPattern extends Pattern {
687 private final char[] token;
688 private final int posMin;
689 private final int posMax;
690
691 EqPattern(String pattern, char[] patternCharArray, int posMin, int posMax) {
692 super(pattern);
693 this.token = patternCharArray;
694 this.posMin = posMin;
695 this.posMax = posMax;
696 }
697
698 @Override
699 public boolean matches(char[][] parts) {
700 for (int i = posMin; i <= posMax; i++) {
701 if (Arrays.equals(token, parts[i])) {
702 return true;
703 }
704 }
705 return false;
706 }
707
708 @Override
709 public String translateEquals() {
710 return translateEquals(0);
711 }
712
713 public String translateEquals(int pos) {
714 return posMin == pos && posMax == pos && (pos < 3 || (token[0] != '[' && token[0] != '('))
715 ? String.valueOf(token)
716 : null;
717 }
718 }
719
720
721
722
723 static class MatchAllPattern extends Pattern {
724 MatchAllPattern(String pattern) {
725 super(pattern);
726 }
727
728 @Override
729 public boolean matches(char[][] parts) {
730 return true;
731 }
732 }
733
734
735
736
737 static class NegativePattern extends Pattern {
738 private final Pattern inner;
739
740 NegativePattern(String pattern, Pattern inner) {
741 super(pattern);
742 this.inner = inner;
743 }
744
745 @Override
746 public boolean matches(char[][] parts) {
747 return inner.matches(parts);
748 }
749 }
750 }