View Javadoc
1   /*
2    * The Apache Software License, Version 1.1
3    *
4    * Copyright (c) 2002-2003 The Apache Software Foundation.  All rights
5    * reserved.
6    *
7    * Redistribution and use in source and binary forms, with or without
8    * modification, are permitted provided that the following conditions
9    * are met:
10   *
11   * 1. Redistributions of source code must retain the above copyright
12   *    notice, this list of conditions and the following disclaimer.
13   *
14   * 2. Redistributions in binary form must reproduce the above copyright
15   *    notice, this list of conditions and the following disclaimer in
16   *    the documentation and/or other materials provided with the
17   *    distribution.
18   *
19   * 3. The end-user documentation included with the redistribution, if
20   *    any, must include the following acknowledgement:
21   *       "This product includes software developed by the
22   *        Apache Software Foundation (http://www.codehaus.org/)."
23   *    Alternately, this acknowledgement may appear in the software itself,
24   *    if and wherever such third-party acknowledgements normally appear.
25   *
26   * 4. The names "Ant" and "Apache Software
27   *    Foundation" must not be used to endorse or promote products derived
28   *    from this software without prior written permission. For written
29   *    permission, please contact codehaus@codehaus.org.
30   *
31   * 5. Products derived from this software may not be called "Apache"
32   *    nor may "Apache" appear in their names without prior written
33   *    permission of the Apache Group.
34   *
35   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
36   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
37   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
38   * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
39   * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
40   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
41   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
42   * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
43   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
44   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
45   * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
46   * SUCH DAMAGE.
47   * ====================================================================
48   *
49   * This software consists of voluntary contributions made by many
50   * individuals on behalf of the Apache Software Foundation.  For more
51   * information on the Apache Software Foundation, please see
52   * <http://www.codehaus.org/>.
53   */
54  
55  package org.codehaus.plexus.util;
56  
57  import java.io.File;
58  import java.util.ArrayList;
59  import java.util.List;
60  import java.util.StringTokenizer;
61  
62  /**
63   * <p>
64   * This is a utility class used by selectors and DirectoryScanner. The functionality more properly belongs just to
65   * selectors, but unfortunately DirectoryScanner exposed these as protected methods. Thus we have to support any
66   * subclasses of DirectoryScanner that may access these methods.
67   * </p>
68   * <p>
69   * This is a Singleton.
70   * </p>
71   *
72   * @author Arnout J. Kuiper <a href="mailto:ajkuiper@wxs.nl">ajkuiper@wxs.nl</a>
73   * @author Magesh Umasankar
74   * @author <a href="mailto:bruce@callenish.com">Bruce Atherton</a>
75   * @version $Id$
76   * @since 1.5
77   */
78  public final class SelectorUtils
79  {
80  
81      public static final String PATTERN_HANDLER_PREFIX = "[";
82  
83      public static final String PATTERN_HANDLER_SUFFIX = "]";
84  
85      public static final String REGEX_HANDLER_PREFIX = "%regex" + PATTERN_HANDLER_PREFIX;
86  
87      public static final String ANT_HANDLER_PREFIX = "%ant" + PATTERN_HANDLER_PREFIX;
88  
89      private static SelectorUtils instance = new SelectorUtils();
90  
91      /**
92       * Private Constructor
93       */
94      private SelectorUtils()
95      {
96      }
97  
98      /**
99       * Retrieves the manager of the Singleton.
100      */
101     public static SelectorUtils getInstance()
102     {
103         return instance;
104     }
105 
106     /**
107      * Tests whether or not a given path matches the start of a given pattern up to the first "**".
108      * <p/>
109      * This is not a general purpose test and should only be used if you can live with false positives. For example,
110      * <code>pattern=**\a</code> and <code>str=b</code> will yield <code>true</code>.
111      *
112      * @param pattern The pattern to match against. Must not be <code>null</code>.
113      * @param str The path to match, as a String. Must not be <code>null</code>.
114      * @return whether or not a given path matches the start of a given pattern up to the first "**".
115      */
116     public static boolean matchPatternStart( String pattern, String str )
117     {
118         return matchPatternStart( pattern, str, true );
119     }
120 
121     /**
122      * Tests whether or not a given path matches the start of a given pattern up to the first "**".
123      * <p/>
124      * This is not a general purpose test and should only be used if you can live with false positives. For example,
125      * <code>pattern=**\a</code> and <code>str=b</code> will yield <code>true</code>.
126      *
127      * @param pattern The pattern to match against. Must not be <code>null</code>.
128      * @param str The path to match, as a String. Must not be <code>null</code>.
129      * @param isCaseSensitive Whether or not matching should be performed case sensitively.
130      * @return whether or not a given path matches the start of a given pattern up to the first "**".
131      */
132     public static boolean matchPatternStart( String pattern, String str, boolean isCaseSensitive )
133     {
134         if ( isRegexPrefixedPattern( pattern ) )
135         {
136             // FIXME: ICK! But we can't do partial matches for regex, so we have to reserve judgement until we have
137             // a file to deal with, or we can definitely say this is an exclusion...
138             return true;
139         }
140         else
141         {
142             if ( isAntPrefixedPattern( pattern ) )
143             {
144                 pattern = pattern.substring( ANT_HANDLER_PREFIX.length(),
145                                              pattern.length() - PATTERN_HANDLER_SUFFIX.length() );
146             }
147 
148             String altStr = str.replace( '\\', '/' );
149 
150             return matchAntPathPatternStart( pattern, str, File.separator, isCaseSensitive )
151                 || matchAntPathPatternStart( pattern, altStr, "/", isCaseSensitive );
152         }
153     }
154 
155     static boolean isAntPrefixedPattern( String pattern )
156     {
157         return pattern.length() > ( ANT_HANDLER_PREFIX.length() + PATTERN_HANDLER_SUFFIX.length() + 1 )
158             && pattern.startsWith( ANT_HANDLER_PREFIX ) && pattern.endsWith( PATTERN_HANDLER_SUFFIX );
159     }
160 
161     @SuppressWarnings( "SimplifiableIfStatement" )
162     static boolean matchAntPathPatternStart( MatchPattern pattern, String str, String separator,
163                                              boolean isCaseSensitive )
164     {
165         if ( separatorPatternStartSlashMismatch( pattern, str, separator ) )
166         {
167             return false;
168         }
169 
170         return matchAntPathPatternStart( pattern.getTokenizedPathString(), str, separator, isCaseSensitive );
171     }
172 
173     static boolean matchAntPathPatternStart( String pattern, String str, String separator, boolean isCaseSensitive )
174     {
175         // When str starts with a File.separator, pattern has to start with a
176         // File.separator.
177         // When pattern starts with a File.separator, str has to start with a
178         // File.separator.
179         if ( separatorPatternStartSlashMismatch( pattern, str, separator ) )
180         {
181             return false;
182         }
183 
184         String[] patDirs = tokenizePathToString( pattern, separator );
185         return matchAntPathPatternStart( patDirs, str, separator, isCaseSensitive );
186     }
187 
188     // When str starts with a File.separator, pattern has to start with a
189     // File.separator.
190     // When pattern starts with a File.separator, str has to start with a
191     // File.separator.
192     private static boolean separatorPatternStartSlashMismatch( String pattern, String str, String separator )
193     {
194         return str.startsWith( separator ) != pattern.startsWith( separator );
195     }
196 
197     private static boolean separatorPatternStartSlashMismatch( MatchPattern matchPattern, String str, String separator )
198     {
199         return str.startsWith( separator ) != matchPattern.startsWith( separator );
200     }
201 
202     static boolean matchAntPathPatternStart( String[] patDirs, String str, String separator, boolean isCaseSensitive )
203     {
204         String[] strDirs = tokenizePathToString( str, separator );
205 
206         int patIdxStart = 0;
207         int patIdxEnd = patDirs.length - 1;
208         int strIdxStart = 0;
209         int strIdxEnd = strDirs.length - 1;
210 
211         // up to first '**'
212         while ( patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd )
213         {
214             String patDir = patDirs[patIdxStart];
215             if ( patDir.equals( "**" ) )
216             {
217                 break;
218             }
219             if ( !match( patDir, strDirs[strIdxStart], isCaseSensitive ) )
220             {
221                 return false;
222             }
223             patIdxStart++;
224             strIdxStart++;
225         }
226 
227         return strIdxStart > strIdxEnd || patIdxStart <= patIdxEnd;
228     }
229 
230     /**
231      * Tests whether or not a given path matches a given pattern.
232      *
233      * @param pattern The pattern to match against. Must not be <code>null</code>.
234      * @param str The path to match, as a String. Must not be <code>null</code>.
235      * @return <code>true</code> if the pattern matches against the string, or <code>false</code> otherwise.
236      */
237     public static boolean matchPath( String pattern, String str )
238     {
239         return matchPath( pattern, str, true );
240     }
241 
242     /**
243      * Tests whether or not a given path matches a given pattern.
244      *
245      * @param pattern The pattern to match against. Must not be <code>null</code>.
246      * @param str The path to match, as a String. Must not be <code>null</code>.
247      * @param isCaseSensitive Whether or not matching should be performed case sensitively.
248      * @return <code>true</code> if the pattern matches against the string, or <code>false</code> otherwise.
249      */
250     public static boolean matchPath( String pattern, String str, boolean isCaseSensitive )
251     {
252         return matchPath( pattern, str, File.separator, isCaseSensitive );
253     }
254 
255     public static boolean matchPath( String pattern, String str, String separator, boolean isCaseSensitive )
256     {
257         if ( isRegexPrefixedPattern( pattern ) )
258         {
259             pattern =
260                 pattern.substring( REGEX_HANDLER_PREFIX.length(), pattern.length() - PATTERN_HANDLER_SUFFIX.length() );
261 
262             return str.matches( pattern );
263         }
264         else
265         {
266             if ( isAntPrefixedPattern( pattern ) )
267             {
268                 pattern = pattern.substring( ANT_HANDLER_PREFIX.length(),
269                                              pattern.length() - PATTERN_HANDLER_SUFFIX.length() );
270             }
271 
272             return matchAntPathPattern( pattern, str, separator, isCaseSensitive );
273         }
274     }
275 
276     static boolean isRegexPrefixedPattern( String pattern )
277     {
278         return pattern.length() > ( REGEX_HANDLER_PREFIX.length() + PATTERN_HANDLER_SUFFIX.length() + 1 )
279             && pattern.startsWith( REGEX_HANDLER_PREFIX ) && pattern.endsWith( PATTERN_HANDLER_SUFFIX );
280     }
281 
282     static boolean matchAntPathPattern( MatchPattern matchPattern, String str, String separator,
283                                         boolean isCaseSensitive )
284     {
285         if ( separatorPatternStartSlashMismatch( matchPattern, str, separator ) )
286         {
287             return false;
288         }
289         String[] patDirs = matchPattern.getTokenizedPathString();
290         String[] strDirs = tokenizePathToString( str, separator );
291         return matchAntPathPattern( patDirs, strDirs, isCaseSensitive );
292     }
293 
294     static boolean matchAntPathPattern( String pattern, String str, String separator, boolean isCaseSensitive )
295     {
296         if ( separatorPatternStartSlashMismatch( pattern, str, separator ) )
297         {
298             return false;
299         }
300         String[] patDirs = tokenizePathToString( pattern, separator );
301         String[] strDirs = tokenizePathToString( str, separator );
302         return matchAntPathPattern( patDirs, strDirs, isCaseSensitive );
303 
304     }
305 
306     static boolean matchAntPathPattern( String[] patDirs, String[] strDirs, boolean isCaseSensitive )
307     {
308         int patIdxStart = 0;
309         int patIdxEnd = patDirs.length - 1;
310         int strIdxStart = 0;
311         int strIdxEnd = strDirs.length - 1;
312 
313         // up to first '**'
314         while ( patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd )
315         {
316             String patDir = patDirs[patIdxStart];
317             if ( patDir.equals( "**" ) )
318             {
319                 break;
320             }
321             if ( !match( patDir, strDirs[strIdxStart], isCaseSensitive ) )
322             {
323                 return false;
324             }
325             patIdxStart++;
326             strIdxStart++;
327         }
328         if ( strIdxStart > strIdxEnd )
329         {
330             // String is exhausted
331             for ( int i = patIdxStart; i <= patIdxEnd; i++ )
332             {
333                 if ( !patDirs[i].equals( "**" ) )
334                 {
335                     return false;
336                 }
337             }
338             return true;
339         }
340         else
341         {
342             if ( patIdxStart > patIdxEnd )
343             {
344                 // String not exhausted, but pattern is. Failure.
345                 return false;
346             }
347         }
348 
349         // up to last '**'
350         while ( patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd )
351         {
352             String patDir = patDirs[patIdxEnd];
353             if ( patDir.equals( "**" ) )
354             {
355                 break;
356             }
357             if ( !match( patDir, strDirs[strIdxEnd], isCaseSensitive ) )
358             {
359                 return false;
360             }
361             patIdxEnd--;
362             strIdxEnd--;
363         }
364         if ( strIdxStart > strIdxEnd )
365         {
366             // String is exhausted
367             for ( int i = patIdxStart; i <= patIdxEnd; i++ )
368             {
369                 if ( !patDirs[i].equals( "**" ) )
370                 {
371                     return false;
372                 }
373             }
374             return true;
375         }
376 
377         while ( patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd )
378         {
379             int patIdxTmp = -1;
380             for ( int i = patIdxStart + 1; i <= patIdxEnd; i++ )
381             {
382                 if ( patDirs[i].equals( "**" ) )
383                 {
384                     patIdxTmp = i;
385                     break;
386                 }
387             }
388             if ( patIdxTmp == patIdxStart + 1 )
389             {
390                 // '**/**' situation, so skip one
391                 patIdxStart++;
392                 continue;
393             }
394             // Find the pattern between padIdxStart & padIdxTmp in str between
395             // strIdxStart & strIdxEnd
396             int patLength = ( patIdxTmp - patIdxStart - 1 );
397             int strLength = ( strIdxEnd - strIdxStart + 1 );
398             int foundIdx = -1;
399             strLoop: for ( int i = 0; i <= strLength - patLength; i++ )
400             {
401                 for ( int j = 0; j < patLength; j++ )
402                 {
403                     String subPat = patDirs[patIdxStart + j + 1];
404                     String subStr = strDirs[strIdxStart + i + j];
405                     if ( !match( subPat, subStr, isCaseSensitive ) )
406                     {
407                         continue strLoop;
408                     }
409                 }
410 
411                 foundIdx = strIdxStart + i;
412                 break;
413             }
414 
415             if ( foundIdx == -1 )
416             {
417                 return false;
418             }
419 
420             patIdxStart = patIdxTmp;
421             strIdxStart = foundIdx + patLength;
422         }
423 
424         for ( int i = patIdxStart; i <= patIdxEnd; i++ )
425         {
426             if ( !patDirs[i].equals( "**" ) )
427             {
428                 return false;
429             }
430         }
431 
432         return true;
433     }
434 
435     static boolean matchAntPathPattern( char[][] patDirs, char[][] strDirs, boolean isCaseSensitive )
436     {
437         int patIdxStart = 0;
438         int patIdxEnd = patDirs.length - 1;
439         int strIdxStart = 0;
440         int strIdxEnd = strDirs.length - 1;
441 
442         // up to first '**'
443         while ( patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd )
444         {
445             char[] patDir = patDirs[patIdxStart];
446             if ( isDoubleStar( patDir ) )
447             {
448                 break;
449             }
450             if ( !match( patDir, strDirs[strIdxStart], isCaseSensitive ) )
451             {
452                 return false;
453             }
454             patIdxStart++;
455             strIdxStart++;
456         }
457         if ( strIdxStart > strIdxEnd )
458         {
459             // String is exhausted
460             for ( int i = patIdxStart; i <= patIdxEnd; i++ )
461             {
462                 if ( !isDoubleStar( patDirs[i] ) )
463                 {
464                     return false;
465                 }
466             }
467             return true;
468         }
469         else
470         {
471             if ( patIdxStart > patIdxEnd )
472             {
473                 // String not exhausted, but pattern is. Failure.
474                 return false;
475             }
476         }
477 
478         // up to last '**'
479         while ( patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd )
480         {
481             char[] patDir = patDirs[patIdxEnd];
482             if ( isDoubleStar( patDir ) )
483             {
484                 break;
485             }
486             if ( !match( patDir, strDirs[strIdxEnd], isCaseSensitive ) )
487             {
488                 return false;
489             }
490             patIdxEnd--;
491             strIdxEnd--;
492         }
493         if ( strIdxStart > strIdxEnd )
494         {
495             // String is exhausted
496             for ( int i = patIdxStart; i <= patIdxEnd; i++ )
497             {
498                 if ( !isDoubleStar( patDirs[i] ) )
499                 {
500                     return false;
501                 }
502             }
503             return true;
504         }
505 
506         while ( patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd )
507         {
508             int patIdxTmp = -1;
509             for ( int i = patIdxStart + 1; i <= patIdxEnd; i++ )
510             {
511                 if ( isDoubleStar( patDirs[i] ) )
512                 {
513                     patIdxTmp = i;
514                     break;
515                 }
516             }
517             if ( patIdxTmp == patIdxStart + 1 )
518             {
519                 // '**/**' situation, so skip one
520                 patIdxStart++;
521                 continue;
522             }
523             // Find the pattern between padIdxStart & padIdxTmp in str between
524             // strIdxStart & strIdxEnd
525             int patLength = ( patIdxTmp - patIdxStart - 1 );
526             int strLength = ( strIdxEnd - strIdxStart + 1 );
527             int foundIdx = -1;
528             strLoop: for ( int i = 0; i <= strLength - patLength; i++ )
529             {
530                 for ( int j = 0; j < patLength; j++ )
531                 {
532                     char[] subPat = patDirs[patIdxStart + j + 1];
533                     char[] subStr = strDirs[strIdxStart + i + j];
534                     if ( !match( subPat, subStr, isCaseSensitive ) )
535                     {
536                         continue strLoop;
537                     }
538                 }
539 
540                 foundIdx = strIdxStart + i;
541                 break;
542             }
543 
544             if ( foundIdx == -1 )
545             {
546                 return false;
547             }
548 
549             patIdxStart = patIdxTmp;
550             strIdxStart = foundIdx + patLength;
551         }
552 
553         for ( int i = patIdxStart; i <= patIdxEnd; i++ )
554         {
555             if ( !isDoubleStar( patDirs[i] ) )
556             {
557                 return false;
558             }
559         }
560 
561         return true;
562     }
563 
564     private static boolean isDoubleStar( char[] patDir )
565     {
566         return patDir != null && patDir.length == 2 && patDir[0] == '*' && patDir[1] == '*';
567     }
568 
569     /**
570      * Tests whether or not a string matches against a pattern. The pattern may contain two special characters:<br>
571      * '*' means zero or more characters<br>
572      * '?' means one and only one character
573      *
574      * @param pattern The pattern to match against. Must not be <code>null</code>.
575      * @param str The string which must be matched against the pattern. Must not be <code>null</code>.
576      * @return <code>true</code> if the string matches against the pattern, or <code>false</code> otherwise.
577      */
578     public static boolean match( String pattern, String str )
579     {
580         return match( pattern, str, true );
581     }
582 
583     /**
584      * Tests whether or not a string matches against a pattern. The pattern may contain two special characters:<br>
585      * '*' means zero or more characters<br>
586      * '?' means one and only one character
587      *
588      * @param pattern The pattern to match against. Must not be <code>null</code>.
589      * @param str The string which must be matched against the pattern. Must not be <code>null</code>.
590      * @param isCaseSensitive Whether or not matching should be performed case sensitively.
591      * @return <code>true</code> if the string matches against the pattern, or <code>false</code> otherwise.
592      */
593     public static boolean match( String pattern, String str, boolean isCaseSensitive )
594     {
595         char[] patArr = pattern.toCharArray();
596         char[] strArr = str.toCharArray();
597         return match( patArr, strArr, isCaseSensitive );
598     }
599 
600     public static boolean match( char[] patArr, char[] strArr, boolean isCaseSensitive )
601     {
602         int patIdxStart = 0;
603         int patIdxEnd = patArr.length - 1;
604         int strIdxStart = 0;
605         int strIdxEnd = strArr.length - 1;
606         char ch;
607 
608         boolean containsStar = false;
609         for ( char aPatArr : patArr )
610         {
611             if ( aPatArr == '*' )
612             {
613                 containsStar = true;
614                 break;
615             }
616         }
617 
618         if ( !containsStar )
619         {
620             // No '*'s, so we make a shortcut
621             if ( patIdxEnd != strIdxEnd )
622             {
623                 return false; // Pattern and string do not have the same size
624             }
625             for ( int i = 0; i <= patIdxEnd; i++ )
626             {
627                 ch = patArr[i];
628                 if ( ch != '?' && !equals( ch, strArr[i], isCaseSensitive ) )
629                 {
630                     return false; // Character mismatch
631                 }
632             }
633             return true; // String matches against pattern
634         }
635 
636         if ( patIdxEnd == 0 )
637         {
638             return true; // Pattern contains only '*', which matches anything
639         }
640 
641         // Process characters before first star
642         while ( ( ch = patArr[patIdxStart] ) != '*' && strIdxStart <= strIdxEnd )
643         {
644             if ( ch != '?' && !equals( ch, strArr[strIdxStart], isCaseSensitive ) )
645             {
646                 return false; // Character mismatch
647             }
648             patIdxStart++;
649             strIdxStart++;
650         }
651         if ( strIdxStart > strIdxEnd )
652         {
653             // All characters in the string are used. Check if only '*'s are
654             // left in the pattern. If so, we succeeded. Otherwise failure.
655             for ( int i = patIdxStart; i <= patIdxEnd; i++ )
656             {
657                 if ( patArr[i] != '*' )
658                 {
659                     return false;
660                 }
661             }
662             return true;
663         }
664 
665         // Process characters after last star
666         while ( ( ch = patArr[patIdxEnd] ) != '*' && strIdxStart <= strIdxEnd )
667         {
668             if ( ch != '?' && !equals( ch, strArr[strIdxEnd], isCaseSensitive ) )
669             {
670                 return false; // Character mismatch
671             }
672             patIdxEnd--;
673             strIdxEnd--;
674         }
675         if ( strIdxStart > strIdxEnd )
676         {
677             // All characters in the string are used. Check if only '*'s are
678             // left in the pattern. If so, we succeeded. Otherwise failure.
679             for ( int i = patIdxStart; i <= patIdxEnd; i++ )
680             {
681                 if ( patArr[i] != '*' )
682                 {
683                     return false;
684                 }
685             }
686             return true;
687         }
688 
689         // process pattern between stars. padIdxStart and patIdxEnd point
690         // always to a '*'.
691         while ( patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd )
692         {
693             int patIdxTmp = -1;
694             for ( int i = patIdxStart + 1; i <= patIdxEnd; i++ )
695             {
696                 if ( patArr[i] == '*' )
697                 {
698                     patIdxTmp = i;
699                     break;
700                 }
701             }
702             if ( patIdxTmp == patIdxStart + 1 )
703             {
704                 // Two stars next to each other, skip the first one.
705                 patIdxStart++;
706                 continue;
707             }
708             // Find the pattern between padIdxStart & padIdxTmp in str between
709             // strIdxStart & strIdxEnd
710             int patLength = ( patIdxTmp - patIdxStart - 1 );
711             int strLength = ( strIdxEnd - strIdxStart + 1 );
712             int foundIdx = -1;
713             strLoop: for ( int i = 0; i <= strLength - patLength; i++ )
714             {
715                 for ( int j = 0; j < patLength; j++ )
716                 {
717                     ch = patArr[patIdxStart + j + 1];
718                     if ( ch != '?' && !equals( ch, strArr[strIdxStart + i + j], isCaseSensitive ) )
719                     {
720                         continue strLoop;
721                     }
722                 }
723 
724                 foundIdx = strIdxStart + i;
725                 break;
726             }
727 
728             if ( foundIdx == -1 )
729             {
730                 return false;
731             }
732 
733             patIdxStart = patIdxTmp;
734             strIdxStart = foundIdx + patLength;
735         }
736 
737         // All characters in the string are used. Check if only '*'s are left
738         // in the pattern. If so, we succeeded. Otherwise failure.
739         for ( int i = patIdxStart; i <= patIdxEnd; i++ )
740         {
741             if ( patArr[i] != '*' )
742             {
743                 return false;
744             }
745         }
746         return true;
747     }
748 
749     /**
750      * Tests whether two characters are equal.
751      */
752     private static boolean equals( char c1, char c2, boolean isCaseSensitive )
753     {
754         if ( c1 == c2 )
755         {
756             return true;
757         }
758         if ( !isCaseSensitive )
759         {
760             // NOTE: Try both upper case and lower case as done by String.equalsIgnoreCase()
761             if ( Character.toUpperCase( c1 ) == Character.toUpperCase( c2 )
762                 || Character.toLowerCase( c1 ) == Character.toLowerCase( c2 ) )
763             {
764                 return true;
765             }
766         }
767         return false;
768     }
769 
770     private static String[] tokenizePathToString( String path, String separator )
771     {
772         List<String> ret = new ArrayList<String>();
773         StringTokenizer st = new StringTokenizer( path, separator );
774         while ( st.hasMoreTokens() )
775         {
776             ret.add( st.nextToken() );
777         }
778         return ret.toArray( new String[ret.size()] );
779     }
780 
781     /**
782      * Returns dependency information on these two files. If src has been modified later than target, it returns true.
783      * If target doesn't exist, it likewise returns true. Otherwise, target is newer than src and is not out of date,
784      * thus the method returns false. It also returns false if the src file doesn't even exist, since how could the
785      * target then be out of date.
786      *
787      * @param src the original file
788      * @param target the file being compared against
789      * @param granularity the amount in seconds of slack we will give in determining out of dateness
790      * @return whether the target is out of date
791      */
792     public static boolean isOutOfDate( File src, File target, int granularity )
793     {
794         if ( !src.exists() )
795         {
796             return false;
797         }
798         if ( !target.exists() )
799         {
800             return true;
801         }
802         if ( ( src.lastModified() - granularity ) > target.lastModified() )
803         {
804             return true;
805         }
806         return false;
807     }
808 
809     /**
810      * "Flattens" a string by removing all whitespace (space, tab, linefeed, carriage return, and formfeed). This uses
811      * StringTokenizer and the default set of tokens as documented in the single argument constructor.
812      *
813      * @param input a String to remove all whitespace.
814      * @return a String that has had all whitespace removed.
815      */
816     public static String removeWhitespace( String input )
817     {
818         StringBuilder result = new StringBuilder();
819         if ( input != null )
820         {
821             StringTokenizer st = new StringTokenizer( input );
822             while ( st.hasMoreTokens() )
823             {
824                 result.append( st.nextToken() );
825             }
826         }
827         return result.toString();
828     }
829 }