diff --git a/proposal/sandbox/selectors/README b/proposal/sandbox/selectors/README
new file mode 100644
index 000000000..fce8eaa32
--- /dev/null
+++ b/proposal/sandbox/selectors/README
@@ -0,0 +1,34 @@
+Selector API
+============
+
+Currently our filesets allow us to select a set of files based on name patterns.
+For instance we could create a set of all the files that end with ".java".
+However there are cases when you wish to select files based on their other
+attributes, such as if they are read only or if they are older than a specified
+date etc.
+
+The selector API is one such mechanism to do this. The selector API will allow
+you to build file sets based on criteria other than name. Some possible criteria
+would be
+
+Is the file readable?
+Is the file writeable?
+What date was the file modified on?
+What size is the file?
+Does the contents contain the string "magic"?
+
+If we end up supporting a VFS then we could expand the number of selectors
+considerably. A mock representation that has been proposed before is the
+following. Of course this is subject to change as soon as someone wants to
+tackle this action ;)
+
+
+ * These criteria consist of a set of include and exclude patterns. With these + * patterns, you can select which files you want to have included, and which + * files you want to have excluded. + *
+ * The idea is simple. A given directory is recursively scanned for all files + * and directories. Each file/directory is matched against a set of include + * and exclude patterns. Only files/directories that match at least one + * pattern of the include pattern list, and don't match a pattern of the + * exclude pattern list will be placed in the list of files/directories found. + *
+ * When no list of include patterns is supplied, "**" will be used, which + * means that everything will be matched. When no list of exclude patterns is + * supplied, an empty list is used, such that nothing will be excluded. + *
+ * The pattern matching is done as follows:
+ * The name to be matched is split up in path segments. A path segment is the
+ * name of a directory or file, which is bounded by
+ * File.separator
('/' under UNIX, '\' under Windows).
+ * E.g. "abc/def/ghi/xyz.java" is split up in the segments "abc", "def", "ghi"
+ * and "xyz.java".
+ * The same is done for the pattern against which should be matched.
+ *
+ * Then the segments of the name and the pattern will be matched against each + * other. When '**' is used for a path segment in the pattern, then it matches + * zero or more path segments of the name. + *
+ * There are special case regarding the use of File.separator
s at
+ * the beginningof the pattern and the string to match:
+ * When a pattern starts with a File.separator
, the string
+ * to match must also start with a File.separator
.
+ * When a pattern does not start with a File.separator
, the
+ * string to match may not start with a File.separator
.
+ * When one of these rules is not obeyed, the string will not
+ * match.
+ *
+ * When a name path segment is matched against a pattern path segment, the + * following special characters can be used: + * '*' matches zero or more characters, + * '?' matches one character. + *
+ * Examples: + *
+ * "**\*.class" matches all .class files/dirs in a directory tree. + *
+ * "test\a??.java" matches all files/dirs which start with an 'a', then two + * more characters and then ".java", in a directory called test. + *
+ * "**" matches everything in a directory tree. + *
+ * "**\test\**\XYZ*" matches all files/dirs that start with "XYZ" and where + * there is a parent directory called test (e.g. "abc\test\def\ghi\XYZ123"). + *
+ * Case sensitivity may be turned off if necessary. By default, it is + * turned on. + *
+ * Example of usage: + *
+ * String[] includes = {"**\\*.class"}; + * String[] excludes = {"modules\\*\\**"}; + * ds.setIncludes(includes); + * ds.setExcludes(excludes); + * ds.setBasedir(new File("test")); + * ds.setCaseSensitive(true); + * ds.scan(); + * + * System.out.println("FILES:"); + * String[] files = ds.getIncludedFiles(); + * for (int i = 0; i < files.length;i++) { + * System.out.println(files[i]); + * } + *+ * This will scan a directory called test for .class files, but excludes all + * .class files in all directories under a directory called "modules" + * + * @author Arnout J. Kuiper ajkuiper@wxs.nl + * @author Magesh Umasankar + */ +public class DirectoryScanner implements FileScanner { + + /** + * Patterns that should be excluded by default. + * + * @see #addDefaultExcludes() + */ + protected final static String[] DEFAULTEXCLUDES = { + "**/*~", + "**/#*#", + "**/.#*", + "**/%*%", + "**/CVS", + "**/CVS/**", + "**/.cvsignore", + "**/SCCS", + "**/SCCS/**", + "**/vssver.scc" + }; + + /** + * The base directory which should be scanned. + */ + protected File basedir; + + /** + * The patterns for the files that should be included. + */ + protected Pattern[] includes; + + /** + * The patterns for the files that should be excluded. + */ + protected Pattern[] excludes; + + /** + * The files that where found and matched at least one includes, and matched + * no excludes. + */ + protected Vector filesIncluded; + + /** + * The files that where found and did not match any includes. + */ + protected Vector filesNotIncluded; + + /** + * The files that where found and matched at least one includes, and also + * matched at least one excludes. + */ + protected Vector filesExcluded; + + /** + * The directories that where found and matched at least one includes, and + * matched no excludes. + */ + protected Vector dirsIncluded; + + /** + * The directories that where found and did not match any includes. + */ + protected Vector dirsNotIncluded; + + /** + * The files that where found and matched at least one includes, and also + * matched at least one excludes. + */ + protected Vector dirsExcluded; + + /** + * Have the Vectors holding our results been built by a slow scan? + */ + protected boolean haveSlowResults = false; + + /** + * Should the file system be treated as a case sensitive one? + */ + protected boolean isCaseSensitive = true; + + /** + * Is everything we've seen so far included? + */ + protected boolean everythingIncluded = true; + + private static Hashtable selectorClasses = null; + + static { + String defs = "/org/apache/tools/ant/selectors/defaults.properties"; + selectorClasses = new Hashtable(); + + try { + Properties props = new Properties(); + InputStream in = DirectoryScanner.class.getResourceAsStream(defs); + if (in == null) { + throw new BuildException("Can't load default selector list"); + } + props.load(in); + in.close(); + + Enumeration enum = props.propertyNames(); + while (enum.hasMoreElements()) { + String key = (String) enum.nextElement(); + String value = props.getProperty(key); + try { + selectorClasses.put(key, Class.forName(value)); + } catch (NoClassDefFoundError ncdfe) { + } catch (ClassNotFoundException cnfe) { + } + } + } catch (IOException ioe) { + throw new BuildException("Can't load default selector list"); + } + } + + /** + * Constructor. + */ + public DirectoryScanner() { + } + + + /** + * Does the path match the start of this pattern up to the first "**". + * + *
This is not a general purpose test and should only be used if you + * can live with false positives.
+ * + *pattern=**\\a
and str=b
will yield true.
+ *
+ * @param pattern the (non-null) pattern to match against
+ * @param str the (non-null) string (path) to match
+ */
+ protected static boolean matchPatternStart(String pattern, String str) {
+ return matchPatternStart(pattern, str, true);
+ }
+
+ /**
+ * Does the path match the start of this pattern up to the first "**".
+ *
+ *
This is not a general purpose test and should only be used if you + * can live with false positives.
+ * + *pattern=**\\a
and str=b
will yield true.
+ *
+ * @param pattern the (non-null) pattern to match against
+ * @param str the (non-null) string (path) to match
+ * @param isCaseSensitive must matches be case sensitive?
+ */
+ protected 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;
+ }
+
+ Vector patDirs = new Vector();
+ StringTokenizer st = new StringTokenizer(pattern,File.separator);
+ while (st.hasMoreTokens()) {
+ patDirs.addElement(st.nextToken());
+ }
+
+ Vector strDirs = new Vector();
+ st = new StringTokenizer(str,File.separator);
+ while (st.hasMoreTokens()) {
+ strDirs.addElement(st.nextToken());
+ }
+
+ int patIdxStart = 0;
+ int patIdxEnd = patDirs.size()-1;
+ int strIdxStart = 0;
+ int strIdxEnd = strDirs.size()-1;
+
+ // up to first '**'
+ while (patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd) {
+ String patDir = (String)patDirs.elementAt(patIdxStart);
+ if (patDir.equals("**")) {
+ break;
+ }
+ if (!match(patDir,(String)strDirs.elementAt(strIdxStart), isCaseSensitive)) {
+ return false;
+ }
+ patIdxStart++;
+ strIdxStart++;
+ }
+
+ 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;
+ }
+ }
+
+ protected static boolean isSelected(String fileToScan, Vector selectorList) {
+ boolean isInclusive = true;
+ if (selectorList != null ) {
+ PatternSet.SelectorEntry[] selectorEntries =
+ new PatternSet.SelectorEntry[selectorList.size()];
+ selectorList.copyInto(selectorEntries);
+ boolean[] selectorIndices = new boolean[selectorEntries.length];
+
+ for (int i = 0; i < selectorEntries.length; i++) {
+ String type = selectorEntries[i].getType();
+ String value = selectorEntries[i].getValue();
+ String operation = selectorEntries[i].getOperation();
+ Class c = (Class) selectorClasses.get(type);
+ if (c != null) {
+ FileSelector s = null;
+ try {
+ s = (FileSelector) c.newInstance();
+ s.setValue(value);
+ s.setOperation(operation);
+ isInclusive = s.isSelected(fileToScan);
+ } catch (InstantiationException ie) {
+ } catch (IllegalAccessException ie) {
+ }
+ if (!isInclusive) {
+ break;
+ }
+ }
+ }
+ }
+ return isInclusive;
+ }
+
+ /**
+ * Matches a path against a pattern.
+ *
+ * @param pattern the (non-null) pattern to match against
+ * @param str the (non-null) string (path) to match
+ *
+ * @return true
when the pattern matches against the string.
+ * false
otherwise.
+ */
+ protected static boolean matchPath(String pattern, String str) {
+ return matchPath(pattern, null, str, true);
+ }
+
+ protected static boolean matchPath(String pattern,
+ String str, boolean isCaseSensitive) {
+ return matchPath(pattern, null, str, isCaseSensitive);
+ }
+
+ /**
+ * Matches a path against a pattern.
+ *
+ * @param pattern the (non-null) pattern to match against
+ * @param str the (non-null) string (path) to match
+ * @param isCaseSensitive must a case sensitive match be done?
+ *
+ * @return true
when the pattern matches against the string.
+ * false
otherwise.
+ */
+ protected static boolean matchPath(String pattern, Vector selectorList,
+ 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;
+ }
+
+ Vector patDirs = new Vector();
+ StringTokenizer st = new StringTokenizer(pattern,File.separator);
+ while (st.hasMoreTokens()) {
+ patDirs.addElement(st.nextToken());
+ }
+
+ Vector strDirs = new Vector();
+ st = new StringTokenizer(str,File.separator);
+ while (st.hasMoreTokens()) {
+ strDirs.addElement(st.nextToken());
+ }
+
+ int patIdxStart = 0;
+ int patIdxEnd = patDirs.size()-1;
+ int strIdxStart = 0;
+ int strIdxEnd = strDirs.size()-1;
+
+ // up to first '**'
+ while (patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd) {
+ String patDir = (String)patDirs.elementAt(patIdxStart);
+ if (patDir.equals("**")) {
+ break;
+ }
+ if (!match(patDir,(String)strDirs.elementAt(strIdxStart), isCaseSensitive)) {
+ return false;
+ }
+ patIdxStart++;
+ strIdxStart++;
+ }
+ if (strIdxStart > strIdxEnd) {
+ // String is exhausted
+ for (int i = patIdxStart; i <= patIdxEnd; i++) {
+ if (!patDirs.elementAt(i).equals("**")) {
+ return false;
+ }
+ }
+ return isSelected(str, selectorList);
+ } else {
+ if (patIdxStart > patIdxEnd) {
+ // String not exhausted, but pattern is. Failure.
+ return false;
+ }
+ }
+
+ // up to last '**'
+ while (patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd) {
+ String patDir = (String)patDirs.elementAt(patIdxEnd);
+ if (patDir.equals("**")) {
+ break;
+ }
+ if (!match(patDir,(String)strDirs.elementAt(strIdxEnd), isCaseSensitive)) {
+ return false;
+ }
+ patIdxEnd--;
+ strIdxEnd--;
+ }
+ if (strIdxStart > strIdxEnd) {
+ // String is exhausted
+ for (int i = patIdxStart; i <= patIdxEnd; i++) {
+ if (!patDirs.elementAt(i).equals("**")) {
+ return false;
+ }
+ }
+ return isSelected(str, selectorList);
+ }
+
+ while (patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd) {
+ int patIdxTmp = -1;
+ for (int i = patIdxStart+1; i <= patIdxEnd; i++) {
+ if (patDirs.elementAt(i).equals("**")) {
+ 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 = (String)patDirs.elementAt(patIdxStart+j+1);
+ String subStr = (String)strDirs.elementAt(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 (!patDirs.elementAt(i).equals("**")) {
+ return false;
+ }
+ }
+
+ return isSelected(str, selectorList);
+ }
+
+
+ /**
+ * Matches a string against a pattern. The pattern contains two special
+ * characters:
+ * '*' which means zero or more characters,
+ * '?' which means one and only one character.
+ *
+ * @param pattern the (non-null) pattern to match against
+ * @param str the (non-null) string that must be matched against the
+ * pattern
+ *
+ * @return true
when the string matches against the pattern,
+ * false
otherwise.
+ */
+ public static boolean match(String pattern, String str) {
+ return match(pattern, str, true);
+ }
+
+
+ /**
+ * Matches a string against a pattern. The pattern contains two special
+ * characters:
+ * '*' which means zero or more characters,
+ * '?' which means one and only one character.
+ *
+ * @param pattern the (non-null) pattern to match against
+ * @param str the (non-null) string that must be matched against the
+ * pattern
+ *
+ * @return true
when the string matches against the pattern,
+ * false
otherwise.
+ */
+ protected static boolean match(String pattern, String str, boolean isCaseSensitive) {
+ 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 (isCaseSensitive && ch != strArr[i]) {
+ return false;// Character mismatch
+ }
+ if (!isCaseSensitive && Character.toUpperCase(ch) !=
+ Character.toUpperCase(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((ch = patArr[patIdxStart]) != '*' && strIdxStart <= strIdxEnd) {
+ if (ch != '?') {
+ if (isCaseSensitive && ch != strArr[strIdxStart]) {
+ return false;// Character mismatch
+ }
+ if (!isCaseSensitive && Character.toUpperCase(ch) !=
+ Character.toUpperCase(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.
+ for (int i = patIdxStart; i <= patIdxEnd; i++) {
+ if (patArr[i] != '*') {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ // Process characters after last star
+ while((ch = patArr[patIdxEnd]) != '*' && strIdxStart <= strIdxEnd) {
+ if (ch != '?') {
+ if (isCaseSensitive && ch != strArr[strIdxEnd]) {
+ return false;// Character mismatch
+ }
+ if (!isCaseSensitive && Character.toUpperCase(ch) !=
+ Character.toUpperCase(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.
+ for (int i = patIdxStart; i <= patIdxEnd; i++) {
+ if (patArr[i] != '*') {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ // 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 (isCaseSensitive && ch != strArr[strIdxStart+i+j]) {
+ continue strLoop;
+ }
+ if (!isCaseSensitive && Character.toUpperCase(ch) !=
+ Character.toUpperCase(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.
+ for (int i = patIdxStart; i <= patIdxEnd; i++) {
+ if (patArr[i] != '*') {
+ return false;
+ }
+ }
+ return true;
+ }
+
+
+
+ /**
+ * Sets the basedir for scanning. This is the directory that is scanned
+ * recursively. All '/' and '\' characters are replaced by
+ * File.separatorChar
. So the separator used need not match
+ * File.separatorChar
.
+ *
+ * @param basedir the (non-null) basedir for scanning
+ */
+ public void setBasedir(String basedir) {
+ setBasedir(new File(basedir.replace('/',File.separatorChar).replace('\\',File.separatorChar)));
+ }
+
+
+
+ /**
+ * Sets the basedir for scanning. This is the directory that is scanned
+ * recursively.
+ *
+ * @param basedir the basedir for scanning
+ */
+ public void setBasedir(File basedir) {
+ this.basedir = basedir;
+ }
+
+
+
+ /**
+ * Gets the basedir that is used for scanning. This is the directory that
+ * is scanned recursively.
+ *
+ * @return the basedir that is used for scanning
+ */
+ public File getBasedir() {
+ return basedir;
+ }
+
+
+
+ /**
+ * Sets the case sensitivity of the file system
+ *
+ * @param specifies if the filesystem is case sensitive
+ */
+ public void setCaseSensitive(boolean isCaseSensitive) {
+ this.isCaseSensitive = isCaseSensitive;
+ }
+
+ /**
+ * Sets the set of include patterns to use. All '/' and '\' characters are
+ * replaced by File.separatorChar
. So the separator used need
+ * not match File.separatorChar
.
+ *
+ * When a pattern ends with a '/' or '\', "**" is appended.
+ *
+ * @param includes list of include patterns
+ */
+ public void setIncludes(String[] includes) {
+ if (includes == null) {
+ this.includes = null;
+ } else {
+ Pattern[] p = new Pattern[includes.length];
+ for (int i = 0; i < includes.length; i++) {
+ p[i] = new Pattern();
+ p[i].setPattern(includes[i]);
+ }
+ setIncludes(p);
+ }
+ }
+
+ public void setIncludes(Pattern[] includes) {
+ if (includes == null) {
+ this.includes = null;
+ } else {
+ this.includes = new Pattern[includes.length];
+ for (int i = 0; i < includes.length; i++) {
+ String pattern;
+ pattern = includes[i].getPattern().replace('/',File.separatorChar).replace('\\',File.separatorChar);
+ if (pattern.endsWith(File.separator)) {
+ pattern += "**";
+ }
+ this.includes[i] = new Pattern();
+ this.includes[i].setPattern(pattern);
+ this.includes[i].setSelectorList(includes[i].getSelectorList());
+ }
+ }
+ }
+
+ /**
+ * Sets the set of exclude patterns to use. All '/' and '\' characters are
+ * replaced by File.separatorChar
. So the separator used need
+ * not match File.separatorChar
.
+ *
+ * When a pattern ends with a '/' or '\', "**" is appended. + * + * @param excludes list of exclude patterns + */ + public void setExcludes(String[] excludes) { + if (excludes == null) { + this.excludes = null; + } else { + Pattern[] p = new Pattern[excludes.length]; + for (int i = 0; i < excludes.length; i++) { + p[i] = new Pattern(); + p[i].setPattern(excludes[i]); + } + setExcludes(p); + } + } + + public void setExcludes(Pattern[] excludes) { + if (excludes == null) { + this.excludes = null; + } else { + this.excludes = new Pattern[excludes.length]; + for (int i = 0; i < excludes.length; i++) { + String pattern; + pattern = excludes[i].getPattern().replace('/',File.separatorChar).replace('\\',File.separatorChar); + if (pattern.endsWith(File.separator)) { + pattern += "**"; + } + this.excludes[i] = new Pattern(); + this.excludes[i].setPattern(pattern); + this.excludes[i].setSelectorList(excludes[i].getSelectorList()); + } + } + } + + /** + * Has the scanner excluded or omitted any files or directories it + * came accross? + * + * @return true if all files and directories that have been found, + * are included. + */ + public boolean isEverythingIncluded() { + return everythingIncluded; + } + + + /** + * Scans the base directory for files that match at least one include + * pattern, and don't match any exclude patterns. + * + * @exception IllegalStateException when basedir was set incorrecly + */ + public void scan() { + if (basedir == null) { + throw new IllegalStateException("No basedir set"); + } + if (!basedir.exists()) { + throw new IllegalStateException("basedir " + basedir + + " does not exist"); + } + if (!basedir.isDirectory()) { + throw new IllegalStateException("basedir " + basedir + + " is not a directory"); + } + + if (includes == null) { + // No includes supplied, so set it to 'matches all' + includes = new Pattern[1]; + includes[0] = new Pattern(); + includes[0].setPattern("**"); + } + if (excludes == null) { + excludes = new Pattern[0]; + } + + filesIncluded = new Vector(); + filesNotIncluded = new Vector(); + filesExcluded = new Vector(); + dirsIncluded = new Vector(); + dirsNotIncluded = new Vector(); + dirsExcluded = new Vector(); + + if (isIncluded("")) { + if (!isExcluded("")) { + dirsIncluded.addElement(""); + } else { + dirsExcluded.addElement(""); + } + } else { + dirsNotIncluded.addElement(""); + } + scandir(basedir, "", true); + } + + /** + * Toplevel invocation for the scan. + * + *
Returns immediately if a slow scan has already been requested.
+ */
+ protected void slowScan() {
+ if (haveSlowResults) {
+ return;
+ }
+
+ String[] excl = new String[dirsExcluded.size()];
+ dirsExcluded.copyInto(excl);
+
+ String[] notIncl = new String[dirsNotIncluded.size()];
+ dirsNotIncluded.copyInto(notIncl);
+
+ for (int i=0; i You must not set another attribute or nest elements inside
+ * this element if you make it a reference. Moved out of MatchingTask to make it a standalone object that
+ * could be referenced (by scripts for example).
+ *
+ * @author Arnout J. Kuiper ajkuiper@wxs.nl
+ * @author Stefano Mazzocchi stefano@apache.org
+ * @author Sam Ruby rubys@us.ibm.com
+ * @author Jon S. Stevens jon@clearink.com
+ * @author Stefan Bodewig
+ * @author Magesh Umasankar
+ */
+public class PatternSet extends DataType {
+ private Vector includeList = new Vector();
+ private Vector excludeList = new Vector();
+ private Vector includesFileList = new Vector();
+ private Vector excludesFileList = new Vector();
+
+
+ /**
+ * inner class to hold a selector list. A SelectorEntry
+ * is made up of the pattern and selection detail.
+ */
+ public static class SelectorEntry {
+ private String type;
+ private String value;
+ private String operation;
+
+ public void setType(String t) {
+ this.type = t;
+ }
+
+ public void setValue(String val) {
+ this.value = val;
+ }
+
+ public void setOperation(String op) {
+ this.operation = op;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public String getOperation() {
+ return operation;
+ }
+ }
+
+ /**
+ * inner class to hold a name on list. "If" and "Unless" attributes
+ * may be used to invalidate the entry based on the existence of a
+ * property (typically set thru the use of the Available task).
+ */
+ public class NameEntry {
+ private String name;
+ private String ifCond;
+ private String unlessCond;
+ private Vector selectorList = new Vector();
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public void setIf(String cond) {
+ ifCond = cond;
+ }
+
+ public void setUnless(String cond) {
+ unlessCond = cond;
+ }
+
+ /**
+ * Include/Exclude can contain nested selectors
+ */
+ public SelectorEntry createSelector() {
+ if (isReference()) {
+ throw noChildrenAllowed();
+ }
+ return addSelectorToList(selectorList);
+ }
+
+ /**
+ * add a selector entry to the given list
+ */
+ private SelectorEntry addSelectorToList(final Vector list) {
+ final SelectorEntry result = new SelectorEntry();
+ list.addElement(result);
+ return result;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public Vector getSelectorList() {
+ return selectorList;
+ }
+
+ public String evalName(Project p) {
+ return valid(p) ? name : null;
+ }
+
+ private boolean valid(Project p) {
+ if (ifCond != null && p.getProperty(ifCond) == null) {
+ return false;
+ } else if (unlessCond != null && p.getProperty(unlessCond) != null) {
+ return false;
+ }
+ return true;
+ }
+
+ public String toString() {
+ StringBuffer buf = new StringBuffer(name);
+ if ((ifCond != null) || (unlessCond != null)) {
+ buf.append(":");
+ String connector = "";
+
+ if (ifCond != null) {
+ buf.append("if->");
+ buf.append(ifCond);
+ connector = ";";
+ }
+ if (unlessCond != null) {
+ buf.append(connector);
+ buf.append("unless->");
+ buf.append(unlessCond);
+ }
+ }
+
+ return buf.toString();
+ }
+
+ public void setSelectorList(Vector list) {
+ this.selectorList = list;
+ }
+ }
+
+ public PatternSet() {
+ super();
+ }
+
+ /**
+ * Makes this instance in effect a reference to another PatternSet
+ * instance.
+ *
+ * You must not set another attribute or nest elements inside
+ * this element if you make it a reference.false
otherwise.
+ */
+ protected boolean isIncluded(String name) {
+ for (int i = 0; i < includes.length; i++) {
+ if (matchPath(includes[i].getPattern(),
+ includes[i].getSelectorList(),
+ name, isCaseSensitive)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Tests whether a name matches the start of at least one include pattern.
+ *
+ * @param name the name to match
+ * @return true
when the name matches against at least one
+ * include pattern, false
otherwise.
+ */
+ protected boolean couldHoldIncluded(String name) {
+ for (int i = 0; i < includes.length; i++) {
+ if (matchPatternStart(includes[i].getPattern(),name, isCaseSensitive)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Tests whether a name matches against at least one exclude pattern.
+ *
+ * @param name the name to match
+ * @return true
when the name matches against at least one
+ * exclude pattern, false
otherwise.
+ */
+ protected boolean isExcluded(String name) {
+ for (int i = 0; i < excludes.length; i++) {
+ if (matchPath(excludes[i].getPattern(),
+ excludes[i].getSelectorList(),
+ name, isCaseSensitive)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+
+ /**
+ * Get the names of the files that matched at least one of the include
+ * patterns, and matched none of the exclude patterns.
+ * The names are relative to the basedir.
+ *
+ * @return the names of the files
+ */
+ public String[] getIncludedFiles() {
+ int count = filesIncluded.size();
+ String[] files = new String[count];
+ for (int i = 0; i < count; i++) {
+ files[i] = (String)filesIncluded.elementAt(i);
+ }
+ return files;
+ }
+
+
+
+ /**
+ * Get the names of the files that matched at none of the include patterns.
+ * The names are relative to the basedir.
+ *
+ * @return the names of the files
+ */
+ public String[] getNotIncludedFiles() {
+ slowScan();
+ int count = filesNotIncluded.size();
+ String[] files = new String[count];
+ for (int i = 0; i < count; i++) {
+ files[i] = (String)filesNotIncluded.elementAt(i);
+ }
+ return files;
+ }
+
+
+
+ /**
+ * Get the names of the files that matched at least one of the include
+ * patterns, an matched also at least one of the exclude patterns.
+ * The names are relative to the basedir.
+ *
+ * @return the names of the files
+ */
+ public String[] getExcludedFiles() {
+ slowScan();
+ int count = filesExcluded.size();
+ String[] files = new String[count];
+ for (int i = 0; i < count; i++) {
+ files[i] = (String)filesExcluded.elementAt(i);
+ }
+ return files;
+ }
+
+
+
+ /**
+ * Get the names of the directories that matched at least one of the include
+ * patterns, an matched none of the exclude patterns.
+ * The names are relative to the basedir.
+ *
+ * @return the names of the directories
+ */
+ public String[] getIncludedDirectories() {
+ int count = dirsIncluded.size();
+ String[] directories = new String[count];
+ for (int i = 0; i < count; i++) {
+ directories[i] = (String)dirsIncluded.elementAt(i);
+ }
+ return directories;
+ }
+
+
+
+ /**
+ * Get the names of the directories that matched at none of the include
+ * patterns.
+ * The names are relative to the basedir.
+ *
+ * @return the names of the directories
+ */
+ public String[] getNotIncludedDirectories() {
+ slowScan();
+ int count = dirsNotIncluded.size();
+ String[] directories = new String[count];
+ for (int i = 0; i < count; i++) {
+ directories[i] = (String)dirsNotIncluded.elementAt(i);
+ }
+ return directories;
+ }
+
+
+
+ /**
+ * Get the names of the directories that matched at least one of the include
+ * patterns, an matched also at least one of the exclude patterns.
+ * The names are relative to the basedir.
+ *
+ * @return the names of the directories
+ */
+ public String[] getExcludedDirectories() {
+ slowScan();
+ int count = dirsExcluded.size();
+ String[] directories = new String[count];
+ for (int i = 0; i < count; i++) {
+ directories[i] = (String)dirsExcluded.elementAt(i);
+ }
+ return directories;
+ }
+
+
+
+ /**
+ * Adds the array with default exclusions to the current exclusions set.
+ *
+ */
+ public void addDefaultExcludes() {
+ int excludesLength = excludes == null ? 0 : excludes.length;
+ Pattern[] newExcludes;
+ newExcludes = new Pattern[excludesLength + DEFAULTEXCLUDES.length];
+ if (excludesLength > 0) {
+ System.arraycopy(excludes,0,newExcludes,0,excludesLength);
+ }
+ for (int i = 0; i < DEFAULTEXCLUDES.length; i++) {
+ newExcludes[i+excludesLength] = new Pattern();
+ newExcludes[i+excludesLength].setPattern(DEFAULTEXCLUDES[i].replace('/',File.separatorChar).replace('\\',File.separatorChar));
+ }
+
+ Pattern[] ep = new Pattern[newExcludes.length];
+ for (int i = 0; i < ep.length; i++) {
+ ep[i] = new Pattern();
+ ep[i].setPattern(newExcludes[i].getPattern());
+ }
+
+ excludes = ep;
+ }
+}
diff --git a/proposal/sandbox/selectors/src/main/org/apache/tools/ant/FileScanner.java b/proposal/sandbox/selectors/src/main/org/apache/tools/ant/FileScanner.java
new file mode 100644
index 000000000..a971a8c8d
--- /dev/null
+++ b/proposal/sandbox/selectors/src/main/org/apache/tools/ant/FileScanner.java
@@ -0,0 +1,175 @@
+/*
+ * The Apache Software License, Version 1.1
+ *
+ * Copyright (c) 2000-2002 The Apache Software Foundation. All rights
+ * reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * 3. The end-user documentation included with the redistribution, if
+ * any, must include the following acknowlegement:
+ * "This product includes software developed by the
+ * Apache Software Foundation (http://www.apache.org/)."
+ * Alternately, this acknowlegement may appear in the software itself,
+ * if and wherever such third-party acknowlegements normally appear.
+ *
+ * 4. The names "The Jakarta Project", "Ant", and "Apache Software
+ * Foundation" must not be used to endorse or promote products derived
+ * from this software without prior written permission. For written
+ * permission, please contact apache@apache.org.
+ *
+ * 5. Products derived from this software may not be called "Apache"
+ * nor may "Apache" appear in their names without prior written
+ * permission of the Apache Group.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * true
if the path should be included
+ * false
otherwise.
+ */
+ public boolean match(String path) {
+ String vpath = path.replace('/', File.separatorChar).
+ replace('\\', File.separatorChar);
+ return isIncluded(vpath) && !isExcluded(vpath);
+ }
+
+}