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