0001: /*
0002: * Licensed to the Apache Software Foundation (ASF) under one or more
0003: * contributor license agreements. See the NOTICE file distributed with
0004: * this work for additional information regarding copyright ownership.
0005: * The ASF licenses this file to You under the Apache License, Version 2.0
0006: * (the "License"); you may not use this file except in compliance with
0007: * the License. You may obtain a copy of the License at
0008: *
0009: * http://www.apache.org/licenses/LICENSE-2.0
0010: *
0011: * Unless required by applicable law or agreed to in writing, software
0012: * distributed under the License is distributed on an "AS IS" BASIS,
0013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0014: * See the License for the specific language governing permissions and
0015: * limitations under the License.
0016: *
0017: */
0018:
0019: package org.apache.tools.ant;
0020:
0021: import java.io.File;
0022: import java.io.IOException;
0023: import java.util.ArrayList;
0024: import java.util.Arrays;
0025: import java.util.HashMap;
0026: import java.util.HashSet;
0027: import java.util.Iterator;
0028: import java.util.Map;
0029: import java.util.Set;
0030: import java.util.Vector;
0031:
0032: import org.apache.tools.ant.taskdefs.condition.Os;
0033: import org.apache.tools.ant.types.Resource;
0034: import org.apache.tools.ant.types.ResourceFactory;
0035: import org.apache.tools.ant.types.resources.FileResource;
0036: import org.apache.tools.ant.types.selectors.FileSelector;
0037: import org.apache.tools.ant.types.selectors.SelectorScanner;
0038: import org.apache.tools.ant.types.selectors.SelectorUtils;
0039: import org.apache.tools.ant.util.FileUtils;
0040:
0041: /**
0042: * Class for scanning a directory for files/directories which match certain
0043: * criteria.
0044: * <p>
0045: * These criteria consist of selectors and patterns which have been specified.
0046: * With the selectors you can select which files you want to have included.
0047: * Files which are not selected are excluded. With patterns you can include
0048: * or exclude files based on their filename.
0049: * <p>
0050: * The idea is simple. A given directory is recursively scanned for all files
0051: * and directories. Each file/directory is matched against a set of selectors,
0052: * including special support for matching against filenames with include and
0053: * and exclude patterns. Only files/directories which match at least one
0054: * pattern of the include pattern list or other file selector, and don't match
0055: * any pattern of the exclude pattern list or fail to match against a required
0056: * selector will be placed in the list of files/directories found.
0057: * <p>
0058: * When no list of include patterns is supplied, "**" will be used, which
0059: * means that everything will be matched. When no list of exclude patterns is
0060: * supplied, an empty list is used, such that nothing will be excluded. When
0061: * no selectors are supplied, none are applied.
0062: * <p>
0063: * The filename pattern matching is done as follows:
0064: * The name to be matched is split up in path segments. A path segment is the
0065: * name of a directory or file, which is bounded by
0066: * <code>File.separator</code> ('/' under UNIX, '\' under Windows).
0067: * For example, "abc/def/ghi/xyz.java" is split up in the segments "abc",
0068: * "def","ghi" and "xyz.java".
0069: * The same is done for the pattern against which should be matched.
0070: * <p>
0071: * The segments of the name and the pattern are then matched against each
0072: * other. When '**' is used for a path segment in the pattern, it matches
0073: * zero or more path segments of the name.
0074: * <p>
0075: * There is a special case regarding the use of <code>File.separator</code>s
0076: * at the beginning of the pattern and the string to match:<br>
0077: * When a pattern starts with a <code>File.separator</code>, the string
0078: * to match must also start with a <code>File.separator</code>.
0079: * When a pattern does not start with a <code>File.separator</code>, the
0080: * string to match may not start with a <code>File.separator</code>.
0081: * When one of these rules is not obeyed, the string will not
0082: * match.
0083: * <p>
0084: * When a name path segment is matched against a pattern path segment, the
0085: * following special characters can be used:<br>
0086: * '*' matches zero or more characters<br>
0087: * '?' matches one character.
0088: * <p>
0089: * Examples:
0090: * <p>
0091: * "**\*.class" matches all .class files/dirs in a directory tree.
0092: * <p>
0093: * "test\a??.java" matches all files/dirs which start with an 'a', then two
0094: * more characters and then ".java", in a directory called test.
0095: * <p>
0096: * "**" matches everything in a directory tree.
0097: * <p>
0098: * "**\test\**\XYZ*" matches all files/dirs which start with "XYZ" and where
0099: * there is a parent directory called test (e.g. "abc\test\def\ghi\XYZ123").
0100: * <p>
0101: * Case sensitivity may be turned off if necessary. By default, it is
0102: * turned on.
0103: * <p>
0104: * Example of usage:
0105: * <pre>
0106: * String[] includes = {"**\\*.class"};
0107: * String[] excludes = {"modules\\*\\**"};
0108: * ds.setIncludes(includes);
0109: * ds.setExcludes(excludes);
0110: * ds.setBasedir(new File("test"));
0111: * ds.setCaseSensitive(true);
0112: * ds.scan();
0113: *
0114: * System.out.println("FILES:");
0115: * String[] files = ds.getIncludedFiles();
0116: * for (int i = 0; i < files.length; i++) {
0117: * System.out.println(files[i]);
0118: * }
0119: * </pre>
0120: * This will scan a directory called test for .class files, but excludes all
0121: * files in all proper subdirectories of a directory called "modules"
0122: *
0123: */
0124: public class DirectoryScanner implements FileScanner, SelectorScanner,
0125: ResourceFactory {
0126:
0127: /** Is OpenVMS the operating system we're running on? */
0128: private static final boolean ON_VMS = Os.isFamily("openvms");
0129:
0130: /**
0131: * Patterns which should be excluded by default.
0132: *
0133: * <p>Note that you can now add patterns to the list of default
0134: * excludes. Added patterns will not become part of this array
0135: * that has only been kept around for backwards compatibility
0136: * reasons.</p>
0137: *
0138: * @deprecated since 1.6.x.
0139: * Use the {@link #getDefaultExcludes getDefaultExcludes}
0140: * method instead.
0141: */
0142: protected static final String[] DEFAULTEXCLUDES = {
0143: // Miscellaneous typical temporary files
0144: "**/*~", "**/#*#", "**/.#*", "**/%*%", "**/._*",
0145:
0146: // CVS
0147: "**/CVS", "**/CVS/**", "**/.cvsignore",
0148:
0149: // SCCS
0150: "**/SCCS", "**/SCCS/**",
0151:
0152: // Visual SourceSafe
0153: "**/vssver.scc",
0154:
0155: // Subversion
0156: "**/.svn", "**/.svn/**",
0157:
0158: // Mac
0159: "**/.DS_Store" };
0160:
0161: /** Helper. */
0162: private static final FileUtils FILE_UTILS = FileUtils
0163: .getFileUtils();
0164:
0165: /** iterations for case-sensitive scanning. */
0166: private static final boolean[] CS_SCAN_ONLY = new boolean[] { true };
0167:
0168: /** iterations for non-case-sensitive scanning. */
0169: private static final boolean[] CS_THEN_NON_CS = new boolean[] {
0170: true, false };
0171:
0172: /**
0173: * Patterns which should be excluded by default.
0174: *
0175: * @see #addDefaultExcludes()
0176: */
0177: private static Vector defaultExcludes = new Vector();
0178: static {
0179: resetDefaultExcludes();
0180: }
0181:
0182: // CheckStyle:VisibilityModifier OFF - bc
0183:
0184: /** The base directory to be scanned. */
0185: protected File basedir;
0186:
0187: /** The patterns for the files to be included. */
0188: protected String[] includes;
0189:
0190: /** The patterns for the files to be excluded. */
0191: protected String[] excludes;
0192:
0193: /** Selectors that will filter which files are in our candidate list. */
0194: protected FileSelector[] selectors = null;
0195:
0196: /**
0197: * The files which matched at least one include and no excludes
0198: * and were selected.
0199: */
0200: protected Vector filesIncluded;
0201:
0202: /** The files which did not match any includes or selectors. */
0203: protected Vector filesNotIncluded;
0204:
0205: /**
0206: * The files which matched at least one include and at least
0207: * one exclude.
0208: */
0209: protected Vector filesExcluded;
0210:
0211: /**
0212: * The directories which matched at least one include and no excludes
0213: * and were selected.
0214: */
0215: protected Vector dirsIncluded;
0216:
0217: /** The directories which were found and did not match any includes. */
0218: protected Vector dirsNotIncluded;
0219:
0220: /**
0221: * The directories which matched at least one include and at least one
0222: * exclude.
0223: */
0224: protected Vector dirsExcluded;
0225:
0226: /**
0227: * The files which matched at least one include and no excludes and
0228: * which a selector discarded.
0229: */
0230: protected Vector filesDeselected;
0231:
0232: /**
0233: * The directories which matched at least one include and no excludes
0234: * but which a selector discarded.
0235: */
0236: protected Vector dirsDeselected;
0237:
0238: /** Whether or not our results were built by a slow scan. */
0239: protected boolean haveSlowResults = false;
0240:
0241: /**
0242: * Whether or not the file system should be treated as a case sensitive
0243: * one.
0244: */
0245: protected boolean isCaseSensitive = true;
0246:
0247: /**
0248: * Whether or not symbolic links should be followed.
0249: *
0250: * @since Ant 1.5
0251: */
0252: private boolean followSymlinks = true;
0253:
0254: /** Whether or not everything tested so far has been included. */
0255: protected boolean everythingIncluded = true;
0256:
0257: // CheckStyle:VisibilityModifier ON
0258:
0259: /**
0260: * Temporary table to speed up the various scanning methods.
0261: *
0262: * @since Ant 1.6
0263: */
0264: private Map fileListMap = new HashMap();
0265:
0266: /**
0267: * List of all scanned directories.
0268: *
0269: * @since Ant 1.6
0270: */
0271: private Set scannedDirs = new HashSet();
0272:
0273: /**
0274: * Set of all include patterns that are full file names and don't
0275: * contain any wildcards.
0276: *
0277: * <p>If this instance is not case sensitive, the file names get
0278: * turned to lower case.</p>
0279: *
0280: * <p>Gets lazily initialized on the first invocation of
0281: * isIncluded or isExcluded and cleared at the end of the scan
0282: * method (cleared in clearCaches, actually).</p>
0283: *
0284: * @since Ant 1.6.3
0285: */
0286: private Set includeNonPatterns = new HashSet();
0287:
0288: /**
0289: * Set of all include patterns that are full file names and don't
0290: * contain any wildcards.
0291: *
0292: * <p>If this instance is not case sensitive, the file names get
0293: * turned to lower case.</p>
0294: *
0295: * <p>Gets lazily initialized on the first invocation of
0296: * isIncluded or isExcluded and cleared at the end of the scan
0297: * method (cleared in clearCaches, actually).</p>
0298: *
0299: * @since Ant 1.6.3
0300: */
0301: private Set excludeNonPatterns = new HashSet();
0302:
0303: /**
0304: * Array of all include patterns that contain wildcards.
0305: *
0306: * <p>Gets lazily initialized on the first invocation of
0307: * isIncluded or isExcluded and cleared at the end of the scan
0308: * method (cleared in clearCaches, actually).</p>
0309: *
0310: * @since Ant 1.6.3
0311: */
0312: private String[] includePatterns;
0313:
0314: /**
0315: * Array of all exclude patterns that contain wildcards.
0316: *
0317: * <p>Gets lazily initialized on the first invocation of
0318: * isIncluded or isExcluded and cleared at the end of the scan
0319: * method (cleared in clearCaches, actually).</p>
0320: *
0321: * @since Ant 1.6.3
0322: */
0323: private String[] excludePatterns;
0324:
0325: /**
0326: * Have the non-pattern sets and pattern arrays for in- and
0327: * excludes been initialized?
0328: *
0329: * @since Ant 1.6.3
0330: */
0331: private boolean areNonPatternSetsReady = false;
0332:
0333: /**
0334: * Scanning flag.
0335: *
0336: * @since Ant 1.6.3
0337: */
0338: private boolean scanning = false;
0339:
0340: /**
0341: * Scanning lock.
0342: *
0343: * @since Ant 1.6.3
0344: */
0345: private Object scanLock = new Object();
0346:
0347: /**
0348: * Slow scanning flag.
0349: *
0350: * @since Ant 1.6.3
0351: */
0352: private boolean slowScanning = false;
0353:
0354: /**
0355: * Slow scanning lock.
0356: *
0357: * @since Ant 1.6.3
0358: */
0359: private Object slowScanLock = new Object();
0360:
0361: /**
0362: * Exception thrown during scan.
0363: *
0364: * @since Ant 1.6.3
0365: */
0366: private IllegalStateException illegal = null;
0367:
0368: /**
0369: * Sole constructor.
0370: */
0371: public DirectoryScanner() {
0372: }
0373:
0374: /**
0375: * Test whether or not a given path matches the start of a given
0376: * pattern up to the first "**".
0377: * <p>
0378: * This is not a general purpose test and should only be used if you
0379: * can live with false positives. For example, <code>pattern=**\a</code>
0380: * and <code>str=b</code> will yield <code>true</code>.
0381: *
0382: * @param pattern The pattern to match against. Must not be
0383: * <code>null</code>.
0384: * @param str The path to match, as a String. Must not be
0385: * <code>null</code>.
0386: *
0387: * @return whether or not a given path matches the start of a given
0388: * pattern up to the first "**".
0389: */
0390: protected static boolean matchPatternStart(String pattern,
0391: String str) {
0392: return SelectorUtils.matchPatternStart(pattern, str);
0393: }
0394:
0395: /**
0396: * Test whether or not a given path matches the start of a given
0397: * pattern up to the first "**".
0398: * <p>
0399: * This is not a general purpose test and should only be used if you
0400: * can live with false positives. For example, <code>pattern=**\a</code>
0401: * and <code>str=b</code> will yield <code>true</code>.
0402: *
0403: * @param pattern The pattern to match against. Must not be
0404: * <code>null</code>.
0405: * @param str The path to match, as a String. Must not be
0406: * <code>null</code>.
0407: * @param isCaseSensitive Whether or not matching should be performed
0408: * case sensitively.
0409: *
0410: * @return whether or not a given path matches the start of a given
0411: * pattern up to the first "**".
0412: */
0413: protected static boolean matchPatternStart(String pattern,
0414: String str, boolean isCaseSensitive) {
0415: return SelectorUtils.matchPatternStart(pattern, str,
0416: isCaseSensitive);
0417: }
0418:
0419: /**
0420: * Test whether or not a given path matches a given pattern.
0421: *
0422: * @param pattern The pattern to match against. Must not be
0423: * <code>null</code>.
0424: * @param str The path to match, as a String. Must not be
0425: * <code>null</code>.
0426: *
0427: * @return <code>true</code> if the pattern matches against the string,
0428: * or <code>false</code> otherwise.
0429: */
0430: protected static boolean matchPath(String pattern, String str) {
0431: return SelectorUtils.matchPath(pattern, str);
0432: }
0433:
0434: /**
0435: * Test whether or not a given path matches a given pattern.
0436: *
0437: * @param pattern The pattern to match against. Must not be
0438: * <code>null</code>.
0439: * @param str The path to match, as a String. Must not be
0440: * <code>null</code>.
0441: * @param isCaseSensitive Whether or not matching should be performed
0442: * case sensitively.
0443: *
0444: * @return <code>true</code> if the pattern matches against the string,
0445: * or <code>false</code> otherwise.
0446: */
0447: protected static boolean matchPath(String pattern, String str,
0448: boolean isCaseSensitive) {
0449: return SelectorUtils.matchPath(pattern, str, isCaseSensitive);
0450: }
0451:
0452: /**
0453: * Test whether or not a string matches against a pattern.
0454: * The pattern may contain two special characters:<br>
0455: * '*' means zero or more characters<br>
0456: * '?' means one and only one character
0457: *
0458: * @param pattern The pattern to match against.
0459: * Must not be <code>null</code>.
0460: * @param str The string which must be matched against the pattern.
0461: * Must not be <code>null</code>.
0462: *
0463: * @return <code>true</code> if the string matches against the pattern,
0464: * or <code>false</code> otherwise.
0465: */
0466: public static boolean match(String pattern, String str) {
0467: return SelectorUtils.match(pattern, str);
0468: }
0469:
0470: /**
0471: * Test whether or not a string matches against a pattern.
0472: * The pattern may contain two special characters:<br>
0473: * '*' means zero or more characters<br>
0474: * '?' means one and only one character
0475: *
0476: * @param pattern The pattern to match against.
0477: * Must not be <code>null</code>.
0478: * @param str The string which must be matched against the pattern.
0479: * Must not be <code>null</code>.
0480: * @param isCaseSensitive Whether or not matching should be performed
0481: * case sensitively.
0482: *
0483: *
0484: * @return <code>true</code> if the string matches against the pattern,
0485: * or <code>false</code> otherwise.
0486: */
0487: protected static boolean match(String pattern, String str,
0488: boolean isCaseSensitive) {
0489: return SelectorUtils.match(pattern, str, isCaseSensitive);
0490: }
0491:
0492: /**
0493: * Get the list of patterns that should be excluded by default.
0494: *
0495: * @return An array of <code>String</code> based on the current
0496: * contents of the <code>defaultExcludes</code>
0497: * <code>Vector</code>.
0498: *
0499: * @since Ant 1.6
0500: */
0501: public static String[] getDefaultExcludes() {
0502: return (String[]) defaultExcludes
0503: .toArray(new String[defaultExcludes.size()]);
0504: }
0505:
0506: /**
0507: * Add a pattern to the default excludes unless it is already a
0508: * default exclude.
0509: *
0510: * @param s A string to add as an exclude pattern.
0511: * @return <code>true</code> if the string was added;
0512: * <code>false</code> if it already existed.
0513: *
0514: * @since Ant 1.6
0515: */
0516: public static boolean addDefaultExclude(String s) {
0517: if (defaultExcludes.indexOf(s) == -1) {
0518: defaultExcludes.add(s);
0519: return true;
0520: }
0521: return false;
0522: }
0523:
0524: /**
0525: * Remove a string if it is a default exclude.
0526: *
0527: * @param s The string to attempt to remove.
0528: * @return <code>true</code> if <code>s</code> was a default
0529: * exclude (and thus was removed);
0530: * <code>false</code> if <code>s</code> was not
0531: * in the default excludes list to begin with.
0532: *
0533: * @since Ant 1.6
0534: */
0535: public static boolean removeDefaultExclude(String s) {
0536: return defaultExcludes.remove(s);
0537: }
0538:
0539: /**
0540: * Go back to the hardwired default exclude patterns.
0541: *
0542: * @since Ant 1.6
0543: */
0544: public static void resetDefaultExcludes() {
0545: defaultExcludes = new Vector();
0546: for (int i = 0; i < DEFAULTEXCLUDES.length; i++) {
0547: defaultExcludes.add(DEFAULTEXCLUDES[i]);
0548: }
0549: }
0550:
0551: /**
0552: * Set the base directory to be scanned. This is the directory which is
0553: * scanned recursively. All '/' and '\' characters are replaced by
0554: * <code>File.separatorChar</code>, so the separator used need not match
0555: * <code>File.separatorChar</code>.
0556: *
0557: * @param basedir The base directory to scan.
0558: */
0559: public void setBasedir(String basedir) {
0560: setBasedir(basedir == null ? (File) null : new File(basedir
0561: .replace('/', File.separatorChar).replace('\\',
0562: File.separatorChar)));
0563: }
0564:
0565: /**
0566: * Set the base directory to be scanned. This is the directory which is
0567: * scanned recursively.
0568: *
0569: * @param basedir The base directory for scanning.
0570: */
0571: public synchronized void setBasedir(File basedir) {
0572: this .basedir = basedir;
0573: }
0574:
0575: /**
0576: * Return the base directory to be scanned.
0577: * This is the directory which is scanned recursively.
0578: *
0579: * @return the base directory to be scanned.
0580: */
0581: public synchronized File getBasedir() {
0582: return basedir;
0583: }
0584:
0585: /**
0586: * Find out whether include exclude patterns are matched in a
0587: * case sensitive way.
0588: * @return whether or not the scanning is case sensitive.
0589: * @since Ant 1.6
0590: */
0591: public synchronized boolean isCaseSensitive() {
0592: return isCaseSensitive;
0593: }
0594:
0595: /**
0596: * Set whether or not include and exclude patterns are matched
0597: * in a case sensitive way.
0598: *
0599: * @param isCaseSensitive whether or not the file system should be
0600: * regarded as a case sensitive one.
0601: */
0602: public synchronized void setCaseSensitive(boolean isCaseSensitive) {
0603: this .isCaseSensitive = isCaseSensitive;
0604: }
0605:
0606: /**
0607: * Get whether or not a DirectoryScanner follows symbolic links.
0608: *
0609: * @return flag indicating whether symbolic links should be followed.
0610: *
0611: * @since Ant 1.6
0612: */
0613: public synchronized boolean isFollowSymlinks() {
0614: return followSymlinks;
0615: }
0616:
0617: /**
0618: * Set whether or not symbolic links should be followed.
0619: *
0620: * @param followSymlinks whether or not symbolic links should be followed.
0621: */
0622: public synchronized void setFollowSymlinks(boolean followSymlinks) {
0623: this .followSymlinks = followSymlinks;
0624: }
0625:
0626: /**
0627: * Set the list of include patterns to use. All '/' and '\' characters
0628: * are replaced by <code>File.separatorChar</code>, so the separator used
0629: * need not match <code>File.separatorChar</code>.
0630: * <p>
0631: * When a pattern ends with a '/' or '\', "**" is appended.
0632: *
0633: * @param includes A list of include patterns.
0634: * May be <code>null</code>, indicating that all files
0635: * should be included. If a non-<code>null</code>
0636: * list is given, all elements must be
0637: * non-<code>null</code>.
0638: */
0639: public synchronized void setIncludes(String[] includes) {
0640: if (includes == null) {
0641: this .includes = null;
0642: } else {
0643: this .includes = new String[includes.length];
0644: for (int i = 0; i < includes.length; i++) {
0645: this .includes[i] = normalizePattern(includes[i]);
0646: }
0647: }
0648: }
0649:
0650: /**
0651: * Set the list of exclude patterns to use. All '/' and '\' characters
0652: * are replaced by <code>File.separatorChar</code>, so the separator used
0653: * need not match <code>File.separatorChar</code>.
0654: * <p>
0655: * When a pattern ends with a '/' or '\', "**" is appended.
0656: *
0657: * @param excludes A list of exclude patterns.
0658: * May be <code>null</code>, indicating that no files
0659: * should be excluded. If a non-<code>null</code> list is
0660: * given, all elements must be non-<code>null</code>.
0661: */
0662: public synchronized void setExcludes(String[] excludes) {
0663: if (excludes == null) {
0664: this .excludes = null;
0665: } else {
0666: this .excludes = new String[excludes.length];
0667: for (int i = 0; i < excludes.length; i++) {
0668: this .excludes[i] = normalizePattern(excludes[i]);
0669: }
0670: }
0671: }
0672:
0673: /**
0674: * Add to the list of exclude patterns to use. All '/' and '\'
0675: * characters are replaced by <code>File.separatorChar</code>, so
0676: * the separator used need not match <code>File.separatorChar</code>.
0677: * <p>
0678: * When a pattern ends with a '/' or '\', "**" is appended.
0679: *
0680: * @param excludes A list of exclude patterns.
0681: * May be <code>null</code>, in which case the
0682: * exclude patterns don't get changed at all.
0683: *
0684: * @since Ant 1.6.3
0685: */
0686: public synchronized void addExcludes(String[] excludes) {
0687: if (excludes != null && excludes.length > 0) {
0688: if (this .excludes != null && this .excludes.length > 0) {
0689: String[] tmp = new String[excludes.length
0690: + this .excludes.length];
0691: System.arraycopy(this .excludes, 0, tmp, 0,
0692: this .excludes.length);
0693: for (int i = 0; i < excludes.length; i++) {
0694: tmp[this .excludes.length + i] = normalizePattern(excludes[i]);
0695: }
0696: this .excludes = tmp;
0697: } else {
0698: setExcludes(excludes);
0699: }
0700: }
0701: }
0702:
0703: /**
0704: * All '/' and '\' characters are replaced by
0705: * <code>File.separatorChar</code>, so the separator used need not
0706: * match <code>File.separatorChar</code>.
0707: *
0708: * <p> When a pattern ends with a '/' or '\', "**" is appended.
0709: *
0710: * @since Ant 1.6.3
0711: */
0712: private static String normalizePattern(String p) {
0713: String pattern = p.replace('/', File.separatorChar).replace(
0714: '\\', File.separatorChar);
0715: if (pattern.endsWith(File.separator)) {
0716: pattern += "**";
0717: }
0718: return pattern;
0719: }
0720:
0721: /**
0722: * Set the selectors that will select the filelist.
0723: *
0724: * @param selectors specifies the selectors to be invoked on a scan.
0725: */
0726: public synchronized void setSelectors(FileSelector[] selectors) {
0727: this .selectors = selectors;
0728: }
0729:
0730: /**
0731: * Return whether or not the scanner has included all the files or
0732: * directories it has come across so far.
0733: *
0734: * @return <code>true</code> if all files and directories which have
0735: * been found so far have been included.
0736: */
0737: public synchronized boolean isEverythingIncluded() {
0738: return everythingIncluded;
0739: }
0740:
0741: /**
0742: * Scan for files which match at least one include pattern and don't match
0743: * any exclude patterns. If there are selectors then the files must pass
0744: * muster there, as well. Scans under basedir, if set; otherwise the
0745: * include patterns without leading wildcards specify the absolute paths of
0746: * the files that may be included.
0747: *
0748: * @exception IllegalStateException if the base directory was set
0749: * incorrectly (i.e. if it doesn't exist or isn't a directory).
0750: */
0751: public void scan() throws IllegalStateException {
0752: synchronized (scanLock) {
0753: if (scanning) {
0754: while (scanning) {
0755: try {
0756: scanLock.wait();
0757: } catch (InterruptedException e) {
0758: continue;
0759: }
0760: }
0761: if (illegal != null) {
0762: throw illegal;
0763: }
0764: return;
0765: }
0766: scanning = true;
0767: }
0768: try {
0769: synchronized (this ) {
0770: illegal = null;
0771: clearResults();
0772:
0773: // set in/excludes to reasonable defaults if needed:
0774: boolean nullIncludes = (includes == null);
0775: includes = nullIncludes ? new String[] { "**" }
0776: : includes;
0777: boolean nullExcludes = (excludes == null);
0778: excludes = nullExcludes ? new String[0] : excludes;
0779:
0780: if (basedir == null) {
0781: // if no basedir and no includes, nothing to do:
0782: if (nullIncludes) {
0783: return;
0784: }
0785: } else {
0786: if (!basedir.exists()) {
0787: illegal = new IllegalStateException("basedir "
0788: + basedir + " does not exist");
0789: }
0790: if (!basedir.isDirectory()) {
0791: illegal = new IllegalStateException("basedir "
0792: + basedir + " is not a directory");
0793: }
0794: if (illegal != null) {
0795: throw illegal;
0796: }
0797: }
0798: if (isIncluded("")) {
0799: if (!isExcluded("")) {
0800: if (isSelected("", basedir)) {
0801: dirsIncluded.addElement("");
0802: } else {
0803: dirsDeselected.addElement("");
0804: }
0805: } else {
0806: dirsExcluded.addElement("");
0807: }
0808: } else {
0809: dirsNotIncluded.addElement("");
0810: }
0811: checkIncludePatterns();
0812: clearCaches();
0813: includes = nullIncludes ? null : includes;
0814: excludes = nullExcludes ? null : excludes;
0815: }
0816: } finally {
0817: synchronized (scanLock) {
0818: scanning = false;
0819: scanLock.notifyAll();
0820: }
0821: }
0822: }
0823:
0824: /**
0825: * This routine is actually checking all the include patterns in
0826: * order to avoid scanning everything under base dir.
0827: * @since Ant 1.6
0828: */
0829: private void checkIncludePatterns() {
0830: Map newroots = new HashMap();
0831: // put in the newroots map the include patterns without
0832: // wildcard tokens
0833: for (int i = 0; i < includes.length; i++) {
0834: if (FileUtils.isAbsolutePath(includes[i])) {
0835: //skip abs. paths not under basedir, if set:
0836: if (basedir != null
0837: && !SelectorUtils.matchPatternStart(
0838: includes[i], basedir.getAbsolutePath(),
0839: isCaseSensitive())) {
0840: continue;
0841: }
0842: } else if (basedir == null) {
0843: //skip non-abs. paths if basedir == null:
0844: continue;
0845: }
0846: newroots.put(
0847: SelectorUtils.rtrimWildcardTokens(includes[i]),
0848: includes[i]);
0849: }
0850: if (newroots.containsKey("") && basedir != null) {
0851: // we are going to scan everything anyway
0852: scandir(basedir, "", true);
0853: } else {
0854: // only scan directories that can include matched files or
0855: // directories
0856: Iterator it = newroots.entrySet().iterator();
0857:
0858: File canonBase = null;
0859: if (basedir != null) {
0860: try {
0861: canonBase = basedir.getCanonicalFile();
0862: } catch (IOException ex) {
0863: throw new BuildException(ex);
0864: }
0865: }
0866: while (it.hasNext()) {
0867: Map.Entry entry = (Map.Entry) it.next();
0868: String currentelement = (String) entry.getKey();
0869: if (basedir == null
0870: && !FileUtils.isAbsolutePath(currentelement)) {
0871: continue;
0872: }
0873: String originalpattern = (String) entry.getValue();
0874: File myfile = new File(basedir, currentelement);
0875:
0876: if (myfile.exists()) {
0877: // may be on a case insensitive file system. We want
0878: // the results to show what's really on the disk, so
0879: // we need to double check.
0880: try {
0881: String path = (basedir == null) ? myfile
0882: .getCanonicalPath() : FILE_UTILS
0883: .removeLeadingPath(canonBase, myfile
0884: .getCanonicalFile());
0885: if (!path.equals(currentelement) || ON_VMS) {
0886: myfile = findFile(basedir, currentelement,
0887: true);
0888: if (myfile != null && basedir != null) {
0889: currentelement = FILE_UTILS
0890: .removeLeadingPath(basedir,
0891: myfile);
0892: }
0893: }
0894: } catch (IOException ex) {
0895: throw new BuildException(ex);
0896: }
0897: }
0898: if ((myfile == null || !myfile.exists())
0899: && !isCaseSensitive()) {
0900: File f = findFile(basedir, currentelement, false);
0901: if (f != null && f.exists()) {
0902: // adapt currentelement to the case we've
0903: // actually found
0904: currentelement = (basedir == null) ? f
0905: .getAbsolutePath() : FILE_UTILS
0906: .removeLeadingPath(basedir, f);
0907: myfile = f;
0908: }
0909: }
0910: if (myfile != null && myfile.exists()) {
0911: if (!followSymlinks
0912: && isSymlink(basedir, currentelement)) {
0913: continue;
0914: }
0915: if (myfile.isDirectory()) {
0916: if (isIncluded(currentelement)
0917: && currentelement.length() > 0) {
0918: accountForIncludedDir(currentelement,
0919: myfile, true);
0920: } else {
0921: if (currentelement.length() > 0) {
0922: if (currentelement
0923: .charAt(currentelement.length() - 1) != File.separatorChar) {
0924: currentelement = currentelement
0925: + File.separatorChar;
0926: }
0927: }
0928: scandir(myfile, currentelement, true);
0929: }
0930: } else {
0931: boolean included = isCaseSensitive() ? originalpattern
0932: .equals(currentelement)
0933: : originalpattern
0934: .equalsIgnoreCase(currentelement);
0935: if (included) {
0936: accountForIncludedFile(currentelement,
0937: myfile);
0938: }
0939: }
0940: }
0941: }
0942: }
0943: }
0944:
0945: /**
0946: * Clear the result caches for a scan.
0947: */
0948: protected synchronized void clearResults() {
0949: filesIncluded = new Vector();
0950: filesNotIncluded = new Vector();
0951: filesExcluded = new Vector();
0952: filesDeselected = new Vector();
0953: dirsIncluded = new Vector();
0954: dirsNotIncluded = new Vector();
0955: dirsExcluded = new Vector();
0956: dirsDeselected = new Vector();
0957: everythingIncluded = (basedir != null);
0958: scannedDirs.clear();
0959: }
0960:
0961: /**
0962: * Top level invocation for a slow scan. A slow scan builds up a full
0963: * list of excluded/included files/directories, whereas a fast scan
0964: * will only have full results for included files, as it ignores
0965: * directories which can't possibly hold any included files/directories.
0966: * <p>
0967: * Returns immediately if a slow scan has already been completed.
0968: */
0969: protected void slowScan() {
0970: synchronized (slowScanLock) {
0971: if (haveSlowResults) {
0972: return;
0973: }
0974: if (slowScanning) {
0975: while (slowScanning) {
0976: try {
0977: slowScanLock.wait();
0978: } catch (InterruptedException e) {
0979: // Empty
0980: }
0981: }
0982: return;
0983: }
0984: slowScanning = true;
0985: }
0986: try {
0987: synchronized (this ) {
0988:
0989: // set in/excludes to reasonable defaults if needed:
0990: boolean nullIncludes = (includes == null);
0991: includes = nullIncludes ? new String[] { "**" }
0992: : includes;
0993: boolean nullExcludes = (excludes == null);
0994: excludes = nullExcludes ? new String[0] : excludes;
0995:
0996: String[] excl = new String[dirsExcluded.size()];
0997: dirsExcluded.copyInto(excl);
0998:
0999: String[] notIncl = new String[dirsNotIncluded.size()];
1000: dirsNotIncluded.copyInto(notIncl);
1001:
1002: processSlowScan(excl);
1003: processSlowScan(notIncl);
1004: clearCaches();
1005: includes = nullIncludes ? null : includes;
1006: excludes = nullExcludes ? null : excludes;
1007: }
1008: } finally {
1009: synchronized (slowScanLock) {
1010: haveSlowResults = true;
1011: slowScanning = false;
1012: slowScanLock.notifyAll();
1013: }
1014: }
1015: }
1016:
1017: private void processSlowScan(String[] arr) {
1018: for (int i = 0; i < arr.length; i++) {
1019: if (!couldHoldIncluded(arr[i])) {
1020: scandir(new File(basedir, arr[i]), arr[i]
1021: + File.separator, false);
1022: }
1023: }
1024: }
1025:
1026: /**
1027: * Scan the given directory for files and directories. Found files and
1028: * directories are placed in their respective collections, based on the
1029: * matching of includes, excludes, and the selectors. When a directory
1030: * is found, it is scanned recursively.
1031: *
1032: * @param dir The directory to scan. Must not be <code>null</code>.
1033: * @param vpath The path relative to the base directory (needed to
1034: * prevent problems with an absolute path when using
1035: * dir). Must not be <code>null</code>.
1036: * @param fast Whether or not this call is part of a fast scan.
1037: *
1038: * @see #filesIncluded
1039: * @see #filesNotIncluded
1040: * @see #filesExcluded
1041: * @see #dirsIncluded
1042: * @see #dirsNotIncluded
1043: * @see #dirsExcluded
1044: * @see #slowScan
1045: */
1046: protected void scandir(File dir, String vpath, boolean fast) {
1047: if (dir == null) {
1048: throw new BuildException("dir must not be null.");
1049: } else if (!dir.exists()) {
1050: throw new BuildException(dir + " doesn't exist.");
1051: } else if (!dir.isDirectory()) {
1052: throw new BuildException(dir + " is not a directory.");
1053: }
1054: // avoid double scanning of directories, can only happen in fast mode
1055: if (fast && hasBeenScanned(vpath)) {
1056: return;
1057: }
1058: String[] newfiles = dir.list();
1059:
1060: if (newfiles == null) {
1061: /*
1062: * two reasons are mentioned in the API docs for File.list
1063: * (1) dir is not a directory. This is impossible as
1064: * we wouldn't get here in this case.
1065: * (2) an IO error occurred (why doesn't it throw an exception
1066: * then???)
1067: */
1068: throw new BuildException("IO error scanning directory '"
1069: + dir.getAbsolutePath() + "'");
1070: }
1071: if (!followSymlinks) {
1072: Vector noLinks = new Vector();
1073: for (int i = 0; i < newfiles.length; i++) {
1074: try {
1075: if (FILE_UTILS.isSymbolicLink(dir, newfiles[i])) {
1076: String name = vpath + newfiles[i];
1077: File file = new File(dir, newfiles[i]);
1078: (file.isDirectory() ? dirsExcluded
1079: : filesExcluded).addElement(name);
1080: } else {
1081: noLinks.addElement(newfiles[i]);
1082: }
1083: } catch (IOException ioe) {
1084: String msg = "IOException caught while checking "
1085: + "for links, couldn't get canonical path!";
1086: // will be caught and redirected to Ant's logging system
1087: System.err.println(msg);
1088: noLinks.addElement(newfiles[i]);
1089: }
1090: }
1091: newfiles = (String[]) (noLinks.toArray(new String[noLinks
1092: .size()]));
1093: }
1094: for (int i = 0; i < newfiles.length; i++) {
1095: String name = vpath + newfiles[i];
1096: File file = new File(dir, newfiles[i]);
1097: if (file.isDirectory()) {
1098: if (isIncluded(name)) {
1099: accountForIncludedDir(name, file, fast);
1100: } else {
1101: everythingIncluded = false;
1102: dirsNotIncluded.addElement(name);
1103: if (fast && couldHoldIncluded(name)) {
1104: scandir(file, name + File.separator, fast);
1105: }
1106: }
1107: if (!fast) {
1108: scandir(file, name + File.separator, fast);
1109: }
1110: } else if (file.isFile()) {
1111: if (isIncluded(name)) {
1112: accountForIncludedFile(name, file);
1113: } else {
1114: everythingIncluded = false;
1115: filesNotIncluded.addElement(name);
1116: }
1117: }
1118: }
1119: }
1120:
1121: /**
1122: * Process included file.
1123: * @param name path of the file relative to the directory of the FileSet.
1124: * @param file included File.
1125: */
1126: private void accountForIncludedFile(String name, File file) {
1127: processIncluded(name, file, filesIncluded, filesExcluded,
1128: filesDeselected);
1129: }
1130:
1131: /**
1132: * Process included directory.
1133: * @param name path of the directory relative to the directory of
1134: * the FileSet.
1135: * @param file directory as File.
1136: * @param fast whether to perform fast scans.
1137: */
1138: private void accountForIncludedDir(String name, File file,
1139: boolean fast) {
1140: processIncluded(name, file, dirsIncluded, dirsExcluded,
1141: dirsDeselected);
1142: if (fast && couldHoldIncluded(name) && !contentsExcluded(name)) {
1143: scandir(file, name + File.separator, fast);
1144: }
1145: }
1146:
1147: private void processIncluded(String name, File file, Vector inc,
1148: Vector exc, Vector des) {
1149:
1150: if (inc.contains(name) || exc.contains(name)
1151: || des.contains(name)) {
1152: return;
1153: }
1154:
1155: boolean included = false;
1156: if (isExcluded(name)) {
1157: exc.add(name);
1158: } else if (isSelected(name, file)) {
1159: included = true;
1160: inc.add(name);
1161: } else {
1162: des.add(name);
1163: }
1164: everythingIncluded &= included;
1165: }
1166:
1167: /**
1168: * Test whether or not a name matches against at least one include
1169: * pattern.
1170: *
1171: * @param name The name to match. Must not be <code>null</code>.
1172: * @return <code>true</code> when the name matches against at least one
1173: * include pattern, or <code>false</code> otherwise.
1174: */
1175: protected boolean isIncluded(String name) {
1176: ensureNonPatternSetsReady();
1177:
1178: if (isCaseSensitive() ? includeNonPatterns.contains(name)
1179: : includeNonPatterns.contains(name.toUpperCase())) {
1180: return true;
1181: }
1182: for (int i = 0; i < includePatterns.length; i++) {
1183: if (matchPath(includePatterns[i], name, isCaseSensitive())) {
1184: return true;
1185: }
1186: }
1187: return false;
1188: }
1189:
1190: /**
1191: * Test whether or not a name matches the start of at least one include
1192: * pattern.
1193: *
1194: * @param name The name to match. Must not be <code>null</code>.
1195: * @return <code>true</code> when the name matches against the start of at
1196: * least one include pattern, or <code>false</code> otherwise.
1197: */
1198: protected boolean couldHoldIncluded(String name) {
1199: for (int i = 0; i < includes.length; i++) {
1200: if (matchPatternStart(includes[i], name, isCaseSensitive())
1201: && isMorePowerfulThanExcludes(name, includes[i])
1202: && isDeeper(includes[i], name)) {
1203: return true;
1204: }
1205: }
1206: return false;
1207: }
1208:
1209: /**
1210: * Verify that a pattern specifies files deeper
1211: * than the level of the specified file.
1212: * @param pattern the pattern to check.
1213: * @param name the name to check.
1214: * @return whether the pattern is deeper than the name.
1215: * @since Ant 1.6.3
1216: */
1217: private boolean isDeeper(String pattern, String name) {
1218: Vector p = SelectorUtils.tokenizePath(pattern);
1219: Vector n = SelectorUtils.tokenizePath(name);
1220: return p.contains("**") || p.size() > n.size();
1221: }
1222:
1223: /**
1224: * Find out whether one particular include pattern is more powerful
1225: * than all the excludes.
1226: * Note: the power comparison is based on the length of the include pattern
1227: * and of the exclude patterns without the wildcards.
1228: * Ideally the comparison should be done based on the depth
1229: * of the match; that is to say how many file separators have been matched
1230: * before the first ** or the end of the pattern.
1231: *
1232: * IMPORTANT : this function should return false "with care".
1233: *
1234: * @param name the relative path to test.
1235: * @param includepattern one include pattern.
1236: * @return true if there is no exclude pattern more powerful than this include pattern.
1237: * @since Ant 1.6
1238: */
1239: private boolean isMorePowerfulThanExcludes(String name,
1240: String includepattern) {
1241: String soughtexclude = name + File.separator + "**";
1242: for (int counter = 0; counter < excludes.length; counter++) {
1243: if (excludes[counter].equals(soughtexclude)) {
1244: return false;
1245: }
1246: }
1247: return true;
1248: }
1249:
1250: /**
1251: * Test whether all contents of the specified directory must be excluded.
1252: * @param name the directory name to check.
1253: * @return whether all the specified directory's contents are excluded.
1254: */
1255: private boolean contentsExcluded(String name) {
1256: name = (name.endsWith(File.separator)) ? name : name
1257: + File.separator;
1258: for (int i = 0; i < excludes.length; i++) {
1259: String e = excludes[i];
1260: if (e.endsWith("**")
1261: && SelectorUtils.matchPath(e.substring(0, e
1262: .length() - 2), name, isCaseSensitive())) {
1263: return true;
1264: }
1265: }
1266: return false;
1267: }
1268:
1269: /**
1270: * Test whether or not a name matches against at least one exclude
1271: * pattern.
1272: *
1273: * @param name The name to match. Must not be <code>null</code>.
1274: * @return <code>true</code> when the name matches against at least one
1275: * exclude pattern, or <code>false</code> otherwise.
1276: */
1277: protected boolean isExcluded(String name) {
1278: ensureNonPatternSetsReady();
1279:
1280: if (isCaseSensitive() ? excludeNonPatterns.contains(name)
1281: : excludeNonPatterns.contains(name.toUpperCase())) {
1282: return true;
1283: }
1284: for (int i = 0; i < excludePatterns.length; i++) {
1285: if (matchPath(excludePatterns[i], name, isCaseSensitive())) {
1286: return true;
1287: }
1288: }
1289: return false;
1290: }
1291:
1292: /**
1293: * Test whether a file should be selected.
1294: *
1295: * @param name the filename to check for selecting.
1296: * @param file the java.io.File object for this filename.
1297: * @return <code>false</code> when the selectors says that the file
1298: * should not be selected, <code>true</code> otherwise.
1299: */
1300: protected boolean isSelected(String name, File file) {
1301: if (selectors != null) {
1302: for (int i = 0; i < selectors.length; i++) {
1303: if (!selectors[i].isSelected(basedir, name, file)) {
1304: return false;
1305: }
1306: }
1307: }
1308: return true;
1309: }
1310:
1311: /**
1312: * Return the names of the files which matched at least one of the
1313: * include patterns and none of the exclude patterns.
1314: * The names are relative to the base directory.
1315: *
1316: * @return the names of the files which matched at least one of the
1317: * include patterns and none of the exclude patterns.
1318: */
1319: public synchronized String[] getIncludedFiles() {
1320: if (filesIncluded == null) {
1321: throw new IllegalStateException("Must call scan() first");
1322: }
1323: String[] files = new String[filesIncluded.size()];
1324: filesIncluded.copyInto(files);
1325: Arrays.sort(files);
1326: return files;
1327: }
1328:
1329: /**
1330: * Return the count of included files.
1331: * @return <code>int</code>.
1332: * @since Ant 1.6.3
1333: */
1334: public synchronized int getIncludedFilesCount() {
1335: if (filesIncluded == null) {
1336: throw new IllegalStateException("Must call scan() first");
1337: }
1338: return filesIncluded.size();
1339: }
1340:
1341: /**
1342: * Return the names of the files which matched none of the include
1343: * patterns. The names are relative to the base directory. This involves
1344: * performing a slow scan if one has not already been completed.
1345: *
1346: * @return the names of the files which matched none of the include
1347: * patterns.
1348: *
1349: * @see #slowScan
1350: */
1351: public synchronized String[] getNotIncludedFiles() {
1352: slowScan();
1353: String[] files = new String[filesNotIncluded.size()];
1354: filesNotIncluded.copyInto(files);
1355: return files;
1356: }
1357:
1358: /**
1359: * Return the names of the files which matched at least one of the
1360: * include patterns and at least one of the exclude patterns.
1361: * The names are relative to the base directory. This involves
1362: * performing a slow scan if one has not already been completed.
1363: *
1364: * @return the names of the files which matched at least one of the
1365: * include patterns and at least one of the exclude patterns.
1366: *
1367: * @see #slowScan
1368: */
1369: public synchronized String[] getExcludedFiles() {
1370: slowScan();
1371: String[] files = new String[filesExcluded.size()];
1372: filesExcluded.copyInto(files);
1373: return files;
1374: }
1375:
1376: /**
1377: * <p>Return the names of the files which were selected out and
1378: * therefore not ultimately included.</p>
1379: *
1380: * <p>The names are relative to the base directory. This involves
1381: * performing a slow scan if one has not already been completed.</p>
1382: *
1383: * @return the names of the files which were deselected.
1384: *
1385: * @see #slowScan
1386: */
1387: public synchronized String[] getDeselectedFiles() {
1388: slowScan();
1389: String[] files = new String[filesDeselected.size()];
1390: filesDeselected.copyInto(files);
1391: return files;
1392: }
1393:
1394: /**
1395: * Return the names of the directories which matched at least one of the
1396: * include patterns and none of the exclude patterns.
1397: * The names are relative to the base directory.
1398: *
1399: * @return the names of the directories which matched at least one of the
1400: * include patterns and none of the exclude patterns.
1401: */
1402: public synchronized String[] getIncludedDirectories() {
1403: if (dirsIncluded == null) {
1404: throw new IllegalStateException("Must call scan() first");
1405: }
1406: String[] directories = new String[dirsIncluded.size()];
1407: dirsIncluded.copyInto(directories);
1408: Arrays.sort(directories);
1409: return directories;
1410: }
1411:
1412: /**
1413: * Return the count of included directories.
1414: * @return <code>int</code>.
1415: * @since Ant 1.6.3
1416: */
1417: public synchronized int getIncludedDirsCount() {
1418: if (dirsIncluded == null) {
1419: throw new IllegalStateException("Must call scan() first");
1420: }
1421: return dirsIncluded.size();
1422: }
1423:
1424: /**
1425: * Return the names of the directories which matched none of the include
1426: * patterns. The names are relative to the base directory. This involves
1427: * performing a slow scan if one has not already been completed.
1428: *
1429: * @return the names of the directories which matched none of the include
1430: * patterns.
1431: *
1432: * @see #slowScan
1433: */
1434: public synchronized String[] getNotIncludedDirectories() {
1435: slowScan();
1436: String[] directories = new String[dirsNotIncluded.size()];
1437: dirsNotIncluded.copyInto(directories);
1438: return directories;
1439: }
1440:
1441: /**
1442: * Return the names of the directories which matched at least one of the
1443: * include patterns and at least one of the exclude patterns.
1444: * The names are relative to the base directory. This involves
1445: * performing a slow scan if one has not already been completed.
1446: *
1447: * @return the names of the directories which matched at least one of the
1448: * include patterns and at least one of the exclude patterns.
1449: *
1450: * @see #slowScan
1451: */
1452: public synchronized String[] getExcludedDirectories() {
1453: slowScan();
1454: String[] directories = new String[dirsExcluded.size()];
1455: dirsExcluded.copyInto(directories);
1456: return directories;
1457: }
1458:
1459: /**
1460: * <p>Return the names of the directories which were selected out and
1461: * therefore not ultimately included.</p>
1462: *
1463: * <p>The names are relative to the base directory. This involves
1464: * performing a slow scan if one has not already been completed.</p>
1465: *
1466: * @return the names of the directories which were deselected.
1467: *
1468: * @see #slowScan
1469: */
1470: public synchronized String[] getDeselectedDirectories() {
1471: slowScan();
1472: String[] directories = new String[dirsDeselected.size()];
1473: dirsDeselected.copyInto(directories);
1474: return directories;
1475: }
1476:
1477: /**
1478: * Add default exclusions to the current exclusions set.
1479: */
1480: public synchronized void addDefaultExcludes() {
1481: int excludesLength = excludes == null ? 0 : excludes.length;
1482: String[] newExcludes;
1483: newExcludes = new String[excludesLength
1484: + defaultExcludes.size()];
1485: if (excludesLength > 0) {
1486: System.arraycopy(excludes, 0, newExcludes, 0,
1487: excludesLength);
1488: }
1489: String[] defaultExcludesTemp = getDefaultExcludes();
1490: for (int i = 0; i < defaultExcludesTemp.length; i++) {
1491: newExcludes[i + excludesLength] = defaultExcludesTemp[i]
1492: .replace('/', File.separatorChar).replace('\\',
1493: File.separatorChar);
1494: }
1495: excludes = newExcludes;
1496: }
1497:
1498: /**
1499: * Get the named resource.
1500: * @param name path name of the file relative to the dir attribute.
1501: *
1502: * @return the resource with the given name.
1503: * @since Ant 1.5.2
1504: */
1505: public synchronized Resource getResource(String name) {
1506: return new FileResource(basedir, name);
1507: }
1508:
1509: /**
1510: * Return a cached result of list performed on file, if
1511: * available. Invokes the method and caches the result otherwise.
1512: *
1513: * @param file File (dir) to list.
1514: * @since Ant 1.6
1515: */
1516: private String[] list(File file) {
1517: String[] files = (String[]) fileListMap.get(file);
1518: if (files == null) {
1519: files = file.list();
1520: if (files != null) {
1521: fileListMap.put(file, files);
1522: }
1523: }
1524: return files;
1525: }
1526:
1527: /**
1528: * From <code>base</code> traverse the filesystem in order to find
1529: * a file that matches the given name.
1530: *
1531: * @param base base File (dir).
1532: * @param path file path.
1533: * @param cs whether to scan case-sensitively.
1534: * @return File object that points to the file in question or null.
1535: *
1536: * @since Ant 1.6.3
1537: */
1538: private File findFile(File base, String path, boolean cs) {
1539: if (FileUtils.isAbsolutePath(path)) {
1540: if (base == null) {
1541: String[] s = FILE_UTILS.dissect(path);
1542: base = new File(s[0]);
1543: path = s[1];
1544: } else {
1545: File f = FILE_UTILS.normalize(path);
1546: String s = FILE_UTILS.removeLeadingPath(base, f);
1547: if (s.equals(f.getAbsolutePath())) {
1548: //removing base from path yields no change; path not child of base
1549: return null;
1550: }
1551: path = s;
1552: }
1553: }
1554: return findFile(base, SelectorUtils.tokenizePath(path), cs);
1555: }
1556:
1557: /**
1558: * From <code>base</code> traverse the filesystem in order to find
1559: * a file that matches the given stack of names.
1560: *
1561: * @param base base File (dir).
1562: * @param pathElements Vector of path elements (dirs...file).
1563: * @param cs whether to scan case-sensitively.
1564: * @return File object that points to the file in question or null.
1565: *
1566: * @since Ant 1.6.3
1567: */
1568: private File findFile(File base, Vector pathElements, boolean cs) {
1569: if (pathElements.size() == 0) {
1570: return base;
1571: }
1572: String current = (String) pathElements.remove(0);
1573: if (base == null) {
1574: return findFile(new File(current), pathElements, cs);
1575: }
1576: if (!base.isDirectory()) {
1577: return null;
1578: }
1579: String[] files = list(base);
1580: if (files == null) {
1581: throw new BuildException("IO error scanning directory "
1582: + base.getAbsolutePath());
1583: }
1584: boolean[] matchCase = cs ? CS_SCAN_ONLY : CS_THEN_NON_CS;
1585: for (int i = 0; i < matchCase.length; i++) {
1586: for (int j = 0; j < files.length; j++) {
1587: if (matchCase[i] ? files[j].equals(current) : files[j]
1588: .equalsIgnoreCase(current)) {
1589: return findFile(new File(base, files[j]),
1590: pathElements, cs);
1591: }
1592: }
1593: }
1594: return null;
1595: }
1596:
1597: /**
1598: * Do we have to traverse a symlink when trying to reach path from
1599: * basedir?
1600: * @param base base File (dir).
1601: * @param path file path.
1602: * @since Ant 1.6
1603: */
1604: private boolean isSymlink(File base, String path) {
1605: return isSymlink(base, SelectorUtils.tokenizePath(path));
1606: }
1607:
1608: /**
1609: * Do we have to traverse a symlink when trying to reach path from
1610: * basedir?
1611: * @param base base File (dir).
1612: * @param pathElements Vector of path elements (dirs...file).
1613: * @since Ant 1.6
1614: */
1615: private boolean isSymlink(File base, Vector pathElements) {
1616: if (pathElements.size() > 0) {
1617: String current = (String) pathElements.remove(0);
1618: try {
1619: return FILE_UTILS.isSymbolicLink(base, current)
1620: || isSymlink(new File(base, current),
1621: pathElements);
1622: } catch (IOException ioe) {
1623: String msg = "IOException caught while checking "
1624: + "for links, couldn't get canonical path!";
1625: // will be caught and redirected to Ant's logging system
1626: System.err.println(msg);
1627: }
1628: }
1629: return false;
1630: }
1631:
1632: /**
1633: * Has the directory with the given path relative to the base
1634: * directory already been scanned?
1635: *
1636: * <p>Registers the given directory as scanned as a side effect.</p>
1637: *
1638: * @since Ant 1.6
1639: */
1640: private boolean hasBeenScanned(String vpath) {
1641: return !scannedDirs.add(vpath);
1642: }
1643:
1644: /**
1645: * This method is of interest for testing purposes. The returned
1646: * Set is live and should not be modified.
1647: * @return the Set of relative directory names that have been scanned.
1648: */
1649: /* package-private */Set getScannedDirs() {
1650: return scannedDirs;
1651: }
1652:
1653: /**
1654: * Clear internal caches.
1655: *
1656: * @since Ant 1.6
1657: */
1658: private synchronized void clearCaches() {
1659: fileListMap.clear();
1660: includeNonPatterns.clear();
1661: excludeNonPatterns.clear();
1662: includePatterns = null;
1663: excludePatterns = null;
1664: areNonPatternSetsReady = false;
1665: }
1666:
1667: /**
1668: * Ensure that the in|exclude "patterns"
1669: * have been properly divided up.
1670: *
1671: * @since Ant 1.6.3
1672: */
1673: private synchronized void ensureNonPatternSetsReady() {
1674: if (!areNonPatternSetsReady) {
1675: includePatterns = fillNonPatternSet(includeNonPatterns,
1676: includes);
1677: excludePatterns = fillNonPatternSet(excludeNonPatterns,
1678: excludes);
1679: areNonPatternSetsReady = true;
1680: }
1681: }
1682:
1683: /**
1684: * Add all patterns that are not real patterns (do not contain
1685: * wildcards) to the set and returns the real patterns.
1686: *
1687: * @param set Set to populate.
1688: * @param patterns String[] of patterns.
1689: * @since Ant 1.6.3
1690: */
1691: private String[] fillNonPatternSet(Set set, String[] patterns) {
1692: ArrayList al = new ArrayList(patterns.length);
1693: for (int i = 0; i < patterns.length; i++) {
1694: if (!SelectorUtils.hasWildcards(patterns[i])) {
1695: set.add(isCaseSensitive() ? patterns[i] : patterns[i]
1696: .toUpperCase());
1697: } else {
1698: al.add(patterns[i]);
1699: }
1700: }
1701: return set.size() == 0 ? patterns : (String[]) al
1702: .toArray(new String[al.size()]);
1703: }
1704:
1705: }
|