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      /**
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 }