Bug 30094 - Performance enhancements using java.nio & lazy calls
Performance enhancements using java.nio & lazy calls
 Status: RESOLVED FIXED None Ant Unclassified Core (show other bugs) 1.6.2 All other P3 enhancement (vote) 1.8.0 Ant Notifications List PatchAvailable 49430 Show dependency tree

 Reported: 2004-07-14 07:14 UTC by J Bleijenbergh 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.
 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. *

* 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. *

* 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. *

* 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. *

* 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 * File.separator ('/' 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. *

* 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. *

* There is a special case regarding the use of File.separators * at the beginning of the pattern and the string to match:
* When a pattern starts with a File.separator, the string * to match must also start with a File.separator. * When a pattern does not start with a File.separator, the * string to match may not start with a File.separator. * When one of these rules is not obeyed, the string will not * match. *

* When a name path segment is matched against a pattern path segment, the * following special characters can be used:
* '*' matches zero or more characters
* '?' matches one character. *

* Examples: *

* "**\*.class" matches all .class files/dirs in a directory tree. *

* "test\a??.java" matches all files/dirs which start with an 'a', then two * more characters and then ".java", in a directory called test. *

* "**" matches everything in a directory tree. *

* "**\test\**\XYZ*" matches all files/dirs which start with "XYZ" and where * there is a parent directory called test (e.g. "abc\test\def\ghi\XYZ123"). *

* Case sensitivity may be turned off if necessary. By default, it is * turned on. *

* Example of usage: *

*   String[] includes = {"**\\*.class"};  *   String[] excludes = {"modules\\*\\**"};  *   ds.setIncludes(includes);  *   ds.setExcludes(excludes);  *   ds.setBasedir(new File("test"));  *   ds.setCaseSensitive(true);  *   ds.scan();  *  *   System.out.println("FILES:");  *   String[] files = ds.getIncludedFiles();  *   for (int i = 0; i < files.length; i++) {  *     System.out.println(files[i]);  *   }  *
* This will scan a directory called test for .class files, but excludes all * 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. * *

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.

* * @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 "**". *

* This is not a general purpose test and should only be used if you * can live with false positives. For example, pattern=**\a * and str=b will yield true. * * @param pattern The pattern to match against. Must not be * null. * @param str The path to match, as a String. Must not be * null. * * @return whether or not a given path matches the start of a given * pattern up to the first "**". */ 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 "**". *

* This is not a general purpose test and should only be used if you * can live with false positives. For example, pattern=**\a * and str=b will yield true. * * @param pattern The pattern to match against. Must not be * null. * @param str The path to match, as a String. Must not be * null. * @param isCaseSensitive Whether or not matching should be performed * case sensitively. * * @return whether or not a given path matches the start of a given * pattern up to the first "**". */ 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 * null. * @param str The path to match, as a String. Must not be * null. * * @return true if the pattern matches against the string, * or false otherwise. */ 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 * null. * @param str The path to match, as a String. Must not be * null. * @param isCaseSensitive Whether or not matching should be performed * case sensitively. * * @return true if the pattern matches against the string, * or false otherwise. */ 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:
* '*' means zero or more characters
* '?' means one and only one character * * @param pattern The pattern to match against. * Must not be null. * @param str The string which must be matched against the pattern. * Must not be null. * * @return true if the string matches against the pattern, * or false otherwise. */ public static boolean match(String pattern, String str) { return SelectorUtils.match(pattern, str); } /** * Tests whether or not a string matches against a pattern. * The pattern may contain two special characters:
* '*' means zero or more characters

* When a pattern ends with a '/' or '\', "**" is appended. * * @param includes A list of include patterns. * May be null, indicating that all files * should be included. If a non-null * list is given, all elements must be * non-null. */ 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 File.separatorChar, so the separator used * need not match File.separatorChar. *

* When a pattern ends with a '/' or '\', "**" is appended. * * @param excludes A list of exclude patterns. * May be null, indicating that no files * should be excluded. If a non-null list is * given, all elements must be non-null. */ 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 true 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 null, 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. *

Returns the names of the files which were selected out and * therefore not ultimately included.

* *

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

Returns the names of the directories which were selected out and * therefore not ultimately included.

* *

The names are relative to the base directory. This involves * performing a slow scan if one has not already been completed.

Registers the given directory as scanned as a side effect.

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.

* *

"/" will be used as the directory separator.

*/ public String getName() { return name; } /** * @param name relative path of the resource. Expects * "/" 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. * *

* This is a dummy, used for not existing resources. *

* * @param name * relative path of the resource. Expects "/" 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. * *

This is a dummy, used for not existing resources.

* * @param name relative path of the resource. Expects * "/" 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 * "/" 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 * "/" 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 true if the path should be included * false otherwise. */ public boolean match(String path) { String vpath = path.replace('/', File.separatorChar). replace('\\', File.separatorChar); return isIncluded(vpath) && !isExcluded(vpath); } /** * @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 null. * @param destFile * Name of file to copy to. Must not be null. * * @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 null. * @param destFile * Name of file to copy to. Must not be null. * @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 null. * @param destFile * Name of file to copy to. Must not be null. * @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 * destFile file should be made equal to the last modified * time of sourceFile. * * @param sourceFile * Name of file to copy from. Must not be null. * @param destFile * Name of file to copy to. Must not be null. * @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 * destFile file should be made equal to the last modified * time of sourceFile. * * @param sourceFile * Name of file to copy from. Must not be null. * @param destFile * Name of file to copy to. Must not be null. * @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 destFile file should be made equal to the * last modified time of sourceFile. * * @param sourceFile * Name of file to copy from. Must not be null. * @param destFile * Name of file to copy to. Must not be null. * @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 destFile file should be made equal to the * last modified time of sourceFile. * * @param sourceFile * Name of file to copy from. Must not be null. * @param destFile * Name of file to copy to. Must not be null. * @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 null. * @param destFile * the file to copy to. Must not be null. * * @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 null. * @param destFile * the file to copy to. Must not be null. * @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 null. * @param destFile * the file to copy to. Must not be null. * @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 * destFile file should be made equal to the last modified * time of sourceFile. * * @param sourceFile * the file to copy from. Must not be null. * @param destFile * the file to copy to. Must not be null. * @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 destFile * file should be made equal to the last modified time of * sourceFile and which character encoding to assume. * * @param sourceFile * the file to copy from. Must not be null. * @param destFile * the file to copy to. Must not be null. * @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 destFile file should be made equal to the * last modified time of sourceFile. * * @param sourceFile * the file to copy from. Must not be null. * @param destFile * the file to copy to. Must not be null. * @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 destFile file should be made equal to the * last modified time of sourceFile. * * @param sourceFile * the file to copy from. Must not be null. * @param destFile * the file to copy to. Must not be null. * @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 "./" or * "../" sequences (same for \ instead of /). If it is * null, this call is equivalent to * new java.io.File(filename). * * @param filename * a file name * * @return an absolute file that doesn't contain "./" or * "../" 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()); } /** * "normalize" the given absolute path. * *

* This includes: *

*
• Uppercase the drive letter if there is one.
• *
• Remove redundant slashes after the drive spec.
• *
• resolve all ./, .\, ../ and ..\ sequences.
• *
• DOS style paths that start with a drive letter will have \ as the * separator.
• *
* Unlike File#getCanonicalPath() 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 File 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 File to get the VMS path for. * @return The absolute VMS path to f. */ 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. * *

* 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. *

* *

* 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. *

* * @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. * *

* 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. *

* This method does not guarantee that the operation is * atomic. *

* * @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. * *

* 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. *

* Will be an absolute URI if the given path is absolute. *

* *

* This code doesn't handle non-ASCII characters properly. *

* * @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 file: URI. * *

* Will be an absolute path if the given URI is absolute. *

* *

* Swallows '%' that are not followed by two characters, doesn't deal with * non-ASCII characters. *

* * @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. * *

* Unlike java.io.File#equals this method will try to compare the absolute * paths and "normalize" the filenames before comparing them. *

* * @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. * *

* This will remove to (if it exists), ensure that * to's parent directory exists and move from, * which involves deleting from as well. *

* */ 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); } } J Bleijenbergh 2004-07-14 07:17:13 UTC Created attachment 12110 [details] Sources and a jar containing the patch 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 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. 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. 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. 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. 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. 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. 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. 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".  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? 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 meaning if compiled on JDK 1.3 the optimization would be unavailable. 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 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 Alexey Solofnenko 2006-04-20 15:32:52 UTC Can static methods just execute non-static methods on util's singleton? 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. 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.  Antoine Levy-Lambert 2006-09-08 18:37:27 UTC Agreed Peter. 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. 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 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`