• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  *  Licensed to the Apache Software Foundation (ASF) under one or more
3  *  contributor license agreements.  See the NOTICE file distributed with
4  *  this work for additional information regarding copyright ownership.
5  *  The ASF licenses this file to You under the Apache License, Version 2.0
6  *  (the "License"); you may not use this file except in compliance with
7  *  the License.  You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  *  Unless required by applicable law or agreed to in writing, software
12  *  distributed under the License is distributed on an "AS IS" BASIS,
13  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  *  See the License for the specific language governing permissions and
15  *  limitations under the License.
16  *
17  */
18 
19 package org.apache.tools.ant.types.selectors;
20 
21 import org.apache.tools.ant.util.FileUtils;
22 
23 import java.io.File;
24 import java.util.StringTokenizer;
25 import java.util.Vector;
26 
27 /**
28  * <p>This is a utility class used by selectors and DirectoryScanner. The
29  * functionality more properly belongs just to selectors, but unfortunately
30  * DirectoryScanner exposed these as protected methods. Thus we have to
31  * support any subclasses of DirectoryScanner that may access these methods.
32  * </p>
33  * <p>This is a Singleton.</p>
34  *
35  * @since 1.5
36  */
37 public final class SelectorUtils {
38 
39     /**
40      * The pattern that matches an arbitrary number of directories.
41      * @since Ant 1.8.0
42      */
43     public static final String DEEP_TREE_MATCH = "**";
44 
45     private static final SelectorUtils instance = new SelectorUtils();
46     private static final FileUtils FILE_UTILS = FileUtils.getFileUtils();
47 
48     /**
49      * Private Constructor
50      */
SelectorUtils()51     private SelectorUtils() {
52     }
53 
54     /**
55      * Retrieves the instance of the Singleton.
56      * @return singleton instance
57      */
getInstance()58     public static SelectorUtils getInstance() {
59         return instance;
60     }
61 
62     /**
63      * Tests whether or not a given path matches the start of a given
64      * pattern up to the first "**".
65      * <p>
66      * This is not a general purpose test and should only be used if you
67      * can live with false positives. For example, <code>pattern=**\a</code>
68      * and <code>str=b</code> will yield <code>true</code>.
69      *
70      * @param pattern The pattern to match against. Must not be
71      *                <code>null</code>.
72      * @param str     The path to match, as a String. Must not be
73      *                <code>null</code>.
74      *
75      * @return whether or not a given path matches the start of a given
76      * pattern up to the first "**".
77      */
matchPatternStart(String pattern, String str)78     public static boolean matchPatternStart(String pattern, String str) {
79         return matchPatternStart(pattern, str, true);
80     }
81 
82     /**
83      * Tests whether or not a given path matches the start of a given
84      * pattern up to the first "**".
85      * <p>
86      * This is not a general purpose test and should only be used if you
87      * can live with false positives. For example, <code>pattern=**\a</code>
88      * and <code>str=b</code> will yield <code>true</code>.
89      *
90      * @param pattern The pattern to match against. Must not be
91      *                <code>null</code>.
92      * @param str     The path to match, as a String. Must not be
93      *                <code>null</code>.
94      * @param isCaseSensitive Whether or not matching should be performed
95      *                        case sensitively.
96      *
97      * @return whether or not a given path matches the start of a given
98      * pattern up to the first "**".
99      */
matchPatternStart(String pattern, String str, boolean isCaseSensitive)100     public static boolean matchPatternStart(String pattern, String str,
101                                             boolean isCaseSensitive) {
102         // When str starts with a File.separator, pattern has to start with a
103         // File.separator.
104         // When pattern starts with a File.separator, str has to start with a
105         // File.separator.
106         if (str.startsWith(File.separator)
107                 != pattern.startsWith(File.separator)) {
108             return false;
109         }
110 
111         String[] patDirs = tokenizePathAsArray(pattern);
112         String[] strDirs = tokenizePathAsArray(str);
113         return matchPatternStart(patDirs, strDirs, isCaseSensitive);
114     }
115 
116 
117     /**
118      * Tests whether or not a given path matches the start of a given
119      * pattern up to the first "**".
120      * <p>
121      * This is not a general purpose test and should only be used if you
122      * can live with false positives. For example, <code>pattern=**\a</code>
123      * and <code>str=b</code> will yield <code>true</code>.
124      *
125      * @param patDirs The tokenized pattern to match against. Must not be
126      *                <code>null</code>.
127      * @param strDirs The tokenized path to match. Must not be
128      *                <code>null</code>.
129      * @param isCaseSensitive Whether or not matching should be performed
130      *                        case sensitively.
131      *
132      * @return whether or not a given path matches the start of a given
133      * pattern up to the first "**".
134      */
matchPatternStart(String[] patDirs, String[] strDirs, boolean isCaseSensitive)135     static boolean matchPatternStart(String[] patDirs, String[] strDirs,
136                                      boolean isCaseSensitive) {
137         int patIdxStart = 0;
138         int patIdxEnd = patDirs.length - 1;
139         int strIdxStart = 0;
140         int strIdxEnd = strDirs.length - 1;
141 
142         // up to first '**'
143         while (patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd) {
144             String patDir = patDirs[patIdxStart];
145             if (patDir.equals(DEEP_TREE_MATCH)) {
146                 break;
147             }
148             if (!match(patDir, strDirs[strIdxStart], isCaseSensitive)) {
149                 return false;
150             }
151             patIdxStart++;
152             strIdxStart++;
153         }
154 
155         // CheckStyle:SimplifyBooleanReturnCheck OFF
156         // Check turned off as the code needs the comments for the various
157         // code paths.
158         if (strIdxStart > strIdxEnd) {
159             // String is exhausted
160             return true;
161         } else if (patIdxStart > patIdxEnd) {
162             // String not exhausted, but pattern is. Failure.
163             return false;
164         } else {
165             // pattern now holds ** while string is not exhausted
166             // this will generate false positives but we can live with that.
167             return true;
168         }
169     }
170 
171     /**
172      * Tests whether or not a given path matches a given pattern.
173      *
174      * If you need to call this method multiple times with the same
175      * pattern you should rather use TokenizedPath
176      *
177      * @see TokenizedPath
178      *
179      * @param pattern The pattern to match against. Must not be
180      *                <code>null</code>.
181      * @param str     The path to match, as a String. Must not be
182      *                <code>null</code>.
183      *
184      * @return <code>true</code> if the pattern matches against the string,
185      *         or <code>false</code> otherwise.
186      */
matchPath(String pattern, String str)187     public static boolean matchPath(String pattern, String str) {
188         String[] patDirs = tokenizePathAsArray(pattern);
189         return matchPath(patDirs, tokenizePathAsArray(str), true);
190     }
191 
192     /**
193      * Tests whether or not a given path matches a given pattern.
194      *
195      * If you need to call this method multiple times with the same
196      * pattern you should rather use TokenizedPattern
197      *
198      * @see TokenizedPattern
199      *
200      * @param pattern The pattern to match against. Must not be
201      *                <code>null</code>.
202      * @param str     The path to match, as a String. Must not be
203      *                <code>null</code>.
204      * @param isCaseSensitive Whether or not matching should be performed
205      *                        case sensitively.
206      *
207      * @return <code>true</code> if the pattern matches against the string,
208      *         or <code>false</code> otherwise.
209      */
matchPath(String pattern, String str, boolean isCaseSensitive)210     public static boolean matchPath(String pattern, String str,
211                                     boolean isCaseSensitive) {
212         String[] patDirs = tokenizePathAsArray(pattern);
213         return matchPath(patDirs, tokenizePathAsArray(str), isCaseSensitive);
214     }
215 
216     /**
217      * Core implementation of matchPath.  It is isolated so that it
218      * can be called from TokenizedPattern.
219      */
matchPath(String[] tokenizedPattern, String[] strDirs, boolean isCaseSensitive)220     static boolean matchPath(String[] tokenizedPattern, String[] strDirs,
221                              boolean isCaseSensitive) {
222         int patIdxStart = 0;
223         int patIdxEnd = tokenizedPattern.length - 1;
224         int strIdxStart = 0;
225         int strIdxEnd = strDirs.length - 1;
226 
227         // up to first '**'
228         while (patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd) {
229             String patDir = tokenizedPattern[patIdxStart];
230             if (patDir.equals(DEEP_TREE_MATCH)) {
231                 break;
232             }
233             if (!match(patDir, strDirs[strIdxStart], isCaseSensitive)) {
234                 return false;
235             }
236             patIdxStart++;
237             strIdxStart++;
238         }
239         if (strIdxStart > strIdxEnd) {
240             // String is exhausted
241             for (int i = patIdxStart; i <= patIdxEnd; i++) {
242                 if (!tokenizedPattern[i].equals(DEEP_TREE_MATCH)) {
243                     return false;
244                 }
245             }
246             return true;
247         } else {
248             if (patIdxStart > patIdxEnd) {
249                 // String not exhausted, but pattern is. Failure.
250                 return false;
251             }
252         }
253 
254         // up to last '**'
255         while (patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd) {
256             String patDir = tokenizedPattern[patIdxEnd];
257             if (patDir.equals(DEEP_TREE_MATCH)) {
258                 break;
259             }
260             if (!match(patDir, strDirs[strIdxEnd], isCaseSensitive)) {
261                 return false;
262             }
263             patIdxEnd--;
264             strIdxEnd--;
265         }
266         if (strIdxStart > strIdxEnd) {
267             // String is exhausted
268             for (int i = patIdxStart; i <= patIdxEnd; i++) {
269                 if (!tokenizedPattern[i].equals(DEEP_TREE_MATCH)) {
270                     return false;
271                 }
272             }
273             return true;
274         }
275 
276         while (patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd) {
277             int patIdxTmp = -1;
278             for (int i = patIdxStart + 1; i <= patIdxEnd; i++) {
279                 if (tokenizedPattern[i].equals(DEEP_TREE_MATCH)) {
280                     patIdxTmp = i;
281                     break;
282                 }
283             }
284             if (patIdxTmp == patIdxStart + 1) {
285                 // '**/**' situation, so skip one
286                 patIdxStart++;
287                 continue;
288             }
289             // Find the pattern between padIdxStart & padIdxTmp in str between
290             // strIdxStart & strIdxEnd
291             int patLength = (patIdxTmp - patIdxStart - 1);
292             int strLength = (strIdxEnd - strIdxStart + 1);
293             int foundIdx = -1;
294             strLoop:
295                         for (int i = 0; i <= strLength - patLength; i++) {
296                             for (int j = 0; j < patLength; j++) {
297                                 String subPat = tokenizedPattern[patIdxStart + j + 1];
298                                 String subStr = strDirs[strIdxStart + i + j];
299                                 if (!match(subPat, subStr, isCaseSensitive)) {
300                                     continue strLoop;
301                                 }
302                             }
303 
304                             foundIdx = strIdxStart + i;
305                             break;
306                         }
307 
308             if (foundIdx == -1) {
309                 return false;
310             }
311 
312             patIdxStart = patIdxTmp;
313             strIdxStart = foundIdx + patLength;
314         }
315 
316         for (int i = patIdxStart; i <= patIdxEnd; i++) {
317             if (!tokenizedPattern[i].equals(DEEP_TREE_MATCH)) {
318                 return false;
319             }
320         }
321 
322         return true;
323     }
324 
325     /**
326      * Tests whether or not a string matches against a pattern.
327      * The pattern may contain two special characters:<br>
328      * '*' means zero or more characters<br>
329      * '?' means one and only one character
330      *
331      * @param pattern The pattern to match against.
332      *                Must not be <code>null</code>.
333      * @param str     The string which must be matched against the pattern.
334      *                Must not be <code>null</code>.
335      *
336      * @return <code>true</code> if the string matches against the pattern,
337      *         or <code>false</code> otherwise.
338      */
match(String pattern, String str)339     public static boolean match(String pattern, String str) {
340         return match(pattern, str, true);
341     }
342 
343     /**
344      * Tests whether or not a string matches against a pattern.
345      * The pattern may contain two special characters:<br>
346      * '*' means zero or more characters<br>
347      * '?' means one and only one character
348      *
349      * @param pattern The pattern to match against.
350      *                Must not be <code>null</code>.
351      * @param str     The string which must be matched against the pattern.
352      *                Must not be <code>null</code>.
353      * @param caseSensitive Whether or not matching should be performed
354      *                        case sensitively.
355      *
356      *
357      * @return <code>true</code> if the string matches against the pattern,
358      *         or <code>false</code> otherwise.
359      */
match(String pattern, String str, boolean caseSensitive)360     public static boolean match(String pattern, String str,
361                                 boolean caseSensitive) {
362         char[] patArr = pattern.toCharArray();
363         char[] strArr = str.toCharArray();
364         int patIdxStart = 0;
365         int patIdxEnd = patArr.length - 1;
366         int strIdxStart = 0;
367         int strIdxEnd = strArr.length - 1;
368         char ch;
369 
370         boolean containsStar = false;
371         for (int i = 0; i < patArr.length; i++) {
372             if (patArr[i] == '*') {
373                 containsStar = true;
374                 break;
375             }
376         }
377 
378         if (!containsStar) {
379             // No '*'s, so we make a shortcut
380             if (patIdxEnd != strIdxEnd) {
381                 return false; // Pattern and string do not have the same size
382             }
383             for (int i = 0; i <= patIdxEnd; i++) {
384                 ch = patArr[i];
385                 if (ch != '?') {
386                     if (different(caseSensitive, ch, strArr[i])) {
387                         return false; // Character mismatch
388                     }
389                 }
390             }
391             return true; // String matches against pattern
392         }
393 
394         if (patIdxEnd == 0) {
395             return true; // Pattern contains only '*', which matches anything
396         }
397 
398         // Process characters before first star
399         while (true) {
400             ch = patArr[patIdxStart];
401             if (ch == '*' || strIdxStart > strIdxEnd) {
402                 break;
403             }
404             if (ch != '?') {
405                 if (different(caseSensitive, ch, strArr[strIdxStart])) {
406                     return false; // Character mismatch
407                 }
408             }
409             patIdxStart++;
410             strIdxStart++;
411         }
412         if (strIdxStart > strIdxEnd) {
413             // All characters in the string are used. Check if only '*'s are
414             // left in the pattern. If so, we succeeded. Otherwise failure.
415             return allStars(patArr, patIdxStart, patIdxEnd);
416         }
417 
418         // Process characters after last star
419         while (true) {
420             ch = patArr[patIdxEnd];
421             if (ch == '*' || strIdxStart > strIdxEnd) {
422                 break;
423             }
424             if (ch != '?') {
425                 if (different(caseSensitive, ch, strArr[strIdxEnd])) {
426                     return false; // Character mismatch
427                 }
428             }
429             patIdxEnd--;
430             strIdxEnd--;
431         }
432         if (strIdxStart > strIdxEnd) {
433             // All characters in the string are used. Check if only '*'s are
434             // left in the pattern. If so, we succeeded. Otherwise failure.
435             return allStars(patArr, patIdxStart, patIdxEnd);
436         }
437 
438         // process pattern between stars. padIdxStart and patIdxEnd point
439         // always to a '*'.
440         while (patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd) {
441             int patIdxTmp = -1;
442             for (int i = patIdxStart + 1; i <= patIdxEnd; i++) {
443                 if (patArr[i] == '*') {
444                     patIdxTmp = i;
445                     break;
446                 }
447             }
448             if (patIdxTmp == patIdxStart + 1) {
449                 // Two stars next to each other, skip the first 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                     ch = patArr[patIdxStart + j + 1];
462                     if (ch != '?') {
463                         if (different(caseSensitive, ch,
464                                       strArr[strIdxStart + i + j])) {
465                             continue strLoop;
466                         }
467                     }
468                 }
469 
470                 foundIdx = strIdxStart + i;
471                 break;
472             }
473 
474             if (foundIdx == -1) {
475                 return false;
476             }
477 
478             patIdxStart = patIdxTmp;
479             strIdxStart = foundIdx + patLength;
480         }
481 
482         // All characters in the string are used. Check if only '*'s are left
483         // in the pattern. If so, we succeeded. Otherwise failure.
484         return allStars(patArr, patIdxStart, patIdxEnd);
485     }
486 
allStars(char[] chars, int start, int end)487     private static boolean allStars(char[] chars, int start, int end) {
488         for (int i = start; i <= end; ++i) {
489             if (chars[i] != '*') {
490                 return false;
491             }
492         }
493         return true;
494     }
495 
different( boolean caseSensitive, char ch, char other)496     private static boolean different(
497         boolean caseSensitive, char ch, char other) {
498         return caseSensitive
499             ? ch != other
500             : Character.toUpperCase(ch) != Character.toUpperCase(other);
501     }
502 
503     /**
504      * Breaks a path up into a Vector of path elements, tokenizing on
505      * <code>File.separator</code>.
506      *
507      * @param path Path to tokenize. Must not be <code>null</code>.
508      *
509      * @return a Vector of path elements from the tokenized path
510      */
511     @SuppressWarnings("rawtypes")
tokenizePath(String path)512     public static Vector tokenizePath (String path) {
513         return tokenizePath(path, File.separator);
514     }
515 
516     /**
517      * Breaks a path up into a Vector of path elements, tokenizing on
518      *
519      * @param path Path to tokenize. Must not be <code>null</code>.
520      * @param separator the separator against which to tokenize.
521      *
522      * @return a Vector of path elements from the tokenized path
523      * @since Ant 1.6
524      */
525     @SuppressWarnings({
526             "rawtypes", "unchecked"
527     })
tokenizePath(String path, String separator)528     public static Vector tokenizePath (String path, String separator) {
529         Vector ret = new Vector();
530         if (FileUtils.isAbsolutePath(path)) {
531             String[] s = FILE_UTILS.dissect(path);
532             ret.add(s[0]);
533             path = s[1];
534         }
535         StringTokenizer st = new StringTokenizer(path, separator);
536         while (st.hasMoreTokens()) {
537             ret.addElement(st.nextToken());
538         }
539         return ret;
540     }
541 
542     /**
543      * Same as {@link #tokenizePath tokenizePath} but hopefully faster.
544      */
tokenizePathAsArray(String path)545     /*package*/ static String[] tokenizePathAsArray(String path) {
546         String root = null;
547         if (FileUtils.isAbsolutePath(path)) {
548             String[] s = FILE_UTILS.dissect(path);
549             root = s[0];
550             path = s[1];
551         }
552         char sep = File.separatorChar;
553         int start = 0;
554         int len = path.length();
555         int count = 0;
556         for (int pos = 0; pos < len; pos++) {
557             if (path.charAt(pos) == sep) {
558                 if (pos != start) {
559                     count++;
560                 }
561                 start = pos + 1;
562             }
563         }
564         if (len != start) {
565             count++;
566         }
567         String[] l = new String[count + ((root == null) ? 0 : 1)];
568 
569         if (root != null) {
570             l[0] = root;
571             count = 1;
572         } else {
573             count = 0;
574         }
575         start = 0;
576         for (int pos = 0; pos < len; pos++) {
577             if (path.charAt(pos) == sep) {
578                 if (pos != start) {
579                     String tok = path.substring(start, pos);
580                     l[count++] = tok;
581                 }
582                 start = pos + 1;
583             }
584         }
585         if (len != start) {
586             String tok = path.substring(start);
587             l[count/*++*/] = tok;
588         }
589         return l;
590     }
591 
592     /**
593      * Returns dependency information on these two files. If src has been
594      * modified later than target, it returns true. If target doesn't exist,
595      * it likewise returns true. Otherwise, target is newer than src and
596      * is not out of date, thus the method returns false. It also returns
597      * false if the src file doesn't even exist, since how could the
598      * target then be out of date.
599      *
600      * @param src the original file
601      * @param target the file being compared against
602      * @param granularity the amount in seconds of slack we will give in
603      *        determining out of dateness
604      * @return whether the target is out of date
605      */
isOutOfDate(File src, File target, int granularity)606     public static boolean isOutOfDate(File src, File target, int granularity) {
607         if (!src.exists()) {
608             return false;
609         }
610         if (!target.exists()) {
611             return true;
612         }
613         if ((src.lastModified() - granularity) > target.lastModified()) {
614             return true;
615         }
616         return false;
617     }
618 
619     /**
620      * "Flattens" a string by removing all whitespace (space, tab, linefeed,
621      * carriage return, and formfeed). This uses StringTokenizer and the
622      * default set of tokens as documented in the single arguement constructor.
623      *
624      * @param input a String to remove all whitespace.
625      * @return a String that has had all whitespace removed.
626      */
removeWhitespace(String input)627     public static String removeWhitespace(String input) {
628         StringBuffer result = new StringBuffer();
629         if (input != null) {
630             StringTokenizer st = new StringTokenizer(input);
631             while (st.hasMoreTokens()) {
632                 result.append(st.nextToken());
633             }
634         }
635         return result.toString();
636     }
637 
638     /**
639      * Tests if a string contains stars or question marks
640      * @param input a String which one wants to test for containing wildcard
641      * @return true if the string contains at least a star or a question mark
642      */
hasWildcards(String input)643     public static boolean hasWildcards(String input) {
644         return (input.indexOf('*') != -1 || input.indexOf('?') != -1);
645     }
646 }
647 
648