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