View Javadoc
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      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       * Private Constructor
56       */
57      private SelectorUtils()
58      {
59      }
60  
61      /**
62       * Tests whether or not a given path matches the start of a given
63       * pattern up to the first "**".
64       * <p/>
65       * This is not a general purpose test and should only be used if you
66       * can live with false positives. For example, <code>pattern=**\a</code>
67       * and <code>str=b</code> will yield <code>true</code>.
68       *
69       * @param pattern The pattern to match against. Must not be
70       *                <code>null</code>.
71       * @param str     The path to match, as a String. Must not be
72       *                <code>null</code>.
73       * @return whether or not a given path matches the start of a given
74       *         pattern up to the first "**".
75       */
76      public static boolean matchPatternStart( String pattern, String str )
77      {
78          return matchPatternStart( pattern, str, true );
79      }
80  
81      /**
82       * Tests whether or not a given path matches the start of a given
83       * pattern up to the first "**".
84       * <p/>
85       * This is not a general purpose test and should only be used if you
86       * can live with false positives. For example, <code>pattern=**\a</code>
87       * and <code>str=b</code> will yield <code>true</code>.
88       *
89       * @param pattern         The pattern to match against. Must not be
90       *                        <code>null</code>.
91       * @param str             The path to match, as a String. Must not be
92       *                        <code>null</code>.
93       * @param isCaseSensitive Whether or not matching should be performed
94       *                        case sensitively.
95       * @return whether or not a given path matches the start of a given
96       *         pattern up to the first "**".
97       */
98      public static boolean matchPatternStart( String pattern, String str, boolean isCaseSensitive )
99      {
100         if ( isRegexPrefixedPattern( pattern ) )
101         {
102             // FIXME: ICK! But we can't do partial matches for regex, so we have to reserve judgement until we have
103             // a file to deal with, or we can definitely say this is an exclusion...
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         // When str starts with a File.separator, pattern has to start with a
125         // File.separator.
126         // When pattern starts with a File.separator, str has to start with a
127         // File.separator.
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         // up to first '**'
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      * Tests whether or not a given path matches a given pattern.
162      *
163      * @param pattern The pattern to match against. Must not be
164      *                <code>null</code>.
165      * @param str     The path to match, as a String. Must not be
166      *                <code>null</code>.
167      * @return <code>true</code> if the pattern matches against the string,
168      *         or <code>false</code> otherwise.
169      */
170     public static boolean matchPath( String pattern, String str )
171     {
172         return matchPath( pattern, str, true );
173     }
174 
175     /**
176      * Tests whether or not a given path matches a given pattern.
177      *
178      * @param pattern         The pattern to match against. Must not be
179      *                        <code>null</code>.
180      * @param str             The path to match, as a String. Must not be
181      *                        <code>null</code>.
182      * @param isCaseSensitive Whether or not matching should be performed
183      *                        case sensitively.
184      * @return <code>true</code> if the pattern matches against the string,
185      *         or <code>false</code> otherwise.
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         // When str starts with a File.separator, pattern has to start with a
213         // File.separator.
214         // When pattern starts with a File.separator, str has to start with a
215         // File.separator.
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         // up to first '**'
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             // String is exhausted
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                 // String not exhausted, but pattern is. Failure.
261                 return false;
262             }
263         }
264 
265         // up to last '**'
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             // String is exhausted
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                 // '**/**' situation, so skip one
307                 patIdxStart++;
308                 continue;
309             }
310             // Find the pattern between padIdxStart & padIdxTmp in str between
311             // strIdxStart & strIdxEnd
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      * Tests whether or not a string matches against a pattern.
354      * The pattern may contain two special characters:<br>
355      * '*' means zero or more characters<br>
356      * '?' means one and only one character
357      *
358      * @param pattern The pattern to match against.
359      *                Must not be <code>null</code>.
360      * @param str     The string which must be matched against the pattern.
361      *                Must not be <code>null</code>.
362      * @return <code>true</code> if the string matches against the pattern,
363      *         or <code>false</code> otherwise.
364      */
365     public static boolean match( String pattern, String str )
366     {
367         return match( pattern, str, true );
368     }
369 
370     /**
371      * Tests whether or not a string matches against a pattern.
372      * The pattern may contain two special characters:<br>
373      * '*' means zero or more characters<br>
374      * '?' means one and only one character
375      *
376      * @param pattern         The pattern to match against.
377      *                        Must not be <code>null</code>.
378      * @param str             The string which must be matched against the pattern.
379      *                        Must not be <code>null</code>.
380      * @param isCaseSensitive Whether or not matching should be performed
381      *                        case sensitively.
382      * @return <code>true</code> if the string matches against the pattern,
383      *         or <code>false</code> otherwise.
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             // No '*'s, so we make a shortcut
408             if ( patIdxEnd != strIdxEnd )
409             {
410                 return false; // Pattern and string do not have the same size
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; // Character mismatch
418                 }
419             }
420             return true; // String matches against pattern
421         }
422 
423         if ( patIdxEnd == 0 )
424         {
425             return true; // Pattern contains only '*', which matches anything
426         }
427 
428         // Process characters before first star
429         // CHECKSTYLE_OFF: InnerAssignment
430         while ( ( ch = patArr[patIdxStart] ) != '*' && strIdxStart <= strIdxEnd )
431         // CHECKSTYLE_ON: InnerAssignment
432         {
433             if ( ch != '?' && !equals( ch, strArr[strIdxStart], isCaseSensitive ) )
434             {
435                 return false; // Character mismatch
436             }
437             patIdxStart++;
438             strIdxStart++;
439         }
440         if ( strIdxStart > strIdxEnd )
441         {
442             // All characters in the string are used. Check if only '*'s are
443             // left in the pattern. If so, we succeeded. Otherwise failure.
444             for ( int i = patIdxStart; i <= patIdxEnd; i++ )
445             {
446                 if ( patArr[i] != '*' )
447                 {
448                     return false;
449                 }
450             }
451             return true;
452         }
453 
454         // Process characters after last star
455         // CHECKSTYLE_OFF: InnerAssignment
456         while ( ( ch = patArr[patIdxEnd] ) != '*' && strIdxStart <= strIdxEnd )
457         // CHECKSTYLE_ON: InnerAssignment
458         {
459             if ( ch != '?' && !equals( ch, strArr[strIdxEnd], isCaseSensitive ) )
460             {
461                 return false; // Character mismatch
462             }
463             patIdxEnd--;
464             strIdxEnd--;
465         }
466         if ( strIdxStart > strIdxEnd )
467         {
468             // All characters in the string are used. Check if only '*'s are
469             // left in the pattern. If so, we succeeded. Otherwise failure.
470             for ( int i = patIdxStart; i <= patIdxEnd; i++ )
471             {
472                 if ( patArr[i] != '*' )
473                 {
474                     return false;
475                 }
476             }
477             return true;
478         }
479 
480         // process pattern between stars. padIdxStart and patIdxEnd point
481         // always to a '*'.
482         while ( patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd )
483         {
484             int patIdxTmp = -1;
485             for ( int i = patIdxStart + 1; i <= patIdxEnd; i++ )
486             {
487                 if ( patArr[i] == '*' )
488                 {
489                     patIdxTmp = i;
490                     break;
491                 }
492             }
493             if ( patIdxTmp == patIdxStart + 1 )
494             {
495                 // Two stars next to each other, skip the first one.
496                 patIdxStart++;
497                 continue;
498             }
499             // Find the pattern between padIdxStart & padIdxTmp in str between
500             // strIdxStart & strIdxEnd
501             int patLength = ( patIdxTmp - patIdxStart - 1 );
502             int strLength = ( strIdxEnd - strIdxStart + 1 );
503             int foundIdx = -1;
504             strLoop:
505             for ( int i = 0; i <= strLength - patLength; i++ )
506             {
507                 for ( int j = 0; j < patLength; j++ )
508                 {
509                     ch = patArr[patIdxStart + j + 1];
510                     if ( ch != '?' && !equals( ch, strArr[strIdxStart + i + j], isCaseSensitive ) )
511                     {
512                         continue strLoop;
513                     }
514                 }
515 
516                 foundIdx = strIdxStart + i;
517                 break;
518             }
519 
520             if ( foundIdx == -1 )
521             {
522                 return false;
523             }
524 
525             patIdxStart = patIdxTmp;
526             strIdxStart = foundIdx + patLength;
527         }
528 
529         // All characters in the string are used. Check if only '*'s are left
530         // in the pattern. If so, we succeeded. Otherwise failure.
531         for ( int i = patIdxStart; i <= patIdxEnd; i++ )
532         {
533             if ( patArr[i] != '*' )
534             {
535                 return false;
536             }
537         }
538         return true;
539     }
540 
541     /**
542      * Tests whether two characters are equal.
543      */
544     private static boolean equals( char c1, char c2, boolean isCaseSensitive )
545     {
546         if ( c1 == c2 )
547         {
548             return true;
549         }
550         if ( !isCaseSensitive )
551         {
552             // NOTE: Try both upper case and lower case as done by String.equalsIgnoreCase()
553             if ( Character.toUpperCase( c1 ) == Character.toUpperCase( c2 )
554                 || Character.toLowerCase( c1 ) == Character.toLowerCase( c2 ) )
555             {
556                 return true;
557             }
558         }
559         return false;
560     }
561 
562     /**
563      * Breaks a path up into a List of path elements, tokenizing on
564      * <code>File.separator</code>.
565      *
566      * @param path Path to tokenize. Must not be <code>null</code>.
567      * @param separator The separator to use
568      * @return a List of path elements from the tokenized path
569      */
570     private static List<String> tokenizePath( String path, String separator )
571     {
572         List<String> ret = new ArrayList<String>();
573         StringTokenizer st = new StringTokenizer( path, separator );
574         while ( st.hasMoreTokens() )
575         {
576             ret.add( st.nextToken() );
577         }
578         return ret;
579     }
580 
581 
582     static boolean matchAntPathPatternStart( @Nonnull MatchPattern pattern,
583                                              @Nonnull String str,
584                                              @Nonnull String separator,
585                                              boolean isCaseSensitive )
586     {
587         return !separatorPatternStartSlashMismatch( pattern, str, separator )
588             && matchAntPathPatternStart( pattern.getTokenizedPathString(), str, separator, isCaseSensitive );
589     }
590 
591     private static String[] tokenizePathToString( @Nonnull String path, @Nonnull String separator )
592     {
593         List<String> ret = new ArrayList<String>();
594         StringTokenizer st = new StringTokenizer( path, separator );
595         while ( st.hasMoreTokens() )
596         {
597             ret.add( st.nextToken() );
598         }
599         return ret.toArray( new String[ret.size()] );
600     }
601 
602     private static boolean matchAntPathPatternStart( @Nonnull String[] patDirs,
603                                                      @Nonnull String str,
604                                                      @Nonnull  String separator,
605                                                      boolean isCaseSensitive )
606     {
607         String[] strDirs = tokenizePathToString( str, separator );
608         return matchAntPathPatternStart( patDirs, strDirs, isCaseSensitive );
609     }
610 
611     private static boolean matchAntPathPatternStart( @Nonnull String[] patDirs,
612                                                      @Nonnull String[] tokenizedFileName,
613                                                      boolean isCaseSensitive )
614     {
615 
616         int patIdxStart = 0;
617         int patIdxEnd = patDirs.length - 1;
618         int strIdxStart = 0;
619         int strIdxEnd = tokenizedFileName.length - 1;
620 
621         // up to first '**'
622         while ( patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd )
623         {
624             String patDir = patDirs[patIdxStart];
625             if ( patDir.equals( "**" ) )
626             {
627                 break;
628             }
629             if ( !match( patDir, tokenizedFileName[strIdxStart], isCaseSensitive ) )
630             {
631                 return false;
632             }
633             patIdxStart++;
634             strIdxStart++;
635         }
636 
637         return strIdxStart > strIdxEnd || patIdxStart <= patIdxEnd;
638     }
639 
640     private static boolean separatorPatternStartSlashMismatch( @Nonnull MatchPattern matchPattern, @Nonnull String str,
641                                                                @Nonnull String separator )
642     {
643         return str.startsWith( separator ) != matchPattern.startsWith( separator );
644     }
645 
646     private static boolean separatorPatternStartSlashMismatch( String pattern, String str, String separator )
647     {
648         return str.startsWith( separator ) != pattern.startsWith( separator );
649     }
650 
651 
652     static boolean matchAntPathPattern( String[] patDirs, String[] strDirs, boolean isCaseSensitive )
653     {
654         int patIdxStart = 0;
655         int patIdxEnd = patDirs.length - 1;
656         int strIdxStart = 0;
657         int strIdxEnd = strDirs.length - 1;
658 
659         // up to first '**'
660         while ( patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd )
661         {
662             String patDir = patDirs[patIdxStart];
663             if ( patDir.equals( "**" ) )
664             {
665                 break;
666             }
667             if ( !match( patDir, strDirs[strIdxStart], isCaseSensitive ) )
668             {
669                 return false;
670             }
671             patIdxStart++;
672             strIdxStart++;
673         }
674         if ( strIdxStart > strIdxEnd )
675         {
676             // String is exhausted
677             for ( int i = patIdxStart; i <= patIdxEnd; i++ )
678             {
679                 if ( !patDirs[i].equals( "**" ) )
680                 {
681                     return false;
682                 }
683             }
684             return true;
685         }
686         else
687         {
688             if ( patIdxStart > patIdxEnd )
689             {
690                 // String not exhausted, but pattern is. Failure.
691                 return false;
692             }
693         }
694 
695         // up to last '**'
696         while ( patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd )
697         {
698             String patDir = patDirs[patIdxEnd];
699             if ( patDir.equals( "**" ) )
700             {
701                 break;
702             }
703             if ( !match( patDir, strDirs[strIdxEnd], isCaseSensitive ) )
704             {
705                 return false;
706             }
707             patIdxEnd--;
708             strIdxEnd--;
709         }
710         if ( strIdxStart > strIdxEnd )
711         {
712             // String is exhausted
713             for ( int i = patIdxStart; i <= patIdxEnd; i++ )
714             {
715                 if ( !patDirs[i].equals( "**" ) )
716                 {
717                     return false;
718                 }
719             }
720             return true;
721         }
722 
723         while ( patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd )
724         {
725             int patIdxTmp = -1;
726             for ( int i = patIdxStart + 1; i <= patIdxEnd; i++ )
727             {
728                 if ( patDirs[i].equals( "**" ) )
729                 {
730                     patIdxTmp = i;
731                     break;
732                 }
733             }
734             if ( patIdxTmp == patIdxStart + 1 )
735             {
736                 // '**/**' situation, so skip one
737                 patIdxStart++;
738                 continue;
739             }
740             // Find the pattern between padIdxStart & padIdxTmp in str between
741             // strIdxStart & strIdxEnd
742             int patLength = ( patIdxTmp - patIdxStart - 1 );
743             int strLength = ( strIdxEnd - strIdxStart + 1 );
744             int foundIdx = -1;
745             strLoop:
746             for ( int i = 0; i <= strLength - patLength; i++ )
747             {
748                 for ( int j = 0; j < patLength; j++ )
749                 {
750                     String subPat = patDirs[patIdxStart + j + 1];
751                     String subStr = strDirs[strIdxStart + i + j];
752                     if ( !match( subPat, subStr, isCaseSensitive ) )
753                     {
754                         continue strLoop;
755                     }
756                 }
757 
758                 foundIdx = strIdxStart + i;
759                 break;
760             }
761 
762             if ( foundIdx == -1 )
763             {
764                 return false;
765             }
766 
767             patIdxStart = patIdxTmp;
768             strIdxStart = foundIdx + patLength;
769         }
770 
771         for ( int i = patIdxStart; i <= patIdxEnd; i++ )
772         {
773             if ( !patDirs[i].equals( "**" ) )
774             {
775                 return false;
776             }
777         }
778 
779         return true;
780     }
781 
782     static boolean isRegexPrefixedPattern( String pattern )
783     {
784         return pattern.length() > ( REGEX_HANDLER_PREFIX.length() + PATTERN_HANDLER_SUFFIX.length() + 1 )
785             && pattern.startsWith( REGEX_HANDLER_PREFIX ) && pattern.endsWith( PATTERN_HANDLER_SUFFIX );
786     }
787 
788     static boolean isAntPrefixedPattern( String pattern )
789     {
790         return pattern.length() > ( ANT_HANDLER_PREFIX.length() + PATTERN_HANDLER_SUFFIX.length() + 1 )
791             && pattern.startsWith( ANT_HANDLER_PREFIX ) && pattern.endsWith( PATTERN_HANDLER_SUFFIX );
792     }
793 
794     static boolean matchAntPathPattern( @Nonnull MatchPattern matchPattern, @Nonnull String str,
795                                         @Nonnull String separator, boolean isCaseSensitive )
796     {
797         if ( separatorPatternStartSlashMismatch( matchPattern, str, separator ) )
798         {
799             return false;
800         }
801         String[] patDirs = matchPattern.getTokenizedPathString();
802         String[] strDirs = tokenizePathToString( str, separator );
803         return matchAntPathPattern( patDirs, strDirs, isCaseSensitive );
804     }
805 }