1 package org.apache.maven.shared.utils.io;
2
3 /*
4 * Licensed to the Apache Software Foundation (ASF) under one
5 * or more contributor license agreements. See the NOTICE file
6 * distributed with this work for additional information
7 * regarding copyright ownership. The ASF licenses this file
8 * to you under the Apache License, Version 2.0 (the
9 * "License"); you may not use this file except in compliance
10 * with the License. You may obtain a copy of the License at
11 *
12 * http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing,
15 * software distributed under the License is distributed on an
16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17 * KIND, either express or implied. See the License for the
18 * specific language governing permissions and limitations
19 * under the License.
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 * <p>This is a utility class used by selectors and DirectoryScanner. The
31 * functionality more properly belongs just to selectors, but unfortunately
32 * DirectoryScanner exposed these as protected methods. Thus we have to
33 * support any subclasses of DirectoryScanner that may access these methods.
34 * </p>
35 * <p>This is a Singleton.</p>
36 *
37 * @author Arnout J. Kuiper
38 * <a href="mailto:ajkuiper@wxs.nl">ajkuiper@wxs.nl</a>
39 * @author Magesh Umasankar
40 * @author <a href="mailto:bruce@callenish.com">Bruce Atherton</a>
41 *
42 */
43 public final class SelectorUtils
44 {
45
46 /**
47 * Pattern handler prefix.
48 */
49 private static final String PATTERN_HANDLER_PREFIX = "[";
50
51 /**
52 * Pattern handler suffix.
53 */
54 public static final String PATTERN_HANDLER_SUFFIX = "]";
55
56 /**
57 * Regex start pattern.
58 */
59 public static final String REGEX_HANDLER_PREFIX = "%regex" + PATTERN_HANDLER_PREFIX;
60
61 /**
62 * ANT pattern prefix.
63 */
64 public static final String ANT_HANDLER_PREFIX = "%ant" + PATTERN_HANDLER_PREFIX;
65
66 /**
67 * Private Constructor
68 */
69 private SelectorUtils()
70 {
71 }
72
73 /**
74 * Tests whether or not a given path matches the start of a given
75 * pattern up to the first "**".
76 * <p/>
77 * This is not a general purpose test and should only be used if you
78 * can live with false positives. For example, <code>pattern=**\a</code>
79 * and <code>str=b</code> will yield <code>true</code>.
80 *
81 * @param pattern The pattern to match against. Must not be
82 * <code>null</code>.
83 * @param str The path to match, as a String. Must not be
84 * <code>null</code>.
85 * @return whether or not a given path matches the start of a given
86 * pattern up to the first "**".
87 */
88 public static boolean matchPatternStart( String pattern, String str )
89 {
90 return matchPatternStart( pattern, str, true );
91 }
92
93 /**
94 * Tests whether or not a given path matches the start of a given
95 * pattern up to the first "**".
96 * <p/>
97 * This is not a general purpose test and should only be used if you
98 * can live with false positives. For example, <code>pattern=**\a</code>
99 * and <code>str=b</code> will yield <code>true</code>.
100 *
101 * @param pattern The pattern to match against. Must not be
102 * <code>null</code>.
103 * @param str The path to match, as a String. Must not be
104 * <code>null</code>.
105 * @param isCaseSensitive Whether or not matching should be performed
106 * case sensitively.
107 * @return whether or not a given path matches the start of a given
108 * pattern up to the first "**".
109 */
110 public static boolean matchPatternStart( String pattern, String str, boolean isCaseSensitive )
111 {
112 if ( isRegexPrefixedPattern( pattern ) )
113 {
114 // FIXME: ICK! But we can't do partial matches for regex, so we have to reserve judgement until we have
115 // a file to deal with, or we can definitely say this is an exclusion...
116 return true;
117 }
118 else
119 {
120 if ( isAntPrefixedPattern( pattern ) )
121 {
122 pattern = pattern.substring( ANT_HANDLER_PREFIX.length(),
123 pattern.length() - PATTERN_HANDLER_SUFFIX.length() );
124 }
125
126 String altPattern = pattern.replace( '\\', '/' );
127 String altStr = str.replace( '\\', '/' );
128
129 return matchAntPathPatternStart( altPattern, altStr, "/", isCaseSensitive );
130 }
131 }
132
133 private static boolean matchAntPathPatternStart( String pattern, String str, String separator,
134 boolean isCaseSensitive )
135 {
136 // When str starts with a File.separator, pattern has to start with a
137 // File.separator.
138 // When pattern starts with a File.separator, str has to start with a
139 // File.separator.
140 if ( separatorPatternStartSlashMismatch( pattern, str, separator ) )
141 {
142 return false;
143 }
144
145 List<String> patDirs = tokenizePath( pattern, separator );
146 List<String> strDirs = tokenizePath( str, separator );
147
148 int patIdxStart = 0;
149 int patIdxEnd = patDirs.size() - 1;
150 int strIdxStart = 0;
151 int strIdxEnd = strDirs.size() - 1;
152
153 // up to first '**'
154 while ( patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd )
155 {
156 String patDir = patDirs.get( patIdxStart );
157 if ( "**".equals( patDir ) )
158 {
159 break;
160 }
161 if ( !match( patDir, strDirs.get( strIdxStart ), isCaseSensitive ) )
162 {
163 return false;
164 }
165 patIdxStart++;
166 strIdxStart++;
167 }
168
169 return strIdxStart > strIdxEnd || patIdxStart <= patIdxEnd;
170 }
171
172 /**
173 * Tests whether or not a given path matches a given pattern.
174 *
175 * @param pattern The pattern to match against. Must not be
176 * <code>null</code>.
177 * @param str The path to match, as a String. Must not be
178 * <code>null</code>.
179 * @return <code>true</code> if the pattern matches against the string,
180 * or <code>false</code> otherwise.
181 */
182 public static boolean matchPath( String pattern, String str )
183 {
184 return matchPath( pattern, str, true );
185 }
186
187 /**
188 * Tests whether or not a given path matches a given pattern.
189 *
190 * @param pattern The pattern to match against. Must not be
191 * <code>null</code>.
192 * @param str The path to match, as a String. Must not be
193 * <code>null</code>.
194 * @param isCaseSensitive Whether or not matching should be performed
195 * case sensitively.
196 * @return <code>true</code> if the pattern matches against the string,
197 * or <code>false</code> otherwise.
198 */
199 public static boolean matchPath( String pattern, String str, boolean isCaseSensitive )
200 {
201 if ( pattern.length() > ( REGEX_HANDLER_PREFIX.length() + PATTERN_HANDLER_SUFFIX.length() + 1 )
202 && pattern.startsWith( REGEX_HANDLER_PREFIX ) && pattern.endsWith( PATTERN_HANDLER_SUFFIX ) )
203 {
204 pattern =
205 pattern.substring( REGEX_HANDLER_PREFIX.length(), pattern.length() - PATTERN_HANDLER_SUFFIX.length() );
206
207 return str.matches( pattern );
208 }
209 else
210 {
211 if ( pattern.length() > ( ANT_HANDLER_PREFIX.length() + PATTERN_HANDLER_SUFFIX.length() + 1 )
212 && pattern.startsWith( ANT_HANDLER_PREFIX ) && pattern.endsWith( PATTERN_HANDLER_SUFFIX ) )
213 {
214 pattern = pattern.substring( ANT_HANDLER_PREFIX.length(),
215 pattern.length() - PATTERN_HANDLER_SUFFIX.length() );
216 }
217
218 return matchAntPathPattern( pattern, str, isCaseSensitive );
219 }
220 }
221
222 private static boolean matchAntPathPattern( String pattern, String str, boolean isCaseSensitive )
223 {
224 // When str starts with a File.separator, pattern has to start with a
225 // File.separator.
226 // When pattern starts with a File.separator, str has to start with a
227 // File.separator.
228 if ( str.startsWith( File.separator ) != pattern.startsWith( File.separator ) )
229 {
230 return false;
231 }
232
233 List<String> patDirs = tokenizePath( pattern, File.separator );
234 List<String> strDirs = tokenizePath( str, File.separator );
235
236 int patIdxStart = 0;
237 int patIdxEnd = patDirs.size() - 1;
238 int strIdxStart = 0;
239 int strIdxEnd = strDirs.size() - 1;
240
241 // up to first '**'
242 while ( patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd )
243 {
244 String patDir = patDirs.get( patIdxStart );
245 if ( "**".equals( patDir ) )
246 {
247 break;
248 }
249 if ( !match( patDir, strDirs.get( strIdxStart ), isCaseSensitive ) )
250 {
251 return false;
252 }
253 patIdxStart++;
254 strIdxStart++;
255 }
256 if ( strIdxStart > strIdxEnd )
257 {
258 // String is exhausted
259 for ( int i = patIdxStart; i <= patIdxEnd; i++ )
260 {
261 if ( !"**".equals( patDirs.get( i ) ) )
262 {
263 return false;
264 }
265 }
266 return true;
267 }
268 else
269 {
270 if ( patIdxStart > patIdxEnd )
271 {
272 // String not exhausted, but pattern is. Failure.
273 return false;
274 }
275 }
276
277 // up to last '**'
278 while ( patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd )
279 {
280 String patDir = patDirs.get( patIdxEnd );
281 if ( "**".equals( patDir ) )
282 {
283 break;
284 }
285 if ( !match( patDir, strDirs.get( strIdxEnd ), isCaseSensitive ) )
286 {
287 return false;
288 }
289 patIdxEnd--;
290 strIdxEnd--;
291 }
292 if ( strIdxStart > strIdxEnd )
293 {
294 // String is exhausted
295 for ( int i = patIdxStart; i <= patIdxEnd; i++ )
296 {
297 if ( !"**".equals( patDirs.get( i ) ) )
298 {
299 return false;
300 }
301 }
302 return true;
303 }
304
305 while ( patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd )
306 {
307 int patIdxTmp = -1;
308 for ( int i = patIdxStart + 1; i <= patIdxEnd; i++ )
309 {
310 if ( "**".equals( patDirs.get( i ) ) )
311 {
312 patIdxTmp = i;
313 break;
314 }
315 }
316 if ( patIdxTmp == patIdxStart + 1 )
317 {
318 // '**/**' situation, so skip one
319 patIdxStart++;
320 continue;
321 }
322 // Find the pattern between padIdxStart & padIdxTmp in str between
323 // strIdxStart & strIdxEnd
324 int patLength = ( patIdxTmp - patIdxStart - 1 );
325 int strLength = ( strIdxEnd - strIdxStart + 1 );
326 int foundIdx = -1;
327 strLoop:
328 for ( int i = 0; i <= strLength - patLength; i++ )
329 {
330 for ( int j = 0; j < patLength; j++ )
331 {
332 String subPat = patDirs.get( patIdxStart + j + 1 );
333 String subStr = strDirs.get( strIdxStart + i + j );
334 if ( !match( subPat, subStr, isCaseSensitive ) )
335 {
336 continue strLoop;
337 }
338 }
339
340 foundIdx = strIdxStart + i;
341 break;
342 }
343
344 if ( foundIdx == -1 )
345 {
346 return false;
347 }
348
349 patIdxStart = patIdxTmp;
350 strIdxStart = foundIdx + patLength;
351 }
352
353 for ( int i = patIdxStart; i <= patIdxEnd; i++ )
354 {
355 if ( !"**".equals( patDirs.get( i ) ) )
356 {
357 return false;
358 }
359 }
360
361 return true;
362 }
363
364 /**
365 * Tests whether or not a string matches against a pattern.
366 * The pattern may contain two special characters:<br>
367 * '*' means zero or more characters<br>
368 * '?' means one and only one character
369 *
370 * @param pattern The pattern to match against.
371 * Must not be <code>null</code>.
372 * @param str The string which must be matched against the pattern.
373 * Must not be <code>null</code>.
374 * @return <code>true</code> if the string matches against the pattern,
375 * or <code>false</code> otherwise.
376 */
377 public static boolean match( String pattern, String str )
378 {
379 return match( pattern, str, true );
380 }
381
382 /**
383 * Tests whether or not a string matches against a pattern.
384 * The pattern may contain two special characters:<br>
385 * '*' means zero or more characters<br>
386 * '?' means one and only one character
387 *
388 * @param pattern The pattern to match against.
389 * Must not be <code>null</code>.
390 * @param str The string which must be matched against the pattern.
391 * Must not be <code>null</code>.
392 * @param isCaseSensitive Whether or not matching should be performed
393 * case sensitively.
394 * @return <code>true</code> if the string matches against the pattern,
395 * or <code>false</code> otherwise.
396 */
397 public static boolean match( String pattern, String str, boolean isCaseSensitive )
398 {
399 char[] patArr = pattern.toCharArray();
400 char[] strArr = str.toCharArray();
401 int patIdxStart = 0;
402 int patIdxEnd = patArr.length - 1;
403 int strIdxStart = 0;
404 int strIdxEnd = strArr.length - 1;
405 char ch;
406
407 boolean containsStar = false;
408 for ( char aPatArr : patArr )
409 {
410 if ( aPatArr == '*' )
411 {
412 containsStar = true;
413 break;
414 }
415 }
416
417 if ( !containsStar )
418 {
419 // No '*'s, so we make a shortcut
420 if ( patIdxEnd != strIdxEnd )
421 {
422 return false; // Pattern and string do not have the same size
423 }
424 for ( int i = 0; i <= patIdxEnd; i++ )
425 {
426 ch = patArr[i];
427 if ( ch != '?' && !equals( ch, strArr[i], isCaseSensitive ) )
428 {
429 return false; // Character mismatch
430 }
431 }
432 return true; // String matches against pattern
433 }
434
435 if ( patIdxEnd == 0 )
436 {
437 return true; // Pattern contains only '*', which matches anything
438 }
439
440 // Process characters before first star
441 // CHECKSTYLE_OFF: InnerAssignment
442 while ( ( ch = patArr[patIdxStart] ) != '*' && strIdxStart <= strIdxEnd )
443 // CHECKSTYLE_ON: InnerAssignment
444 {
445 if ( ch != '?' && !equals( ch, strArr[strIdxStart], isCaseSensitive ) )
446 {
447 return false; // Character mismatch
448 }
449 patIdxStart++;
450 strIdxStart++;
451 }
452 if ( strIdxStart > strIdxEnd )
453 {
454 // All characters in the string are used. Check if only '*'s are
455 // left in the pattern. If so, we succeeded. Otherwise failure.
456 for ( int i = patIdxStart; i <= patIdxEnd; i++ )
457 {
458 if ( patArr[i] != '*' )
459 {
460 return false;
461 }
462 }
463 return true;
464 }
465
466 // Process characters after last star
467 // CHECKSTYLE_OFF: InnerAssignment
468 while ( ( ch = patArr[patIdxEnd] ) != '*' && strIdxStart <= strIdxEnd )
469 // CHECKSTYLE_ON: InnerAssignment
470 {
471 if ( ch != '?' && !equals( ch, strArr[strIdxEnd], isCaseSensitive ) )
472 {
473 return false; // Character mismatch
474 }
475 patIdxEnd--;
476 strIdxEnd--;
477 }
478 if ( strIdxStart > strIdxEnd )
479 {
480 // All characters in the string are used. Check if only '*'s are
481 // left in the pattern. If so, we succeeded. Otherwise failure.
482 for ( int i = patIdxStart; i <= patIdxEnd; i++ )
483 {
484 if ( patArr[i] != '*' )
485 {
486 return false;
487 }
488 }
489 return true;
490 }
491
492 // process pattern between stars. padIdxStart and patIdxEnd point
493 // always to a '*'.
494 while ( patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd )
495 {
496 int patIdxTmp = -1;
497 for ( int i = patIdxStart + 1; i <= patIdxEnd; i++ )
498 {
499 if ( patArr[i] == '*' )
500 {
501 patIdxTmp = i;
502 break;
503 }
504 }
505 if ( patIdxTmp == patIdxStart + 1 )
506 {
507 // Two stars next to each other, skip the first one.
508 patIdxStart++;
509 continue;
510 }
511 // Find the pattern between padIdxStart & padIdxTmp in str between
512 // strIdxStart & strIdxEnd
513 int patLength = ( patIdxTmp - patIdxStart - 1 );
514 int strLength = ( strIdxEnd - strIdxStart + 1 );
515 int foundIdx = -1;
516 strLoop:
517 for ( int i = 0; i <= strLength - patLength; i++ )
518 {
519 for ( int j = 0; j < patLength; j++ )
520 {
521 ch = patArr[patIdxStart + j + 1];
522 if ( ch != '?' && !equals( ch, strArr[strIdxStart + i + j], isCaseSensitive ) )
523 {
524 continue strLoop;
525 }
526 }
527
528 foundIdx = strIdxStart + i;
529 break;
530 }
531
532 if ( foundIdx == -1 )
533 {
534 return false;
535 }
536
537 patIdxStart = patIdxTmp;
538 strIdxStart = foundIdx + patLength;
539 }
540
541 // All characters in the string are used. Check if only '*'s are left
542 // in the pattern. If so, we succeeded. Otherwise failure.
543 for ( int i = patIdxStart; i <= patIdxEnd; i++ )
544 {
545 if ( patArr[i] != '*' )
546 {
547 return false;
548 }
549 }
550 return true;
551 }
552
553 /**
554 * Tests whether two characters are equal.
555 */
556 private static boolean equals( char c1, char c2, boolean isCaseSensitive )
557 {
558 if ( c1 == c2 )
559 {
560 return true;
561 }
562 if ( !isCaseSensitive )
563 {
564 // NOTE: Try both upper case and lower case as done by String.equalsIgnoreCase()
565 if ( Character.toUpperCase( c1 ) == Character.toUpperCase( c2 )
566 || Character.toLowerCase( c1 ) == Character.toLowerCase( c2 ) )
567 {
568 return true;
569 }
570 }
571 return false;
572 }
573
574 /**
575 * Breaks a path up into a List of path elements, tokenizing on
576 * <code>File.separator</code>.
577 *
578 * @param path Path to tokenize. Must not be <code>null</code>.
579 * @param separator The separator to use
580 * @return a List of path elements from the tokenized path
581 */
582 private static List<String> tokenizePath( String path, String separator )
583 {
584 List<String> ret = new ArrayList<String>();
585 StringTokenizer st = new StringTokenizer( path, separator );
586 while ( st.hasMoreTokens() )
587 {
588 ret.add( st.nextToken() );
589 }
590 return ret;
591 }
592
593
594 static boolean matchAntPathPatternStart( @Nonnull MatchPattern pattern,
595 @Nonnull String str,
596 @Nonnull String separator,
597 boolean isCaseSensitive )
598 {
599 return !separatorPatternStartSlashMismatch( pattern, str, separator )
600 && matchAntPathPatternStart( pattern.getTokenizedPathString(), str, separator, isCaseSensitive );
601 }
602
603 private static String[] tokenizePathToString( @Nonnull String path, @Nonnull String separator )
604 {
605 List<String> ret = new ArrayList<String>();
606 StringTokenizer st = new StringTokenizer( path, separator );
607 while ( st.hasMoreTokens() )
608 {
609 ret.add( st.nextToken() );
610 }
611 return ret.toArray( new String[ret.size()] );
612 }
613
614 private static boolean matchAntPathPatternStart( @Nonnull String[] patDirs,
615 @Nonnull String str,
616 @Nonnull String separator,
617 boolean isCaseSensitive )
618 {
619 String[] strDirs = tokenizePathToString( str, separator );
620 return matchAntPathPatternStart( patDirs, strDirs, isCaseSensitive );
621 }
622
623 private static boolean matchAntPathPatternStart( @Nonnull String[] patDirs,
624 @Nonnull String[] tokenizedFileName,
625 boolean isCaseSensitive )
626 {
627
628 int patIdxStart = 0;
629 int patIdxEnd = patDirs.length - 1;
630 int strIdxStart = 0;
631 int strIdxEnd = tokenizedFileName.length - 1;
632
633 // up to first '**'
634 while ( patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd )
635 {
636 String patDir = patDirs[patIdxStart];
637 if ( patDir.equals( "**" ) )
638 {
639 break;
640 }
641 if ( !match( patDir, tokenizedFileName[strIdxStart], isCaseSensitive ) )
642 {
643 return false;
644 }
645 patIdxStart++;
646 strIdxStart++;
647 }
648
649 return strIdxStart > strIdxEnd || patIdxStart <= patIdxEnd;
650 }
651
652 private static boolean separatorPatternStartSlashMismatch( @Nonnull MatchPattern matchPattern, @Nonnull String str,
653 @Nonnull String separator )
654 {
655 return str.startsWith( separator ) != matchPattern.startsWith( separator );
656 }
657
658 private static boolean separatorPatternStartSlashMismatch( String pattern, String str, String separator )
659 {
660 return str.startsWith( separator ) != pattern.startsWith( separator );
661 }
662
663
664 static boolean matchAntPathPattern( String[] patDirs, String[] strDirs, boolean isCaseSensitive )
665 {
666 int patIdxStart = 0;
667 int patIdxEnd = patDirs.length - 1;
668 int strIdxStart = 0;
669 int strIdxEnd = strDirs.length - 1;
670
671 // up to first '**'
672 while ( patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd )
673 {
674 String patDir = patDirs[patIdxStart];
675 if ( patDir.equals( "**" ) )
676 {
677 break;
678 }
679 if ( !match( patDir, strDirs[strIdxStart], isCaseSensitive ) )
680 {
681 return false;
682 }
683 patIdxStart++;
684 strIdxStart++;
685 }
686 if ( strIdxStart > strIdxEnd )
687 {
688 // String is exhausted
689 for ( int i = patIdxStart; i <= patIdxEnd; i++ )
690 {
691 if ( !patDirs[i].equals( "**" ) )
692 {
693 return false;
694 }
695 }
696 return true;
697 }
698 else
699 {
700 if ( patIdxStart > patIdxEnd )
701 {
702 // String not exhausted, but pattern is. Failure.
703 return false;
704 }
705 }
706
707 // up to last '**'
708 while ( patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd )
709 {
710 String patDir = patDirs[patIdxEnd];
711 if ( patDir.equals( "**" ) )
712 {
713 break;
714 }
715 if ( !match( patDir, strDirs[strIdxEnd], isCaseSensitive ) )
716 {
717 return false;
718 }
719 patIdxEnd--;
720 strIdxEnd--;
721 }
722 if ( strIdxStart > strIdxEnd )
723 {
724 // String is exhausted
725 for ( int i = patIdxStart; i <= patIdxEnd; i++ )
726 {
727 if ( !patDirs[i].equals( "**" ) )
728 {
729 return false;
730 }
731 }
732 return true;
733 }
734
735 while ( patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd )
736 {
737 int patIdxTmp = -1;
738 for ( int i = patIdxStart + 1; i <= patIdxEnd; i++ )
739 {
740 if ( patDirs[i].equals( "**" ) )
741 {
742 patIdxTmp = i;
743 break;
744 }
745 }
746 if ( patIdxTmp == patIdxStart + 1 )
747 {
748 // '**/**' situation, so skip one
749 patIdxStart++;
750 continue;
751 }
752 // Find the pattern between padIdxStart & padIdxTmp in str between
753 // strIdxStart & strIdxEnd
754 int patLength = ( patIdxTmp - patIdxStart - 1 );
755 int strLength = ( strIdxEnd - strIdxStart + 1 );
756 int foundIdx = -1;
757 strLoop:
758 for ( int i = 0; i <= strLength - patLength; i++ )
759 {
760 for ( int j = 0; j < patLength; j++ )
761 {
762 String subPat = patDirs[patIdxStart + j + 1];
763 String subStr = strDirs[strIdxStart + i + j];
764 if ( !match( subPat, subStr, isCaseSensitive ) )
765 {
766 continue strLoop;
767 }
768 }
769
770 foundIdx = strIdxStart + i;
771 break;
772 }
773
774 if ( foundIdx == -1 )
775 {
776 return false;
777 }
778
779 patIdxStart = patIdxTmp;
780 strIdxStart = foundIdx + patLength;
781 }
782
783 for ( int i = patIdxStart; i <= patIdxEnd; i++ )
784 {
785 if ( !patDirs[i].equals( "**" ) )
786 {
787 return false;
788 }
789 }
790
791 return true;
792 }
793
794 static boolean isRegexPrefixedPattern( String pattern )
795 {
796 return pattern.length() > ( REGEX_HANDLER_PREFIX.length() + PATTERN_HANDLER_SUFFIX.length() + 1 )
797 && pattern.startsWith( REGEX_HANDLER_PREFIX ) && pattern.endsWith( PATTERN_HANDLER_SUFFIX );
798 }
799
800 static boolean isAntPrefixedPattern( String pattern )
801 {
802 return pattern.length() > ( ANT_HANDLER_PREFIX.length() + PATTERN_HANDLER_SUFFIX.length() + 1 )
803 && pattern.startsWith( ANT_HANDLER_PREFIX ) && pattern.endsWith( PATTERN_HANDLER_SUFFIX );
804 }
805
806 static boolean matchAntPathPattern( @Nonnull MatchPattern matchPattern, @Nonnull String str,
807 @Nonnull String separator, boolean isCaseSensitive )
808 {
809 if ( separatorPatternStartSlashMismatch( matchPattern, str, separator ) )
810 {
811 return false;
812 }
813 String[] patDirs = matchPattern.getTokenizedPathString();
814 String[] strDirs = tokenizePathToString( str, separator );
815 return matchAntPathPattern( patDirs, strDirs, isCaseSensitive );
816 }
817 }