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