org.apache.tools.ant.util.FileUtils Maven / Gradle / Ivy
/*
* 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.MalformedURLException;
import java.net.HttpURLConnection;
import java.net.JarURLConnection;
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(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);
}
// 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
* 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 {
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
* 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 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
* 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 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 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 (!(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.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 (!(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.
*
* 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 null
.
*
* @return the native version of the specified path or
* an empty string if the path is null
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.
*
*
This includes:
*
* - Uppercase the drive letter if there is one.
* - Remove redundant slashes after the drive spec.
* - Resolve all ./, .\, ../ and ..\ sequences.
* - DOS style paths that start with a drive letter will have
* \ as the separator.
*
* Unlike {@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 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
// 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.
*
*
* 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.
*
*
* @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.
*
* 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.
*
* @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.
*
*
* 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.
*
*
* @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).
*
* 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
* @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 file:
URI that represents the
* external form of the given pathname.
*
* Will be an absolute URI if the given path is absolute.
*
* This code encodes non ASCII characters too.
*
* The coding of the output is the same as what File.toURI().toASCIIString() produces
*
* See dt-sysid
* 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).getAbsoluteFile().toURI().toASCIIString();
}
/**
* 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) {
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.
*
* 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()).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.
*
* 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 {
// 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.exists() && !parent.mkdirs()) {
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).
* It will return true only if 3 conditions are met :
*
*
* - operating system is case insensitive
* - file exists
* - actual name from directory reading is different from the
* supplied argument
*
*
* the purpose is to identify files or directories on case-insensitive
* filesystems whose case is not what is expected.
* 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.
*
* Implementation note:
This function may throw an IOException if an I/O error occurs
* because its use of the canonical pathname may require filesystem queries.
*
*
* @param fromFile the File
to calculate the path from
* @param toFile the File
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 String
s.
*
* @param path to get names from
* @return String
s, never null
*
* @since Ant 1.7
*/
public static String[] getPathStack(String path) {
String normalizedPath = path.replace(File.separatorChar, '/');
return normalizedPath.split("/");
}
/**
* Gets path from a List
of String
s.
*
* @param pathStack List
of String
s to be concatenated as a path.
* @return String
, never null
*
* @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 List
of String
s.
*
* @param pathStack List
of String
s to be concated as a path.
* @param separatorChar char
to be used as separator between names in path
* @return String
, never null
*
* @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);
}
}
}