Bug 30094 - Performance enhancements using java.nio & lazy calls
Summary: Performance enhancements using java.nio & lazy calls
Status: RESOLVED FIXED
Alias: None
Product: Ant
Classification: Unclassified
Component: Core (show other bugs)
Version: 1.6.2
Hardware: All other
: P3 enhancement (vote)
Target Milestone: 1.8.0
Assignee: Ant Notifications List
URL:
Keywords: PatchAvailable
Depends on: 49430
Blocks:
  Show dependency tree
 
Reported: 2004-07-14 07:14 UTC by J Bleijenbergh
Modified: 2010-06-11 14:38 UTC (History)
0 users



Attachments
Sources and a jar containing the patch (82.85 KB, application/zip)
2004-07-14 07:17 UTC, J Bleijenbergh
Details
[faulty] java.nio changes against Ant 1.6.5, in unified diff format (11.46 KB, patch)
2006-01-11 18:52 UTC, Robin Verduijn
Details | Diff
lazy init changes against Ant 1.6.5, in unified diff format (22.20 KB, patch)
2006-01-11 18:53 UTC, Robin Verduijn
Details | Diff
[faulty] java.nio changes against Ant 1.6.5, in unified diff format (11.21 KB, patch)
2006-01-11 19:27 UTC, Robin Verduijn
Details | Diff
java.nio changes against Ant 1.6.5, in unified diff format (11.52 KB, patch)
2006-01-11 19:38 UTC, Robin Verduijn
Details | Diff
[faulty] java.nio changes against Ant 1.7.0, in unified diff format (2.83 KB, patch)
2008-01-09 11:54 UTC, Robin Verduijn
Details | Diff
java.nio changes against Ant 1.7.0, in unified diff format (2.75 KB, patch)
2008-01-09 12:06 UTC, Robin Verduijn
Details | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description J Bleijenbergh 2004-07-14 07:14:09 UTC
-Added NIO support to FileUtils 
-Changed call to File.exists(), File.isDir() & File.lastModified() in Resource 
to use a lazy approach

/*
 * Copyright  2000-2004 The Apache Software Foundation
 *
 *  Licensed 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.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
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.ResourceFile;
import org.apache.tools.ant.types.ResourceFactory;
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.util.FileUtils;

/**
 * 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 use the {@link #getDefaultExcludes
     * getDefaultExcludes} method instead.
     */
    protected static final String[] DEFAULTEXCLUDES = {
        // Miscellaneous typical temporary files
        "**/*~",
        "**/#*#",
        "**/.#*",
        "**/%*%",
        "**/._*",

        // CVS
        "**/CVS",
        "**/CVS/**",
        "**/.cvsignore",

        // SCCS
        "**/SCCS",
        "**/SCCS/**",

        // Visual SourceSafe
        "**/vssver.scc",

        // Subversion
        "**/.svn",
        "**/.svn/**",

        // Mac
        "**/.DS_Store"
    };

    /**
     * Patterns which should be excluded by default.
     *
     * @see #addDefaultExcludes()
     */
    private static Vector defaultExcludes = new Vector();
    static {
        resetDefaultExcludes();
    }

    /** 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 filesIncluded;

    /** The files which did not match any includes or selectors. */
    protected Vector filesNotIncluded;

    /**
     * The files which matched at least one include and at least
     * one exclude.
     */
    protected Vector filesExcluded;

    /** The directories which matched at least one include and no excludes
     *  and were selected.
     */
    protected Vector dirsIncluded;

    /** The directories which were found and did not match any includes. */
    protected Vector dirsNotIncluded;

    /**
     * The directories which matched at least one include and at least one
     * exclude.
     */
    protected Vector dirsExcluded;

    /** The files which matched at least one include and no excludes and
     *  which a selector discarded.
     */
    protected Vector filesDeselected;

    /** The directories which matched at least one include and no excludes
     *  but which a selector discarded.
     */
    protected Vector 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 or not symbolic links should be followed.
     *
     * @since Ant 1.5
     */
    private boolean followSymlinks = true;

    /** Helper. */
    private static final FileUtils FILE_UTILS = FileUtils.newFileUtils();

    /** Whether or not everything tested so far has been included. */
    protected boolean everythingIncluded = true;

    /**
     * Sole constructor.
     */
    public DirectoryScanner() {
    }

    /**
     * Tests 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(String pattern, String str) {
        return SelectorUtils.matchPatternStart(pattern, str);
    }
    /**
     * Tests 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(String pattern, String str,
                                               boolean isCaseSensitive) {
        return SelectorUtils.matchPatternStart(pattern, str, isCaseSensitive);
    }

    /**
     * Tests 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(String pattern, String str) {
        return SelectorUtils.matchPath(pattern, str);
    }

    /**
     * Tests 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(String pattern, String str,
                                       boolean isCaseSensitive) {
        return SelectorUtils.matchPath(pattern, str, isCaseSensitive);
    }

    /**
     * Tests 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(String pattern, String str) {
        return SelectorUtils.match(pattern, str);
    }

    /**
     * Tests 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(String pattern, String str,
                                   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>Vector</code>.
     *
     * @since Ant 1.6
     */
    public static String[] getDefaultExcludes() {
        return (String[]) 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(String s) {
        if (defaultExcludes.indexOf(s) == -1) {
            defaultExcludes.add(s);
            return true;
        }
        return false;
    }

    /**
     * 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(String s) {
        return defaultExcludes.remove(s);
    }

    /**
     *  Go back to the hard wired default exclude patterns
     *
     * @since Ant 1.6
     */
    public static void resetDefaultExcludes() {
    defaultExcludes = new Vector();

        for (int i = 0; i < DEFAULTEXCLUDES.length; i++) {
            defaultExcludes.add(DEFAULTEXCLUDES[i]);
        }
    }

    /**
     * Sets 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.
     *                Must not be <code>null</code>.
     */
    public void setBasedir(String basedir) {
        setBasedir(new File(basedir.replace('/', File.separatorChar).replace(
                '\\', File.separatorChar)));
    }

    /**
     * Sets the base directory to be scanned. This is the directory which is
     * scanned recursively.
     *
     * @param basedir The base directory for scanning.
     *                Should not be <code>null</code>.
     */
    public void setBasedir(File basedir) {
        this.basedir = basedir;
    }

    /**
     * Returns the base directory to be scanned.
     * This is the directory which is scanned recursively.
     *
     * @return the base directory to be scanned
     */
    public 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 boolean isCaseSensitive() {
        return isCaseSensitive;
    }
    /**
     * Sets 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 void setCaseSensitive(boolean isCaseSensitive) {
        this.isCaseSensitive = isCaseSensitive;
    }

    /**
     * gets whether or not a DirectoryScanner follows symbolic links
     *
     * @return flag indicating whether symbolic links should be followed
     *
     * @since ant 1.6
     */
    public boolean isFollowSymlinks() {
        return followSymlinks;
    }

    /**
     * Sets whether or not symbolic links should be followed.
     *
     * @param followSymlinks whether or not symbolic links should be followed
     */
    public void setFollowSymlinks(boolean followSymlinks) {
        this.followSymlinks = followSymlinks;
    }

    /**
     * Sets 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 void setIncludes(String[] includes) {
        if (includes == null) {
            this.includes = null;
        } else {
            this.includes = new String[includes.length];
            for (int i = 0; i < includes.length; i++) {
                String pattern;
                pattern = includes[i].replace('/', File.separatorChar).replace(
                        '\\', File.separatorChar);
                if (pattern.endsWith(File.separator)) {
                    pattern += "**";
                }
                this.includes[i] = pattern;
            }
        }
    }


    /**
     * Sets 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 void setExcludes(String[] excludes) {
        if (excludes == null) {
            this.excludes = null;
        } else {
            this.excludes = new String[excludes.length];
            for (int i = 0; i < excludes.length; i++) {
                String pattern;
                pattern = excludes[i].replace('/', File.separatorChar).replace(
                        '\\', File.separatorChar);
                if (pattern.endsWith(File.separator)) {
                    pattern += "**";
                }
                this.excludes[i] = pattern;
            }
        }
    }


    /**
     * Sets the selectors that will select the filelist.
     *
     * @param selectors specifies the selectors to be invoked on a scan
     */
    public void setSelectors(FileSelector[] selectors) {
        this.selectors = selectors;
    }


    /**
     * Returns 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 boolean isEverythingIncluded() {
        return everythingIncluded;
    }

    /**
     * Scans the base directory 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.
     *
     * @exception IllegalStateException if the base directory was set
     *            incorrectly (i.e. if it is <code>null</code>, doesn't exist,
     *            or isn't a directory).
     */
    public void scan() throws IllegalStateException {
        if (basedir == null) {
            throw new IllegalStateException("No basedir set");
        }
        if (!basedir.exists()) {
            throw new IllegalStateException("basedir " + basedir
                                            + " does not exist");
        }
        if (!basedir.isDirectory()) {
            throw new IllegalStateException("basedir " + basedir
                                            + " is not a directory");
        }

        if (includes == null) {
            // No includes supplied, so set it to 'matches all'
            includes = new String[1];
            includes[0] = "**";
        }
        if (excludes == null) {
            excludes = new String[0];
        }

        filesIncluded    = new Vector();
        filesNotIncluded = new Vector();
        filesExcluded    = new Vector();
        filesDeselected  = new Vector();
        dirsIncluded     = new Vector();
        dirsNotIncluded  = new Vector();
        dirsExcluded     = new Vector();
        dirsDeselected   = new Vector();

        if (isIncluded("")) {
            if (!isExcluded("")) {
                if (isSelected("", basedir)) {
                    dirsIncluded.addElement("");
                } else {
                    dirsDeselected.addElement("");
                }
            } else {
                dirsExcluded.addElement("");
            }
        } else {
            dirsNotIncluded.addElement("");
        }
        checkIncludePatterns();
        clearCaches();
    }

    /**
     * this routine is actually checking all the include patterns in
     * order to avoid scanning everything under base dir
     * @since ant1.6
     */
    private void checkIncludePatterns() {
        Hashtable newroots = new Hashtable();
        // put in the newroots vector the include patterns without
        // wildcard tokens
        for (int icounter = 0; icounter < includes.length; icounter++) {
            String newpattern =
                SelectorUtils.rtrimWildcardTokens(includes[icounter]);
            newroots.put(newpattern, includes[icounter]);
        }

        if (newroots.containsKey("")) {
            // we are going to scan everything anyway
            scandir(basedir, "", true);
        } else {
            // only scan directories that can include matched files or
            // directories
            Enumeration enum2 = newroots.keys();

            File canonBase = null;
            try {
                canonBase = basedir.getCanonicalFile();
            } catch (IOException ex) {
                throw new BuildException(ex);
            }

            while (enum2.hasMoreElements()) {
                String currentelement = (String) enum2.nextElement();
                String originalpattern = (String) newroots.get(currentelement);
                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 {
                        File canonFile = myfile.getCanonicalFile();
                        String path = FILE_UTILS.removeLeadingPath(canonBase,
                                                                  canonFile);
                        if (!path.equals(currentelement) || ON_VMS) {
                            myfile = findFile(basedir, currentelement);
                            if (myfile != null) {
                                currentelement =
                                    FILE_UTILS.removeLeadingPath(basedir,
                                                                 myfile);
                            }
                        }
                    } catch (IOException ex) {
                        throw new BuildException(ex);
                    }
                }

                if ((myfile == null || !myfile.exists()) && !isCaseSensitive) {
                    File f = findFileCaseInsensitive(basedir, currentelement);
                    if (f.exists()) {
                        // adapt currentelement to the case we've
                        // actually found
                        currentelement = FILE_UTILS.removeLeadingPath(basedir,
                                                                     f);
                        myfile = f;
                    }
                }

                if (myfile != null && myfile.exists()) {
                    if (!followSymlinks
                        && isSymlink(basedir, currentelement)) {
                        continue;
                    }

                    if (myfile.isDirectory()) {
                        if (isIncluded(currentelement)
                            && currentelement.length() > 0) {
                            accountForIncludedDir(currentelement, myfile, true);
                        }  else {
                            if (currentelement.length() > 0) {
                                if (currentelement.charAt(currentelement
                                                          .length() - 1)
                                    != File.separatorChar) {
                                    currentelement =
                                        currentelement + File.separatorChar;
                                }
                            }
                            scandir(myfile, currentelement, true);
                        }
                    } else {
                        if (isCaseSensitive
                            && originalpattern.equals(currentelement)) {
                            accountForIncludedFile(currentelement, myfile);
                        } else if (!isCaseSensitive
                                   && originalpattern
                                   .equalsIgnoreCase(currentelement)) {
                            accountForIncludedFile(currentelement, myfile);
                        }
                    }
                }
            }
        }
    }

    /**
     * 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() {
        if (haveSlowResults) {
            return;
        }

        String[] excl = new String[dirsExcluded.size()];
        dirsExcluded.copyInto(excl);

        String[] notIncl = new String[dirsNotIncluded.size()];
        dirsNotIncluded.copyInto(notIncl);

        for (int i = 0; i < excl.length; i++) {
            if (!couldHoldIncluded(excl[i])) {
                scandir(new File(basedir, excl[i]),
                        excl[i] + File.separator, false);
            }
        }

        for (int i = 0; i < notIncl.length; i++) {
            if (!couldHoldIncluded(notIncl[i])) {
                scandir(new File(basedir, notIncl[i]),
                        notIncl[i] + File.separator, false);
            }
        }

        haveSlowResults  = true;
    }

    /**
     * Scans 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(File dir, String vpath, boolean fast) {
        if (dir == null) {
            throw new BuildException("dir must not be null.");
        } else if (!dir.exists()) {
            throw new BuildException(dir + " doesn't exists.");
        } else if (!dir.isDirectory()) {
            throw new BuildException(dir + " is not a directory.");
        }

        // avoid double scanning of directories, can only happen in fast mode
        if (fast && hasBeenScanned(vpath)) {
            return;
        }
        String[] newfiles = dir.list();

        if (newfiles == null) {
            /*
             * two reasons are mentioned in the API docs for File.list
             * (1) dir is not a directory. This is impossible as
             *     we wouldn't get here in this case.
             * (2) an IO error occurred (why doesn't it throw an exception
             *     then???)
             */
            throw new BuildException("IO error scanning directory "
                                     + dir.getAbsolutePath());
        }

        if (!followSymlinks) {
            Vector noLinks = new Vector();
            for (int i = 0; i < newfiles.length; i++) {
                try {
                    if (FILE_UTILS.isSymbolicLink(dir, newfiles[i])) {
                        String name = vpath + newfiles[i];
                        File   file = new File(dir, newfiles[i]);
                        if (file.isDirectory()) {
                            dirsExcluded.addElement(name);
                        } else {
                            filesExcluded.addElement(name);
                        }
                    } else {
                        noLinks.addElement(newfiles[i]);
                    }
                } catch (IOException ioe) {
                    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.addElement(newfiles[i]);
                }
            }
            newfiles = new String[noLinks.size()];
            noLinks.copyInto(newfiles);
        }

        for (int i = 0; i < newfiles.length; i++) {
            String name = vpath + newfiles[i];
            File   file = new File(dir, newfiles[i]);
            if (file.isDirectory()) {
                if (isIncluded(name)) {
                    accountForIncludedDir(name, file, fast);
                } else {
                    everythingIncluded = false;
                    dirsNotIncluded.addElement(name);
                    if (fast && couldHoldIncluded(name)) {
                        scandir(file, name + File.separator, fast);
                    }
                }
                if (!fast) {
                    scandir(file, name + File.separator, fast);
                }
            } else if (file.isFile()) {
                if (isIncluded(name)) {
                    accountForIncludedFile(name, file);
                } else {
                    everythingIncluded = false;
                    filesNotIncluded.addElement(name);
                }
            }
        }
    }
    /**
     * process included file
     * @param name  path of the file relative to the directory of the fileset
     * @param file  included file
     */
    private void accountForIncludedFile(String name, File file) {
        if (!filesIncluded.contains(name)
            && !filesExcluded.contains(name)
            && !filesDeselected.contains(name)) {

            if (!isExcluded(name)) {
                if (isSelected(name, file)) {
                    filesIncluded.addElement(name);
                } else {
                    everythingIncluded = false;
                    filesDeselected.addElement(name);
                }
            } else {
                everythingIncluded = false;
                filesExcluded.addElement(name);
            }
        }
    }

    /**
     *
     * @param name path of the directory relative to the directory of
     * the fileset
     * @param file directory as file
     * @param fast
     */
    private void accountForIncludedDir(String name, File file, boolean fast) {
        if (!dirsIncluded.contains(name)
            && !dirsExcluded.contains(name)
            && !dirsDeselected.contains(name)) {

            if (!isExcluded(name)) {
                if (isSelected(name, file)) {
                    dirsIncluded.addElement(name);
                    if (fast) {
                        scandir(file, name + File.separator, fast);
                    }
                } else {
                    everythingIncluded = false;
                    dirsDeselected.addElement(name);
                    if (fast && couldHoldIncluded(name)) {
                        scandir(file, name + File.separator, fast);
                    }
                }

            } else {
                everythingIncluded = false;
                dirsExcluded.addElement(name);
                if (fast && couldHoldIncluded(name)) {
                    scandir(file, name + File.separator, fast);
                }
            }
        }
    }
    /**
     * Tests 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(String name) {
        for (int i = 0; i < includes.length; i++) {
            if (matchPath(includes[i], name, isCaseSensitive)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Tests 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(String name) {
        for (int i = 0; i < includes.length; i++) {
            if (matchPatternStart(includes[i], name, isCaseSensitive)) {
                if (isMorePowerfulThanExcludes(name, includes[i])) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     *  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 that one want to test
     *  @param includepattern  one include pattern
     *  @return true if there is no exclude pattern more powerful than this 
include pattern
     *  @since ant1.6
     */
    private boolean isMorePowerfulThanExcludes(String name, String 
includepattern) {
        String soughtexclude = name + File.separator + "**";
        for (int counter = 0; counter < excludes.length; counter++) {
            if (excludes[counter].equals(soughtexclude))  {
                return false;
            }
        }
        return true;
    }
    /**
     * Tests 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(String name) {
        for (int i = 0; i < excludes.length; i++) {
            if (matchPath(excludes[i], name, isCaseSensitive)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Tests whether a name 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(String name, File file) {
        if (selectors != null) {
            for (int i = 0; i < selectors.length; i++) {
                if (!selectors[i].isSelected(basedir, name, file)) {
                    return false;
                }
            }
        }
        return true;
    }

    /**
     * Returns 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 = new String[filesIncluded.size()];
        filesIncluded.copyInto(files);
        Arrays.sort(files);
        return files;
    }

    /**
     * Returns 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 String[] getNotIncludedFiles() {
        slowScan();
        String[] files = new String[filesNotIncluded.size()];
        filesNotIncluded.copyInto(files);
        return files;
    }

    /**
     * Returns 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 String[] getExcludedFiles() {
        slowScan();
        String[] files = new String[filesExcluded.size()];
        filesExcluded.copyInto(files);
        return files;
    }

    /**
     * <p>Returns 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 String[] getDeselectedFiles() {
        slowScan();
        String[] files = new String[filesDeselected.size()];
        filesDeselected.copyInto(files);
        return files;
    }

    /**
     * Returns 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 = new String[dirsIncluded.size()];
        dirsIncluded.copyInto(directories);
        Arrays.sort(directories);
        return directories;
    }

    /**
     * Returns 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 String[] getNotIncludedDirectories() {
        slowScan();
        String[] directories = new String[dirsNotIncluded.size()];
        dirsNotIncluded.copyInto(directories);
        return directories;
    }

    /**
     * Returns 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 String[] getExcludedDirectories() {
        slowScan();
        String[] directories = new String[dirsExcluded.size()];
        dirsExcluded.copyInto(directories);
        return directories;
    }

    /**
     * <p>Returns 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 String[] getDeselectedDirectories() {
        slowScan();
        String[] directories = new String[dirsDeselected.size()];
        dirsDeselected.copyInto(directories);
        return directories;
    }

    /**
     * Adds default exclusions to the current exclusions set.
     */
    public void addDefaultExcludes() {
        int excludesLength = excludes == null ? 0 : excludes.length;
        String[] newExcludes;
        newExcludes = new String[excludesLength + defaultExcludes.size()];
        if (excludesLength > 0) {
            System.arraycopy(excludes, 0, newExcludes, 0, excludesLength);
        }
        String[] defaultExcludesTemp = getDefaultExcludes();
        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 Resource getResource(String name) {
        File f = FILE_UTILS.resolveFile(basedir, name);
        return new ResourceFile(name);
    }

    /**
     * temporary table to speed up the various scanning methods below
     *
     * @since Ant 1.6
     */
    private Map fileListMap = new HashMap();

    /**
     * Returns a cached result of list performed on file, if
     * available.  Invokes the method and caches the result otherwise.
     *
     * @since Ant 1.6
     */
    private String[] list(File file) {
        String[] files = (String[]) fileListMap.get(file);
        if (files == null) {
            files = file.list();
            if (files != null) {
                fileListMap.put(file, files);
            }
        }
        return files;
    }

    /**
     * From <code>base</code> traverse the filesystem in a case
     * insensitive manner in order to find a file that matches the
     * given name.
     *
     * @return File object that points to the file in question.  if it
     * hasn't been found it will simply be <code>new File(base,
     * path)</code>.
     *
     * @since Ant 1.6
     */
    private File findFileCaseInsensitive(File base, String path) {
        File f = findFileCaseInsensitive(base,
                                         SelectorUtils.tokenizePath(path));
        return  f == null ? new File(base, path) : f;
    }

    /**
     * From <code>base</code> traverse the filesystem in a case
     * insensitive manner in order to find a file that matches the
     * given stack of names.
     *
     * @return File object that points to the file in question or null.
     *
     * @since Ant 1.6
     */
    private File findFileCaseInsensitive(File base, Vector pathElements) {
        if (pathElements.size() == 0) {
            return base;
        } else {
            if (!base.isDirectory()) {
                return null;
            }
            String[] files = list(base);
            if (files == null) {
                throw new BuildException("IO error scanning directory "
                                         + base.getAbsolutePath());
            }
            String current = (String) pathElements.remove(0);
            for (int i = 0; i < files.length; i++) {
                if (files[i].equals(current)) {
                    base = new File(base, files[i]);
                    return findFileCaseInsensitive(base, pathElements);
                }
            }
            for (int i = 0; i < files.length; i++) {
                if (files[i].equalsIgnoreCase(current)) {
                    base = new File(base, files[i]);
                    return findFileCaseInsensitive(base, pathElements);
                }
            }
        }
        return null;
    }

    /**
     * From <code>base</code> traverse the filesystem in order to find
     * a file that matches the given name.
     *
     * @return File object that points to the file in question or null.
     *
     * @since Ant 1.6
     */
    private File findFile(File base, String path) {
        return findFile(base, SelectorUtils.tokenizePath(path));
    }

    /**
     * From <code>base</code> traverse the filesystem in order to find
     * a file that matches the given stack of names.
     *
     * @return File object that points to the file in question or null.
     *
     * @since Ant 1.6
     */
    private File findFile(File base, Vector pathElements) {
        if (pathElements.size() == 0) {
            return base;
        } else {
            if (!base.isDirectory()) {
                return null;
            }
            String[] files = list(base);
            if (files == null) {
                throw new BuildException("IO error scanning directory "
                                         + base.getAbsolutePath());
            }
            String current = (String) pathElements.remove(0);
            for (int i = 0; i < files.length; i++) {
                if (files[i].equals(current)) {
                    base = new File(base, files[i]);
                    return findFile(base, pathElements);
                }
            }
        }
        return null;
    }

    /**
     * Do we have to traverse a symlink when trying to reach path from
     * basedir?
     * @since Ant 1.6
     */
    private boolean isSymlink(File base, String path) {
        return isSymlink(base, SelectorUtils.tokenizePath(path));
    }

    /**
     * Do we have to traverse a symlink when trying to reach path from
     * basedir?
     * @since Ant 1.6
     */
    private boolean isSymlink(File base, Vector pathElements) {
        if (pathElements.size() > 0) {
            String current = (String) pathElements.remove(0);
            try {
                if (FILE_UTILS.isSymbolicLink(base, current)) {
                    return true;
                } else {
                    base = new File(base, current);
                    return isSymlink(base, pathElements);
                }
            } catch (IOException ioe) {
                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);
                return false;
            }
        }
        return false;
    }

    /**
     * List of all scanned directories.
     *
     * @since Ant 1.6
     */
    private Set scannedDirs = new HashSet();

    /**
     * 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(String vpath) {
        return !scannedDirs.add(vpath);
    }

    /**
     * Clear internal caches.
     *
     * @since Ant 1.6
     */
    private void clearCaches() {
        fileListMap.clear();
        scannedDirs.clear();
    }
}

/*
 * Copyright  2003-2004 The Apache Software Foundation
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 */
package org.apache.tools.ant.types;

/**
 * describes a File or a ZipEntry
 *
 * this class is meant to be used by classes needing to record path
 * and date/time information about a file, a zip entry or some similar
 * resource (URL, archive in a version control repository, ...)
 *
 * @since Ant 1.5.2
 */
public abstract class Resource implements Cloneable, Comparable {
    protected String name = null;
 
    /**
     * default constructor
     */
    protected Resource() {
    }

 
    /**
     * name attribute will contain the path of a file relative to the
     * root directory of its fileset or the recorded path of a zip
     * entry.
     *
     * <p>example for a file with fullpath /var/opt/adm/resource.txt
     * in a file set with root dir /var/opt it will be
     * adm/resource.txt.</p>
     *
     * <p>&quot;/&quot; will be used as the directory separator.</p>
     */
    public String getName() {
        return name;
    }

    /**
     * @param name relative path of the resource.  Expects
     * &quot;/&quot; to be used as the directory separator.
     */
    public void setName(String name) {
        this.name = name;
    }
    /**
     * the exists attribute tells whether a file exists
     */
    public abstract boolean isExists();

    public abstract void setExists(boolean exists);

    /**
     * tells the modification time in milliseconds since 01.01.1970 of
     *
     * @return 0 if the resource does not exist to mirror the behavior
     * of {@link java.io.File File}.
     */
    public abstract long getLastModified();

    public abstract void setLastModified(long lastmodified);
    
    /**
     * tells if the resource is a directory
     * @return boolean flag indicating if the resource is a directory
     */
    public abstract boolean isDirectory();

    public abstract void setDirectory(boolean directory);

    /**
     * @return copy of this
     */
    public Object clone() {
        try {
            return super.clone();
        } catch (CloneNotSupportedException e) {
            throw new Error("CloneNotSupportedException for a "
                            + "Clonable Resource caught?");
        }
    }

    /**
     * delegates to a comparison of names.
     *
     * @since Ant 1.6
     */
    public int compareTo(Object other) {
        if (!(other instanceof Resource)) {
            throw new IllegalArgumentException("Can only be compared with "
                                               + "Resources");
        }
        Resource r = (Resource) other;
        return getName().compareTo(r.getName());
    }
}

/*
 * Created on Jul 10, 2004
 *
 * TODO To change the template for this generated file go to
 * Window - Preferences - Java - Code Style - Code Templates
 */
package org.apache.tools.ant.types;

import java.io.*;

/**
 * @author jbleijen
 * 
 * TODO To change the template for this generated type comment go to Window -
 * Preferences - Java - Code Style - Code Templates
 */
public class ResourceFile extends Resource {
	protected File file;

	private final static int UNKNOWN = 1;

	private final static int FILE_EXISTS = 2;

	private final static int FILE_DOES_NOT_EXISTS = 3;

	private final static int IS_DIRECTORY = 4;

	private final static int IS_NOT_DIRECTORY = 5;

	private int exists = UNKNOWN;

	private int isDir = UNKNOWN;

	private long lastModified = -1l;

	/**
	 * only sets the name.
	 * 
	 * <p>
	 * This is a dummy, used for not existing resources.
	 * </p>
	 * 
	 * @param name
	 *            relative path of the resource. Expects &quot;/&quot; to be
	 *            used as the directory separator.
	 */
	public ResourceFile(String name) {
		this(name, new File(name));
	}

	public ResourceFile(String name, File file) {
		this.name = name;
		this.file = file;
	}

	/**
	 * the exists attribute tells whether a file exists
	 */
	public boolean isExists() {
		validateFileExists();
		return exists == FILE_EXISTS;
	}

	public void setExists(boolean exists) {
		if (exists) {
			this.exists = FILE_EXISTS;
		} else {
			this.exists = FILE_DOES_NOT_EXISTS;
		}
	}

	/**
	 * tells the modification time in milliseconds since 01.01.1970 of
	 * 
	 * @return 0 if the resource does not exist to mirror the behavior of
	 *         {@link java.io.File File}.
	 */
	public long getLastModified() {
		if (!isExists())
			return 0;
		if (lastModified == -1) {
			lastModified = file.lastModified();
		}
		return lastModified;
	}

	public void setLastModified(long lastmodified) {
		this.lastModified = lastmodified;
	}

	/**
	 * tells if the resource is a directory
	 * 
	 * @return boolean flag indicating if the resource is a directory
	 */
	public boolean isDirectory() {
		validateFileIsDir();
		return isDir == IS_DIRECTORY;
	}

	public void setDirectory(boolean directory) {
		if (directory) {
			this.isDir = IS_DIRECTORY;
		} else {
			this.isDir = IS_NOT_DIRECTORY;
		}
	}

	/**
	 * @return copy of this
	 */
	public Object clone() {
		return super.clone();
	}

	/**
	 * delegates to a comparison of names.
	 * 
	 * @since Ant 1.6
	 */
	public int compareTo(Object other) {
		if (!(other instanceof Resource)) {
			throw new IllegalArgumentException("Can only be 
compared with "
					+ "Resources");
		}
		Resource r = (Resource) other;
		return getName().compareTo(r.getName());
	}

	/*
	 * TODO
	 */
	protected void validateFileExists() {
		if (exists == UNKNOWN) {
			this.lastModified = file.lastModified();
			if (this.lastModified == 0l) {
				this.exists = FILE_DOES_NOT_EXISTS;
			} else {
				this.exists = FILE_EXISTS;
			}
		}
	}

	/*
	 * TODO @author jbleijen
	 *  
	 */
	protected void validateFileIsDir() {
		if (exists == FILE_DOES_NOT_EXISTS) {
			isDir = IS_NOT_DIRECTORY;
			return;
		}
		if (isDir == UNKNOWN) {
			if (file.isDirectory()) {
				isDir = IS_DIRECTORY;
			} else {
				isDir = IS_NOT_DIRECTORY;
			}
		}
	}
}

/*
 * Created on Jul 10, 2004
 *
 * TODO To change the template for this generated file go to
 * Window - Preferences - Java - Code Style - Code Templates
 */
package org.apache.tools.ant.types;

/**
 * @author jbleijen
 *
 * TODO To change the template for this generated type comment go to
 * Window - Preferences - Java - Code Style - Code Templates
 */
public class ResourceZipEntry extends Resource
{
	  protected boolean exists = true;
	    protected long lastmodified = 0;
	    protected boolean directory = false;

	   /**
     * only sets the name.
     *
     * <p>This is a dummy, used for not existing resources.</p>
     *
     * @param name relative path of the resource.  Expects
     * &quot;/&quot; to be used as the directory separator.
     */
    public ResourceZipEntry(String name) {
        this(name, false, 0, false);
    }

    /**
     * sets the name, lastmodified flag, and exists flag
     *
     * @param name relative path of the resource.  Expects
     * &quot;/&quot; to be used as the directory separator.
     */
    public ResourceZipEntry(String name, boolean exists, long lastmodified) {
        this(name, exists, lastmodified, false);
    }

    /**
     * @param name relative path of the resource.  Expects
     * &quot;/&quot; to be used as the directory separator.
     */
    public ResourceZipEntry(String name, boolean exists, long lastmodified,
                    boolean directory) {
        this.name = name;
        this.exists = exists;
        this.lastmodified = lastmodified;
        this.directory = directory;
    }
    
    /**
     * the exists attribute tells whether a file exists
     */
    public boolean isExists() {
        return exists;
    }

    public void setExists(boolean exists) {
        this.exists = exists;
    }

    /**
     * tells the modification time in milliseconds since 01.01.1970 of
     *
     * @return 0 if the resource does not exist to mirror the behavior
     * of {@link java.io.File File}.
     */
    public long getLastModified() {
        return !exists || lastmodified < 0 ? 0 : lastmodified;
    }

    public void setLastModified(long lastmodified) {
        this.lastmodified = lastmodified;
    }
    /**
     * tells if the resource is a directory
     * @return boolean flag indicating if the resource is a directory
     */
    public boolean isDirectory() {
        return directory;
    }

    public void setDirectory(boolean directory) {
        this.directory = directory;
    }

    /**
     * @return copy of this
     */
    public Object clone() {
            return super.clone();
    }

    /**
     * delegates to a comparison of names.
     *
     * @since Ant 1.6
     */
    public int compareTo(Object other) {
        if (!(other instanceof ResourceZipEntry)) {
            throw new IllegalArgumentException("Can only be compared with "
                                               + "Resources");
        }
        ResourceZipEntry r = (ResourceZipEntry) other;
        return getName().compareTo(r.getName());
    }
}
/*
 * Copyright  2001-2004 The Apache Software Foundation
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 */

package org.apache.tools.ant.types;

import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Vector;
import java.util.Hashtable;
import java.util.Enumeration;
import java.util.zip.ZipException;

import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.DirectoryScanner;
import org.apache.tools.zip.ZipEntry;
import org.apache.tools.zip.ZipFile;

/**
 * ZipScanner accesses the pattern matching algorithm in DirectoryScanner,
 * which are protected methods that can only be accessed by subclassing.
 *
 * This implementation of FileScanner defines getIncludedFiles to return
 * the matching Zip entries.
 *
 */
public class ZipScanner extends DirectoryScanner {

    /**
     * The zip file which should be scanned.
     */
    protected File srcFile;
    /**
     * to record the last scanned zip file with its modification date
     */
    private Resource lastScannedResource;
    /**
     * record list of all zip entries
     */
    private Hashtable myentries;

    /**
     * encoding of file names.
     *
     * @since Ant 1.6
     */
    private String encoding;

    /**
     * Sets the srcFile for scanning. This is the jar or zip file that
     * is scanned for matching entries.
     *
     * @param srcFile the (non-null) zip file name for scanning
     */
    public void setSrc(File srcFile) {
        this.srcFile = srcFile;
    }

    /**
     * Sets encoding of file names.
     *
     * @since Ant 1.6
     */
    public void setEncoding(String encoding) {
        this.encoding = encoding;
    }

    /**
     * Returns 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() {
        if (srcFile != null) {
            Vector myvector = new Vector();
            // first check if the archive needs to be scanned again
            scanme();
            for (Enumeration e = myentries.elements(); e.hasMoreElements();) {
                Resource myresource = (Resource) e.nextElement();
                if (!myresource.isDirectory() && match(myresource.getName())) {
                    myvector.addElement(myresource.getName());
                }
            }
            String[] files = new String[myvector.size()];
            myvector.copyInto(files);
            Arrays.sort(files);
            return files;
        } else {
            return super.getIncludedFiles();
        }
    }

    /**
     * Returns 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() {
        if (srcFile != null) {
            Vector myvector = new Vector();
            // first check if the archive needs to be scanned again
            scanme();
            for (Enumeration e = myentries.elements(); e.hasMoreElements();) {
                Resource myresource = (Resource) e.nextElement();
                if (myresource.isDirectory() && match(myresource.getName())) {
                    myvector.addElement(myresource.getName());
                }
            }
            String[] files = new String[myvector.size()];
            myvector.copyInto(files);
            Arrays.sort(files);
            return files;
        } else {
            return super.getIncludedDirectories();
        }
    }

    /**
     * Initialize DirectoryScanner data structures.
     */
    public void init() {
        if (includes == null) {
            // No includes supplied, so set it to 'matches all'
            includes = new String[1];
            includes[0] = "**";
        }
        if (excludes == null) {
            excludes = new String[0];
        }
    }

    /**
     * Matches a jar entry against the includes/excludes list,
     * normalizing the path separator.
     *
     * @param path the (non-null) path name to test for inclusion
     *
     * @return <code>true</code> if the path should be included
     *         <code>false</code> otherwise.
     */
    public boolean match(String path) {
        String vpath = path.replace('/', File.separatorChar).
            replace('\\', File.separatorChar);
        return isIncluded(vpath) && !isExcluded(vpath);
    }

    /**
     * @param name path name of the file sought in the archive
     *
     * @since Ant 1.5.2
     */
    public Resource getResource(String name) {
        if (srcFile == null) {
            return super.getResource(name);
        } else if (name.equals("")) {
            // special case in ZIPs, we do not want this thing included
            return new ResourceZipEntry("", true, Long.MAX_VALUE, true);
        }

        // first check if the archive needs to be scanned again
        scanme();
        if (myentries.containsKey(name)) {
            return (Resource) myentries.get(name);
        } else if (myentries.containsKey(name + "/")) {
            return (Resource) myentries.get(name + "/");
        } else {
            return new ResourceZipEntry(name);
        }
    }

    /**
     * if the datetime of the archive did not change since
     * lastScannedResource was initialized returns immediately else if
     * the archive has not been scanned yet, then all the zip entries
     * are put into the vector myentries as a vector of the resource
     * type
     */
    private void scanme() {
        Resource thisresource = new ResourceZipEntry(srcFile.getAbsolutePath(),
                                             srcFile.exists(),
                                             srcFile.lastModified());

        // spare scanning again and again
        if (lastScannedResource != null
            && lastScannedResource.getName().equals(thisresource.getName())
            && lastScannedResource.getLastModified()
            == thisresource.getLastModified()) {
            return;
        }

        ZipEntry entry = null;
        ZipFile zf = null;
        myentries = new Hashtable();
        try {
            try {
                zf = new ZipFile(srcFile, encoding);
            } catch (ZipException ex) {
                throw new BuildException("problem reading " + srcFile, ex);
            } catch (IOException ex) {
                throw new BuildException("problem opening " + srcFile, ex);
            }

            Enumeration e = zf.getEntries();
            while (e.hasMoreElements()) {
                entry = (ZipEntry) e.nextElement();
                myentries.put(new String(entry.getName()),
                              new ResourceZipEntry(entry.getName(), true,
                                           entry.getTime(),
                                           entry.isDirectory()));
            }
        } finally {
            if (zf != null) {
                try {
                    zf.close();
                } catch (IOException ex) {
                    // swallow
                }
            }
        }
        // record data about the last scanned resource
        lastScannedResource = thisresource;
    }
}

/*
 * Copyright  2001-2004 The Apache Software Foundation
 *
 *  Licensed 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.util;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.CharacterIterator;
import java.text.DecimalFormat;
import java.text.StringCharacterIterator;
import java.util.Random;
import java.util.Stack;
import java.util.StringTokenizer;
import java.util.Vector;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.filters.util.ChainReaderHelper;
import org.apache.tools.ant.taskdefs.condition.Os;
import org.apache.tools.ant.types.FilterSetCollection;
import org.apache.tools.ant.launch.Locator;

/**
 * This class also encapsulates methods which allow Files to be refered to using
 * abstract path names which are translated to native system file paths at
 * runtime as well as copying files or setting there last modification time.
 * 
 * @version $Revision: 1.56.2.7 $
 */

public class FileUtils {
	private static Random rand = new Random(System.currentTimeMillis());

	private static Object lockReflection = new Object();

	private static java.lang.reflect.Method setLastModified = null;

	private boolean onNetWare = Os.isFamily("netware");

	// for toURI
	private static boolean[] isSpecial = new boolean[256];

	private static char[] escapedChar1 = new char[256];

	private static char[] escapedChar2 = new char[256];

	/**
	 * the granularity of timestamps under FAT
	 */
	public static final long FAT_FILE_TIMESTAMP_GRANULARITY = 2000;

	// stolen from FilePathToURI of the Xerces-J team
	static {
		for (int i = 0; i <= 0x20; i++) {
			isSpecial[i] = true;
			escapedChar1[i] = Character.forDigit(i >> 4, 16);
			escapedChar2[i] = Character.forDigit(i & 0xf, 16);
		}
		isSpecial[0x7f] = true;
		escapedChar1[0x7f] = '7';
		escapedChar2[0x7f] = 'F';
		char[] escChs = 
{ '<', '>', '#', '%', '"', '{', '}', '|', '\\', '^',
				'~', '[', ']', '`' };
		int len = escChs.length;
		char ch;
		for (int i = 0; i < len; i++) {
			ch = escChs[i];
			isSpecial[ch] = true;
			escapedChar1[ch] = Character.forDigit(ch >> 4, 16);
			escapedChar2[ch] = Character.forDigit(ch & 0xf, 16);
		}
	}

	/**
	 * Factory method.
	 * 
	 * @return a new instance of FileUtils.
	 */
	public static FileUtils newFileUtils() {
		String version = System.getProperty("java.version");
		StringTokenizer st = new StringTokenizer(version, ".");
		boolean supportsNio = false;
		try {
			if (st.countTokens() > 2) {
				int major = Integer.parseInt(st.nextToken());
				int minor = Integer.parseInt(st.nextToken());

				if (major > 1 || (major == 1 && minor >= 4)) {
					supportsNio = true;
					try {
						Class.forName
("org.apache.tools.ant.util.NioFileUtils");
						return new NioFileUtils();
					} catch (ClassNotFoundException e1) {
					    // class not included
						// fall back
					}
				}
			}
		} catch (NumberFormatException e) { 
			//ignore
		}
		return new FileUtils();
	}

	/**
	 * Empty constructor.
	 */
	protected FileUtils() {
	}

	/**
	 * Get the URL for a file taking into account # characters
	 * 
	 * @param file
	 *            the file whose URL representation is required.
	 * @return The FileURL value
	 * @throws MalformedURLException
	 *             if the URL representation cannot be formed.
	 */
	public URL getFileURL(File file) throws MalformedURLException {
		return new URL(toURI(file.getAbsolutePath()));
	}

	/**
	 * Convienence method to copy a file from a source to a destination. No
	 * filtering is performed.
	 * 
	 * @param sourceFile
	 *            Name of file to copy from. Must not be <code>null</code>.
	 * @param destFile
	 *            Name of file to copy to. Must not be <code>null</code>.
	 * 
	 * @throws IOException
	 *             if the copying fails
	 */
	public void copyFile(String sourceFile, String destFile) throws 
IOException {
		copyFile(new File(sourceFile), new File(destFile), null, false, 
false);
	}

	/**
	 * Convienence method to copy a file from a source to a destination
	 * specifying if token filtering must be used.
	 * 
	 * @param sourceFile
	 *            Name of file to copy from. Must not be <code>null</code>.
	 * @param destFile
	 *            Name of file to copy to. Must not be <code>null</code>.
	 * @param filters
	 *            the collection of filters to apply to this copy
	 * 
	 * @throws IOException
	 *             if the copying fails
	 */
	public void copyFile(String sourceFile, String destFile,
			FilterSetCollection filters) throws IOException {
		copyFile(new File(sourceFile), new File(destFile), filters, 
false,
				false);
	}

	/**
	 * Convienence method to copy a file from a source to a destination
	 * specifying if token filtering must be used and if source files may
	 * overwrite newer destination files.
	 * 
	 * @param sourceFile
	 *            Name of file to copy from. Must not be <code>null</code>.
	 * @param destFile
	 *            Name of file to copy to. Must not be <code>null</code>.
	 * @param filters
	 *            the collection of filters to apply to this copy
	 * @param overwrite
	 *            Whether or not the destination file should be overwritten 
if
	 *            it already exists.
	 * 
	 * @throws IOException
	 *             if the copying fails
	 */
	public void copyFile(String sourceFile, String destFile,
			FilterSetCollection filters, boolean overwrite) throws 
IOException {
		copyFile(new File(sourceFile), new File(destFile), filters, 
overwrite,
				false);
	}

	/**
	 * Convienence method to copy a file from a source to a destination
	 * specifying if token filtering must be used, if source files may 
overwrite
	 * newer destination files and the last modified time of
	 * <code>destFile</code> file should be made equal to the last modified
	 * time of <code>sourceFile</code>.
	 * 
	 * @param sourceFile
	 *            Name of file to copy from. Must not be <code>null</code>.
	 * @param destFile
	 *            Name of file to copy to. Must not be <code>null</code>.
	 * @param filters
	 *            the collection of filters to apply to this copy
	 * @param overwrite
	 *            Whether or not the destination file should be overwritten 
if
	 *            it already exists.
	 * @param preserveLastModified
	 *            Whether or not the last modified time of the resulting 
file
	 *            should be set to that of the source file.
	 * 
	 * @throws IOException
	 *             if the copying fails
	 */
	public void copyFile(String sourceFile, String destFile,
			FilterSetCollection filters, boolean overwrite,
			boolean preserveLastModified) throws IOException {
		copyFile(new File(sourceFile), new File(destFile), filters, 
overwrite,
				preserveLastModified);
	}

	/**
	 * Convienence method to copy a file from a source to a destination
	 * specifying if token filtering must be used, if source files may 
overwrite
	 * newer destination files and the last modified time of
	 * <code>destFile</code> file should be made equal to the last modified
	 * time of <code>sourceFile</code>.
	 * 
	 * @param sourceFile
	 *            Name of file to copy from. Must not be <code>null</code>.
	 * @param destFile
	 *            Name of file to copy to. Must not be <code>null</code>.
	 * @param filters
	 *            the collection of filters to apply to this copy
	 * @param overwrite
	 *            Whether or not the destination file should be overwritten 
if
	 *            it already exists.
	 * @param preserveLastModified
	 *            Whether or not the last modified time of the resulting 
file
	 *            should be set to that of the source file.
	 * @param encoding
	 *            the encoding used to read and write the files.
	 * 
	 * @throws IOException
	 *             if the copying fails
	 * 
	 * @since Ant 1.5
	 */
	public void copyFile(String sourceFile, String destFile,
			FilterSetCollection filters, boolean overwrite,
			boolean preserveLastModified, String encoding) throws 
IOException {
		copyFile(new File(sourceFile), new File(destFile), filters, 
overwrite,
				preserveLastModified, encoding);
	}

	/**
	 * Convienence method to copy a file from a source to a destination
	 * specifying if token filtering must be used, if filter chains must be
	 * used, if source files may overwrite newer destination files and the 
last
	 * modified time of <code>destFile</code> file should be made equal to 
the
	 * last modified time of <code>sourceFile</code>.
	 * 
	 * @param sourceFile
	 *            Name of file to copy from. Must not be <code>null</code>.
	 * @param destFile
	 *            Name of file to copy to. Must not be <code>null</code>.
	 * @param filters
	 *            the collection of filters to apply to this copy
	 * @param filterChains
	 *            filterChains to apply during the copy.
	 * @param overwrite
	 *            Whether or not the destination file should be overwritten 
if
	 *            it already exists.
	 * @param preserveLastModified
	 *            Whether or not the last modified time of the resulting 
file
	 *            should be set to that of the source file.
	 * @param encoding
	 *            the encoding used to read and write the files.
	 * @param project
	 *            the project instance
	 * 
	 * @throws IOException
	 *             if the copying fails
	 * 
	 * @since Ant 1.5
	 */
	public void copyFile(String sourceFile, String destFile,
			FilterSetCollection filters, Vector filterChains,
			boolean overwrite, boolean preserveLastModified, String 
encoding,
			Project project) throws IOException {
		copyFile(new File(sourceFile), new File(destFile), filters,
				filterChains, overwrite, preserveLastModified, 
encoding,
				project);
	}

	/**
	 * Convienence method to copy a file from a source to a destination
	 * specifying if token filtering must be used, if filter chains must be
	 * used, if source files may overwrite newer destination files and the 
last
	 * modified time of <code>destFile</code> file should be made equal to 
the
	 * last modified time of <code>sourceFile</code>.
	 * 
	 * @param sourceFile
	 *            Name of file to copy from. Must not be <code>null</code>.
	 * @param destFile
	 *            Name of file to copy to. Must not be <code>null</code>.
	 * @param filters
	 *            the collection of filters to apply to this copy
	 * @param filterChains
	 *            filterChains to apply during the copy.
	 * @param overwrite
	 *            Whether or not the destination file should be overwritten 
if
	 *            it already exists.
	 * @param preserveLastModified
	 *            Whether or not the last modified time of the resulting 
file
	 *            should be set to that of the source file.
	 * @param inputEncoding
	 *            the encoding used to read the files.
	 * @param outputEncoding
	 *            the encoding used to write the files.
	 * @param project
	 *            the project instance
	 * 
	 * @throws IOException
	 *             if the copying fails
	 * 
	 * @since Ant 1.6
	 */
	public void copyFile(String sourceFile, String destFile,
			FilterSetCollection filters, Vector filterChains,
			boolean overwrite, boolean preserveLastModified,
			String inputEncoding, String outputEncoding, Project 
project)
			throws IOException {
		copyFile(new File(sourceFile), new File(destFile), filters,
				filterChains, overwrite, preserveLastModified, 
inputEncoding,
				outputEncoding, project);
	}

	/**
	 * Convienence method to copy a file from a source to a destination. No
	 * filtering is performed.
	 * 
	 * @param sourceFile
	 *            the file to copy from. Must not be <code>null</code>.
	 * @param destFile
	 *            the file to copy to. Must not be <code>null</code>.
	 * 
	 * @throws IOException
	 *             if the copying fails
	 */
	public void copyFile(File sourceFile, File destFile) throws IOException 
{
		copyFile(sourceFile, destFile, null, false, false);
	}

	/**
	 * Convienence method to copy a file from a source to a destination
	 * specifying if token filtering must be used.
	 * 
	 * @param sourceFile
	 *            the file to copy from. Must not be <code>null</code>.
	 * @param destFile
	 *            the file to copy to. Must not be <code>null</code>.
	 * @param filters
	 *            the collection of filters to apply to this copy
	 * 
	 * @throws IOException
	 *             if the copying fails
	 */
	public void copyFile(File sourceFile, File destFile,
			FilterSetCollection filters) throws IOException {
		copyFile(sourceFile, destFile, filters, false, false);
	}

	/**
	 * Convienence method to copy a file from a source to a destination
	 * specifying if token filtering must be used and if source files may
	 * overwrite newer destination files.
	 * 
	 * @param sourceFile
	 *            the file to copy from. Must not be <code>null</code>.
	 * @param destFile
	 *            the file to copy to. Must not be <code>null</code>.
	 * @param filters
	 *            the collection of filters to apply to this copy
	 * @param overwrite
	 *            Whether or not the destination file should be overwritten 
if
	 *            it already exists.
	 * 
	 * @throws IOException
	 *             if the copying fails
	 */
	public void copyFile(File sourceFile, File destFile,
			FilterSetCollection filters, boolean overwrite) throws 
IOException {
		copyFile(sourceFile, destFile, filters, overwrite, false);
	}

	/**
	 * Convienence method to copy a file from a source to a destination
	 * specifying if token filtering must be used, if source files may 
overwrite
	 * newer destination files and the last modified time of
	 * <code>destFile</code> file should be made equal to the last modified
	 * time of <code>sourceFile</code>.
	 * 
	 * @param sourceFile
	 *            the file to copy from. Must not be <code>null</code>.
	 * @param destFile
	 *            the file to copy to. Must not be <code>null</code>.
	 * @param filters
	 *            the collection of filters to apply to this copy
	 * @param overwrite
	 *            Whether or not the destination file should be overwritten 
if
	 *            it already exists.
	 * @param preserveLastModified
	 *            Whether or not the last modified time of the resulting 
file
	 *            should be set to that of the source file.
	 * 
	 * @throws IOException
	 *             if the copying fails
	 */
	public void copyFile(File sourceFile, File destFile,
			FilterSetCollection filters, boolean overwrite,
			boolean preserveLastModified) throws IOException {
		copyFile(sourceFile, destFile, filters, overwrite,
				preserveLastModified, null);
	}

	/**
	 * Convienence method to copy a file from a source to a destination
	 * specifying if token filtering must be used, if source files may 
overwrite
	 * newer destination files, the last modified time of 
<code>destFile</code>
	 * file should be made equal to the last modified time of
	 * <code>sourceFile</code> and which character encoding to assume.
	 * 
	 * @param sourceFile
	 *            the file to copy from. Must not be <code>null</code>.
	 * @param destFile
	 *            the file to copy to. Must not be <code>null</code>.
	 * @param filters
	 *            the collection of filters to apply to this copy
	 * @param overwrite
	 *            Whether or not the destination file should be overwritten 
if
	 *            it already exists.
	 * @param preserveLastModified
	 *            Whether or not the last modified time of the resulting 
file
	 *            should be set to that of the source file.
	 * @param encoding
	 *            the encoding used to read and write the files.
	 * 
	 * @throws IOException
	 *             if the copying fails
	 * 
	 * @since Ant 1.5
	 */
	public void copyFile(File sourceFile, File destFile,
			FilterSetCollection filters, boolean overwrite,
			boolean preserveLastModified, String encoding) throws 
IOException {
		copyFile(sourceFile, destFile, filters, null, overwrite,
				preserveLastModified, encoding, null);
	}

	/**
	 * Convienence method to copy a file from a source to a destination
	 * specifying if token filtering must be used, if filter chains must be
	 * used, if source files may overwrite newer destination files and the 
last
	 * modified time of <code>destFile</code> file should be made equal to 
the
	 * last modified time of <code>sourceFile</code>.
	 * 
	 * @param sourceFile
	 *            the file to copy from. Must not be <code>null</code>.
	 * @param destFile
	 *            the file to copy to. Must not be <code>null</code>.
	 * @param filters
	 *            the collection of filters to apply to this copy
	 * @param filterChains
	 *            filterChains to apply during the copy.
	 * @param overwrite
	 *            Whether or not the destination file should be overwritten 
if
	 *            it already exists.
	 * @param preserveLastModified
	 *            Whether or not the last modified time of the resulting 
file
	 *            should be set to that of the source file.
	 * @param encoding
	 *            the encoding used to read and write the files.
	 * @param project
	 *            the project instance
	 * 
	 * @throws IOException
	 *             if the copying fails
	 * 
	 * @since Ant 1.5
	 */
	public void copyFile(File sourceFile, File destFile,
			FilterSetCollection filters, Vector filterChains,
			boolean overwrite, boolean preserveLastModified, String 
encoding,
			Project project) throws IOException {
		copyFile(sourceFile, destFile, filters, filterChains, overwrite,
				preserveLastModified, encoding, encoding, 
project);
	}

	/**
	 * Convienence method to copy a file from a source to a destination
	 * specifying if token filtering must be used, if filter chains must be
	 * used, if source files may overwrite newer destination files and the 
last
	 * modified time of <code>destFile</code> file should be made equal to 
the
	 * last modified time of <code>sourceFile</code>.
	 * 
	 * @param sourceFile
	 *            the file to copy from. Must not be <code>null</code>.
	 * @param destFile
	 *            the file to copy to. Must not be <code>null</code>.
	 * @param filters
	 *            the collection of filters to apply to this copy
	 * @param filterChains
	 *            filterChains to apply during the copy.
	 * @param overwrite
	 *            Whether or not the destination file should be overwritten 
if
	 *            it already exists.
	 * @param preserveLastModified
	 *            Whether or not the last modified time of the resulting 
file
	 *            should be set to that of the source file.
	 * @param inputEncoding
	 *            the encoding used to read the files.
	 * @param outputEncoding
	 *            the encoding used to write the files.
	 * @param project
	 *            the project instance
	 * 
	 * 
	 * @throws IOException
	 *             if the copying fails
	 * 
	 * @since Ant 1.6
	 */
	public void copyFile(File sourceFile, File destFile,
			FilterSetCollection filters, Vector filterChains,
			boolean overwrite, boolean preserveLastModified,
			String inputEncoding, String outputEncoding, Project 
project)
			throws IOException {

		if (overwrite || !destFile.exists()
				|| destFile.lastModified() < 
sourceFile.lastModified()) {

			if (destFile.exists() && destFile.isFile()) {
				destFile.delete();
			}

			// ensure that parent dir of dest file exists!
			// not using getParentFile method to stay 1.1 compat
			File parent = getParentFile(destFile);
			if (parent != null && !parent.exists()) {
				parent.mkdirs();
			}

			final boolean filterSetsAvailable = (filters != null && 
filters
					.hasFilters());
			final boolean filterChainsAvailable = (filterChains != 
null && filterChains
					.size() > 0);

			if (filterSetsAvailable) {
				BufferedReader in = null;
				BufferedWriter out = null;

				try {
					if (inputEncoding == null) {
						in = new BufferedReader(new 
FileReader(sourceFile));
					} else {
						InputStreamReader isr = new 
InputStreamReader(
								new 
FileInputStream(sourceFile), inputEncoding);
						in = new BufferedReader(isr);
					}

					if (outputEncoding == null) {
						out = new BufferedWriter(new 
FileWriter(destFile));
					} else {
						OutputStreamWriter osw = new 
OutputStreamWriter(
								new 
FileOutputStream(destFile), outputEncoding);
						out = new BufferedWriter(osw);
					}

					if (filterChainsAvailable) {
						ChainReaderHelper crh = new 
ChainReaderHelper();
						crh.setBufferSize(8192);
						crh.setPrimaryReader(in);
						crh.setFilterChains
(filterChains);
						crh.setProject(project);
						Reader rdr = 
crh.getAssembledReader();
						in = new BufferedReader(rdr);
					}

					LineTokenizer lineTokenizer = new 
LineTokenizer();
					lineTokenizer.setIncludeDelims(true);
					String newline = null;
					String line = lineTokenizer.getToken
(in);
					while (line != null) {
						if (line.length() == 0) {
							// this should not 
happen, because the lines are
							// returned with the 
end of line delimiter
							out.newLine();
						} else {
							newline = 
filters.replaceTokens(line);
							out.write(newline);
						}
						line = lineTokenizer.getToken
(in);
					}
				} finally {
					if (out != null) {
						out.close();
					}
					if (in != null) {
						in.close();
					}
				}
			} else if (filterChainsAvailable
					|| (inputEncoding != null && !
inputEncoding
							.equals(outputEncoding))
					|| (inputEncoding == null && 
outputEncoding != null)) {
				BufferedReader in = null;
				BufferedWriter out = null;

				try {
					if (inputEncoding == null) {
						in = new BufferedReader(new 
FileReader(sourceFile));
					} else {
						in = new BufferedReader(new 
InputStreamReader(
								new 
FileInputStream(sourceFile), inputEncoding));
					}

					if (outputEncoding == null) {
						out = new BufferedWriter(new 
FileWriter(destFile));
					} else {
						out = new BufferedWriter(new 
OutputStreamWriter(
								new 
FileOutputStream(destFile), outputEncoding));
					}

					if (filterChainsAvailable) {
						ChainReaderHelper crh = new 
ChainReaderHelper();
						crh.setBufferSize(8192);
						crh.setPrimaryReader(in);
						crh.setFilterChains
(filterChains);
						crh.setProject(project);
						Reader rdr = 
crh.getAssembledReader();
						in = new BufferedReader(rdr);
					}
					char[] buffer = new char[1024 * 8];
					while (true) {
						int nRead = in.read(buffer, 0, 
buffer.length);
						if (nRead == -1) {
							break;
						}
						out.write(buffer, 0, nRead);
					}
				} finally {
					if (out != null) {
						out.close();
					}
					if (in != null) {
						in.close();
					}
				}
			} else {
				FileInputStream in = null;
				FileOutputStream out = null;
				try {
					in = new FileInputStream(sourceFile);
					out = new FileOutputStream(destFile);

					byte[] buffer = new byte[8 * 1024];
					int count = 0;
					do {
						out.write(buffer, 0, count);
						count = in.read(buffer, 0, 
buffer.length);
					} while (count != -1);
				} finally {
					if (out != null) {
						out.close();
					}
					if (in != null) {
						in.close();
					}
				}
			}

			if (preserveLastModified) {
				setFileLastModified(destFile, 
sourceFile.lastModified());
			}
		}
	}

	/**
	 * see whether we have a setLastModified method in File and return it.
	 * 
	 * @return a method to setLastModified.
	 */
	protected final Method getSetLastModified() {
		if (JavaEnvUtils.isJavaVersion(JavaEnvUtils.JAVA_1_1)) {
			return null;
		}
		synchronized (lockReflection) {
			if (setLastModified == null) {
				try {
					setLastModified = 
java.io.File.class.getMethod(
							"setLastModified", new 
Class[] { Long.TYPE });
				} catch (NoSuchMethodException nse) {
					throw new BuildException(
							"File.setlastModified 
not in JDK > 1.1?", nse);
				}
			}
		}
		return setLastModified;
	}

	/**
	 * Calls File.setLastModified(long time) in a Java 1.1 compatible way.
	 * 
	 * @param file
	 *            the file whose modified time is to be set
	 * @param time
	 *            the time to which the last modified time is to be set.
	 * 
	 * @throws BuildException
	 *             if the time cannot be set.
	 */
	public void setFileLastModified(File file, long time) throws 
BuildException {
		if (JavaEnvUtils.isJavaVersion(JavaEnvUtils.JAVA_1_1)) {
			return;
		}
		Long[] times = new Long[1];
		if (time < 0) {
			times[0] = new Long(System.currentTimeMillis());
		} else {
			times[0] = new Long(time);
		}

		try {
			getSetLastModified().invoke(file, times);
		} catch (java.lang.reflect.InvocationTargetException ite) {
			Throwable nested = ite.getTargetException();
			throw new BuildException("Exception setting the 
modification time "
					+ "of " + file, nested);
		} catch (Throwable other) {
			throw new BuildException("Exception setting the 
modification time "
					+ "of " + file, other);
		}
	}

	/**
	 * Interpret the filename as a file relative to the given file - unless 
the
	 * filename already represents an absolute filename.
	 * 
	 * @param file
	 *            the "reference" file for relative paths. This instance 
must be
	 *            an absolute file and must not contain &quot;./&quot; or
	 *            &quot;../&quot; sequences (same for \ instead of /). If 
it is
	 *            null, this call is equivalent to
	 *            <code>new java.io.File(filename)</code>.
	 * 
	 * @param filename
	 *            a file name
	 * 
	 * @return an absolute file that doesn't contain &quot;./&quot; or
	 *         &quot;../&quot; sequences and uses the correct separator for 
the
	 *         current platform.
	 */
	public File resolveFile(File file, String filename) {
		filename = filename.replace('/', File.separatorChar).replace
('\\',
				File.separatorChar);

		// deal with absolute files
		if (!onNetWare) {
			if (filename.startsWith(File.separator)
					|| (filename.length() >= 2
							&& Character.isLetter
(filename.charAt(0)) && filename
							.charAt(1) == ':')) {
				return normalize(filename);
			}
		} else {
			// the assumption that the : will appear as the second 
character in
			// the path name breaks down when NetWare is a 
supported platform.
			// Netware volumes are of the pattern: "data:\"
			int colon = filename.indexOf(":");
			if (filename.startsWith(File.separator) || (colon > -
1)) {
				return normalize(filename);
			}
		}

		if (file == null) {
			return new File(filename);
		}

		File helpFile = new File(file.getAbsolutePath());
		StringTokenizer tok = new StringTokenizer(filename, 
File.separator);
		while (tok.hasMoreTokens()) {
			String part = tok.nextToken();
			if (part.equals("..")) {
				helpFile = getParentFile(helpFile);
				if (helpFile == null) {
					String msg = "The file or path you 
specified (" + filename
							+ ") is invalid 
relative to " + file.getPath();
					throw new BuildException(msg);
				}
			} else if (part.equals(".")) {
				// Do nothing here
			} else {
				helpFile = new File(helpFile, part);
			}
		}

		return new File(helpFile.getAbsolutePath());
	}

	/**
	 * &quot;normalize&quot; the given absolute path.
	 * 
	 * <p>
	 * This includes:
	 * <ul>
	 * <li>Uppercase the drive letter if there is one.</li>
	 * <li>Remove redundant slashes after the drive spec.</li>
	 * <li>resolve all ./, .\, ../ and ..\ sequences.</li>
	 * <li>DOS style paths that start with a drive letter will have \ as the
	 * separator.</li>
	 * </ul>
	 * Unlike <code>File#getCanonicalPath()</code> it specifically doesn't
	 * resolve symbolic links.
	 * 
	 * @param path
	 *            the path to be normalized
	 * @return the normalized version of the path.
	 * 
	 * @throws java.lang.NullPointerException
	 *             if the file path is equal to null.
	 */
	public File normalize(String path) {
		String orig = path;

		path = path.replace('/', File.separatorChar).replace('\\',
				File.separatorChar);

		// make sure we are dealing with an absolute path
		int colon = path.indexOf(":");

		if (!onNetWare) {
			if (!path.startsWith(File.separator)
					&& !(path.length() >= 2
							&& Character.isLetter
(path.charAt(0)) && colon == 1)) {
				String msg = path + " is not an absolute path";
				throw new BuildException(msg);
			}
		} else {
			if (!path.startsWith(File.separator) && (colon == -1)) {
				String msg = path + " is not an absolute path";
				throw new BuildException(msg);
			}
		}

		boolean dosWithDrive = false;
		String root = null;
		// Eliminate consecutive slashes after the drive spec
		if ((!onNetWare && path.length() >= 2
				&& Character.isLetter(path.charAt(0)) && 
path.charAt(1) == ':')
				|| (onNetWare && colon > -1)) {

			dosWithDrive = true;

			char[] ca = path.replace('/', '\\').toCharArray();
			StringBuffer sbRoot = new StringBuffer();
			for (int i = 0; i < colon; i++) {
				sbRoot.append(Character.toUpperCase(ca[i]));
			}
			sbRoot.append(':');
			if (colon + 1 < path.length()) {
				sbRoot.append(File.separatorChar);
			}
			root = sbRoot.toString();

			// Eliminate consecutive slashes after the drive spec
			StringBuffer sbPath = new StringBuffer();
			for (int i = colon + 1; i < ca.length; i++) {
				if ((ca[i] != '\\') || (ca[i] == '\\' && ca[i - 
1] != '\\')) {
					sbPath.append(ca[i]);
				}
			}
			path = sbPath.toString().replace('\\', 
File.separatorChar);

		} else {
			if (path.length() == 1) {
				root = File.separator;
				path = "";
			} else if (path.charAt(1) == File.separatorChar) {
				// UNC drive
				root = File.separator + File.separator;
				path = path.substring(2);
			} else {
				root = File.separator;
				path = path.substring(1);
			}
		}

		Stack s = new Stack();
		s.push(root);
		StringTokenizer tok = new StringTokenizer(path, File.separator);
		while (tok.hasMoreTokens()) {
			String thisToken = tok.nextToken();
			if (".".equals(thisToken)) {
				continue;
			} else if ("..".equals(thisToken)) {
				if (s.size() < 2) {
					throw new BuildException("Cannot 
resolve path " + orig);
				} else {
					s.pop();
				}
			} else { // plain component
				s.push(thisToken);
			}
		}

		StringBuffer sb = new StringBuffer();
		for (int i = 0; i < s.size(); i++) {
			if (i > 1) {
				// not before the filesystem root and not after 
it, since root
				// already contains one
				sb.append(File.separatorChar);
			}
			sb.append(s.elementAt(i));
		}

		path = sb.toString();
		if (dosWithDrive) {
			path = path.replace('/', '\\');
		}
		return new File(path);
	}

	/**
	 * Returns a VMS String representation of a <code>File</code> object. 
This
	 * is useful since the JVM by default internally converts VMS paths to 
Unix
	 * style. The returned String is always an absolute path.
	 * 
	 * @param f
	 *            The <code>File</code> to get the VMS path for.
	 * @return The absolute VMS path to <code>f</code>.
	 */
	public String toVMSPath(File f) {
		// format: "DEVICE:[DIR.SUBDIR]FILE"
		String osPath;
		String path = normalize(f.getAbsolutePath()).getPath();
		String name = f.getName();
		boolean isAbsolute = path.charAt(0) == File.separatorChar;
		// treat directories specified using .DIR syntax as files
		boolean isDirectory = f.isDirectory()
				&& !name.regionMatches(true, name.length() - 
4, ".DIR", 0, 4);

		String device = null;
		StringBuffer directory = null;
		String file = null;

		int index = 0;

		if (isAbsolute) {
			index = path.indexOf(File.separatorChar, 1);
			if (index == -1) {
				return path.substring(1) + ":[000000]";
			} else {
				device = path.substring(1, index++);
			}
		}
		if (isDirectory) {
			directory = new StringBuffer(path.substring
(index).replace(
					File.separatorChar, '.'));
		} else {
			int dirEnd = path.lastIndexOf(File.separatorChar, 
path.length());
			if (dirEnd == -1 || dirEnd < index) {
				file = path.substring(index);
			} else {
				directory = new StringBuffer(path.substring
(index, dirEnd)
						.replace
(File.separatorChar, '.'));
				index = dirEnd + 1;
				if (path.length() > index) {
					file = path.substring(index);
				}
			}
		}
		if (!isAbsolute && directory != null) {
			directory.insert(0, '.');
		}
		osPath = ((device != null) ? device + ":" : "")
				+ ((directory != null) ? "[" + directory 
+ "]" : "")
				+ ((file != null) ? file : "");
		return osPath;
	}

	/**
	 * Create a temporary file in a given directory.
	 * 
	 * <p>
	 * The file denoted by the returned abstract pathname did not exist 
before
	 * this method was invoked, any subsequent invocation of this method 
will
	 * yield a different file name.
	 * </p>
	 * 
	 * <p>
	 * This method is different to File.createTempFile of JDK 1.2 as it 
doesn't
	 * create the file itself. It uses the location pointed to by 
java.io.tmpdir
	 * when the parentDir attribute is null.
	 * </p>
	 * 
	 * @param parentDir
	 *            Directory to create the temporary file in - current 
working
	 *            directory will be assumed if this parameter is null.
	 * 
	 * @return a File reference to the new temporary file.
	 * @since ant 1.5
	 */
	public File createTempFile(String prefix, String suffix, File 
parentDir) {

		File result = null;
		String parent = System.getProperty("java.io.tmpdir");
		if (parentDir != null) {
			parent = parentDir.getPath();
		}
		DecimalFormat fmt = new DecimalFormat("#####");
		synchronized (rand) {
			do {
				result = new File(parent, prefix
						+ fmt.format(Math.abs
(rand.nextInt())) + suffix);
			} while (result.exists());
		}
		return result;
	}

	/**
	 * Compares the contents of two files.
	 * 
	 * <p>
	 * simple but sub-optimal comparision algorithm. written for working 
rather
	 * than fast. Better would be a block read into buffers followed by long
	 * comparisions apart from the final 1-7 bytes.
	 * </p>
	 * 
	 * @param f1
	 *            the file whose content is to be compared.
	 * @param f2
	 *            the other file whose content is to be compared.
	 * 
	 * @return true if the content of the files is the same.
	 * 
	 * @throws IOException
	 *             if the files cannot be read.
	 * 
	 * @since 1.9
	 */
	public boolean contentEquals(File f1, File f2) throws IOException {
		if (f1.exists() != f2.exists()) {
			return false;
		}

		if (!f1.exists()) {
			// two not existing files are equal
			return true;
		}

		if (f1.isDirectory() || f2.isDirectory()) {
			// don't want to compare directory contents for now
			return false;
		}

		if (fileNameEquals(f1, f2)) {
			// same filename => true
			return true;
		}

		if (f1.length() != f2.length()) {
			// different size =>false
			return false;
		}

		InputStream in1 = null;
		InputStream in2 = null;
		try {
			in1 = new BufferedInputStream(new FileInputStream(f1));
			in2 = new BufferedInputStream(new FileInputStream(f2));

			int expectedByte = in1.read();
			while (expectedByte != -1) {
				if (expectedByte != in2.read()) {
					return false;
				}
				expectedByte = in1.read();
			}
			if (in2.read() != -1) {
				return false;
			}
			return true;
		} finally {
			if (in1 != null) {
				try {
					in1.close();
				} catch (IOException e) {
					// ignore
				}
			}
			if (in2 != null) {
				try {
					in2.close();
				} catch (IOException e) {
					// ignore
				}
			}
		}
	}

	/**
	 * Emulation of File.getParentFile for JDK 1.1
	 * 
	 * 
	 * @param f
	 *            the file whose parent is required.
	 * @return the given file's parent, or null if the file does not have a
	 *         parent.
	 * @since 1.10
	 */
	public File getParentFile(File f) {
		if (f != null) {
			String p = f.getParent();
			if (p != null) {
				return new File(p);
			}
		}
		return null;
	}

	/**
	 * Read from reader till EOF
	 * 
	 * @param rdr
	 *            the reader from which to read.
	 * @return the contents read out of the given reader
	 * 
	 * @throws IOException
	 *             if the contents could not be read out from the reader.
	 */
	public static final String readFully(Reader rdr) throws IOException {
		return readFully(rdr, 8192);
	}

	/**
	 * Read from reader till EOF
	 * 
	 * @param rdr
	 *            the reader from which to read.
	 * @param bufferSize
	 *            the buffer size to use when reading
	 * 
	 * @return the contents read out of the given reader
	 * 
	 * @throws IOException
	 *             if the contents could not be read out from the reader.
	 */
	public static final String readFully(Reader rdr, int bufferSize)
			throws IOException {
		if (bufferSize <= 0) {
			throw new IllegalArgumentException("Buffer size must be 
greater "
					+ "than 0");
		}
		final char[] buffer = new char[bufferSize];
		int bufferLength = 0;
		String text = null;
		StringBuffer textBuffer = null;
		while (bufferLength != -1) {
			bufferLength = rdr.read(buffer);
			if (bufferLength != -1) {
				if (textBuffer == null) {
					textBuffer = new StringBuffer(new String
(buffer, 0,
							bufferLength));
				} else {
					textBuffer.append(new String(buffer, 0, 
bufferLength));
				}
			}
		}
		if (textBuffer != null) {
			text = textBuffer.toString();
		}
		return text;
	}

	/**
	 * Emulation of File.createNewFile for JDK 1.1.
	 * 
	 * <p>
	 * This method does <strong>not </strong> guarantee that the operation 
is
	 * atomic.
	 * </p>
	 * 
	 * @param f
	 *            the file to be created
	 * @return true if the file did not exist already.
	 * @since Ant 1.5
	 */
	public boolean createNewFile(File f) throws IOException {
		if (f != null) {
			if (f.exists()) {
				return false;
			}

			FileOutputStream fos = null;
			try {
				fos = new FileOutputStream(f);
				fos.write(new byte[0]);
			} finally {
				if (fos != null) {
					fos.close();
				}
			}

			return true;
		}
		return false;
	}

	/**
	 * Checks whether a given file is a symbolic link.
	 * 
	 * <p>
	 * It doesn't really test for symbolic links but whether the canonical 
and
	 * absolute paths of the file are identical - this may lead to false
	 * positives on some platforms.
	 * </p>
	 * 
	 * @param parent
	 *            the parent directory of the file to test
	 * @param name
	 *            the name of the file to test.
	 * 
	 * @return true if the file is a symbolic link.
	 * @since Ant 1.5
	 */
	public boolean isSymbolicLink(File parent, String name) throws 
IOException {
		File resolvedParent = new File(parent.getCanonicalPath());
		File toTest = new File(resolvedParent, name);
		return !toTest.getAbsolutePath().equals(toTest.getCanonicalPath
());
	}

	/**
	 * Removes a leading path from a second path.
	 * 
	 * @param leading
	 *            The leading path, must not be null, must be absolute.
	 * @param path
	 *            The path to remove from, must not be null, must be 
absolute.
	 * 
	 * @return path's normalized absolute if it doesn't start with leading,
	 *         path's path with leading's path removed otherwise.
	 * 
	 * @since Ant 1.5
	 */
	public String removeLeadingPath(File leading, File path) {
		String l = normalize(leading.getAbsolutePath()).getAbsolutePath
();
		String p = normalize(path.getAbsolutePath()).getAbsolutePath();
		if (l.equals(p)) {
			return "";
		}

		// ensure that l ends with a /
		// so we never think /foo was a parent directory of /foobar
		if (!l.endsWith(File.separator)) {
			l += File.separator;
		}

		if (p.startsWith(l)) {
			return p.substring(l.length());
		} else {
			return p;
		}
	}

	/**
	 * Constructs a <code>file:</code> URI that represents the external form
	 * of the given pathname.
	 * 
	 * <p>
	 * Will be an absolute URI if the given path is absolute.
	 * </p>
	 * 
	 * <p>
	 * This code doesn't handle non-ASCII characters properly.
	 * </p>
	 * 
	 * @param path
	 *            the path in the local file system
	 * @return the URI version of the local path.
	 * @since Ant 1.6
	 */
	public String toURI(String path) {
		boolean isDir = (new File(path)).isDirectory();

		StringBuffer sb = new StringBuffer("file:");

		// catch exception if normalize thinks this is not an absolute 
path
		try {
			path = normalize(path).getAbsolutePath();
			sb.append("//");
			// add an extra slash for filesystems with drive-
specifiers
			if (!path.startsWith(File.separator)) {
				sb.append("/");
			}

		} catch (BuildException e) {
			// relative path
		}

		path = path.replace('\\', '/');

		CharacterIterator iter = new StringCharacterIterator(path);
		for (char c = iter.first(); c != CharacterIterator.DONE; c = 
iter
				.next()) {
			if (c < 256 && isSpecial[c]) {
				sb.append('%');
				sb.append(escapedChar1[c]);
				sb.append(escapedChar2[c]);
			} else {
				sb.append(c);
			}
		}
		if (isDir && !path.endsWith("/")) {
			sb.append('/');
		}
		return sb.toString();
	}

	/**
	 * Constructs a file path from a <code>file:</code> URI.
	 * 
	 * <p>
	 * Will be an absolute path if the given URI is absolute.
	 * </p>
	 * 
	 * <p>
	 * Swallows '%' that are not followed by two characters, doesn't deal 
with
	 * non-ASCII characters.
	 * </p>
	 * 
	 * @param uri
	 *            the URI designating a file in the local filesystem.
	 * @return the local file system path for the file.
	 * @since Ant 1.6
	 */
	public String fromURI(String uri) {
		String path = Locator.fromURI(uri);

		// catch exception if normalize thinks this is not an absolute 
path
		try {
			path = normalize(path).getAbsolutePath();
		} catch (BuildException e) {
			// relative path
		}
		return path;
	}

	/**
	 * Compares two filenames.
	 * 
	 * <p>
	 * Unlike java.io.File#equals this method will try to compare the 
absolute
	 * paths and &quot;normalize&quot; the filenames before comparing them.
	 * </p>
	 * 
	 * @param f1
	 *            the file whose name is to be compared.
	 * @param f2
	 *            the other file whose name is to be compared.
	 * 
	 * @return true if the file are for the same file.
	 * 
	 * @since Ant 1.5.3
	 */
	public boolean fileNameEquals(File f1, File f2) {
		return normalize(f1.getAbsolutePath()).equals(
				normalize(f2.getAbsolutePath()));
	}

	/**
	 * Renames a file, even if that involves crossing file system 
boundaries.
	 * 
	 * <p>
	 * This will remove <code>to</code> (if it exists), ensure that
	 * <code>to</code>'s parent directory exists and move <code>from</code>,
	 * which involves deleting <code>from</code> as well.
	 * </p>
	 * 
	 * @throws IOException
	 *             if anything bad happens during this process. Note that
	 *             <code>to</code> may have been deleted already when this
	 *             happens.
	 * 
	 * @param from
	 *            the file to move
	 * @param to
	 *            the new file name
	 * 
	 * @since Ant 1.6
	 */
	public void rename(File from, File to) throws IOException {
		if (to.exists() && !to.delete()) {
			throw new IOException("Failed to delete " + to
					+ " while trying to rename " + from);
		}

		File parent = getParentFile(to);
		if (parent != null && !parent.exists() && !parent.mkdirs()) {
			throw new IOException("Failed to create directory " + 
parent
					+ " while trying to rename " + from);
		}

		if (!from.renameTo(to)) {
			copyFile(from, to);
			if (!from.delete()) {
				throw new IOException("Failed to delete " + from
						+ " while trying to rename 
it.");
			}
		}
	}

	public long getFileTimestampGranularity() {
		if (Os.isFamily("dos")) {
			return FAT_FILE_TIMESTAMP_GRANULARITY;
		} else {
			return 0;
		}
	}
}

/*
 * Created on Jul 12, 2004
 *
 * TODO To change the template for this generated file go to
 * Window - Preferences - Java - Code Style - Code Templates
 */
package org.apache.tools.ant.util;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.nio.channels.FileChannel;
import java.util.Vector;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.filters.util.ChainReaderHelper;
import org.apache.tools.ant.types.FilterSetCollection;

/**
 * @author jbleijen
 * 
 * TODO To change the template for this generated type comment go to Window -
 * Preferences - Java - Code Style - Code Templates
 */
public class NioFileUtils extends FileUtils {

	/**
	 * Empty constructor.
	 */
	protected NioFileUtils() {
		super();
	}

	/**
	 * Convienence method to copy a file from a source to a destination
	 * specifying if token filtering must be used, if filter chains must be
	 * used, if source files may overwrite newer destination files and the 
last
	 * modified time of <code>destFile</code> file should be made equal to 
the
	 * last modified time of <code>sourceFile</code>.
	 * 
	 * @param sourceFile
	 *            the file to copy from. Must not be <code>null</code>.
	 * @param destFile
	 *            the file to copy to. Must not be <code>null</code>.
	 * @param filters
	 *            the collection of filters to apply to this copy
	 * @param filterChains
	 *            filterChains to apply during the copy.
	 * @param overwrite
	 *            Whether or not the destination file should be overwritten 
if
	 *            it already exists.
	 * @param preserveLastModified
	 *            Whether or not the last modified time of the resulting 
file
	 *            should be set to that of the source file.
	 * @param inputEncoding
	 *            the encoding used to read the files.
	 * @param outputEncoding
	 *            the encoding used to write the files.
	 * @param project
	 *            the project instance
	 * 
	 * 
	 * @throws IOException
	 *             if the copying fails
	 * 
	 * @since Ant 1.6
	 */
	public void copyFile(File sourceFile, File destFile,
			FilterSetCollection filters, Vector filterChains,
			boolean overwrite, boolean preserveLastModified,
			String inputEncoding, String outputEncoding, Project 
project)
			throws IOException {
		if (overwrite || !destFile.exists()
				|| destFile.lastModified() < 
sourceFile.lastModified()) {

			//if (destFile.exists() && destFile.isFile()) {
			//	destFile.delete();
			//}

			// ensure that parent dir of dest file exists!
			// not using getParentFile method to stay 1.1 compat
			File parent = getParentFile(destFile);
			if (parent != null && !parent.exists()) {
				parent.mkdirs();
			}

			final boolean filterSetsAvailable = (filters != null && 
filters
					.hasFilters());
			final boolean filterChainsAvailable = (filterChains != 
null && filterChains
					.size() > 0);

			if (filterSetsAvailable) {
				BufferedReader in = null;
				BufferedWriter out = null;

				try {
					if (inputEncoding == null) {
						in = new BufferedReader(new 
FileReader(sourceFile));
					} else {
						InputStreamReader isr = new 
InputStreamReader(
								new 
FileInputStream(sourceFile), inputEncoding);
						in = new BufferedReader(isr);
					}

					if (outputEncoding == null) {
						out = new BufferedWriter(new 
FileWriter(destFile));
					} else {
						OutputStreamWriter osw = new 
OutputStreamWriter(
								new 
FileOutputStream(destFile), outputEncoding);
						out = new BufferedWriter(osw);
					}

					if (filterChainsAvailable) {
						ChainReaderHelper crh = new 
ChainReaderHelper();
						crh.setBufferSize(8192);
						crh.setPrimaryReader(in);
						crh.setFilterChains
(filterChains);
						crh.setProject(project);
						Reader rdr = 
crh.getAssembledReader();
						in = new BufferedReader(rdr);
					}

					LineTokenizer lineTokenizer = new 
LineTokenizer();
					lineTokenizer.setIncludeDelims(true);
					String newline = null;
					String line = lineTokenizer.getToken
(in);
					while (line != null) {
						if (line.length() == 0) {
							// this should not 
happen, because the lines are
							// returned with the 
end of line delimiter
							out.newLine();
						} else {
							newline = 
filters.replaceTokens(line);
							out.write(newline);
						}
						line = lineTokenizer.getToken
(in);
					}
				} finally {
					if (out != null) {
						out.close();
					}
					if (in != null) {
						in.close();
					}
				}
			} else if (filterChainsAvailable
					|| (inputEncoding != null && !
inputEncoding
							.equals(outputEncoding))
					|| (inputEncoding == null && 
outputEncoding != null)) {
				BufferedReader in = null;
				BufferedWriter out = null;

				try {
					if (inputEncoding == null) {
						in = new BufferedReader(new 
FileReader(sourceFile));
					} else {
						in = new BufferedReader(new 
InputStreamReader(
								new 
FileInputStream(sourceFile), inputEncoding));
					}

					if (outputEncoding == null) {
						out = new BufferedWriter(new 
FileWriter(destFile));
					} else {
						out = new BufferedWriter(new 
OutputStreamWriter(
								new 
FileOutputStream(destFile), outputEncoding));
					}

					if (filterChainsAvailable) {
						ChainReaderHelper crh = new 
ChainReaderHelper();
						crh.setBufferSize(8192);
						crh.setPrimaryReader(in);
						crh.setFilterChains
(filterChains);
						crh.setProject(project);
						Reader rdr = 
crh.getAssembledReader();
						in = new BufferedReader(rdr);
					}
					char[] buffer = new char[1024 * 8];
					while (true) {
						int nRead = in.read(buffer, 0, 
buffer.length);
						if (nRead == -1) {
							break;
						}
						out.write(buffer, 0, nRead);
					}
				} finally {
					if (out != null) {
						out.close();
					}
					if (in != null) {
						in.close();
					}
				}
			} else {
				FileInputStream in = null;
				FileOutputStream out = null;
				FileChannel srcChannel=null;
				FileChannel destChannel=null;

				try {
					// we can use direct copy with nio
					in = new FileInputStream(sourceFile);
					out = new FileOutputStream(destFile);
						
					srcChannel=in.getChannel();
					destChannel=out.getChannel();
					
					long 
bytesThatShouldBeCopied=srcChannel.size();
					long bytesCopied=srcChannel.transferTo
(0, bytesThatShouldBeCopied, destChannel);
					if (bytesThatShouldBeCopied!
=bytesCopied)
					{
						throw new IOException("Error 
copying file from '"+sourceFile.getAbsolutePath()+"' 
to '"+destFile.getAbsolutePath()+"' copied only:"+bytesCopied+" bytes instead 
of "+bytesThatShouldBeCopied);
					}
					
				} finally {
					if (srcChannel != null) {
						srcChannel.close();
					}
					if (destChannel != null) {
						destChannel.close();
					}
					if (out != null) {
						out.close();
					}
					if (in != null) {
						in.close();
					}
				}
			}

			if (preserveLastModified) {
				setFileLastModified(destFile, 
sourceFile.lastModified());
			}
		}
	}
}

/*
 * Copyright  2003-2004 The Apache Software Foundation
 *
 *  Licensed 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.util;

import org.apache.tools.ant.Project;
import org.apache.tools.ant.ProjectComponent;
import org.apache.tools.ant.types.Resource;
import org.apache.tools.ant.types.ResourceFactory;
import org.apache.tools.ant.types.selectors.SelectorUtils;

import java.io.File;
import java.util.Vector;

/**
 * this class provides utility methods to process resources
 *
 * @since Ant 1.5.2
 */
public class ResourceUtils {

    /**
     * tells which source files should be reprocessed based on the
     * last modification date of target files
     * @param logTo where to send (more or less) interesting output
     * @param source array of resources bearing relative path and last
     * modification date
     * @param mapper filename mapper indicating how to find the target
     * files
     * @param targets object able to map as a resource a relative path
     * at <b>destination</b>
     * @return array containing the source files which need to be
     * copied or processed, because the targets are out of date or do
     * not exist
     */
    public static Resource[] selectOutOfDateSources(ProjectComponent logTo,
                                                    Resource[] source,
                                                    FileNameMapper mapper,
                                                    ResourceFactory targets) {
        return selectOutOfDateSources(logTo, source, mapper, targets,
                                      FileUtils.newFileUtils()
                                      .getFileTimestampGranularity());
    }

    /**
     * tells which source files should be reprocessed based on the
     * last modification date of target files
     * @param logTo where to send (more or less) interesting output
     * @param source array of resources bearing relative path and last
     * modification date
     * @param mapper filename mapper indicating how to find the target
     * files
     * @param targets object able to map as a resource a relative path
     * at <b>destination</b>
     * @param granularity The number of milliseconds leeway to give
     * before deciding a target is out of date.
     * @return array containing the source files which need to be
     * copied or processed, because the targets are out of date or do
     * not exist
     * @since Ant 1.6.2
     */
    public static Resource[] selectOutOfDateSources(ProjectComponent logTo,
                                                    Resource[] source,
                                                    FileNameMapper mapper,
                                                    ResourceFactory targets,
                                                    long granularity) {
        long now = (new java.util.Date()).getTime() + granularity;

        Vector vresult = new Vector();
        for (int counter = 0; counter < source.length; counter++) {
//            if (source[counter].getLastModified() > now) {
//                logTo.log("Warning: " + source[counter].getName()
//                         + " modified in the future.",
//                         Project.MSG_WARN);
//            }

            String[] targetnames =
                mapper.mapFileName(source[counter].getName()
                                   .replace('/', File.separatorChar));
            if (targetnames != null) {
                boolean added = false;
                StringBuffer targetList = new StringBuffer();
                for (int ctarget = 0; !added && ctarget < targetnames.length;
                     ctarget++) {
                    Resource atarget =
                        targets.getResource(targetnames[ctarget]
                                            .replace(File.separatorChar, '/'));
                    // if the target does not exist, or exists and
                    // is older than the source, then we want to
                    // add the resource to what needs to be copied
                    if (!atarget.isExists()) {
                        logTo.log(source[counter].getName() + " added as "
                                  + atarget.getName()
                                  + " doesn\'t exist.", Project.MSG_VERBOSE);
                        vresult.addElement(source[counter]);
                        added = true;
                    } else if (!atarget.isDirectory() &&
                               SelectorUtils.isOutOfDate(source[counter],
                                                         atarget,
                                                         (int) granularity)) {
                        logTo.log(source[counter].getName() + " added as "
                                  + atarget.getName()
                                  + " is outdated.", Project.MSG_VERBOSE);
                        vresult.addElement(source[counter]);
                        added = true;
                    } else {
                        if (targetList.length() > 0) {
                            targetList.append(", ");
                        }
                        targetList.append(atarget.getName());
                    }
                }

                if (!added) {
                    logTo.log(source[counter].getName()
                              + " omitted as " + targetList.toString()
                              + (targetnames.length == 1 ? " is" : " are ")
                              + " up to date.", Project.MSG_VERBOSE);
                }
            } else {
                logTo.log(source[counter].getName()
                          + " skipped - don\'t know how to handle it",
                          Project.MSG_VERBOSE);
            }
        }
        Resource[] result = new Resource[vresult.size()];
        vresult.copyInto(result);
        return result;
    }
}

/*
 * Copyright  2000-2004 The Apache Software Foundation
 *
 *  Licensed 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.util;

import java.io.File;
import java.util.Vector;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.types.ResourceFactory;
import org.apache.tools.ant.types.Resource;
import org.apache.tools.ant.types.ResourceFile;

/**
 * Utility class that collects the functionality of the various
 * scanDir methods that have been scattered in several tasks before.
 *
 * <p>The only method returns an array of source files. The array is a
 * subset of the files given as a parameter and holds only those that
 * are newer than their corresponding target files.</p>
 *
 */
public class SourceFileScanner implements ResourceFactory {

    protected Task task;

    private FileUtils fileUtils;
    private File destDir;     // base directory of the fileset

    /**
     * @param task The task we should log messages through
     */
    public SourceFileScanner(Task task) {
        this.task = task;
        fileUtils = FileUtils.newFileUtils();
    }

    /**
     * Restrict the given set of files to those that are newer than
     * their corresponding target files.
     *
     * @param files   the original set of files
     * @param srcDir  all files are relative to this directory
     * @param destDir target files live here. if null file names
     *                returned by the mapper are assumed to be absolute.
     * @param mapper  knows how to construct a target file names from
     *                source file names.
     */
    public String[] restrict(String[] files, File srcDir, File destDir,
                             FileNameMapper mapper) {
        return restrict(files, srcDir, destDir, mapper,
                        fileUtils.getFileTimestampGranularity());
    }

    /**
     * Restrict the given set of files to those that are newer than
     * their corresponding target files.
     *
     * @param files   the original set of files
     * @param srcDir  all files are relative to this directory
     * @param destDir target files live here. if null file names
     *                returned by the mapper are assumed to be absolute.
     * @param mapper  knows how to construct a target file names from
     *                source file names.
     * @param granularity The number of milliseconds leeway to give
     *                    before deciding a target is out of date.
     *
     * @since Ant 1.6.2
     */
    public String[] restrict(String[] files, File srcDir, File destDir,
                             FileNameMapper mapper, long granularity) {
        // record destdir for later use in getResource
        this.destDir = destDir;
        Vector v = new Vector();
        for (int i = 0; i < files.length; i++) {
            File src = fileUtils.resolveFile(srcDir, files[i]);
            ResourceFile rf=new ResourceFile(files[i],src);
            rf.setExists(true);
            v.addElement(rf);
        }
        Resource[] sourceresources = new Resource[v.size()];
        v.copyInto(sourceresources);

        // build the list of sources which are out of date with
        // respect to the target
        Resource[] outofdate =
            ResourceUtils.selectOutOfDateSources(task, sourceresources,
                                                 mapper, this, granularity);
        String[] result = new String[outofdate.length];
        for (int counter = 0; counter < outofdate.length; counter++) {
            result[counter] = outofdate[counter].getName();
        }
        return result;
    }

    /**
     * Convinience layer on top of restrict that returns the source
     * files as File objects (containing absolute paths if srcDir is
     * absolute).
     */
    public File[] restrictAsFiles(String[] files, File srcDir, File destDir,
                                  FileNameMapper mapper) {
        return restrictAsFiles(files, srcDir, destDir, mapper,
                               fileUtils.getFileTimestampGranularity());
    }

    /**
     * Convinience layer on top of restrict that returns the source
     * files as File objects (containing absolute paths if srcDir is
     * absolute).
     *
     * @since Ant 1.6.2
     */
    public File[] restrictAsFiles(String[] files, File srcDir, File destDir,
                                  FileNameMapper mapper, long granularity) {
        String[] res = restrict(files, srcDir, destDir, mapper, granularity);
        File[] result = new File[res.length];
        for (int i = 0; i < res.length; i++) {
            result[i] = new File(srcDir, res[i]);
        }
        return result;
    }

    /**
     * returns resource information for a file at destination
     * @param name relative path of file at destination
     * @return data concerning a file whose relative path to destDir is name
     *
     * @since Ant 1.5.2
     */
    public Resource getResource(String name) {
        File src = fileUtils.resolveFile(destDir, name);
        return new ResourceFile(name,src);
    }
}
Comment 1 J Bleijenbergh 2004-07-14 07:17:13 UTC
Created attachment 12110 [details]
Sources and a jar containing the patch
Comment 2 Matt Benson 2004-07-16 15:43:03 UTC
The preferred form for a patch is a unified diff attachment.  This way we can 
quickly evaluate the changes suggested.

Thanks
Comment 3 Robin Verduijn 2006-01-11 18:52:33 UTC
Created attachment 17385 [details]
[faulty] java.nio changes against Ant 1.6.5, in unified diff format

I've used the original modified source files attached to this bug and create a
patch against 1.6.5 from them.
Comment 4 Robin Verduijn 2006-01-11 18:53:10 UTC
Created attachment 17386 [details]
lazy init changes against Ant 1.6.5, in unified diff format

I've used the original modified source files attached to this bug and create a
patch against 1.6.5 from them.
Comment 5 Matt Benson 2006-01-11 19:21:00 UTC
The second patch is completely irrelevant to Ant HEAD... one might argue the
second patch could be structured into Ant HEAD in more valuable ways as well.
Comment 6 Robin Verduijn 2006-01-11 19:24:03 UTC
Comment on attachment 17385 [details]
[faulty] java.nio changes against Ant 1.6.5, in unified diff format

Patch fails on copying large files; need to change transfer size in
transferTo() call to a lower value.
Comment 7 Robin Verduijn 2006-01-11 19:27:33 UTC
Created attachment 17388 [details]
[faulty] java.nio changes against Ant 1.6.5, in unified diff format

I've used the original modified source files attached to this bug and created a

patch against 1.6.5 from them.
Comment 8 Robin Verduijn 2006-01-11 19:29:20 UTC
Can't say I disagree with Matt Benson there; but I just wanted to make sure that
the original submitter's changes were both available in patch format rather than
modified Java code. In all fairness, the lazy init calls patch should not be in
this bug but in a feature request of its own.
Comment 9 Robin Verduijn 2006-01-11 19:38:23 UTC
Created attachment 17389 [details]
java.nio changes against Ant 1.6.5, in unified diff format

I've used the original modified source files attached to this bug and created a
patch against 1.6.5 from them.

One last time, to fix transferTo() buffer size.
Apologies for the attachment spam.
Comment 10 Matt Benson 2006-01-11 19:47:40 UTC
(In reply to comment #5)
> The second patch is completely irrelevant to Ant HEAD... one might argue the
> second patch could be structured into Ant HEAD in more valuable ways as well.

correction: the second occurrence of "second" should be "first".
Comment 11 Kev Jackson 2006-01-12 01:53:47 UTC
java.nio are only available as part of Java 1.4+, to accept this into the trunk
would mean a dependency on 1.4+ compiler to build ant - is this ok?  My
understanding was that we should try to preserve bwc within ant (as a tool) and
for building ant itself - is this no longer a concern?
Comment 12 Jesse Glick 2006-01-12 07:35:34 UTC
(In reply to comment #11)
> java.nio are only available as part of Java 1.4+, to accept this into the trunk
> would mean a dependency on 1.4+ compiler to build ant [...]

Could probably be solved by adding to build.xml#needs.jdk1.4+ something like

<filename name="${util.package}/NioFileUtils.java"/>

meaning if compiled on JDK 1.3 the optimization would be unavailable.
Comment 13 Steve Loughran 2006-01-12 11:36:00 UTC
It could be a compile-time-optional part of the runtime, something that is only
delegated to when the runtime is 1.4+. Which means that we'd need an
interface/facade to the operations and then an original+nio implementation, the
default behaviour being to use nio on 1.4+, though with a switch to override
that so that the original code can still be tested.

This is a lot of effort; the speedup would have to be tangible. Are the any
measurements, both for local and remote filesystems?

There is some justification for doing such a facade, and it isnt just possible
speedup -the facade could also offer access to the file permissions stuff coming
in Java6.0, letting ant tasks work with permissions properly on the 6.0
platform, as and when it comes out. 

-steve
Comment 14 Kev Jackson 2006-04-20 07:54:14 UTC
(In reply to comment #13)
> It could be a compile-time-optional part of the runtime, something that is only
> delegated to when the runtime is 1.4+. Which means that we'd need an
> interface/facade to the operations and then an original+nio implementation, 

(pushed here from Antoine on the dev list ...)

I've started looking into this and I can see that we need an interface of the
current FileUtils class (minus static methods) and then a factory to select the
implementation, and 2+ implementations, original/classic (ie what we have right
now), NioFileUtils (such as this code here in this bug report) and
Java6FileUtils (for all the file permission goodies).

I'm not sure how to resolve the problem that *a lot* of the current code
presumes a lot about FileUtils (ie that certain methods are static etc).  Since
we have to maintain bwc, this will probably have to mean that the interface
contains definitions of all instance methods, and the original FileUtils will
have to keep the static methods (as they are public) and any new implementation
will have to delegate to the original for these methods.

Is this a sensible strategy?
Kev
Comment 15 Alexey Solofnenko 2006-04-20 15:32:52 UTC
Can static methods just execute non-static methods on util's singleton?
Comment 16 J.M. (Martijn) Kruithof 2006-04-20 18:30:44 UTC
(In reply to comment #15)
> Can static methods just execute non-static methods on util's singleton?

Definetely,

We could even use the singleton  / constructor as factory and turn the FileUtils
into a proxy with embedded factory.
Comment 17 Peter Reilly 2006-09-08 17:12:00 UTC
I do not think that we need to make a version
of FileUtils for java 1.3, 1.4, 5, 6, 7 etc. It is far too
big for that. The
only difference that I can see in the patch
is part of the (overlong) copyfile method - the raw
file copy without filtering.
This part could be extracted from FileUtils.copyFile and
it could use a runtime found implemention of the most efficient
RawFileCopy class for this particular JVM.

Comment 18 Antoine Levy-Lambert 2006-09-08 18:37:27 UTC
Agreed Peter.
Comment 19 Robin Verduijn 2008-01-09 11:54:23 UTC
Created attachment 21368 [details]
[faulty] java.nio changes against Ant 1.7.0, in unified diff format

I have modified the original NIO changes and applied them against Ant 1.7.0.
These changes work with the new Resources framework.
Comment 20 Robin Verduijn 2008-01-09 12:06:59 UTC
Created attachment 21369 [details]
java.nio changes against Ant 1.7.0, in unified diff format
Comment 21 Stefan Bodewig 2009-08-25 01:50:49 UTC
since Ant 1.8.0 requires Java 1.4 the patch can go in more or less unchanged.

I've kept the buffer size at 8k like we use for the loop-operation - doesn anybody know whether 64k (in the original patch) would be better/worse for specific operating systems or VMs?

svn revision 807523