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