1 package org.apache.maven.shared.utils.io;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import java.io.File;
23 import java.util.ArrayList;
24 import java.util.List;
25 import java.util.StringTokenizer;
26
27 import javax.annotation.Nonnull;
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43 public final class SelectorUtils
44 {
45
46 private static final String PATTERN_HANDLER_PREFIX = "[";
47
48 public static final String PATTERN_HANDLER_SUFFIX = "]";
49
50 public static final String REGEX_HANDLER_PREFIX = "%regex" + PATTERN_HANDLER_PREFIX;
51
52 public static final String ANT_HANDLER_PREFIX = "%ant" + PATTERN_HANDLER_PREFIX;
53
54
55
56
57 private SelectorUtils()
58 {
59 }
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76 public static boolean matchPatternStart( String pattern, String str )
77 {
78 return matchPatternStart( pattern, str, true );
79 }
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98 public static boolean matchPatternStart( String pattern, String str, boolean isCaseSensitive )
99 {
100 if ( isRegexPrefixedPattern( pattern ) )
101 {
102
103
104 return true;
105 }
106 else
107 {
108 if ( isAntPrefixedPattern( pattern ) )
109 {
110 pattern = pattern.substring( ANT_HANDLER_PREFIX.length(),
111 pattern.length() - PATTERN_HANDLER_SUFFIX.length() );
112 }
113
114 String altPattern = pattern.replace( '\\', '/' );
115 String altStr = str.replace( '\\', '/' );
116
117 return matchAntPathPatternStart( altPattern, altStr, "/", isCaseSensitive );
118 }
119 }
120
121 private static boolean matchAntPathPatternStart( String pattern, String str, String separator,
122 boolean isCaseSensitive )
123 {
124
125
126
127
128 if ( separatorPatternStartSlashMismatch( pattern, str, separator ) )
129 {
130 return false;
131 }
132
133 List<String> patDirs = tokenizePath( pattern, separator );
134 List<String> strDirs = tokenizePath( str, separator );
135
136 int patIdxStart = 0;
137 int patIdxEnd = patDirs.size() - 1;
138 int strIdxStart = 0;
139 int strIdxEnd = strDirs.size() - 1;
140
141
142 while ( patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd )
143 {
144 String patDir = patDirs.get( patIdxStart );
145 if ( "**".equals( patDir ) )
146 {
147 break;
148 }
149 if ( !match( patDir, strDirs.get( strIdxStart ), isCaseSensitive ) )
150 {
151 return false;
152 }
153 patIdxStart++;
154 strIdxStart++;
155 }
156
157 return strIdxStart > strIdxEnd || patIdxStart <= patIdxEnd;
158 }
159
160
161
162
163
164
165
166
167
168
169
170 public static boolean matchPath( String pattern, String str )
171 {
172 return matchPath( pattern, str, true );
173 }
174
175
176
177
178
179
180
181
182
183
184
185
186
187 public static boolean matchPath( String pattern, String str, boolean isCaseSensitive )
188 {
189 if ( pattern.length() > ( REGEX_HANDLER_PREFIX.length() + PATTERN_HANDLER_SUFFIX.length() + 1 )
190 && pattern.startsWith( REGEX_HANDLER_PREFIX ) && pattern.endsWith( PATTERN_HANDLER_SUFFIX ) )
191 {
192 pattern =
193 pattern.substring( REGEX_HANDLER_PREFIX.length(), pattern.length() - PATTERN_HANDLER_SUFFIX.length() );
194
195 return str.matches( pattern );
196 }
197 else
198 {
199 if ( pattern.length() > ( ANT_HANDLER_PREFIX.length() + PATTERN_HANDLER_SUFFIX.length() + 1 )
200 && pattern.startsWith( ANT_HANDLER_PREFIX ) && pattern.endsWith( PATTERN_HANDLER_SUFFIX ) )
201 {
202 pattern = pattern.substring( ANT_HANDLER_PREFIX.length(),
203 pattern.length() - PATTERN_HANDLER_SUFFIX.length() );
204 }
205
206 return matchAntPathPattern( pattern, str, isCaseSensitive );
207 }
208 }
209
210 private static boolean matchAntPathPattern( String pattern, String str, boolean isCaseSensitive )
211 {
212
213
214
215
216 if ( str.startsWith( File.separator ) != pattern.startsWith( File.separator ) )
217 {
218 return false;
219 }
220
221 List<String> patDirs = tokenizePath( pattern, File.separator );
222 List<String> strDirs = tokenizePath( str, File.separator );
223
224 int patIdxStart = 0;
225 int patIdxEnd = patDirs.size() - 1;
226 int strIdxStart = 0;
227 int strIdxEnd = strDirs.size() - 1;
228
229
230 while ( patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd )
231 {
232 String patDir = patDirs.get( patIdxStart );
233 if ( "**".equals( patDir ) )
234 {
235 break;
236 }
237 if ( !match( patDir, strDirs.get( strIdxStart ), isCaseSensitive ) )
238 {
239 return false;
240 }
241 patIdxStart++;
242 strIdxStart++;
243 }
244 if ( strIdxStart > strIdxEnd )
245 {
246
247 for ( int i = patIdxStart; i <= patIdxEnd; i++ )
248 {
249 if ( !"**".equals( patDirs.get( i ) ) )
250 {
251 return false;
252 }
253 }
254 return true;
255 }
256 else
257 {
258 if ( patIdxStart > patIdxEnd )
259 {
260
261 return false;
262 }
263 }
264
265
266 while ( patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd )
267 {
268 String patDir = patDirs.get( patIdxEnd );
269 if ( "**".equals( patDir ) )
270 {
271 break;
272 }
273 if ( !match( patDir, strDirs.get( strIdxEnd ), isCaseSensitive ) )
274 {
275 return false;
276 }
277 patIdxEnd--;
278 strIdxEnd--;
279 }
280 if ( strIdxStart > strIdxEnd )
281 {
282
283 for ( int i = patIdxStart; i <= patIdxEnd; i++ )
284 {
285 if ( !"**".equals( patDirs.get( i ) ) )
286 {
287 return false;
288 }
289 }
290 return true;
291 }
292
293 while ( patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd )
294 {
295 int patIdxTmp = -1;
296 for ( int i = patIdxStart + 1; i <= patIdxEnd; i++ )
297 {
298 if ( "**".equals( patDirs.get( i ) ) )
299 {
300 patIdxTmp = i;
301 break;
302 }
303 }
304 if ( patIdxTmp == patIdxStart + 1 )
305 {
306
307 patIdxStart++;
308 continue;
309 }
310
311
312 int patLength = ( patIdxTmp - patIdxStart - 1 );
313 int strLength = ( strIdxEnd - strIdxStart + 1 );
314 int foundIdx = -1;
315 strLoop:
316 for ( int i = 0; i <= strLength - patLength; i++ )
317 {
318 for ( int j = 0; j < patLength; j++ )
319 {
320 String subPat = patDirs.get( patIdxStart + j + 1 );
321 String subStr = strDirs.get( strIdxStart + i + j );
322 if ( !match( subPat, subStr, isCaseSensitive ) )
323 {
324 continue strLoop;
325 }
326 }
327
328 foundIdx = strIdxStart + i;
329 break;
330 }
331
332 if ( foundIdx == -1 )
333 {
334 return false;
335 }
336
337 patIdxStart = patIdxTmp;
338 strIdxStart = foundIdx + patLength;
339 }
340
341 for ( int i = patIdxStart; i <= patIdxEnd; i++ )
342 {
343 if ( !"**".equals( patDirs.get( i ) ) )
344 {
345 return false;
346 }
347 }
348
349 return true;
350 }
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365 public static boolean match( String pattern, String str )
366 {
367 return match( pattern, str, true );
368 }
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385 public static boolean match( String pattern, String str, boolean isCaseSensitive )
386 {
387 char[] patArr = pattern.toCharArray();
388 char[] strArr = str.toCharArray();
389 int patIdxStart = 0;
390 int patIdxEnd = patArr.length - 1;
391 int strIdxStart = 0;
392 int strIdxEnd = strArr.length - 1;
393 char ch;
394
395 boolean containsStar = false;
396 for ( char aPatArr : patArr )
397 {
398 if ( aPatArr == '*' )
399 {
400 containsStar = true;
401 break;
402 }
403 }
404
405 if ( !containsStar )
406 {
407
408 if ( patIdxEnd != strIdxEnd )
409 {
410 return false;
411 }
412 for ( int i = 0; i <= patIdxEnd; i++ )
413 {
414 ch = patArr[i];
415 if ( ch != '?' && !equals( ch, strArr[i], isCaseSensitive ) )
416 {
417 return false;
418 }
419 }
420 return true;
421 }
422
423 if ( patIdxEnd == 0 )
424 {
425 return true;
426 }
427
428
429 while ( ( ch = patArr[patIdxStart] ) != '*' && strIdxStart <= strIdxEnd )
430 {
431 if ( ch != '?' && !equals( ch, strArr[strIdxStart], isCaseSensitive ) )
432 {
433 return false;
434 }
435 patIdxStart++;
436 strIdxStart++;
437 }
438 if ( strIdxStart > strIdxEnd )
439 {
440
441
442 for ( int i = patIdxStart; i <= patIdxEnd; i++ )
443 {
444 if ( patArr[i] != '*' )
445 {
446 return false;
447 }
448 }
449 return true;
450 }
451
452
453 while ( ( ch = patArr[patIdxEnd] ) != '*' && strIdxStart <= strIdxEnd )
454 {
455 if ( ch != '?' && !equals( ch, strArr[strIdxEnd], isCaseSensitive ) )
456 {
457 return false;
458 }
459 patIdxEnd--;
460 strIdxEnd--;
461 }
462 if ( strIdxStart > strIdxEnd )
463 {
464
465
466 for ( int i = patIdxStart; i <= patIdxEnd; i++ )
467 {
468 if ( patArr[i] != '*' )
469 {
470 return false;
471 }
472 }
473 return true;
474 }
475
476
477
478 while ( patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd )
479 {
480 int patIdxTmp = -1;
481 for ( int i = patIdxStart + 1; i <= patIdxEnd; i++ )
482 {
483 if ( patArr[i] == '*' )
484 {
485 patIdxTmp = i;
486 break;
487 }
488 }
489 if ( patIdxTmp == patIdxStart + 1 )
490 {
491
492 patIdxStart++;
493 continue;
494 }
495
496
497 int patLength = ( patIdxTmp - patIdxStart - 1 );
498 int strLength = ( strIdxEnd - strIdxStart + 1 );
499 int foundIdx = -1;
500 strLoop:
501 for ( int i = 0; i <= strLength - patLength; i++ )
502 {
503 for ( int j = 0; j < patLength; j++ )
504 {
505 ch = patArr[patIdxStart + j + 1];
506 if ( ch != '?' && !equals( ch, strArr[strIdxStart + i + j], isCaseSensitive ) )
507 {
508 continue strLoop;
509 }
510 }
511
512 foundIdx = strIdxStart + i;
513 break;
514 }
515
516 if ( foundIdx == -1 )
517 {
518 return false;
519 }
520
521 patIdxStart = patIdxTmp;
522 strIdxStart = foundIdx + patLength;
523 }
524
525
526
527 for ( int i = patIdxStart; i <= patIdxEnd; i++ )
528 {
529 if ( patArr[i] != '*' )
530 {
531 return false;
532 }
533 }
534 return true;
535 }
536
537
538
539
540 private static boolean equals( char c1, char c2, boolean isCaseSensitive )
541 {
542 if ( c1 == c2 )
543 {
544 return true;
545 }
546 if ( !isCaseSensitive )
547 {
548
549 if ( Character.toUpperCase( c1 ) == Character.toUpperCase( c2 )
550 || Character.toLowerCase( c1 ) == Character.toLowerCase( c2 ) )
551 {
552 return true;
553 }
554 }
555 return false;
556 }
557
558
559
560
561
562
563
564
565
566 private static List<String> tokenizePath( String path, String separator )
567 {
568 List<String> ret = new ArrayList<String>();
569 StringTokenizer st = new StringTokenizer( path, separator );
570 while ( st.hasMoreTokens() )
571 {
572 ret.add( st.nextToken() );
573 }
574 return ret;
575 }
576
577
578 static boolean matchAntPathPatternStart( @Nonnull MatchPattern pattern,
579 @Nonnull String str,
580 @Nonnull String separator,
581 boolean isCaseSensitive )
582 {
583 return !separatorPatternStartSlashMismatch( pattern, str, separator )
584 && matchAntPathPatternStart( pattern.getTokenizedPathString(), str, separator, isCaseSensitive );
585 }
586
587 private static String[] tokenizePathToString( @Nonnull String path, @Nonnull String separator )
588 {
589 List<String> ret = new ArrayList<String>();
590 StringTokenizer st = new StringTokenizer( path, separator );
591 while ( st.hasMoreTokens() )
592 {
593 ret.add( st.nextToken() );
594 }
595 return ret.toArray( new String[ret.size()] );
596 }
597
598 private static boolean matchAntPathPatternStart( @Nonnull String[] patDirs,
599 @Nonnull String str,
600 @Nonnull String separator,
601 boolean isCaseSensitive )
602 {
603 String[] strDirs = tokenizePathToString( str, separator );
604 return matchAntPathPatternStart( patDirs, strDirs, isCaseSensitive );
605 }
606
607 private static boolean matchAntPathPatternStart( @Nonnull String[] patDirs,
608 @Nonnull String[] tokenizedFileName,
609 boolean isCaseSensitive )
610 {
611
612 int patIdxStart = 0;
613 int patIdxEnd = patDirs.length - 1;
614 int strIdxStart = 0;
615 int strIdxEnd = tokenizedFileName.length - 1;
616
617
618 while ( patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd )
619 {
620 String patDir = patDirs[patIdxStart];
621 if ( patDir.equals( "**" ) )
622 {
623 break;
624 }
625 if ( !match( patDir, tokenizedFileName[strIdxStart], isCaseSensitive ) )
626 {
627 return false;
628 }
629 patIdxStart++;
630 strIdxStart++;
631 }
632
633 return strIdxStart > strIdxEnd || patIdxStart <= patIdxEnd;
634 }
635
636 private static boolean separatorPatternStartSlashMismatch( @Nonnull MatchPattern matchPattern, @Nonnull String str,
637 @Nonnull String separator )
638 {
639 return str.startsWith( separator ) != matchPattern.startsWith( separator );
640 }
641
642 private static boolean separatorPatternStartSlashMismatch( String pattern, String str, String separator )
643 {
644 return str.startsWith( separator ) != pattern.startsWith( separator );
645 }
646
647
648 static boolean matchAntPathPattern( String[] patDirs, String[] strDirs, boolean isCaseSensitive )
649 {
650 int patIdxStart = 0;
651 int patIdxEnd = patDirs.length - 1;
652 int strIdxStart = 0;
653 int strIdxEnd = strDirs.length - 1;
654
655
656 while ( patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd )
657 {
658 String patDir = patDirs[patIdxStart];
659 if ( patDir.equals( "**" ) )
660 {
661 break;
662 }
663 if ( !match( patDir, strDirs[strIdxStart], isCaseSensitive ) )
664 {
665 return false;
666 }
667 patIdxStart++;
668 strIdxStart++;
669 }
670 if ( strIdxStart > strIdxEnd )
671 {
672
673 for ( int i = patIdxStart; i <= patIdxEnd; i++ )
674 {
675 if ( !patDirs[i].equals( "**" ) )
676 {
677 return false;
678 }
679 }
680 return true;
681 }
682 else
683 {
684 if ( patIdxStart > patIdxEnd )
685 {
686
687 return false;
688 }
689 }
690
691
692 while ( patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd )
693 {
694 String patDir = patDirs[patIdxEnd];
695 if ( patDir.equals( "**" ) )
696 {
697 break;
698 }
699 if ( !match( patDir, strDirs[strIdxEnd], isCaseSensitive ) )
700 {
701 return false;
702 }
703 patIdxEnd--;
704 strIdxEnd--;
705 }
706 if ( strIdxStart > strIdxEnd )
707 {
708
709 for ( int i = patIdxStart; i <= patIdxEnd; i++ )
710 {
711 if ( !patDirs[i].equals( "**" ) )
712 {
713 return false;
714 }
715 }
716 return true;
717 }
718
719 while ( patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd )
720 {
721 int patIdxTmp = -1;
722 for ( int i = patIdxStart + 1; i <= patIdxEnd; i++ )
723 {
724 if ( patDirs[i].equals( "**" ) )
725 {
726 patIdxTmp = i;
727 break;
728 }
729 }
730 if ( patIdxTmp == patIdxStart + 1 )
731 {
732
733 patIdxStart++;
734 continue;
735 }
736
737
738 int patLength = ( patIdxTmp - patIdxStart - 1 );
739 int strLength = ( strIdxEnd - strIdxStart + 1 );
740 int foundIdx = -1;
741 strLoop:
742 for ( int i = 0; i <= strLength - patLength; i++ )
743 {
744 for ( int j = 0; j < patLength; j++ )
745 {
746 String subPat = patDirs[patIdxStart + j + 1];
747 String subStr = strDirs[strIdxStart + i + j];
748 if ( !match( subPat, subStr, isCaseSensitive ) )
749 {
750 continue strLoop;
751 }
752 }
753
754 foundIdx = strIdxStart + i;
755 break;
756 }
757
758 if ( foundIdx == -1 )
759 {
760 return false;
761 }
762
763 patIdxStart = patIdxTmp;
764 strIdxStart = foundIdx + patLength;
765 }
766
767 for ( int i = patIdxStart; i <= patIdxEnd; i++ )
768 {
769 if ( !patDirs[i].equals( "**" ) )
770 {
771 return false;
772 }
773 }
774
775 return true;
776 }
777
778 static boolean isRegexPrefixedPattern( String pattern )
779 {
780 return pattern.length() > ( REGEX_HANDLER_PREFIX.length() + PATTERN_HANDLER_SUFFIX.length() + 1 )
781 && pattern.startsWith( REGEX_HANDLER_PREFIX ) && pattern.endsWith( PATTERN_HANDLER_SUFFIX );
782 }
783
784 static boolean isAntPrefixedPattern( String pattern )
785 {
786 return pattern.length() > ( ANT_HANDLER_PREFIX.length() + PATTERN_HANDLER_SUFFIX.length() + 1 )
787 && pattern.startsWith( ANT_HANDLER_PREFIX ) && pattern.endsWith( PATTERN_HANDLER_SUFFIX );
788 }
789
790 static boolean matchAntPathPattern( @Nonnull MatchPattern matchPattern, @Nonnull String str, @Nonnull String separator,
791 boolean isCaseSensitive )
792 {
793 if ( separatorPatternStartSlashMismatch( matchPattern, str, separator ) )
794 {
795 return false;
796 }
797 String[] patDirs = matchPattern.getTokenizedPathString();
798 String[] strDirs = tokenizePathToString( str, separator );
799 return matchAntPathPattern( patDirs, strDirs, isCaseSensitive );
800 }
801 }