Home | History | Annotate | Download | only in selectors
      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      */
     51     private SelectorUtils() {
     52     }
     53 
     54     /**
     55      * Retrieves the instance of the Singleton.
     56      * @return singleton instance
     57      */
     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      */
     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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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 
    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 
    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")
    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     })
    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      */
    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      */
    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      */
    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      */
    643     public static boolean hasWildcards(String input) {
    644         return (input.indexOf('*') != -1 || input.indexOf('?') != -1);
    645     }
    646 }
    647 
    648