diff options
Diffstat (limited to 'framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/DirectoryScanner.java')
-rw-r--r-- | framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/DirectoryScanner.java | 1900 |
1 files changed, 1900 insertions, 0 deletions
diff --git a/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/DirectoryScanner.java b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/DirectoryScanner.java new file mode 100644 index 00000000..709779a7 --- /dev/null +++ b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/DirectoryScanner.java @@ -0,0 +1,1900 @@ +/* + * 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; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.Map; +import java.util.Set; +import java.util.Vector; + +import org.apache.tools.ant.taskdefs.condition.Os; +import org.apache.tools.ant.types.Resource; +import org.apache.tools.ant.types.ResourceFactory; +import org.apache.tools.ant.types.resources.FileResource; +import org.apache.tools.ant.types.selectors.FileSelector; +import org.apache.tools.ant.types.selectors.SelectorScanner; +import org.apache.tools.ant.types.selectors.SelectorUtils; +import org.apache.tools.ant.types.selectors.TokenizedPath; +import org.apache.tools.ant.types.selectors.TokenizedPattern; +import org.apache.tools.ant.util.CollectionUtils; +import org.apache.tools.ant.util.FileUtils; +import org.apache.tools.ant.util.SymbolicLinkUtils; +import org.apache.tools.ant.util.VectorSet; + +/** + * Class for scanning a directory for files/directories which match certain + * criteria. + * <p> + * These criteria consist of selectors and patterns which have been specified. + * With the selectors you can select which files you want to have included. + * Files which are not selected are excluded. With patterns you can include + * or exclude files based on their filename. + * <p> + * The idea is simple. A given directory is recursively scanned for all files + * and directories. Each file/directory is matched against a set of selectors, + * including special support for matching against filenames with include and + * and exclude patterns. Only files/directories which match at least one + * pattern of the include pattern list or other file selector, and don't match + * any pattern of the exclude pattern list or fail to match against a required + * selector will be placed in the list of files/directories found. + * <p> + * 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. When + * no selectors are supplied, none are applied. + * <p> + * The filename 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 + * <code>File.separator</code> ('/' under UNIX, '\' under Windows). + * For example, "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. + * <p> + * The segments of the name and the pattern are then matched against each + * other. When '**' is used for a path segment in the pattern, it matches + * zero or more path segments of the name. + * <p> + * There is a special case regarding the use of <code>File.separator</code>s + * at the beginning of the pattern and the string to match:<br> + * When a pattern starts with a <code>File.separator</code>, the string + * to match must also start with a <code>File.separator</code>. + * When a pattern does not start with a <code>File.separator</code>, the + * string to match may not start with a <code>File.separator</code>. + * When one of these rules is not obeyed, the string will not + * match. + * <p> + * When a name path segment is matched against a pattern path segment, the + * following special characters can be used:<br> + * '*' matches zero or more characters<br> + * '?' matches one character. + * <p> + * Examples: + * <p> + * "**\*.class" matches all .class files/dirs in a directory tree. + * <p> + * "test\a??.java" matches all files/dirs which start with an 'a', then two + * more characters and then ".java", in a directory called test. + * <p> + * "**" matches everything in a directory tree. + * <p> + * "**\test\**\XYZ*" matches all files/dirs which start with "XYZ" and where + * there is a parent directory called test (e.g. "abc\test\def\ghi\XYZ123"). + * <p> + * Case sensitivity may be turned off if necessary. By default, it is + * turned on. + * <p> + * Example of usage: + * <pre> + * 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]); + * } + * </pre> + * This will scan a directory called test for .class files, but excludes all + * files in all proper subdirectories of a directory called "modules". + * + */ +public class DirectoryScanner + implements FileScanner, SelectorScanner, ResourceFactory { + + /** Is OpenVMS the operating system we're running on? */ + private static final boolean ON_VMS = Os.isFamily("openvms"); + + /** + * Patterns which should be excluded by default. + * + * <p>Note that you can now add patterns to the list of default + * excludes. Added patterns will not become part of this array + * that has only been kept around for backwards compatibility + * reasons.</p> + * + * @deprecated since 1.6.x. + * Use the {@link #getDefaultExcludes getDefaultExcludes} + * method instead. + */ + @Deprecated + protected static final String[] DEFAULTEXCLUDES = { + // Miscellaneous typical temporary files + SelectorUtils.DEEP_TREE_MATCH + "/*~", + SelectorUtils.DEEP_TREE_MATCH + "/#*#", + SelectorUtils.DEEP_TREE_MATCH + "/.#*", + SelectorUtils.DEEP_TREE_MATCH + "/%*%", + SelectorUtils.DEEP_TREE_MATCH + "/._*", + + // CVS + SelectorUtils.DEEP_TREE_MATCH + "/CVS", + SelectorUtils.DEEP_TREE_MATCH + "/CVS/" + SelectorUtils.DEEP_TREE_MATCH, + SelectorUtils.DEEP_TREE_MATCH + "/.cvsignore", + + // SCCS + SelectorUtils.DEEP_TREE_MATCH + "/SCCS", + SelectorUtils.DEEP_TREE_MATCH + "/SCCS/" + SelectorUtils.DEEP_TREE_MATCH, + + // Visual SourceSafe + SelectorUtils.DEEP_TREE_MATCH + "/vssver.scc", + + // Subversion + SelectorUtils.DEEP_TREE_MATCH + "/.svn", + SelectorUtils.DEEP_TREE_MATCH + "/.svn/" + SelectorUtils.DEEP_TREE_MATCH, + + // Git + SelectorUtils.DEEP_TREE_MATCH + "/.git", + SelectorUtils.DEEP_TREE_MATCH + "/.git/" + SelectorUtils.DEEP_TREE_MATCH, + SelectorUtils.DEEP_TREE_MATCH + "/.gitattributes", + SelectorUtils.DEEP_TREE_MATCH + "/.gitignore", + SelectorUtils.DEEP_TREE_MATCH + "/.gitmodules", + + // Mercurial + SelectorUtils.DEEP_TREE_MATCH + "/.hg", + SelectorUtils.DEEP_TREE_MATCH + "/.hg/" + SelectorUtils.DEEP_TREE_MATCH, + SelectorUtils.DEEP_TREE_MATCH + "/.hgignore", + SelectorUtils.DEEP_TREE_MATCH + "/.hgsub", + SelectorUtils.DEEP_TREE_MATCH + "/.hgsubstate", + SelectorUtils.DEEP_TREE_MATCH + "/.hgtags", + + // Bazaar + SelectorUtils.DEEP_TREE_MATCH + "/.bzr", + SelectorUtils.DEEP_TREE_MATCH + "/.bzr/" + SelectorUtils.DEEP_TREE_MATCH, + SelectorUtils.DEEP_TREE_MATCH + "/.bzrignore", + + // Mac + SelectorUtils.DEEP_TREE_MATCH + "/.DS_Store" + }; + + /** + * default value for {@link #maxLevelsOfSymlinks maxLevelsOfSymlinks} + * @since Ant 1.8.0 + */ + public static final int MAX_LEVELS_OF_SYMLINKS = 5; + /** + * The end of the exception message if something that should be + * there doesn't exist. + */ + public static final String DOES_NOT_EXIST_POSTFIX = " does not exist."; + + /** Helper. */ + private static final FileUtils FILE_UTILS = FileUtils.getFileUtils(); + + /** Helper. */ + private static final SymbolicLinkUtils SYMLINK_UTILS = + SymbolicLinkUtils.getSymbolicLinkUtils(); + + /** + * Patterns which should be excluded by default. + * + * @see #addDefaultExcludes() + */ + private static final Set<String> defaultExcludes = new HashSet<String>(); + static { + resetDefaultExcludes(); + } + + // CheckStyle:VisibilityModifier OFF - bc + + /** The base directory to be scanned. */ + protected File basedir; + + /** The patterns for the files to be included. */ + protected String[] includes; + + /** The patterns for the files to be excluded. */ + protected String[] excludes; + + /** Selectors that will filter which files are in our candidate list. */ + protected FileSelector[] selectors = null; + + /** + * The files which matched at least one include and no excludes + * and were selected. + */ + protected Vector<String> filesIncluded; + + /** The files which did not match any includes or selectors. */ + protected Vector<String> filesNotIncluded; + + /** + * The files which matched at least one include and at least + * one exclude. + */ + protected Vector<String> filesExcluded; + + /** + * The directories which matched at least one include and no excludes + * and were selected. + */ + protected Vector<String> dirsIncluded; + + /** The directories which were found and did not match any includes. */ + protected Vector<String> dirsNotIncluded; + + /** + * The directories which matched at least one include and at least one + * exclude. + */ + protected Vector<String> dirsExcluded; + + /** + * The files which matched at least one include and no excludes and + * which a selector discarded. + */ + protected Vector<String> filesDeselected; + + /** + * The directories which matched at least one include and no excludes + * but which a selector discarded. + */ + protected Vector<String> dirsDeselected; + + /** Whether or not our results were built by a slow scan. */ + protected boolean haveSlowResults = false; + + /** + * Whether or not the file system should be treated as a case sensitive + * one. + */ + protected boolean isCaseSensitive = true; + + /** + * Whether a missing base directory is an error. + * @since Ant 1.7.1 + */ + protected boolean errorOnMissingDir = true; + + /** + * Whether or not symbolic links should be followed. + * + * @since Ant 1.5 + */ + private boolean followSymlinks = true; + + /** Whether or not everything tested so far has been included. */ + protected boolean everythingIncluded = true; + + // CheckStyle:VisibilityModifier ON + + /** + * List of all scanned directories. + * + * @since Ant 1.6 + */ + private final Set<String> scannedDirs = new HashSet<String>(); + + /** + * Map of all include patterns that are full file names and don't + * contain any wildcards. + * + * <p>Maps pattern string to TokenizedPath.</p> + * + * <p>If this instance is not case sensitive, the file names get + * turned to upper case.</p> + * + * <p>Gets lazily initialized on the first invocation of + * isIncluded or isExcluded and cleared at the end of the scan + * method (cleared in clearCaches, actually).</p> + * + * @since Ant 1.8.0 + */ + private final Map<String, TokenizedPath> includeNonPatterns = new HashMap<String, TokenizedPath>(); + + /** + * Map of all exclude patterns that are full file names and don't + * contain any wildcards. + * + * <p>Maps pattern string to TokenizedPath.</p> + * + * <p>If this instance is not case sensitive, the file names get + * turned to upper case.</p> + * + * <p>Gets lazily initialized on the first invocation of + * isIncluded or isExcluded and cleared at the end of the scan + * method (cleared in clearCaches, actually).</p> + * + * @since Ant 1.8.0 + */ + private final Map<String, TokenizedPath> excludeNonPatterns = new HashMap<String, TokenizedPath>(); + + /** + * Array of all include patterns that contain wildcards. + * + * <p>Gets lazily initialized on the first invocation of + * isIncluded or isExcluded and cleared at the end of the scan + * method (cleared in clearCaches, actually).</p> + */ + private TokenizedPattern[] includePatterns; + + /** + * Array of all exclude patterns that contain wildcards. + * + * <p>Gets lazily initialized on the first invocation of + * isIncluded or isExcluded and cleared at the end of the scan + * method (cleared in clearCaches, actually).</p> + */ + private TokenizedPattern[] excludePatterns; + + /** + * Have the non-pattern sets and pattern arrays for in- and + * excludes been initialized? + * + * @since Ant 1.6.3 + */ + private boolean areNonPatternSetsReady = false; + + /** + * Scanning flag. + * + * @since Ant 1.6.3 + */ + private boolean scanning = false; + + /** + * Scanning lock. + * + * @since Ant 1.6.3 + */ + private final Object scanLock = new Object(); + + /** + * Slow scanning flag. + * + * @since Ant 1.6.3 + */ + private boolean slowScanning = false; + + /** + * Slow scanning lock. + * + * @since Ant 1.6.3 + */ + private final Object slowScanLock = new Object(); + + /** + * Exception thrown during scan. + * + * @since Ant 1.6.3 + */ + private IllegalStateException illegal = null; + + /** + * The maximum number of times a symbolic link may be followed + * during a scan. + * + * @since Ant 1.8.0 + */ + private int maxLevelsOfSymlinks = MAX_LEVELS_OF_SYMLINKS; + + + /** + * Absolute paths of all symlinks that haven't been followed but + * would have been if followsymlinks had been true or + * maxLevelsOfSymlinks had been higher. + * + * @since Ant 1.8.0 + */ + private final Set<String> notFollowedSymlinks = new HashSet<String>(); + + /** + * Sole constructor. + */ + public DirectoryScanner() { + } + + /** + * Test whether or not a given path matches the start of a given + * pattern up to the first "**". + * <p> + * This is not a general purpose test and should only be used if you + * can live with false positives. For example, <code>pattern=**\a</code> + * and <code>str=b</code> will yield <code>true</code>. + * + * @param pattern The pattern to match against. Must not be + * <code>null</code>. + * @param str The path to match, as a String. Must not be + * <code>null</code>. + * + * @return whether or not a given path matches the start of a given + * pattern up to the first "**". + */ + protected static boolean matchPatternStart(final String pattern, final String str) { + return SelectorUtils.matchPatternStart(pattern, str); + } + + /** + * Test whether or not a given path matches the start of a given + * pattern up to the first "**". + * <p> + * This is not a general purpose test and should only be used if you + * can live with false positives. For example, <code>pattern=**\a</code> + * and <code>str=b</code> will yield <code>true</code>. + * + * @param pattern The pattern to match against. Must not be + * <code>null</code>. + * @param str The path to match, as a String. Must not be + * <code>null</code>. + * @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 "**". + */ + protected static boolean matchPatternStart(final String pattern, final String str, + final boolean isCaseSensitive) { + return SelectorUtils.matchPatternStart(pattern, str, isCaseSensitive); + } + + /** + * Test whether or not a given path matches a given pattern. + * + * @param pattern The pattern to match against. Must not be + * <code>null</code>. + * @param str The path to match, as a String. Must not be + * <code>null</code>. + * + * @return <code>true</code> if the pattern matches against the string, + * or <code>false</code> otherwise. + */ + protected static boolean matchPath(final String pattern, final String str) { + return SelectorUtils.matchPath(pattern, str); + } + + /** + * Test whether or not a given path matches a given pattern. + * + * @param pattern The pattern to match against. Must not be + * <code>null</code>. + * @param str The path to match, as a String. Must not be + * <code>null</code>. + * @param isCaseSensitive Whether or not matching should be performed + * case sensitively. + * + * @return <code>true</code> if the pattern matches against the string, + * or <code>false</code> otherwise. + */ + protected static boolean matchPath(final String pattern, final String str, + final boolean isCaseSensitive) { + return SelectorUtils.matchPath(pattern, str, isCaseSensitive); + } + + /** + * Test whether or not a string matches against a pattern. + * The pattern may contain two special characters:<br> + * '*' means zero or more characters<br> + * '?' means one and only one character + * + * @param pattern The pattern to match against. + * Must not be <code>null</code>. + * @param str The string which must be matched against the pattern. + * Must not be <code>null</code>. + * + * @return <code>true</code> if the string matches against the pattern, + * or <code>false</code> otherwise. + */ + public static boolean match(final String pattern, final String str) { + return SelectorUtils.match(pattern, str); + } + + /** + * Test whether or not a string matches against a pattern. + * The pattern may contain two special characters:<br> + * '*' means zero or more characters<br> + * '?' means one and only one character + * + * @param pattern The pattern to match against. + * Must not be <code>null</code>. + * @param str The string which must be matched against the pattern. + * Must not be <code>null</code>. + * @param isCaseSensitive Whether or not matching should be performed + * case sensitively. + * + * + * @return <code>true</code> if the string matches against the pattern, + * or <code>false</code> otherwise. + */ + protected static boolean match(final String pattern, final String str, + final boolean isCaseSensitive) { + return SelectorUtils.match(pattern, str, isCaseSensitive); + } + + + /** + * Get the list of patterns that should be excluded by default. + * + * @return An array of <code>String</code> based on the current + * contents of the <code>defaultExcludes</code> + * <code>Set</code>. + * + * @since Ant 1.6 + */ + public static String[] getDefaultExcludes() { + synchronized (defaultExcludes) { + return defaultExcludes.toArray(new String[defaultExcludes + .size()]); + } + } + + /** + * Add a pattern to the default excludes unless it is already a + * default exclude. + * + * @param s A string to add as an exclude pattern. + * @return <code>true</code> if the string was added; + * <code>false</code> if it already existed. + * + * @since Ant 1.6 + */ + public static boolean addDefaultExclude(final String s) { + synchronized (defaultExcludes) { + return defaultExcludes.add(s); + } + } + + /** + * Remove a string if it is a default exclude. + * + * @param s The string to attempt to remove. + * @return <code>true</code> if <code>s</code> was a default + * exclude (and thus was removed); + * <code>false</code> if <code>s</code> was not + * in the default excludes list to begin with. + * + * @since Ant 1.6 + */ + public static boolean removeDefaultExclude(final String s) { + synchronized (defaultExcludes) { + return defaultExcludes.remove(s); + } + } + + /** + * Go back to the hardwired default exclude patterns. + * + * @since Ant 1.6 + */ + public static void resetDefaultExcludes() { + synchronized (defaultExcludes) { + defaultExcludes.clear(); + for (int i = 0; i < DEFAULTEXCLUDES.length; i++) { + defaultExcludes.add(DEFAULTEXCLUDES[i]); + } + } + } + + /** + * Set the base directory to be scanned. This is the directory which is + * scanned recursively. All '/' and '\' characters are replaced by + * <code>File.separatorChar</code>, so the separator used need not match + * <code>File.separatorChar</code>. + * + * @param basedir The base directory to scan. + */ + public void setBasedir(final String basedir) { + setBasedir(basedir == null ? (File) null + : new File(basedir.replace('/', File.separatorChar).replace( + '\\', File.separatorChar))); + } + + /** + * Set the base directory to be scanned. This is the directory which is + * scanned recursively. + * + * @param basedir The base directory for scanning. + */ + public synchronized void setBasedir(final File basedir) { + this.basedir = basedir; + } + + /** + * Return the base directory to be scanned. + * This is the directory which is scanned recursively. + * + * @return the base directory to be scanned. + */ + public synchronized File getBasedir() { + return basedir; + } + + /** + * Find out whether include exclude patterns are matched in a + * case sensitive way. + * @return whether or not the scanning is case sensitive. + * @since Ant 1.6 + */ + public synchronized boolean isCaseSensitive() { + return isCaseSensitive; + } + + /** + * Set whether or not include and exclude patterns are matched + * in a case sensitive way. + * + * @param isCaseSensitive whether or not the file system should be + * regarded as a case sensitive one. + */ + public synchronized void setCaseSensitive(final boolean isCaseSensitive) { + this.isCaseSensitive = isCaseSensitive; + } + + /** + * Sets whether or not a missing base directory is an error + * + * @param errorOnMissingDir whether or not a missing base directory + * is an error + * @since Ant 1.7.1 + */ + public void setErrorOnMissingDir(final boolean errorOnMissingDir) { + this.errorOnMissingDir = errorOnMissingDir; + } + + /** + * Get whether or not a DirectoryScanner follows symbolic links. + * + * @return flag indicating whether symbolic links should be followed. + * + * @since Ant 1.6 + */ + public synchronized boolean isFollowSymlinks() { + return followSymlinks; + } + + /** + * Set whether or not symbolic links should be followed. + * + * @param followSymlinks whether or not symbolic links should be followed. + */ + public synchronized void setFollowSymlinks(final boolean followSymlinks) { + this.followSymlinks = followSymlinks; + } + + /** + * The maximum number of times a symbolic link may be followed + * during a scan. + * + * @since Ant 1.8.0 + */ + public void setMaxLevelsOfSymlinks(final int max) { + maxLevelsOfSymlinks = max; + } + + /** + * Set the list of include patterns to use. All '/' and '\' characters + * are replaced by <code>File.separatorChar</code>, so the separator used + * need not match <code>File.separatorChar</code>. + * <p> + * When a pattern ends with a '/' or '\', "**" is appended. + * + * @param includes A list of include patterns. + * May be <code>null</code>, indicating that all files + * should be included. If a non-<code>null</code> + * list is given, all elements must be + * non-<code>null</code>. + */ + public synchronized void setIncludes(final String[] includes) { + if (includes == null) { + this.includes = null; + } else { + this.includes = new String[includes.length]; + for (int i = 0; i < includes.length; i++) { + this.includes[i] = normalizePattern(includes[i]); + } + } + } + + /** + * Set the list of exclude patterns to use. All '/' and '\' characters + * are replaced by <code>File.separatorChar</code>, so the separator used + * need not match <code>File.separatorChar</code>. + * <p> + * When a pattern ends with a '/' or '\', "**" is appended. + * + * @param excludes A list of exclude patterns. + * May be <code>null</code>, indicating that no files + * should be excluded. If a non-<code>null</code> list is + * given, all elements must be non-<code>null</code>. + */ + public synchronized void setExcludes(final String[] excludes) { + if (excludes == null) { + this.excludes = null; + } else { + this.excludes = new String[excludes.length]; + for (int i = 0; i < excludes.length; i++) { + this.excludes[i] = normalizePattern(excludes[i]); + } + } + } + + /** + * Add to the list of exclude patterns to use. All '/' and '\' + * characters are replaced by <code>File.separatorChar</code>, so + * the separator used need not match <code>File.separatorChar</code>. + * <p> + * When a pattern ends with a '/' or '\', "**" is appended. + * + * @param excludes A list of exclude patterns. + * May be <code>null</code>, in which case the + * exclude patterns don't get changed at all. + * + * @since Ant 1.6.3 + */ + public synchronized void addExcludes(final String[] excludes) { + if (excludes != null && excludes.length > 0) { + if (this.excludes != null && this.excludes.length > 0) { + final String[] tmp = new String[excludes.length + + this.excludes.length]; + System.arraycopy(this.excludes, 0, tmp, 0, + this.excludes.length); + for (int i = 0; i < excludes.length; i++) { + tmp[this.excludes.length + i] = + normalizePattern(excludes[i]); + } + this.excludes = tmp; + } else { + setExcludes(excludes); + } + } + } + + /** + * All '/' and '\' characters are replaced by + * <code>File.separatorChar</code>, so the separator used need not + * match <code>File.separatorChar</code>. + * + * <p> When a pattern ends with a '/' or '\', "**" is appended. + * + * @since Ant 1.6.3 + */ + private static String normalizePattern(final String p) { + String pattern = p.replace('/', File.separatorChar) + .replace('\\', File.separatorChar); + if (pattern.endsWith(File.separator)) { + pattern += SelectorUtils.DEEP_TREE_MATCH; + } + return pattern; + } + + /** + * Set the selectors that will select the filelist. + * + * @param selectors specifies the selectors to be invoked on a scan. + */ + public synchronized void setSelectors(final FileSelector[] selectors) { + this.selectors = selectors; + } + + /** + * Return whether or not the scanner has included all the files or + * directories it has come across so far. + * + * @return <code>true</code> if all files and directories which have + * been found so far have been included. + */ + public synchronized boolean isEverythingIncluded() { + return everythingIncluded; + } + + /** + * Scan for files which match at least one include pattern and don't match + * any exclude patterns. If there are selectors then the files must pass + * muster there, as well. Scans under basedir, if set; otherwise the + * include patterns without leading wildcards specify the absolute paths of + * the files that may be included. + * + * @exception IllegalStateException if the base directory was set + * incorrectly (i.e. if it doesn't exist or isn't a directory). + */ + public void scan() throws IllegalStateException { + synchronized (scanLock) { + if (scanning) { + while (scanning) { + try { + scanLock.wait(); + } catch (final InterruptedException e) { + continue; + } + } + if (illegal != null) { + throw illegal; + } + return; + } + scanning = true; + } + final File savedBase = basedir; + try { + synchronized (this) { + illegal = null; + clearResults(); + + // set in/excludes to reasonable defaults if needed: + final boolean nullIncludes = (includes == null); + includes = nullIncludes + ? new String[] {SelectorUtils.DEEP_TREE_MATCH} : includes; + final boolean nullExcludes = (excludes == null); + excludes = nullExcludes ? new String[0] : excludes; + + if (basedir != null && !followSymlinks + && SYMLINK_UTILS.isSymbolicLink(basedir)) { + notFollowedSymlinks.add(basedir.getAbsolutePath()); + basedir = null; + } + + if (basedir == null) { + // if no basedir and no includes, nothing to do: + if (nullIncludes) { + return; + } + } else { + if (!basedir.exists()) { + if (errorOnMissingDir) { + illegal = new IllegalStateException("basedir " + + basedir + + DOES_NOT_EXIST_POSTFIX); + } else { + // Nothing to do - basedir does not exist + return; + } + } else if (!basedir.isDirectory()) { + illegal = new IllegalStateException("basedir " + + basedir + + " is not a" + + " directory."); + } + if (illegal != null) { + throw illegal; + } + } + if (isIncluded(TokenizedPath.EMPTY_PATH)) { + if (!isExcluded(TokenizedPath.EMPTY_PATH)) { + if (isSelected("", basedir)) { + dirsIncluded.addElement(""); + } else { + dirsDeselected.addElement(""); + } + } else { + dirsExcluded.addElement(""); + } + } else { + dirsNotIncluded.addElement(""); + } + checkIncludePatterns(); + clearCaches(); + includes = nullIncludes ? null : includes; + excludes = nullExcludes ? null : excludes; + } + } catch (final IOException ex) { + throw new BuildException(ex); + } finally { + basedir = savedBase; + synchronized (scanLock) { + scanning = false; + scanLock.notifyAll(); + } + } + } + + /** + * This routine is actually checking all the include patterns in + * order to avoid scanning everything under base dir. + * @since Ant 1.6 + */ + private void checkIncludePatterns() { + ensureNonPatternSetsReady(); + final Map<TokenizedPath, String> newroots = new HashMap<TokenizedPath, String>(); + + // put in the newroots map the include patterns without + // wildcard tokens + for (int i = 0; i < includePatterns.length; i++) { + final String pattern = includePatterns[i].toString(); + if (!shouldSkipPattern(pattern)) { + newroots.put(includePatterns[i].rtrimWildcardTokens(), + pattern); + } + } + for (final Map.Entry<String, TokenizedPath> entry : includeNonPatterns.entrySet()) { + final String pattern = entry.getKey(); + if (!shouldSkipPattern(pattern)) { + newroots.put(entry.getValue(), pattern); + } + } + + if (newroots.containsKey(TokenizedPath.EMPTY_PATH) + && basedir != null) { + // we are going to scan everything anyway + scandir(basedir, "", true); + } else { + File canonBase = null; + if (basedir != null) { + try { + canonBase = basedir.getCanonicalFile(); + } catch (final IOException ex) { + throw new BuildException(ex); + } + } + // only scan directories that can include matched files or + // directories + for (final Map.Entry<TokenizedPath, String> entry : newroots.entrySet()) { + TokenizedPath currentPath = entry.getKey(); + String currentelement = currentPath.toString(); + if (basedir == null + && !FileUtils.isAbsolutePath(currentelement)) { + continue; + } + File myfile = new File(basedir, currentelement); + + if (myfile.exists()) { + // may be on a case insensitive file system. We want + // the results to show what's really on the disk, so + // we need to double check. + try { + final String path = (basedir == null) + ? myfile.getCanonicalPath() + : FILE_UTILS.removeLeadingPath(canonBase, + myfile.getCanonicalFile()); + if (!path.equals(currentelement) || ON_VMS) { + myfile = currentPath.findFile(basedir, true); + if (myfile != null && basedir != null) { + currentelement = FILE_UTILS.removeLeadingPath( + basedir, myfile); + if (!currentPath.toString() + .equals(currentelement)) { + currentPath = + new TokenizedPath(currentelement); + } + } + } + } catch (final IOException ex) { + throw new BuildException(ex); + } + } + + if ((myfile == null || !myfile.exists()) && !isCaseSensitive()) { + final File f = currentPath.findFile(basedir, false); + if (f != null && f.exists()) { + // adapt currentelement to the case we've + // actually found + currentelement = (basedir == null) + ? f.getAbsolutePath() + : FILE_UTILS.removeLeadingPath(basedir, f); + myfile = f; + currentPath = new TokenizedPath(currentelement); + } + } + + if (myfile != null && myfile.exists()) { + if (!followSymlinks && currentPath.isSymlink(basedir)) { + accountForNotFollowedSymlink(currentPath, myfile); + continue; + } + if (myfile.isDirectory()) { + if (isIncluded(currentPath) + && currentelement.length() > 0) { + accountForIncludedDir(currentPath, myfile, true); + } else { + scandir(myfile, currentPath, true); + } + } else if (myfile.isFile()) { + final String originalpattern = entry.getValue(); + final boolean included = isCaseSensitive() + ? originalpattern.equals(currentelement) + : originalpattern.equalsIgnoreCase(currentelement); + if (included) { + accountForIncludedFile(currentPath, myfile); + } + } + } + } + } + } + + /** + * true if the pattern specifies a relative path without basedir + * or an absolute path not inside basedir. + * + * @since Ant 1.8.0 + */ + private boolean shouldSkipPattern(final String pattern) { + if (FileUtils.isAbsolutePath(pattern)) { + //skip abs. paths not under basedir, if set: + if (basedir != null + && !SelectorUtils.matchPatternStart(pattern, + basedir.getAbsolutePath(), + isCaseSensitive())) { + return true; + } + } else if (basedir == null) { + //skip non-abs. paths if basedir == null: + return true; + } + return false; + } + + /** + * Clear the result caches for a scan. + */ + protected synchronized void clearResults() { + filesIncluded = new VectorSet<String>(); + filesNotIncluded = new VectorSet<String>(); + filesExcluded = new VectorSet<String>(); + filesDeselected = new VectorSet<String>(); + dirsIncluded = new VectorSet<String>(); + dirsNotIncluded = new VectorSet<String>(); + dirsExcluded = new VectorSet<String>(); + dirsDeselected = new VectorSet<String>(); + everythingIncluded = (basedir != null); + scannedDirs.clear(); + notFollowedSymlinks.clear(); + } + + /** + * Top level invocation for a slow scan. A slow scan builds up a full + * list of excluded/included files/directories, whereas a fast scan + * will only have full results for included files, as it ignores + * directories which can't possibly hold any included files/directories. + * <p> + * Returns immediately if a slow scan has already been completed. + */ + protected void slowScan() { + synchronized (slowScanLock) { + if (haveSlowResults) { + return; + } + if (slowScanning) { + while (slowScanning) { + try { + slowScanLock.wait(); + } catch (final InterruptedException e) { + // Empty + } + } + return; + } + slowScanning = true; + } + try { + synchronized (this) { + + // set in/excludes to reasonable defaults if needed: + final boolean nullIncludes = (includes == null); + includes = nullIncludes + ? new String[] {SelectorUtils.DEEP_TREE_MATCH} : includes; + final boolean nullExcludes = (excludes == null); + excludes = nullExcludes ? new String[0] : excludes; + + final String[] excl = new String[dirsExcluded.size()]; + dirsExcluded.copyInto(excl); + + final String[] notIncl = new String[dirsNotIncluded.size()]; + dirsNotIncluded.copyInto(notIncl); + + ensureNonPatternSetsReady(); + + processSlowScan(excl); + processSlowScan(notIncl); + clearCaches(); + includes = nullIncludes ? null : includes; + excludes = nullExcludes ? null : excludes; + } + } finally { + synchronized (slowScanLock) { + haveSlowResults = true; + slowScanning = false; + slowScanLock.notifyAll(); + } + } + } + + private void processSlowScan(final String[] arr) { + for (int i = 0; i < arr.length; i++) { + final TokenizedPath path = new TokenizedPath(arr[i]); + if (!couldHoldIncluded(path) || contentsExcluded(path)) { + scandir(new File(basedir, arr[i]), path, false); + } + } + } + + /** + * Scan the given directory for files and directories. Found files and + * directories are placed in their respective collections, based on the + * matching of includes, excludes, and the selectors. When a directory + * is found, it is scanned recursively. + * + * @param dir The directory to scan. Must not be <code>null</code>. + * @param vpath The path relative to the base directory (needed to + * prevent problems with an absolute path when using + * dir). Must not be <code>null</code>. + * @param fast Whether or not this call is part of a fast scan. + * + * @see #filesIncluded + * @see #filesNotIncluded + * @see #filesExcluded + * @see #dirsIncluded + * @see #dirsNotIncluded + * @see #dirsExcluded + * @see #slowScan + */ + protected void scandir(final File dir, final String vpath, final boolean fast) { + scandir(dir, new TokenizedPath(vpath), fast); + } + + /** + * Scan the given directory for files and directories. Found files and + * directories are placed in their respective collections, based on the + * matching of includes, excludes, and the selectors. When a directory + * is found, it is scanned recursively. + * + * @param dir The directory to scan. Must not be <code>null</code>. + * @param path The path relative to the base directory (needed to + * prevent problems with an absolute path when using + * dir). Must not be <code>null</code>. + * @param fast Whether or not this call is part of a fast scan. + * + * @see #filesIncluded + * @see #filesNotIncluded + * @see #filesExcluded + * @see #dirsIncluded + * @see #dirsNotIncluded + * @see #dirsExcluded + * @see #slowScan + */ + private void scandir(final File dir, final TokenizedPath path, final boolean fast) { + if (dir == null) { + throw new BuildException("dir must not be null."); + } + final String[] newfiles = dir.list(); + if (newfiles == null) { + if (!dir.exists()) { + throw new BuildException(dir + DOES_NOT_EXIST_POSTFIX); + } else if (!dir.isDirectory()) { + throw new BuildException(dir + " is not a directory."); + } else { + throw new BuildException("IO error scanning directory '" + + dir.getAbsolutePath() + "'"); + } + } + scandir(dir, path, fast, newfiles, new LinkedList<String>()); + } + + private void scandir(final File dir, final TokenizedPath path, final boolean fast, + String[] newfiles, final LinkedList<String> directoryNamesFollowed) { + String vpath = path.toString(); + if (vpath.length() > 0 && !vpath.endsWith(File.separator)) { + vpath += File.separator; + } + + // avoid double scanning of directories, can only happen in fast mode + if (fast && hasBeenScanned(vpath)) { + return; + } + if (!followSymlinks) { + final ArrayList<String> noLinks = new ArrayList<String>(); + for (int i = 0; i < newfiles.length; i++) { + try { + if (SYMLINK_UTILS.isSymbolicLink(dir, newfiles[i])) { + final String name = vpath + newfiles[i]; + final File file = new File(dir, newfiles[i]); + if (file.isDirectory()) { + dirsExcluded.addElement(name); + } else if (file.isFile()) { + filesExcluded.addElement(name); + } + accountForNotFollowedSymlink(name, file); + } else { + noLinks.add(newfiles[i]); + } + } catch (final IOException ioe) { + final String msg = "IOException caught while checking " + + "for links, couldn't get canonical path!"; + // will be caught and redirected to Ant's logging system + System.err.println(msg); + noLinks.add(newfiles[i]); + } + } + newfiles = (noLinks.toArray(new String[noLinks.size()])); + } else { + directoryNamesFollowed.addFirst(dir.getName()); + } + + for (int i = 0; i < newfiles.length; i++) { + final String name = vpath + newfiles[i]; + final TokenizedPath newPath = new TokenizedPath(path, newfiles[i]); + final File file = new File(dir, newfiles[i]); + final String[] children = file.list(); + if (children == null || (children.length == 0 && file.isFile())) { + if (isIncluded(newPath)) { + accountForIncludedFile(newPath, file); + } else { + everythingIncluded = false; + filesNotIncluded.addElement(name); + } + } else if (file.isDirectory()) { // dir + + if (followSymlinks + && causesIllegalSymlinkLoop(newfiles[i], dir, + directoryNamesFollowed)) { + // will be caught and redirected to Ant's logging system + System.err.println("skipping symbolic link " + + file.getAbsolutePath() + + " -- too many levels of symbolic" + + " links."); + notFollowedSymlinks.add(file.getAbsolutePath()); + continue; + } + + if (isIncluded(newPath)) { + accountForIncludedDir(newPath, file, fast, children, + directoryNamesFollowed); + } else { + everythingIncluded = false; + dirsNotIncluded.addElement(name); + if (fast && couldHoldIncluded(newPath) + && !contentsExcluded(newPath)) { + scandir(file, newPath, fast, children, + directoryNamesFollowed); + } + } + if (!fast) { + scandir(file, newPath, fast, children, directoryNamesFollowed); + } + } + } + + if (followSymlinks) { + directoryNamesFollowed.removeFirst(); + } + } + + /** + * Process included file. + * @param name path of the file relative to the directory of the FileSet. + * @param file included File. + */ + private void accountForIncludedFile(final TokenizedPath name, final File file) { + processIncluded(name, file, filesIncluded, filesExcluded, + filesDeselected); + } + + /** + * Process included directory. + * @param name path of the directory relative to the directory of + * the FileSet. + * @param file directory as File. + * @param fast whether to perform fast scans. + */ + private void accountForIncludedDir(final TokenizedPath name, final File file, + final boolean fast) { + processIncluded(name, file, dirsIncluded, dirsExcluded, dirsDeselected); + if (fast && couldHoldIncluded(name) && !contentsExcluded(name)) { + scandir(file, name, fast); + } + } + + private void accountForIncludedDir(final TokenizedPath name, + final File file, final boolean fast, + final String[] children, + final LinkedList<String> directoryNamesFollowed) { + processIncluded(name, file, dirsIncluded, dirsExcluded, dirsDeselected); + if (fast && couldHoldIncluded(name) && !contentsExcluded(name)) { + scandir(file, name, fast, children, directoryNamesFollowed); + } + } + + private void accountForNotFollowedSymlink(final String name, final File file) { + accountForNotFollowedSymlink(new TokenizedPath(name), file); + } + + private void accountForNotFollowedSymlink(final TokenizedPath name, final File file) { + if (!isExcluded(name) && + (isIncluded(name) + || (file.isDirectory() && couldHoldIncluded(name) + && !contentsExcluded(name)))) { + notFollowedSymlinks.add(file.getAbsolutePath()); + } + } + + private void processIncluded(final TokenizedPath path, + final File file, final Vector<String> inc, final Vector<String> exc, + final Vector<String> des) { + final String name = path.toString(); + if (inc.contains(name) || exc.contains(name) || des.contains(name)) { + return; + } + + boolean included = false; + if (isExcluded(path)) { + exc.add(name); + } else if (isSelected(name, file)) { + included = true; + inc.add(name); + } else { + des.add(name); + } + everythingIncluded &= included; + } + + /** + * Test whether or not a name matches against at least one include + * pattern. + * + * @param name The name to match. Must not be <code>null</code>. + * @return <code>true</code> when the name matches against at least one + * include pattern, or <code>false</code> otherwise. + */ + protected boolean isIncluded(final String name) { + return isIncluded(new TokenizedPath(name)); + } + + /** + * Test whether or not a name matches against at least one include + * pattern. + * + * @param name The name to match. Must not be <code>null</code>. + * @return <code>true</code> when the name matches against at least one + * include pattern, or <code>false</code> otherwise. + */ + private boolean isIncluded(final TokenizedPath path) { + ensureNonPatternSetsReady(); + + if (isCaseSensitive() + ? includeNonPatterns.containsKey(path.toString()) + : includeNonPatterns.containsKey(path.toString().toUpperCase())) { + return true; + } + for (int i = 0; i < includePatterns.length; i++) { + if (includePatterns[i].matchPath(path, isCaseSensitive())) { + return true; + } + } + return false; + } + + /** + * Test whether or not a name matches the start of at least one include + * pattern. + * + * @param name The name to match. Must not be <code>null</code>. + * @return <code>true</code> when the name matches against the start of at + * least one include pattern, or <code>false</code> otherwise. + */ + protected boolean couldHoldIncluded(final String name) { + return couldHoldIncluded(new TokenizedPath(name)); + } + + /** + * Test whether or not a name matches the start of at least one include + * pattern. + * + * @param tokenizedName The name to match. Must not be <code>null</code>. + * @return <code>true</code> when the name matches against the start of at + * least one include pattern, or <code>false</code> otherwise. + */ + private boolean couldHoldIncluded(final TokenizedPath tokenizedName) { + for (int i = 0; i < includePatterns.length; i++) { + if (couldHoldIncluded(tokenizedName, includePatterns[i])) { + return true; + } + } + for (final Iterator<TokenizedPath> iter = includeNonPatterns.values().iterator(); + iter.hasNext();) { + if (couldHoldIncluded(tokenizedName, + iter.next().toPattern())) { + return true; + } + } + return false; + } + + /** + * Test whether or not a name matches the start of the given + * include pattern. + * + * @param tokenizedName The name to match. Must not be <code>null</code>. + * @return <code>true</code> when the name matches against the start of the + * include pattern, or <code>false</code> otherwise. + */ + private boolean couldHoldIncluded(final TokenizedPath tokenizedName, + final TokenizedPattern tokenizedInclude) { + return tokenizedInclude.matchStartOf(tokenizedName, isCaseSensitive()) + && isMorePowerfulThanExcludes(tokenizedName.toString()) + && isDeeper(tokenizedInclude, tokenizedName); + } + + /** + * Verify that a pattern specifies files deeper + * than the level of the specified file. + * @param pattern the pattern to check. + * @param name the name to check. + * @return whether the pattern is deeper than the name. + * @since Ant 1.6.3 + */ + private boolean isDeeper(final TokenizedPattern pattern, final TokenizedPath name) { + return pattern.containsPattern(SelectorUtils.DEEP_TREE_MATCH) + || pattern.depth() > name.depth(); + } + + /** + * Find out whether one particular include pattern is more powerful + * than all the excludes. + * Note: the power comparison is based on the length of the include pattern + * and of the exclude patterns without the wildcards. + * Ideally the comparison should be done based on the depth + * of the match; that is to say how many file separators have been matched + * before the first ** or the end of the pattern. + * + * IMPORTANT : this function should return false "with care". + * + * @param name the relative path to test. + * @return true if there is no exclude pattern more powerful than + * this include pattern. + * @since Ant 1.6 + */ + private boolean isMorePowerfulThanExcludes(final String name) { + final String soughtexclude = + name + File.separatorChar + SelectorUtils.DEEP_TREE_MATCH; + for (int counter = 0; counter < excludePatterns.length; counter++) { + if (excludePatterns[counter].toString().equals(soughtexclude)) { + return false; + } + } + return true; + } + + /** + * Test whether all contents of the specified directory must be excluded. + * @param path the path to check. + * @return whether all the specified directory's contents are excluded. + */ + /* package */ boolean contentsExcluded(final TokenizedPath path) { + for (int i = 0; i < excludePatterns.length; i++) { + if (excludePatterns[i].endsWith(SelectorUtils.DEEP_TREE_MATCH) + && excludePatterns[i].withoutLastToken() + .matchPath(path, isCaseSensitive())) { + return true; + } + } + return false; + } + + /** + * Test whether or not a name matches against at least one exclude + * pattern. + * + * @param name The name to match. Must not be <code>null</code>. + * @return <code>true</code> when the name matches against at least one + * exclude pattern, or <code>false</code> otherwise. + */ + protected boolean isExcluded(final String name) { + return isExcluded(new TokenizedPath(name)); + } + + /** + * Test whether or not a name matches against at least one exclude + * pattern. + * + * @param name The name to match. Must not be <code>null</code>. + * @return <code>true</code> when the name matches against at least one + * exclude pattern, or <code>false</code> otherwise. + */ + private boolean isExcluded(final TokenizedPath name) { + ensureNonPatternSetsReady(); + + if (isCaseSensitive() + ? excludeNonPatterns.containsKey(name.toString()) + : excludeNonPatterns.containsKey(name.toString().toUpperCase())) { + return true; + } + for (int i = 0; i < excludePatterns.length; i++) { + if (excludePatterns[i].matchPath(name, isCaseSensitive())) { + return true; + } + } + return false; + } + + /** + * Test whether a file should be selected. + * + * @param name the filename to check for selecting. + * @param file the java.io.File object for this filename. + * @return <code>false</code> when the selectors says that the file + * should not be selected, <code>true</code> otherwise. + */ + protected boolean isSelected(final String name, final File file) { + if (selectors != null) { + for (int i = 0; i < selectors.length; i++) { + if (!selectors[i].isSelected(basedir, name, file)) { + return false; + } + } + } + return true; + } + + /** + * Return the names of the files which matched at least one of the + * include patterns and none of the exclude patterns. + * The names are relative to the base directory. + * + * @return the names of the files which matched at least one of the + * include patterns and none of the exclude patterns. + */ + public String[] getIncludedFiles() { + String[] files; + synchronized (this) { + if (filesIncluded == null) { + throw new IllegalStateException("Must call scan() first"); + } + files = new String[filesIncluded.size()]; + filesIncluded.copyInto(files); + } + Arrays.sort(files); + return files; + } + + /** + * Return the count of included files. + * @return <code>int</code>. + * @since Ant 1.6.3 + */ + public synchronized int getIncludedFilesCount() { + if (filesIncluded == null) { + throw new IllegalStateException("Must call scan() first"); + } + return filesIncluded.size(); + } + + /** + * Return the names of the files which matched none of the include + * patterns. The names are relative to the base directory. This involves + * performing a slow scan if one has not already been completed. + * + * @return the names of the files which matched none of the include + * patterns. + * + * @see #slowScan + */ + public synchronized String[] getNotIncludedFiles() { + slowScan(); + final String[] files = new String[filesNotIncluded.size()]; + filesNotIncluded.copyInto(files); + return files; + } + + /** + * Return the names of the files which matched at least one of the + * include patterns and at least one of the exclude patterns. + * The names are relative to the base directory. This involves + * performing a slow scan if one has not already been completed. + * + * @return the names of the files which matched at least one of the + * include patterns and at least one of the exclude patterns. + * + * @see #slowScan + */ + public synchronized String[] getExcludedFiles() { + slowScan(); + final String[] files = new String[filesExcluded.size()]; + filesExcluded.copyInto(files); + return files; + } + + /** + * <p>Return the names of the files which were selected out and + * therefore not ultimately included.</p> + * + * <p>The names are relative to the base directory. This involves + * performing a slow scan if one has not already been completed.</p> + * + * @return the names of the files which were deselected. + * + * @see #slowScan + */ + public synchronized String[] getDeselectedFiles() { + slowScan(); + final String[] files = new String[filesDeselected.size()]; + filesDeselected.copyInto(files); + return files; + } + + /** + * Return the names of the directories which matched at least one of the + * include patterns and none of the exclude patterns. + * The names are relative to the base directory. + * + * @return the names of the directories which matched at least one of the + * include patterns and none of the exclude patterns. + */ + public String[] getIncludedDirectories() { + String[] directories; + synchronized (this) { + if (dirsIncluded == null) { + throw new IllegalStateException("Must call scan() first"); + } + directories = new String[dirsIncluded.size()]; + dirsIncluded.copyInto(directories); + } + Arrays.sort(directories); + return directories; + } + + /** + * Return the count of included directories. + * @return <code>int</code>. + * @since Ant 1.6.3 + */ + public synchronized int getIncludedDirsCount() { + if (dirsIncluded == null) { + throw new IllegalStateException("Must call scan() first"); + } + return dirsIncluded.size(); + } + + /** + * Return the names of the directories which matched none of the include + * patterns. The names are relative to the base directory. This involves + * performing a slow scan if one has not already been completed. + * + * @return the names of the directories which matched none of the include + * patterns. + * + * @see #slowScan + */ + public synchronized String[] getNotIncludedDirectories() { + slowScan(); + final String[] directories = new String[dirsNotIncluded.size()]; + dirsNotIncluded.copyInto(directories); + return directories; + } + + /** + * Return the names of the directories which matched at least one of the + * include patterns and at least one of the exclude patterns. + * The names are relative to the base directory. This involves + * performing a slow scan if one has not already been completed. + * + * @return the names of the directories which matched at least one of the + * include patterns and at least one of the exclude patterns. + * + * @see #slowScan + */ + public synchronized String[] getExcludedDirectories() { + slowScan(); + final String[] directories = new String[dirsExcluded.size()]; + dirsExcluded.copyInto(directories); + return directories; + } + + /** + * <p>Return the names of the directories which were selected out and + * therefore not ultimately included.</p> + * + * <p>The names are relative to the base directory. This involves + * performing a slow scan if one has not already been completed.</p> + * + * @return the names of the directories which were deselected. + * + * @see #slowScan + */ + public synchronized String[] getDeselectedDirectories() { + slowScan(); + final String[] directories = new String[dirsDeselected.size()]; + dirsDeselected.copyInto(directories); + return directories; + } + + /** + * Absolute paths of all symbolic links that haven't been followed + * but would have been followed had followsymlinks been true or + * maxLevelsOfSymlinks been bigger. + * + * @return sorted array of not followed symlinks + * @since Ant 1.8.0 + * @see #notFollowedSymlinks + */ + public synchronized String[] getNotFollowedSymlinks() { + String[] links; + synchronized (this) { + links = notFollowedSymlinks + .toArray(new String[notFollowedSymlinks.size()]); + } + Arrays.sort(links); + return links; + } + + /** + * Add default exclusions to the current exclusions set. + */ + public synchronized void addDefaultExcludes() { + final int excludesLength = excludes == null ? 0 : excludes.length; + String[] newExcludes; + final String[] defaultExcludesTemp = getDefaultExcludes(); + newExcludes = new String[excludesLength + defaultExcludesTemp.length]; + if (excludesLength > 0) { + System.arraycopy(excludes, 0, newExcludes, 0, excludesLength); + } + for (int i = 0; i < defaultExcludesTemp.length; i++) { + newExcludes[i + excludesLength] = + defaultExcludesTemp[i].replace('/', File.separatorChar) + .replace('\\', File.separatorChar); + } + excludes = newExcludes; + } + + /** + * Get the named resource. + * @param name path name of the file relative to the dir attribute. + * + * @return the resource with the given name. + * @since Ant 1.5.2 + */ + public synchronized Resource getResource(final String name) { + return new FileResource(basedir, name); + } + + /** + * Has the directory with the given path relative to the base + * directory already been scanned? + * + * <p>Registers the given directory as scanned as a side effect.</p> + * + * @since Ant 1.6 + */ + private boolean hasBeenScanned(final String vpath) { + return !scannedDirs.add(vpath); + } + + /** + * This method is of interest for testing purposes. The returned + * Set is live and should not be modified. + * @return the Set of relative directory names that have been scanned. + */ + /* package-private */ Set<String> getScannedDirs() { + return scannedDirs; + } + + /** + * Clear internal caches. + * + * @since Ant 1.6 + */ + private synchronized void clearCaches() { + includeNonPatterns.clear(); + excludeNonPatterns.clear(); + includePatterns = null; + excludePatterns = null; + areNonPatternSetsReady = false; + } + + /** + * Ensure that the in|exclude "patterns" + * have been properly divided up. + * + * @since Ant 1.6.3 + */ + /* package */ synchronized void ensureNonPatternSetsReady() { + if (!areNonPatternSetsReady) { + includePatterns = fillNonPatternSet(includeNonPatterns, includes); + excludePatterns = fillNonPatternSet(excludeNonPatterns, excludes); + areNonPatternSetsReady = true; + } + } + + /** + * Add all patterns that are not real patterns (do not contain + * wildcards) to the set and returns the real patterns. + * + * @param map Map to populate. + * @param patterns String[] of patterns. + * @since Ant 1.8.0 + */ + private TokenizedPattern[] fillNonPatternSet(final Map<String, TokenizedPath> map, final String[] patterns) { + final ArrayList<TokenizedPattern> al = new ArrayList<TokenizedPattern>(patterns.length); + for (int i = 0; i < patterns.length; i++) { + if (!SelectorUtils.hasWildcards(patterns[i])) { + final String s = isCaseSensitive() + ? patterns[i] : patterns[i].toUpperCase(); + map.put(s, new TokenizedPath(s)); + } else { + al.add(new TokenizedPattern(patterns[i])); + } + } + return al.toArray(new TokenizedPattern[al.size()]); + } + + /** + * Would following the given directory cause a loop of symbolic + * links deeper than allowed? + * + * <p>Can only happen if the given directory has been seen at + * least more often than allowed during the current scan and it is + * a symbolic link and enough other occurrences of the same name + * higher up are symbolic links that point to the same place.</p> + * + * @since Ant 1.8.0 + */ + private boolean causesIllegalSymlinkLoop(final String dirName, final File parent, + final LinkedList<String> directoryNamesFollowed) { + try { + if (directoryNamesFollowed.size() >= maxLevelsOfSymlinks + && CollectionUtils.frequency(directoryNamesFollowed, dirName) + >= maxLevelsOfSymlinks + && SYMLINK_UTILS.isSymbolicLink(parent, dirName)) { + + final ArrayList<String> files = new ArrayList<String>(); + File f = FILE_UTILS.resolveFile(parent, dirName); + final String target = f.getCanonicalPath(); + files.add(target); + + String relPath = ""; + for (final String dir : directoryNamesFollowed) { + relPath += "../"; + if (dirName.equals(dir)) { + f = FILE_UTILS.resolveFile(parent, relPath + dir); + files.add(f.getCanonicalPath()); + if (files.size() > maxLevelsOfSymlinks + && CollectionUtils.frequency(files, target) + > maxLevelsOfSymlinks) { + return true; + } + } + } + + } + return false; + } catch (final IOException ex) { + throw new BuildException("Caught error while checking for" + + " symbolic links", ex); + } + } + +} |