/* * Copyright 2001-2005 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.io.Writer; import java.io.OutputStream; 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.Resource; import org.apache.tools.ant.types.FilterSetCollection; import org.apache.tools.ant.types.resources.Touchable; import org.apache.tools.ant.types.resources.FileResource; import org.apache.tools.ant.launch.Locator; /** * 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 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 boolean onNetWare = Os.isFamily("netware"); private static boolean onDos = Os.isFamily("dos"); private static boolean onWin9x = Os.isFamily("win9x"); private static boolean onWindows = Os.isFamily("windows"); private static final int BUF_SIZE = 8192; // 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; /** * 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; // 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. * @deprecated 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(toURI(file.getAbsolutePath())); } /** * 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 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); } /** * 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 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); } /** * 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 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); } /** * 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 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); } /** * 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 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); } /** * 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 * 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); } /** * 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 * 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); } /** * 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 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); } /** * 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 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); } /** * 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 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); } /** * 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 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); } /** * 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 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); } /** * 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 * 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); } /** * 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 * 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 { copyResource(new FileResource(sourceFile), new FileResource(destFile), filters, filterChains, overwrite, preserveLastModified, inputEncoding, outputEncoding, project); } /** * Convenience method to copy content from one Resource to another. * No filtering is performed. * * @param source the Resource to copy from. * Must not be null. * @param dest the Resource to copy to. * Must not be null. * * @throws IOException if the copying fails. * * @since Ant 1.7 */ public void copyResource(Resource source, Resource dest) throws IOException { copyResource(source, dest, null); } /** * Convenience method to copy content from one Resource to another. * No filtering is performed. * * @param source the Resource to copy from. * Must not be null. * @param dest the Resource to copy to. * Must not be null. * @param project the project instance. * * @throws IOException if the copying fails. * * @since Ant 1.7 */ public void copyResource(Resource source, Resource dest, Project project) throws IOException { copyResource(source, dest, null, null, false, false, null, null, project); } /** * Convenience method to copy content from one Resource to another * specifying whether token filtering must be used, whether filter chains * must be used, whether newer destination files may be overwritten and * whether the last modified time of dest file should be made * equal to the last modified time of source. * * @param source the Resource to copy from. * Must not be null. * @param dest the Resource 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 Resource should be * overwritten if it already exists. * @param preserveLastModified Whether or not the last modified time of * the destination Resource should be set to that * of the source. * @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.7 */ public void copyResource(Resource source, Resource dest, FilterSetCollection filters, Vector filterChains, boolean overwrite, boolean preserveLastModified, String inputEncoding, String outputEncoding, Project project) throws IOException { if (!overwrite) { long slm = source.getLastModified(); if (dest.isExists() && slm != 0 && dest.getLastModified() > slm) { return; } } final boolean filterSetsAvailable = (filters != null && filters.hasFilters()); final boolean filterChainsAvailable = (filterChains != null && filterChains.size() > 0); if (filterSetsAvailable) { BufferedReader in = null; BufferedWriter out = null; try { InputStreamReader isr = null; if (inputEncoding == null) { isr = new InputStreamReader(source.getInputStream()); } else { isr = new InputStreamReader(source.getInputStream(), inputEncoding); } in = new BufferedReader(isr); OutputStreamWriter osw = null; if (outputEncoding == null) { osw = new OutputStreamWriter(dest.getOutputStream()); } else { osw = new OutputStreamWriter(dest.getOutputStream(), outputEncoding); } out = new BufferedWriter(osw); if (filterChainsAvailable) { ChainReaderHelper crh = new ChainReaderHelper(); crh.setBufferSize(BUF_SIZE); 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 { close(out); close(in); } } else if (filterChainsAvailable || (inputEncoding != null && !inputEncoding.equals(outputEncoding)) || (inputEncoding == null && outputEncoding != null)) { BufferedReader in = null; BufferedWriter out = null; try { InputStreamReader isr = null; if (inputEncoding == null) { isr = new InputStreamReader(source.getInputStream()); } else { isr = new InputStreamReader(source.getInputStream(), inputEncoding); } in = new BufferedReader(isr); OutputStreamWriter osw = null; if (outputEncoding == null) { osw = new OutputStreamWriter(dest.getOutputStream()); } else { osw = new OutputStreamWriter(dest.getOutputStream(), outputEncoding); } out = new BufferedWriter(osw); if (filterChainsAvailable) { ChainReaderHelper crh = new ChainReaderHelper(); crh.setBufferSize(BUF_SIZE); crh.setPrimaryReader(in); crh.setFilterChains(filterChains); crh.setProject(project); Reader rdr = crh.getAssembledReader(); in = new BufferedReader(rdr); } char[] buffer = new char[BUF_SIZE]; while (true) { int nRead = in.read(buffer, 0, buffer.length); if (nRead == -1) { break; } out.write(buffer, 0, nRead); } } finally { close(out); close(in); } } else { InputStream in = null; OutputStream out = null; try { in = source.getInputStream(); out = dest.getOutputStream(); byte[] buffer = new byte[BUF_SIZE]; int count = 0; do { out.write(buffer, 0, count); count = in.read(buffer, 0, buffer.length); } while (count != -1); } finally { close(out); close(in); } } if (preserveLastModified && dest instanceof Touchable) { setLastModified((Touchable) dest, source.getLastModified()); } } /** * 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) { setLastModified(new FileResource(file), time); } /** * Set the last modified time of an object implementing * org.apache.tools.ant.types.resources.Touchable . * * @param t the Touchable 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 setLastModified(Touchable t, long time) { t.touch((time < 0) ? System.currentTimeMillis() : time); } /** * Interpret the filename as a file relative to the given file * unless the filename already represents an absolute filename. * Differs from new File(file, filename) in that * the resulting File's path will always be a normalized, * absolute pathname. Also, if it is determined that * filename is context-relative, file * 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 * new java.io.File(filename).getAbsoluteFile(). * * @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 (!(onDos || onNetWare) || 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.indexOf(':') == 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 (!(onDos || onNetWare)) { return (c == sep); } if (c == sep) { if (!(onDos && len > 4 && filename.charAt(1) == sep)) { return false; } 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) || (onNetWare && colon > 0); } /** * "Normalize" the given absolute path. * *

This includes: *

* Unlike 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; } else if ("..".equals(thisToken)) { if (s.size() < 2) { throw new BuildException("Cannot resolve path " + path); } 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)); } 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. */ 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 && (onDos || onNetWare)) { int next = colon + 1; root = path.substring(0, next).toUpperCase(); 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 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.

*

* The filename is prefixNNNNNsuffix where NNNN is a random number. *

*

This method is different from 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 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. * * @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 = (parentDir == null) ? System.getProperty("java.io.tmpdir") : 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. * * @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 contentEquals(new FileResource(f1), new FileResource(f2), textfile); } /** * Compares the contents of two Resources. * * @param r1 the Resource whose content is to be compared. * @param r2 the other Resource whose content is to be compared. * @param text true if the content is to be treated as text and * differences in kind of line break are to be ignored. * * @return true if the content of the Resources is the same. * * @throws IOException if the Resources cannot be read. * @since Ant 1.6.3 */ public boolean contentEquals(Resource r1, Resource r2, boolean text) throws IOException { if (r1.isExists() != r2.isExists()) { return false; } if (!r1.isExists()) { // two not existing files are equal return true; } // should the following two be switched? If r1 and r2 refer to the same file, // isn't their content equal regardless of whether that file is a directory? if (r1.isDirectory() || r2.isDirectory()) { // don't want to compare directory contents for now return false; } if (r1.equals(r2)) { return true; } if (!text && r1.getSize() != r2.getSize()) { return false; } return compareContent(r1, r2, text) == 0; } /** * Compare the content of two Resources. A nonexistent Resource's * content is "less than" that of an existing Resource; a directory-type * Resource's content is "less than" that of a file-type Resource. * @param r1 the Resource whose content is to be compared. * @param r2 the other Resource whose content is to be compared. * @param text true if the content is to be treated as text and * differences in kind of line break are to be ignored. * @return a negative integer, zero, or a positive integer as the first * argument is less than, equal to, or greater than the second. * @throws IOException if the Resources cannot be read. */ public int compareContent(Resource r1, Resource r2, boolean text) throws IOException { if (r1.equals(r2)) { return 0; } boolean e1 = r1.isExists(); boolean e2 = r2.isExists(); if (!(e1 || e2)) { return 0; } if (e1 != e2) { return e1 ? 1 : -1; } boolean d1 = r1.isDirectory(); boolean d2 = r2.isDirectory(); if (d1 && d2) { return 0; } if (d1 || d2) { return d1 ? -1 : 1; } return text ? textCompare(r1, r2) : binaryCompare(r1, r2); } /** * Binary compares the contents of two Resources. *

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

* * @param r1 the Resource whose content is to be compared. * @param r2 the other Resource whose content is to be compared. * @return a negative integer, zero, or a positive integer as the first * argument is less than, equal to, or greater than the second. * @throws IOException if the Resources cannot be read. */ private int binaryCompare(Resource r1, Resource r2) throws IOException { InputStream in1 = null; InputStream in2 = null; try { in1 = new BufferedInputStream(r1.getInputStream()); in2 = new BufferedInputStream(r2.getInputStream()); for (int b1 = in1.read(); b1 != -1; b1 = in1.read()) { int b2 = in2.read(); if (b1 != b2) { return b1 > b2 ? 1 : -1; } } return in2.read() == -1 ? 0 : -1; } finally { close(in1); close(in2); } } /** * Text compares the contents of two Resources. * Ignores different kinds of line endings. * @param r1 the Resource whose content is to be compared. * @param r2 the other Resource whose content is to be compared. * @return a negative integer, zero, or a positive integer as the first * argument is less than, equal to, or greater than the second. * @throws IOException if the Resources cannot be read. */ private int textCompare(Resource r1, Resource r2) throws IOException { BufferedReader in1 = null; BufferedReader in2 = null; try { in1 = new BufferedReader(new InputStreamReader(r1.getInputStream())); in2 = new BufferedReader(new InputStreamReader(r2.getInputStream())); String expected = in1.readLine(); while (expected != null) { String actual = in2.readLine(); if (!expected.equals(actual)) { return expected.compareTo(actual); } expected = in1.readLine(); } return in2.readLine() == null ? 0 : -1; } finally { close(in1); close(in2); } } /** * 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 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 final 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 final String readFully(Reader rdr, int bufferSize) throws IOException { if (bufferSize <= 0) { throw new IllegalArgumentException("Buffer size must be greater " + "than 0"); } final char[] buffer = new char[bufferSize]; int bufferLength = 0; 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(); } /** * 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). * *

This method has historically not 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 boolean 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. * *

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.

* * @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 */ public boolean isSymbolicLink(File parent, String name) throws IOException { if (parent == null) { File f = new File(name); parent = f.getParentFile(); name = f.getName(); } File toTest = new File(parent.getCanonicalPath(), name); return !toTest.getAbsolutePath().equals(toTest.getCanonicalPath()); } /** * Removes a leading path from a second path. * * @param leading The leading path, must not be null, must be absolute. * @param path The path to remove from, must not be null, must be absolute. * * @return path's normalized absolute if it doesn't start with * leading; path's path with leading's path removed otherwise. * * @since Ant 1.5 */ public String removeLeadingPath(File leading, File path) { String l = normalize(leading.getAbsolutePath()).getAbsolutePath(); String p = normalize(path.getAbsolutePath()).getAbsolutePath(); if (l.equals(p)) { return ""; } // ensure that l ends with a / // so we never think /foo was a parent directory of /foobar if (!l.endsWith(File.separator)) { l += File.separator; } return (p.startsWith(l)) ? p.substring(l.length()) : p; } /** * Constructs a file: URI that represents the * external form of the given pathname. * *

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:"); path = resolveFile(null, path).getPath(); sb.append("//"); // add an extra slash for filesystems with drive-specifiers if (!path.startsWith(File.separator)) { sb.append("/"); } 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.

* * @param from the file to move. * @param to the new file name. * * @throws IOException if anything bad happens during this * process. Note that to may have been deleted * already when this happens. * * @since Ant 1.6 */ public void rename(File from, File to) throws IOException { if (to.exists() && !to.delete()) { throw new IOException("Failed to delete " + to + " while trying to rename " + from); } File parent = to.getParentFile(); if (parent != null && !parent.exists() && !parent.mkdirs()) { throw new IOException("Failed to create directory " + parent + " while trying to rename " + from); } if (!from.renameTo(to)) { copyFile(from, to); if (!from.delete()) { throw new IOException("Failed to delete " + from + " while trying to rename it."); } } } /** * 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 (onWin9x) { return FAT_FILE_TIMESTAMP_GRANULARITY; } else if (onWindows) { return NTFS_FILE_TIMESTAMP_GRANULARITY; } else if (onDos) { return FAT_FILE_TIMESTAMP_GRANULARITY; } return UNIX_FILE_TIMESTAMP_GRANULARITY; } /** * 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) { if (destTime == -1) { return false; } return 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 (device != null) { try { device.close(); } catch (IOException ioex) { //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 Reader, can be null. */ public static void close(Reader device) { if (device != null) { try { device.close(); } catch (IOException ioex) { //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 (device != null) { try { device.close(); } catch (IOException ioex) { //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 (device != null) { try { device.close(); } catch (IOException ioex) { //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(); } } }