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