diff options
Diffstat (limited to 'framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/util/FileUtils.java')
-rw-r--r-- | framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/util/FileUtils.java | 1722 |
1 files changed, 1722 insertions, 0 deletions
diff --git a/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/util/FileUtils.java b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/util/FileUtils.java new file mode 100644 index 00000000..bcef5ecf --- /dev/null +++ b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/util/FileUtils.java @@ -0,0 +1,1722 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.tools.ant.util; + +import java.io.File; +import java.io.FilenameFilter; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.Reader; +import java.io.Writer; +import java.net.HttpURLConnection; +import java.net.JarURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLConnection; +import java.nio.channels.Channel; +import java.text.DecimalFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.Random; +import java.util.Stack; +import java.util.StringTokenizer; +import java.util.Vector; +import java.util.jar.JarFile; + +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.PathTokenizer; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.launch.Locator; +import org.apache.tools.ant.taskdefs.condition.Os; +import org.apache.tools.ant.types.FilterSetCollection; +import org.apache.tools.ant.types.resources.FileResource; + +/** + * This class also encapsulates methods which allow Files to be + * referred to using abstract path names which are translated to native + * system file paths at runtime as well as copying files or setting + * their last modification time. + * + */ +public class FileUtils { + private static final int DELETE_RETRY_SLEEP_MILLIS = 10; + private static final int EXPAND_SPACE = 50; + private static final FileUtils PRIMARY_INSTANCE = new FileUtils(); + + //get some non-crypto-grade randomness from various places. + private static Random rand = new Random(System.currentTimeMillis() + + Runtime.getRuntime().freeMemory()); + + private static final boolean ON_NETWARE = Os.isFamily("netware"); + private static final boolean ON_DOS = Os.isFamily("dos"); + private static final boolean ON_WIN9X = Os.isFamily("win9x"); + private static final boolean ON_WINDOWS = Os.isFamily("windows"); + + static final int BUF_SIZE = 8192; + + + /** + * The granularity of timestamps under FAT. + */ + public static final long FAT_FILE_TIMESTAMP_GRANULARITY = 2000; + + /** + * The granularity of timestamps under Unix. + */ + public static final long UNIX_FILE_TIMESTAMP_GRANULARITY = 1000; + + /** + * The granularity of timestamps under the NT File System. + * NTFS has a granularity of 100 nanoseconds, which is less + * than 1 millisecond, so we round this up to 1 millisecond. + */ + public static final long NTFS_FILE_TIMESTAMP_GRANULARITY = 1; + + /** + * A one item cache for fromUri. + * fromUri is called for each element when parseing ant build + * files. It is a costly operation. This just caches the result + * of the last call. + */ + private Object cacheFromUriLock = new Object(); + private String cacheFromUriRequest = null; + private String cacheFromUriResponse = null; + + /** + * Factory method. + * + * @return a new instance of FileUtils. + * @deprecated since 1.7. + * Use getFileUtils instead, + * FileUtils do not have state. + */ + public static FileUtils newFileUtils() { + return new FileUtils(); + } + + /** + * Method to retrieve The FileUtils, which is shared by all users of this + * method. + * @return an instance of FileUtils. + * @since Ant 1.6.3 + */ + public static FileUtils getFileUtils() { + return PRIMARY_INSTANCE; + } + + /** + * 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(file.toURI().toASCIIString()); + } + + /** + * Convenience 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); + } + + /** + * Convenience 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); + } + + /** + * Convenience 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); + } + + /** + * Convenience 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); + } + + /** + * Convenience 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); + } + + // CheckStyle:ParameterNumberCheck OFF - bc + /** + * Convenience 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); + } + + /** + * Convenience 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); + } + + /** + * Convenience 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); + } + + /** + * Convenience 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); + } + + /** + * Convenience 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); + } + + /** + * Convenience 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); + } + + /** + * Convenience 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); + } + + /** + * Convenience 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); + } + + /** + * Convenience 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 { + copyFile(sourceFile, destFile, filters, filterChains, overwrite, preserveLastModified, + false, inputEncoding, outputEncoding, project); + } + + /** + * Convenience 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 append whether to append to the destination 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.8 + */ + public void copyFile(File sourceFile, File destFile, + FilterSetCollection filters, Vector filterChains, + boolean overwrite, boolean preserveLastModified, + boolean append, + String inputEncoding, String outputEncoding, + Project project) throws IOException { + copyFile(sourceFile, destFile, filters, filterChains, overwrite, + preserveLastModified, append, inputEncoding, outputEncoding, + project, /* force: */ false); + } + + /** + * Convenience 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 append whether to append to the destination file. + * @param inputEncoding the encoding used to read the files. + * @param outputEncoding the encoding used to write the files. + * @param project the project instance. + * @param force whether to overwrite read-only destination files. + * + * @throws IOException if the copying fails. + * + * @since Ant 1.8.2 + */ + public void copyFile(File sourceFile, File destFile, + FilterSetCollection filters, Vector filterChains, + boolean overwrite, boolean preserveLastModified, + boolean append, + String inputEncoding, String outputEncoding, + Project project, boolean force) throws IOException { + ResourceUtils.copyResource(new FileResource(sourceFile), + new FileResource(destFile), + filters, filterChains, overwrite, + preserveLastModified, append, inputEncoding, + outputEncoding, project, force); + } + + // CheckStyle:ParameterNumberCheck ON + + /** + * Calls File.setLastModified(long time). Originally written to + * to dynamically bind to that call on Java1.2+. + * + * @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. + * if this is -1, the current time is used. + */ + public void setFileLastModified(File file, long time) { + ResourceUtils.setLastModified(new FileResource(file), time); + } + + /** + * Interpret the filename as a file relative to the given file + * unless the filename already represents an absolute filename. + * Differs from <code>new File(file, filename)</code> in that + * the resulting File's path will always be a normalized, + * absolute pathname. Also, if it is determined that + * <code>filename</code> is context-relative, <code>file</code> + * will be discarded and the reference will be resolved using + * available context/state information about the filesystem. + * + * @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 + * <code>new java.io.File(filename).getAbsoluteFile()</code>. + * + * @param filename a file name. + * + * @return an absolute file. + * @throws java.lang.NullPointerException if filename is null. + */ + public File resolveFile(File file, String filename) { + if (!isAbsolutePath(filename)) { + char sep = File.separatorChar; + filename = filename.replace('/', sep).replace('\\', sep); + if (isContextRelativePath(filename)) { + file = null; + // on cygwin, our current directory can be a UNC; + // assume user.dir is absolute or all hell breaks loose... + String udir = System.getProperty("user.dir"); + if (filename.charAt(0) == sep && udir.charAt(0) == sep) { + filename = dissect(udir)[0] + filename.substring(1); + } + } + filename = new File(file, filename).getAbsolutePath(); + } + return normalize(filename); + } + + /** + * On DOS and NetWare, the evaluation of certain file + * specifications is context-dependent. These are filenames + * beginning with a single separator (relative to current root directory) + * and filenames with a drive specification and no intervening separator + * (relative to current directory of the specified root). + * @param filename the filename to evaluate. + * @return true if the filename is relative to system context. + * @throws java.lang.NullPointerException if filename is null. + * @since Ant 1.7 + */ + public static boolean isContextRelativePath(String filename) { + if (!(ON_DOS || ON_NETWARE) || filename.length() == 0) { + return false; + } + char sep = File.separatorChar; + filename = filename.replace('/', sep).replace('\\', sep); + char c = filename.charAt(0); + int len = filename.length(); + return (c == sep && (len == 1 || filename.charAt(1) != sep)) + || (Character.isLetter(c) && len > 1 + && filename.charAt(1) == ':' + && (len == 2 || filename.charAt(2) != sep)); + } + + /** + * Verifies that the specified filename represents an absolute path. + * Differs from new java.io.File("filename").isAbsolute() in that a path + * beginning with a double file separator--signifying a Windows UNC--must + * at minimum match "\\a\b" to be considered an absolute path. + * @param filename the filename to be checked. + * @return true if the filename represents an absolute path. + * @throws java.lang.NullPointerException if filename is null. + * @since Ant 1.6.3 + */ + public static boolean isAbsolutePath(String filename) { + int len = filename.length(); + if (len == 0) { + return false; + } + char sep = File.separatorChar; + filename = filename.replace('/', sep).replace('\\', sep); + char c = filename.charAt(0); + if (!(ON_DOS || ON_NETWARE)) { + return (c == sep); + } + if (c == sep) { + // CheckStyle:MagicNumber OFF + if (!(ON_DOS && len > 4 && filename.charAt(1) == sep)) { + return false; + } + // CheckStyle:MagicNumber ON + int nextsep = filename.indexOf(sep, 2); + return nextsep > 2 && nextsep + 1 < len; + } + int colon = filename.indexOf(':'); + return (Character.isLetter(c) && colon == 1 + && filename.length() > 2 && filename.charAt(2) == sep) + || (ON_NETWARE && colon > 0); + } + + /** + * Translate a path into its native (platform specific) format. + * <p> + * This method uses PathTokenizer to separate the input path + * into its components. This handles DOS style paths in a relatively + * sensible way. The file separators are then converted to their platform + * specific versions. + * + * @param toProcess The path to be translated. + * May be <code>null</code>. + * + * @return the native version of the specified path or + * an empty string if the path is <code>null</code> or empty. + * + * @since ant 1.7 + * @see PathTokenizer + */ + public static String translatePath(String toProcess) { + if (toProcess == null || toProcess.length() == 0) { + return ""; + } + StringBuffer path = new StringBuffer(toProcess.length() + EXPAND_SPACE); + PathTokenizer tokenizer = new PathTokenizer(toProcess); + while (tokenizer.hasMoreTokens()) { + String pathComponent = tokenizer.nextToken(); + pathComponent = pathComponent.replace('/', File.separatorChar); + pathComponent = pathComponent.replace('\\', File.separatorChar); + if (path.length() != 0) { + path.append(File.pathSeparatorChar); + } + path.append(pathComponent); + } + return path.toString(); + } + + /** + * "Normalize" 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 {@link File#getCanonicalPath()} this method + * specifically does not resolve symbolic links. + * + * @param path the path to be normalized. + * @return the normalized version of the path. + * + * @throws java.lang.NullPointerException if path is null. + */ + public File normalize(final String path) { + Stack s = new Stack(); + String[] dissect = dissect(path); + s.push(dissect[0]); + + StringTokenizer tok = new StringTokenizer(dissect[1], File.separator); + while (tok.hasMoreTokens()) { + String thisToken = tok.nextToken(); + if (".".equals(thisToken)) { + continue; + } + if ("..".equals(thisToken)) { + if (s.size() < 2) { + // Cannot resolve it, so skip it. + return new File(path); + } + s.pop(); + } else { // plain component + s.push(thisToken); + } + } + StringBuffer sb = new StringBuffer(); + final int size = s.size(); + for (int i = 0; i < 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)); + } + return new File(sb.toString()); + } + + /** + * Dissect the specified absolute path. + * @param path the path to dissect. + * @return String[] {root, remaining path}. + * @throws java.lang.NullPointerException if path is null. + * @since Ant 1.7 + */ + public String[] dissect(String path) { + char sep = File.separatorChar; + path = path.replace('/', sep).replace('\\', sep); + + // make sure we are dealing with an absolute path + if (!isAbsolutePath(path)) { + throw new BuildException(path + " is not an absolute path"); + } + String root = null; + int colon = path.indexOf(':'); + if (colon > 0 && (ON_DOS || ON_NETWARE)) { + + int next = colon + 1; + root = path.substring(0, next); + char[] ca = path.toCharArray(); + root += sep; + //remove the initial separator; the root has it. + next = (ca[next] == sep) ? next + 1 : next; + + StringBuffer sbPath = new StringBuffer(); + // Eliminate consecutive slashes after the drive spec: + for (int i = next; i < ca.length; i++) { + if (ca[i] != sep || ca[i - 1] != sep) { + sbPath.append(ca[i]); + } + } + path = sbPath.toString(); + } else if (path.length() > 1 && path.charAt(1) == sep) { + // UNC drive + int nextsep = path.indexOf(sep, 2); + nextsep = path.indexOf(sep, nextsep + 1); + root = (nextsep > 2) ? path.substring(0, nextsep + 1) : path; + path = path.substring(root.length()); + } else { + root = File.separator; + path = path.substring(1); + } + return new String[] {root, 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 + // CheckStyle:MagicNumber OFF + boolean isDirectory = f.isDirectory() + && !name.regionMatches(true, name.length() - 4, ".DIR", 0, 4); + // CheckStyle:MagicNumber ON + 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]"; + } + 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 File object for a temporary file in a given directory. Without + * actually creating the file. + * + * <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> + * The filename is prefixNNNNNsuffix where NNNN is a random number. + * </p> + * + * @param prefix + * prefix before the random number. + * @param suffix + * file extension; include the '.'. + * @param parentDir + * Directory to create the temporary file in; java.io.tmpdir used + * if not specified. + * + * @deprecated since ant 1.7.1 use createTempFile(String, String, File, + * boolean, boolean) instead. + * @return a File reference to the new, nonexistent temporary file. + */ + public File createTempFile(String prefix, String suffix, File parentDir) { + return createTempFile(prefix, suffix, parentDir, false, false); + } + + private static final String NULL_PLACEHOLDER = "null"; + + /** + * 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> + * + * @param prefix prefix before the random number. + * @param suffix file extension; include the '.'. + * @param parentDir Directory to create the temporary file in; + * java.io.tmpdir used if not specified. + * @param deleteOnExit whether to set the tempfile for deletion on + * normal VM exit. + * @param createFile true if the file must actually be created. If false + * chances exist that a file with the same name is created in the time + * between invoking this method and the moment the file is actually created. + * If possible set to true. + * + * @return a File reference to the new temporary file. + * @since Ant 1.7.1 + */ + public File createTempFile(String prefix, String suffix, File parentDir, + boolean deleteOnExit, boolean createFile) { + File result = null; + String parent = (parentDir == null) + ? System.getProperty("java.io.tmpdir") + : parentDir.getPath(); + if (prefix == null) { + prefix = NULL_PLACEHOLDER; + } + if (suffix == null) { + suffix = NULL_PLACEHOLDER; + } + + if (createFile) { + try { + result = File.createTempFile(prefix, suffix, new File(parent)); + } catch (IOException e) { + throw new BuildException("Could not create tempfile in " + + parent, e); + } + } else { + DecimalFormat fmt = new DecimalFormat("#####"); + synchronized (rand) { + do { + result = new File(parent, prefix + + fmt.format(rand.nextInt(Integer.MAX_VALUE)) + suffix); + } while (result.exists()); + } + } + + if (deleteOnExit) { + result.deleteOnExit(); + } + return result; + } + + /** + * Create a File object for a temporary file in a given directory. Without + * actually creating the file. + * + * <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> + * The filename is prefixNNNNNsuffix where NNNN is a random number. + * </p> + * + * @param prefix + * prefix before the random number. + * @param suffix + * file extension; include the '.'. + * @param parentDir + * Directory to create the temporary file in; java.io.tmpdir used + * if not specified. + * @param deleteOnExit + * whether to set the tempfile for deletion on normal VM exit. + * + * @deprecated since ant 1.7.1 use createTempFile(String, String, File, + * boolean, boolean) instead. + * @return a File reference to the new, nonexistent temporary file. + */ + public File createTempFile(String prefix, String suffix, + File parentDir, boolean deleteOnExit) { + return createTempFile(prefix, suffix, parentDir, deleteOnExit, false); + } + + /** + * Compares the contents of two files. + * + * @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. + */ + public boolean contentEquals(File f1, File f2) throws IOException { + return contentEquals(f1, f2, false); + } + + /** + * Compares the contents of two files. + * + * @param f1 the file whose content is to be compared. + * @param f2 the other file whose content is to be compared. + * @param textfile true if the file is to be treated as a text file and + * differences in kind of line break are to be ignored. + * + * @return true if the content of the files is the same. + * + * @throws IOException if the files cannot be read. + * @since Ant 1.6.3 + */ + public boolean contentEquals(File f1, File f2, boolean textfile) throws IOException { + return ResourceUtils.contentEquals(new FileResource(f1), new FileResource(f2), textfile); + } + + /** + * This was originally an emulation of {@link File#getParentFile} for JDK 1.1, but it is now + * implemented using that method (Ant 1.6.3 onwards). + * + * @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 + * @deprecated since 1.7. Just use {@link File#getParentFile} directly. + */ + public File getParentFile(File f) { + return (f == null) ? null : f.getParentFile(); + } + + /** + * 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 String readFully(Reader rdr) throws IOException { + return readFully(rdr, BUF_SIZE); + } + + /** + * 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 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; + StringBuffer textBuffer = null; + while (bufferLength != -1) { + bufferLength = rdr.read(buffer); + if (bufferLength > 0) { + textBuffer = (textBuffer == null) ? new StringBuffer() : textBuffer; + textBuffer.append(new String(buffer, 0, bufferLength)); + } + } + return (textBuffer == null) ? null : textBuffer.toString(); + } + + /** + * Safe read fully - do not return a null for an empty reader. + * @param reader the input to read from. + * @return the string. + * @throws IOException if unable to read from reader. + * @since Ant 1.7.1 + */ + public static String safeReadFully(Reader reader) throws IOException { + String ret = readFully(reader); + return ret == null ? "" : ret; + } + + /** + * This was originally an emulation of File.createNewFile for JDK 1.1, + * but it is now implemented using that method (Ant 1.6.3 onwards). + * + * <p>This method has historically <strong>not</strong> guaranteed that the + * operation was atomic. In its current implementation it is. + * + * @param f the file to be created. + * @return true if the file did not exist already. + * @throws IOException on error. + * @since Ant 1.5 + */ + public boolean createNewFile(File f) throws IOException { + return f.createNewFile(); + } + + /** + * Create a new file, optionally creating parent directories. + * + * @param f the file to be created. + * @param mkdirs <code>boolean</code> whether to create parent directories. + * @return true if the file did not exist already. + * @throws IOException on error. + * @since Ant 1.6.3 + */ + public boolean createNewFile(File f, boolean mkdirs) throws IOException { + File parent = f.getParentFile(); + if (mkdirs && !(parent.exists())) { + parent.mkdirs(); + } + return f.createNewFile(); + } + + /** + * 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. + * @throws IOException on error. + * @since Ant 1.5 + * @deprecated use SymbolicLinkUtils instead + */ + public boolean isSymbolicLink(File parent, String name) + throws IOException { + SymbolicLinkUtils u = SymbolicLinkUtils.getSymbolicLinkUtils(); + if (parent == null) { + return u.isSymbolicLink(name); + } + return u.isSymbolicLink(parent, name); + } + + /** + * 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; + } + return (p.startsWith(l)) ? p.substring(l.length()) : p; + } + + /** + * Learn whether one path "leads" another. + * @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 true if path starts with leading; false otherwise. + * @since Ant 1.7 + */ + public boolean isLeadingPath(File leading, File path) { + String l = normalize(leading.getAbsolutePath()).getAbsolutePath(); + String p = normalize(path.getAbsolutePath()).getAbsolutePath(); + if (l.equals(p)) { + return true; + } + // 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; + } + return p.startsWith(l); + } + + /** + * 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 encodes non ASCII characters too.</p> + * + * <p>The coding of the output is the same as what File.toURI().toASCIIString() produces</p> + * + * See <a href="http://www.w3.org/TR/xml11/#dt-sysid">dt-sysid</a> + * which makes some mention of how + * characters not supported by URI Reference syntax should be escaped. + * + * @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) { + return new File(path).toURI().toASCIIString(); + } + + /** + * 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) { + synchronized (cacheFromUriLock) { + if (uri.equals(cacheFromUriRequest)) { + return cacheFromUriResponse; + } + String path = Locator.fromURI(uri); + String ret = isAbsolutePath(path) ? normalize(path).getAbsolutePath() : path; + cacheFromUriRequest = uri; + cacheFromUriResponse = ret; + return ret; + } + } + + /** + * Compares two filenames. + * + * <p>Unlike java.io.File#equals this method will try to compare + * the absolute paths and "normalize" 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()).getAbsolutePath().equals( + normalize(f2.getAbsolutePath()).getAbsolutePath()); + } + + /** + * Are the two File instances pointing to the same object on the + * file system? + * @since Ant 1.8.2 + */ + public boolean areSame(File f1, File f2) throws IOException { + if (f1 == null && f2 == null) { + return true; + } + if (f1 == null || f2 == null) { + return false; + } + File f1Normalized = normalize(f1.getAbsolutePath()); + File f2Normalized = normalize(f2.getAbsolutePath()); + return f1Normalized.equals(f2Normalized) + || f1Normalized.getCanonicalFile().equals(f2Normalized + .getCanonicalFile()); + } + + /** + * 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> + * + * @param from the file to move. + * @param to the new file name. + * + * @throws IOException if anything bad happens during this + * process. Note that <code>to</code> may have been deleted + * already when this happens. + * + * @since Ant 1.6 + */ + public void rename(File from, File to) throws IOException { + // identical logic lives in Move.renameFile(): + from = normalize(from.getAbsolutePath()).getCanonicalFile(); + to = normalize(to.getAbsolutePath()); + if (!from.exists()) { + System.err.println("Cannot rename nonexistent file " + from); + return; + } + if (from.getAbsolutePath().equals(to.getAbsolutePath())) { + System.err.println("Rename of " + from + " to " + to + " is a no-op."); + return; + } + if (to.exists() && !(areSame(from, to) || tryHardToDelete(to))) { + throw new IOException("Failed to delete " + to + " while trying to rename " + from); + } + File parent = to.getParentFile(); + if (parent != null && !parent.isDirectory() + && !(parent.mkdirs() || parent.isDirectory())) { + throw new IOException("Failed to create directory " + parent + + " while trying to rename " + from); + } + if (!from.renameTo(to)) { + copyFile(from, to); + if (!tryHardToDelete(from)) { + throw new IOException("Failed to delete " + from + " while trying to rename it."); + } + } + } + + /** + * Get the granularity of file timestamps. The choice is made based on OS, which is + * incorrect--it should really be by filesystem. We do not have an easy way to probe for file + * systems, however, so this heuristic gives us a decent default. + * + * @return the difference, in milliseconds, which two file timestamps must have in order for the + * two files to be considered to have different timestamps. + */ + public long getFileTimestampGranularity() { + if (ON_WIN9X) { + return FAT_FILE_TIMESTAMP_GRANULARITY; + } + if (ON_WINDOWS) { + return NTFS_FILE_TIMESTAMP_GRANULARITY; + } + if (ON_DOS) { + return FAT_FILE_TIMESTAMP_GRANULARITY; + } + return UNIX_FILE_TIMESTAMP_GRANULARITY; + } + + /** + * test whether a file or directory exists, with an error in the + * upper/lower case spelling of the name. + * Using this method is only interesting on case insensitive file systems + * (Windows).<br> + * It will return true only if 3 conditions are met : + * <br> + * <ul> + * <li>operating system is case insensitive</li> + * <li>file exists</li> + * <li>actual name from directory reading is different from the + * supplied argument</li> + * </ul> + * <br> + * the purpose is to identify files or directories on case-insensitive + * filesystems whose case is not what is expected.<br> + * Possibly to rename them afterwards to the desired upper/lowercase + * combination. + * + * @param localFile file to test + * @return true if the file exists and the case of the actual file + * is not the case of the parameter + * @since Ant 1.7.1 + */ + public boolean hasErrorInCase(File localFile) { + localFile = normalize(localFile.getAbsolutePath()); + if (!localFile.exists()) { + return false; + } + final String localFileName = localFile.getName(); + FilenameFilter ff = new FilenameFilter () { + public boolean accept(File dir, String name) { + return name.equalsIgnoreCase(localFileName) && (!name.equals(localFileName)); + } + }; + String[] names = localFile.getParentFile().list(ff); + return names != null && names.length == 1; + } + + /** + * Returns true if the source is older than the dest. + * If the dest file does not exist, then the test returns false; it is + * implicitly not up do date. + * @param source source file (should be the older). + * @param dest dest file (should be the newer). + * @param granularity an offset added to the source time. + * @return true if the source is older than the dest after accounting + * for granularity. + * @since Ant 1.6.3 + */ + public boolean isUpToDate(File source, File dest, long granularity) { + //do a check for the destination file existing + if (!dest.exists()) { + //if it does not, then the file is not up to date. + return false; + } + long sourceTime = source.lastModified(); + long destTime = dest.lastModified(); + return isUpToDate(sourceTime, destTime, granularity); + } + + /** + * Returns true if the source is older than the dest. + * @param source source file (should be the older). + * @param dest dest file (should be the newer). + * @return true if the source is older than the dest, taking the granularity into account. + * @since Ant 1.6.3 + */ + public boolean isUpToDate(File source, File dest) { + return isUpToDate(source, dest, getFileTimestampGranularity()); + } + + /** + * Compare two timestamps for being up to date using + * the specified granularity. + * + * @param sourceTime timestamp of source file. + * @param destTime timestamp of dest file. + * @param granularity os/filesys granularity. + * @return true if the dest file is considered up to date. + */ + public boolean isUpToDate(long sourceTime, long destTime, long granularity) { + return destTime != -1 && destTime >= sourceTime + granularity; + } + + /** + * Compare two timestamps for being up to date using the + * current granularity. + * + * @param sourceTime timestamp of source file. + * @param destTime timestamp of dest file. + * @return true if the dest file is considered up to date. + */ + public boolean isUpToDate(long sourceTime, long destTime) { + return isUpToDate(sourceTime, destTime, getFileTimestampGranularity()); + } + + /** + * Close a Writer without throwing any exception if something went wrong. + * Do not attempt to close it if the argument is null. + * @param device output writer, can be null. + */ + public static void close(Writer device) { + if (null != device) { + try { + device.close(); + } catch (IOException e) { + //ignore + } + } + } + + /** + * Close a Reader without throwing any exception if something went wrong. + * Do not attempt to close it if the argument is null. + * + * @param device Reader, can be null. + */ + public static void close(Reader device) { + if (null != device) { + try { + device.close(); + } catch (IOException e) { + //ignore + } + } + } + + /** + * Close a stream without throwing any exception if something went wrong. + * Do not attempt to close it if the argument is null. + * + * @param device stream, can be null. + */ + public static void close(OutputStream device) { + if (null != device) { + try { + device.close(); + } catch (IOException e) { + //ignore + } + } + } + + /** + * Close a stream without throwing any exception if something went wrong. + * Do not attempt to close it if the argument is null. + * + * @param device stream, can be null. + */ + public static void close(InputStream device) { + if (null != device) { + try { + device.close(); + } catch (IOException e) { + //ignore + } + } + } + + /** + * Close a Channel without throwing any exception if something went wrong. + * Do not attempt to close it if the argument is null. + * + * @param device channel, can be null. + * @since Ant 1.8.0 + */ + public static void close(Channel device) { + if (null != device) { + try { + device.close(); + } catch (IOException e) { + //ignore + } + } + } + + /** + * Closes an URLConnection if its concrete implementation provides + * a way to close it that Ant knows of. + * + * @param conn connection, can be null + * @since Ant 1.8.0 + */ + public static void close(URLConnection conn) { + if (conn != null) { + try { + if (conn instanceof JarURLConnection) { + JarURLConnection juc = (JarURLConnection) conn; + JarFile jf = juc.getJarFile(); + jf.close(); + jf = null; + } else if (conn instanceof HttpURLConnection) { + ((HttpURLConnection) conn).disconnect(); + } + } catch (IOException exc) { + //ignore + } + } + } + + /** + * Delete the file with {@link File#delete()} if the argument is not null. + * Do nothing on a null argument. + * @param file file to delete. + */ + public static void delete(File file) { + if (file != null) { + file.delete(); + } + } + + /** + * Accommodate Windows bug encountered in both Sun and IBM JDKs. + * Others possible. If the delete does not work, call System.gc(), + * wait a little and try again. + * + * @return whether deletion was successful + * @since Ant 1.8.0 + */ + public boolean tryHardToDelete(File f) { + return tryHardToDelete(f, ON_WINDOWS); + } + + /** + * If delete does not work, call System.gc() if asked to, wait a + * little and try again. + * + * @return whether deletion was successful + * @since Ant 1.8.3 + */ + public boolean tryHardToDelete(File f, boolean runGC) { + if (!f.delete()) { + if (runGC) { + System.gc(); + } + try { + Thread.sleep(DELETE_RETRY_SLEEP_MILLIS); + } catch (InterruptedException ex) { + // Ignore Exception + } + return f.delete(); + } + return true; + } + + /** + * Calculates the relative path between two files. + * <p> + * Implementation note:<br>This function may throw an IOException if an I/O error occurs + * because its use of the canonical pathname may require filesystem queries. + * </p> + * + * @param fromFile the <code>File</code> to calculate the path from + * @param toFile the <code>File</code> to calculate the path to + * @return the relative path between the files + * @throws Exception for undocumented reasons + * @see File#getCanonicalPath() + * + * @since Ant 1.7 + */ + public static String getRelativePath(File fromFile, File toFile) throws Exception { + String fromPath = fromFile.getCanonicalPath(); + String toPath = toFile.getCanonicalPath(); + + // build the path stack info to compare + String[] fromPathStack = getPathStack(fromPath); + String[] toPathStack = getPathStack(toPath); + + if (0 < toPathStack.length && 0 < fromPathStack.length) { + if (!fromPathStack[0].equals(toPathStack[0])) { + // not the same device (would be "" on Linux/Unix) + + return getPath(Arrays.asList(toPathStack)); + } + } else { + // no comparison possible + return getPath(Arrays.asList(toPathStack)); + } + + int minLength = Math.min(fromPathStack.length, toPathStack.length); + int same = 1; // Used outside the for loop + + // get index of parts which are equal + for (; + same < minLength && fromPathStack[same].equals(toPathStack[same]); + same++) { + // Do nothing + } + + List relativePathStack = new ArrayList(); + + // if "from" part is longer, fill it up with ".." + // to reach path which is equal to both paths + for (int i = same; i < fromPathStack.length; i++) { + relativePathStack.add(".."); + } + + // fill it up path with parts which were not equal + for (int i = same; i < toPathStack.length; i++) { + relativePathStack.add(toPathStack[i]); + } + + return getPath(relativePathStack); + } + + /** + * Gets all names of the path as an array of <code>String</code>s. + * + * @param path to get names from + * @return <code>String</code>s, never <code>null</code> + * + * @since Ant 1.7 + */ + public static String[] getPathStack(String path) { + String normalizedPath = path.replace(File.separatorChar, '/'); + + return normalizedPath.split("/"); + } + + /** + * Gets path from a <code>List</code> of <code>String</code>s. + * + * @param pathStack <code>List</code> of <code>String</code>s to be concatenated as a path. + * @return <code>String</code>, never <code>null</code> + * + * @since Ant 1.7 + */ + public static String getPath(List pathStack) { + // can safely use '/' because Windows understands '/' as separator + return getPath(pathStack, '/'); + } + + /** + * Gets path from a <code>List</code> of <code>String</code>s. + * + * @param pathStack <code>List</code> of <code>String</code>s to be concated as a path. + * @param separatorChar <code>char</code> to be used as separator between names in path + * @return <code>String</code>, never <code>null</code> + * + * @since Ant 1.7 + */ + public static String getPath(final List pathStack, final char separatorChar) { + final StringBuffer buffer = new StringBuffer(); + + final Iterator iter = pathStack.iterator(); + if (iter.hasNext()) { + buffer.append(iter.next()); + } + while (iter.hasNext()) { + buffer.append(separatorChar); + buffer.append(iter.next()); + } + return buffer.toString(); + } + + /** + * Get the default encoding. + * This is done by opening an InputStreamReader on + * a dummy InputStream and getting the encoding. + * Could use System.getProperty("file.encoding"), but cannot + * see where this is documented. + * @return the default file encoding. + */ + public String getDefaultEncoding() { + InputStreamReader is = new InputStreamReader( + new InputStream() { + public int read() { + return -1; + } + }); + try { + return is.getEncoding(); + } finally { + close(is); + } + } +} |