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