From 5716c15248acdb7dba42d951cf8273ee87cc6846 Mon Sep 17 00:00:00 2001 From: Xavier Ducrohet Date: Mon, 14 May 2012 17:53:39 -0700 Subject: Original upload of the glob library This is a few extracted classes from Ant, under Apache License Public 2.0 Change-Id: Id1b91ebab95e8d746787d40d5a1a820bd77bff8d --- .../tools/ant/types/selectors/SelectorUtils.java | 647 +++++++++++++++++++++ 1 file changed, 647 insertions(+) create mode 100644 src/org/apache/tools/ant/types/selectors/SelectorUtils.java (limited to 'src/org/apache/tools/ant/types/selectors/SelectorUtils.java') diff --git a/src/org/apache/tools/ant/types/selectors/SelectorUtils.java b/src/org/apache/tools/ant/types/selectors/SelectorUtils.java new file mode 100644 index 0000000..388d4d5 --- /dev/null +++ b/src/org/apache/tools/ant/types/selectors/SelectorUtils.java @@ -0,0 +1,647 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.tools.ant.types.selectors; + +import org.apache.tools.ant.util.FileUtils; + +import java.io.File; +import java.util.StringTokenizer; +import java.util.Vector; + +/** + *

This is a utility class used by selectors and DirectoryScanner. The + * functionality more properly belongs just to selectors, but unfortunately + * DirectoryScanner exposed these as protected methods. Thus we have to + * support any subclasses of DirectoryScanner that may access these methods. + *

+ *

This is a Singleton.

+ * + * @since 1.5 + */ +public final class SelectorUtils { + + /** + * The pattern that matches an arbitrary number of directories. + * @since Ant 1.8.0 + */ + public static final String DEEP_TREE_MATCH = "**"; + + private static final SelectorUtils instance = new SelectorUtils(); + private static final FileUtils FILE_UTILS = FileUtils.getFileUtils(); + + /** + * Private Constructor + */ + private SelectorUtils() { + } + + /** + * Retrieves the instance of the Singleton. + * @return singleton instance + */ + public static SelectorUtils getInstance() { + return instance; + } + + /** + * Tests whether or not a given path matches the start of a given + * pattern up to the first "**". + *

+ * This is not a general purpose test and should only be used if you + * can live with false positives. For example, pattern=**\a + * and str=b will yield true. + * + * @param pattern The pattern to match against. Must not be + * null. + * @param str The path to match, as a String. Must not be + * null. + * + * @return whether or not a given path matches the start of a given + * pattern up to the first "**". + */ + public static boolean matchPatternStart(String pattern, String str) { + return matchPatternStart(pattern, str, true); + } + + /** + * Tests whether or not a given path matches the start of a given + * pattern up to the first "**". + *

+ * This is not a general purpose test and should only be used if you + * can live with false positives. For example, pattern=**\a + * and str=b will yield true. + * + * @param pattern The pattern to match against. Must not be + * null. + * @param str The path to match, as a String. Must not be + * null. + * @param isCaseSensitive Whether or not matching should be performed + * case sensitively. + * + * @return whether or not a given path matches the start of a given + * pattern up to the first "**". + */ + public static boolean matchPatternStart(String pattern, String str, + boolean isCaseSensitive) { + // When str starts with a File.separator, pattern has to start with a + // File.separator. + // When pattern starts with a File.separator, str has to start with a + // File.separator. + if (str.startsWith(File.separator) + != pattern.startsWith(File.separator)) { + return false; + } + + String[] patDirs = tokenizePathAsArray(pattern); + String[] strDirs = tokenizePathAsArray(str); + return matchPatternStart(patDirs, strDirs, isCaseSensitive); + } + + + /** + * Tests whether or not a given path matches the start of a given + * pattern up to the first "**". + *

+ * This is not a general purpose test and should only be used if you + * can live with false positives. For example, pattern=**\a + * and str=b will yield true. + * + * @param patDirs The tokenized pattern to match against. Must not be + * null. + * @param strDirs The tokenized path to match. Must not be + * null. + * @param isCaseSensitive Whether or not matching should be performed + * case sensitively. + * + * @return whether or not a given path matches the start of a given + * pattern up to the first "**". + */ + static boolean matchPatternStart(String[] patDirs, String[] strDirs, + boolean isCaseSensitive) { + int patIdxStart = 0; + int patIdxEnd = patDirs.length - 1; + int strIdxStart = 0; + int strIdxEnd = strDirs.length - 1; + + // up to first '**' + while (patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd) { + String patDir = patDirs[patIdxStart]; + if (patDir.equals(DEEP_TREE_MATCH)) { + break; + } + if (!match(patDir, strDirs[strIdxStart], isCaseSensitive)) { + return false; + } + patIdxStart++; + strIdxStart++; + } + + // CheckStyle:SimplifyBooleanReturnCheck OFF + // Check turned off as the code needs the comments for the various + // code paths. + if (strIdxStart > strIdxEnd) { + // String is exhausted + return true; + } else if (patIdxStart > patIdxEnd) { + // String not exhausted, but pattern is. Failure. + return false; + } else { + // pattern now holds ** while string is not exhausted + // this will generate false positives but we can live with that. + return true; + } + } + + /** + * Tests whether or not a given path matches a given pattern. + * + * If you need to call this method multiple times with the same + * pattern you should rather use TokenizedPath + * + * @see TokenizedPath + * + * @param pattern The pattern to match against. Must not be + * null. + * @param str The path to match, as a String. Must not be + * null. + * + * @return true if the pattern matches against the string, + * or false otherwise. + */ + public static boolean matchPath(String pattern, String str) { + String[] patDirs = tokenizePathAsArray(pattern); + return matchPath(patDirs, tokenizePathAsArray(str), true); + } + + /** + * Tests whether or not a given path matches a given pattern. + * + * If you need to call this method multiple times with the same + * pattern you should rather use TokenizedPattern + * + * @see TokenizedPattern + * + * @param pattern The pattern to match against. Must not be + * null. + * @param str The path to match, as a String. Must not be + * null. + * @param isCaseSensitive Whether or not matching should be performed + * case sensitively. + * + * @return true if the pattern matches against the string, + * or false otherwise. + */ + public static boolean matchPath(String pattern, String str, + boolean isCaseSensitive) { + String[] patDirs = tokenizePathAsArray(pattern); + return matchPath(patDirs, tokenizePathAsArray(str), isCaseSensitive); + } + + /** + * Core implementation of matchPath. It is isolated so that it + * can be called from TokenizedPattern. + */ + static boolean matchPath(String[] tokenizedPattern, String[] strDirs, + boolean isCaseSensitive) { + int patIdxStart = 0; + int patIdxEnd = tokenizedPattern.length - 1; + int strIdxStart = 0; + int strIdxEnd = strDirs.length - 1; + + // up to first '**' + while (patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd) { + String patDir = tokenizedPattern[patIdxStart]; + if (patDir.equals(DEEP_TREE_MATCH)) { + break; + } + if (!match(patDir, strDirs[strIdxStart], isCaseSensitive)) { + return false; + } + patIdxStart++; + strIdxStart++; + } + if (strIdxStart > strIdxEnd) { + // String is exhausted + for (int i = patIdxStart; i <= patIdxEnd; i++) { + if (!tokenizedPattern[i].equals(DEEP_TREE_MATCH)) { + return false; + } + } + return true; + } else { + if (patIdxStart > patIdxEnd) { + // String not exhausted, but pattern is. Failure. + return false; + } + } + + // up to last '**' + while (patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd) { + String patDir = tokenizedPattern[patIdxEnd]; + if (patDir.equals(DEEP_TREE_MATCH)) { + break; + } + if (!match(patDir, strDirs[strIdxEnd], isCaseSensitive)) { + return false; + } + patIdxEnd--; + strIdxEnd--; + } + if (strIdxStart > strIdxEnd) { + // String is exhausted + for (int i = patIdxStart; i <= patIdxEnd; i++) { + if (!tokenizedPattern[i].equals(DEEP_TREE_MATCH)) { + return false; + } + } + return true; + } + + while (patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd) { + int patIdxTmp = -1; + for (int i = patIdxStart + 1; i <= patIdxEnd; i++) { + if (tokenizedPattern[i].equals(DEEP_TREE_MATCH)) { + patIdxTmp = i; + break; + } + } + if (patIdxTmp == patIdxStart + 1) { + // '**/**' situation, so skip one + patIdxStart++; + continue; + } + // Find the pattern between padIdxStart & padIdxTmp in str between + // strIdxStart & strIdxEnd + int patLength = (patIdxTmp - patIdxStart - 1); + int strLength = (strIdxEnd - strIdxStart + 1); + int foundIdx = -1; + strLoop: + for (int i = 0; i <= strLength - patLength; i++) { + for (int j = 0; j < patLength; j++) { + String subPat = tokenizedPattern[patIdxStart + j + 1]; + String subStr = strDirs[strIdxStart + i + j]; + if (!match(subPat, subStr, isCaseSensitive)) { + continue strLoop; + } + } + + foundIdx = strIdxStart + i; + break; + } + + if (foundIdx == -1) { + return false; + } + + patIdxStart = patIdxTmp; + strIdxStart = foundIdx + patLength; + } + + for (int i = patIdxStart; i <= patIdxEnd; i++) { + if (!tokenizedPattern[i].equals(DEEP_TREE_MATCH)) { + return false; + } + } + + return true; + } + + /** + * Tests whether or not a string matches against a pattern. + * The pattern may contain two special characters:
+ * '*' means zero or more characters
+ * '?' means one and only one character + * + * @param pattern The pattern to match against. + * Must not be null. + * @param str The string which must be matched against the pattern. + * Must not be null. + * + * @return true if the string matches against the pattern, + * or false otherwise. + */ + public static boolean match(String pattern, String str) { + return match(pattern, str, true); + } + + /** + * Tests whether or not a string matches against a pattern. + * The pattern may contain two special characters:
+ * '*' means zero or more characters
+ * '?' means one and only one character + * + * @param pattern The pattern to match against. + * Must not be null. + * @param str The string which must be matched against the pattern. + * Must not be null. + * @param caseSensitive Whether or not matching should be performed + * case sensitively. + * + * + * @return true if the string matches against the pattern, + * or false otherwise. + */ + public static boolean match(String pattern, String str, + boolean caseSensitive) { + char[] patArr = pattern.toCharArray(); + char[] strArr = str.toCharArray(); + int patIdxStart = 0; + int patIdxEnd = patArr.length - 1; + int strIdxStart = 0; + int strIdxEnd = strArr.length - 1; + char ch; + + boolean containsStar = false; + for (int i = 0; i < patArr.length; i++) { + if (patArr[i] == '*') { + containsStar = true; + break; + } + } + + if (!containsStar) { + // No '*'s, so we make a shortcut + if (patIdxEnd != strIdxEnd) { + return false; // Pattern and string do not have the same size + } + for (int i = 0; i <= patIdxEnd; i++) { + ch = patArr[i]; + if (ch != '?') { + if (different(caseSensitive, ch, strArr[i])) { + return false; // Character mismatch + } + } + } + return true; // String matches against pattern + } + + if (patIdxEnd == 0) { + return true; // Pattern contains only '*', which matches anything + } + + // Process characters before first star + while (true) { + ch = patArr[patIdxStart]; + if (ch == '*' || strIdxStart > strIdxEnd) { + break; + } + if (ch != '?') { + if (different(caseSensitive, ch, strArr[strIdxStart])) { + return false; // Character mismatch + } + } + patIdxStart++; + strIdxStart++; + } + if (strIdxStart > strIdxEnd) { + // All characters in the string are used. Check if only '*'s are + // left in the pattern. If so, we succeeded. Otherwise failure. + return allStars(patArr, patIdxStart, patIdxEnd); + } + + // Process characters after last star + while (true) { + ch = patArr[patIdxEnd]; + if (ch == '*' || strIdxStart > strIdxEnd) { + break; + } + if (ch != '?') { + if (different(caseSensitive, ch, strArr[strIdxEnd])) { + return false; // Character mismatch + } + } + patIdxEnd--; + strIdxEnd--; + } + if (strIdxStart > strIdxEnd) { + // All characters in the string are used. Check if only '*'s are + // left in the pattern. If so, we succeeded. Otherwise failure. + return allStars(patArr, patIdxStart, patIdxEnd); + } + + // process pattern between stars. padIdxStart and patIdxEnd point + // always to a '*'. + while (patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd) { + int patIdxTmp = -1; + for (int i = patIdxStart + 1; i <= patIdxEnd; i++) { + if (patArr[i] == '*') { + patIdxTmp = i; + break; + } + } + if (patIdxTmp == patIdxStart + 1) { + // Two stars next to each other, skip the first one. + patIdxStart++; + continue; + } + // Find the pattern between padIdxStart & padIdxTmp in str between + // strIdxStart & strIdxEnd + int patLength = (patIdxTmp - patIdxStart - 1); + int strLength = (strIdxEnd - strIdxStart + 1); + int foundIdx = -1; + strLoop: + for (int i = 0; i <= strLength - patLength; i++) { + for (int j = 0; j < patLength; j++) { + ch = patArr[patIdxStart + j + 1]; + if (ch != '?') { + if (different(caseSensitive, ch, + strArr[strIdxStart + i + j])) { + continue strLoop; + } + } + } + + foundIdx = strIdxStart + i; + break; + } + + if (foundIdx == -1) { + return false; + } + + patIdxStart = patIdxTmp; + strIdxStart = foundIdx + patLength; + } + + // All characters in the string are used. Check if only '*'s are left + // in the pattern. If so, we succeeded. Otherwise failure. + return allStars(patArr, patIdxStart, patIdxEnd); + } + + private static boolean allStars(char[] chars, int start, int end) { + for (int i = start; i <= end; ++i) { + if (chars[i] != '*') { + return false; + } + } + return true; + } + + private static boolean different( + boolean caseSensitive, char ch, char other) { + return caseSensitive + ? ch != other + : Character.toUpperCase(ch) != Character.toUpperCase(other); + } + + /** + * Breaks a path up into a Vector of path elements, tokenizing on + * File.separator. + * + * @param path Path to tokenize. Must not be null. + * + * @return a Vector of path elements from the tokenized path + */ + @SuppressWarnings("rawtypes") + public static Vector tokenizePath (String path) { + return tokenizePath(path, File.separator); + } + + /** + * Breaks a path up into a Vector of path elements, tokenizing on + * + * @param path Path to tokenize. Must not be null. + * @param separator the separator against which to tokenize. + * + * @return a Vector of path elements from the tokenized path + * @since Ant 1.6 + */ + @SuppressWarnings({ + "rawtypes", "unchecked" + }) + public static Vector tokenizePath (String path, String separator) { + Vector ret = new Vector(); + if (FileUtils.isAbsolutePath(path)) { + String[] s = FILE_UTILS.dissect(path); + ret.add(s[0]); + path = s[1]; + } + StringTokenizer st = new StringTokenizer(path, separator); + while (st.hasMoreTokens()) { + ret.addElement(st.nextToken()); + } + return ret; + } + + /** + * Same as {@link #tokenizePath tokenizePath} but hopefully faster. + */ + /*package*/ static String[] tokenizePathAsArray(String path) { + String root = null; + if (FileUtils.isAbsolutePath(path)) { + String[] s = FILE_UTILS.dissect(path); + root = s[0]; + path = s[1]; + } + char sep = File.separatorChar; + int start = 0; + int len = path.length(); + int count = 0; + for (int pos = 0; pos < len; pos++) { + if (path.charAt(pos) == sep) { + if (pos != start) { + count++; + } + start = pos + 1; + } + } + if (len != start) { + count++; + } + String[] l = new String[count + ((root == null) ? 0 : 1)]; + + if (root != null) { + l[0] = root; + count = 1; + } else { + count = 0; + } + start = 0; + for (int pos = 0; pos < len; pos++) { + if (path.charAt(pos) == sep) { + if (pos != start) { + String tok = path.substring(start, pos); + l[count++] = tok; + } + start = pos + 1; + } + } + if (len != start) { + String tok = path.substring(start); + l[count/*++*/] = tok; + } + return l; + } + + /** + * Returns dependency information on these two files. If src has been + * modified later than target, it returns true. If target doesn't exist, + * it likewise returns true. Otherwise, target is newer than src and + * is not out of date, thus the method returns false. It also returns + * false if the src file doesn't even exist, since how could the + * target then be out of date. + * + * @param src the original file + * @param target the file being compared against + * @param granularity the amount in seconds of slack we will give in + * determining out of dateness + * @return whether the target is out of date + */ + public static boolean isOutOfDate(File src, File target, int granularity) { + if (!src.exists()) { + return false; + } + if (!target.exists()) { + return true; + } + if ((src.lastModified() - granularity) > target.lastModified()) { + return true; + } + return false; + } + + /** + * "Flattens" a string by removing all whitespace (space, tab, linefeed, + * carriage return, and formfeed). This uses StringTokenizer and the + * default set of tokens as documented in the single arguement constructor. + * + * @param input a String to remove all whitespace. + * @return a String that has had all whitespace removed. + */ + public static String removeWhitespace(String input) { + StringBuffer result = new StringBuffer(); + if (input != null) { + StringTokenizer st = new StringTokenizer(input); + while (st.hasMoreTokens()) { + result.append(st.nextToken()); + } + } + return result.toString(); + } + + /** + * Tests if a string contains stars or question marks + * @param input a String which one wants to test for containing wildcard + * @return true if the string contains at least a star or a question mark + */ + public static boolean hasWildcards(String input) { + return (input.indexOf('*') != -1 || input.indexOf('?') != -1); + } +} + -- cgit v1.2.3