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