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