blob: 788bc0aff8d95b94c9210e858625d0f101294bf9 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2005, 2010 Oracle. All rights reserved.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v1.0, which accompanies this distribution
* and is available at http://www.eclipse.org/legal/epl-v10.html.
*
* Contributors:
* Oracle - initial API and implementation
******************************************************************************/
package org.eclipse.jpt.utility.internal;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.eclipse.jpt.utility.internal.iterators.ArrayIterator;
import org.eclipse.jpt.utility.internal.iterators.CompositeIterator;
import org.eclipse.jpt.utility.internal.iterators.FilteringIterator;
import org.eclipse.jpt.utility.internal.iterators.TransformationIterator;
/**
* Assorted file tools:
* - delete entire trees of directories and files
* - build iterators on entire trees of directories and files
* - build a temporary directory
* - "canonize" files
*/
public final class FileTools {
public static final String USER_HOME_DIRECTORY_NAME = System.getProperty("user.home"); //$NON-NLS-1$
public static final String USER_TEMPORARY_DIRECTORY_NAME = System.getProperty("java.io.tmpdir"); //$NON-NLS-1$
public static String DEFAULT_TEMPORARY_DIRECTORY_NAME = "tmpdir"; //$NON-NLS-1$
public static final String CURRENT_WORKING_DIRECTORY_NAME = System.getProperty("user.dir"); //$NON-NLS-1$
/** A list of some invalid file name characters.
: is the filename separator in MacOS and the drive indicator in DOS
* is a DOS wildcard character
| is a DOS redirection character
& is our own escape character
/ is the filename separator in Unix and the command option tag in DOS
\ is the filename separator in DOS/Windows and the escape character in Unix
; is ???
? is a DOS wildcard character
[ is ???
] is ???
= is ???
+ is ???
< is a DOS redirection character
> is a DOS redirection character
" is used by DOS to delimit file names with spaces
, is ???
*/
public static final char[] INVALID_FILENAME_CHARACTERS = { ':', '*', '|', '&', '/', '\\', ';', '?', '[', ']', '=', '+', '<', '>', '"', ',' };
/** This encoder will convert strings into valid file names. */
public static final XMLStringEncoder FILE_NAME_ENCODER = new XMLStringEncoder(INVALID_FILENAME_CHARACTERS);
/** Windows files that are redirected to devices etc. */
@SuppressWarnings("nls")
private static final String[] WINDOWS_RESERVED_FILE_NAMES = {
"con",
"aux",
"com1", "com2", "com3", "com4", "com5", "com6", "com7", "com8", "com9",
"lpt1", "lpt2", "lpt3", "lpt4", "lpt5", "lpt6", "lpt7", "lpt8", "lpt9",
"prn",
"nul"
};
/** The default length of a shortened file name. */
public static final int MAXIMUM_SHORTENED_FILE_NAME_LENGTH = 60;
// ********** deleting directories **********
/**
* Delete the specified directory and all of its contents.
* <em>USE WITH CARE.</em>
* File#deleteAll()?
*/
public static void deleteDirectory(String directoryName) {
deleteDirectory(new File(directoryName));
}
/**
* Delete the specified directory and all of its contents.
* <em>USE WITH CARE.</em>
* File#deleteAll()?
*/
public static void deleteDirectory(File directory) {
deleteDirectoryContents(directory);
if ( ! directory.delete()) {
throw new RuntimeException("unable to delete directory: " + directory.getAbsolutePath()); //$NON-NLS-1$
}
}
/**
* Delete the contents of the specified directory
* (but not the directory itself).
* <em>USE WITH CARE.</em>
* File#deleteFiles()
*/
public static void deleteDirectoryContents(String directoryName) {
deleteDirectoryContents(new File(directoryName));
}
/**
* Delete the contents of the specified directory
* (but not the directory itself).
* <em>USE WITH CARE.</em>
* File#deleteFiles()
*/
public static void deleteDirectoryContents(File directory) {
for (File file : directory.listFiles()) {
if (file.isDirectory()) {
deleteDirectory(file); // recurse through subdirectories
} else {
if ( ! file.delete()) {
throw new RuntimeException("unable to delete file: " + file.getAbsolutePath()); //$NON-NLS-1$
}
}
}
}
// ********** copying files **********
/**
* Copies the content of the source file to the destination file.
* File#copy(File destinationFile)
*/
public static void copyToFile(File sourceFile, File destinationFile)
throws IOException
{
FileChannel sourceChannel = new FileInputStream(sourceFile).getChannel();
FileChannel destinationChannel = new FileOutputStream(destinationFile).getChannel();
try {
destinationChannel.transferFrom(sourceChannel, 0, sourceChannel.size());
} finally {
sourceChannel.close();
destinationChannel.close();
}
}
/**
* Copies the content of the source file to a file by
* the same name in the destination directory.
* File#copyToDirectory(File destinationDirectory)
*/
public static void copyToDirectory(File sourceFile, File destinationDirectory)
throws IOException
{
File destinationFile = new File(destinationDirectory, sourceFile.getName());
if ( ! destinationFile.exists() && ! destinationFile.createNewFile()) {
throw new RuntimeException("createNewFile() failed: " + destinationFile); //$NON-NLS-1$
}
copyToFile(sourceFile, destinationFile);
}
// ********** iteratoring over files and directories **********
/**
* Return an iterator on all the files in the specified directory.
* The iterator will skip over subdirectories.
* File#files()
*/
public static Iterator<File> filesIn(String directoryName) {
return filesIn(new File(directoryName));
}
/**
* Return an iterator on all the files in the specified directory.
* The iterator will skip over subdirectories.
* File#files()
*/
public static Iterator<File> filesIn(File directory) {
return filesIn(directory.listFiles());
}
private static Iterator<File> filesIn(File[] files) {
return new FilteringIterator<File>(new ArrayIterator<File>(files)) {
@Override
protected boolean accept(File next) {
return next.isFile();
}
};
}
/**
* Return an iterator on all the subdirectories
* in the specified directory.
* File#subDirectories()
*/
public static Iterator<File> directoriesIn(String directoryName) {
return directoriesIn(new File(directoryName));
}
/**
* Return an iterator on all the subdirectories
* in the specified directory.
* File#subDirectories()
*/
public static Iterator<File> directoriesIn(File directory) {
return directoriesIn(directory.listFiles());
}
private static Iterator<File> directoriesIn(File[] files) {
return new FilteringIterator<File>(new ArrayIterator<File>(files)) {
@Override
protected boolean accept(File next) {
return next.isDirectory();
}
};
}
/**
* Return an iterator on all the files under the specified
* directory, recursing into subdirectories.
* The iterator will skip over the subdirectories themselves.
* File#filesRecurse()
*/
public static Iterator<File> filesInTree(String directoryName) {
return filesInTree(new File(directoryName));
}
/**
* Return an iterator on all the files under the specified
* directory, recursing into subdirectories.
* The iterator will skip over the subdirectories themselves.
* File#filesRecurse()
*/
public static Iterator<File> filesInTree(File directory) {
return filesInTreeAsSet(directory).iterator();
}
private static Set<File> filesInTreeAsSet(File directory) {
Set<File> files = new HashSet<File>(10000);
addFilesInTreeTo(directory, files);
return files;
}
private static void addFilesInTreeTo(File directory, Collection<File> allFiles) {
for (File file : directory.listFiles()) {
if (file.isFile()) {
allFiles.add(file);
} else if (file.isDirectory()) {
addFilesInTreeTo(file, allFiles);
}
}
}
/**
* Return an iterator on all the directories under the specified
* directory, recursing into subdirectories.
* File#subDirectoriesRecurse()
*/
public static Iterator<File> directoriesInTree(String directoryName) {
return directoriesInTree(new File(directoryName));
}
/**
* Return an iterator on all the directories under the specified
* directory, recursing into subdirectories.
* File#subDirectoriesRecurse()
*/
@SuppressWarnings("unchecked")
public static Iterator<File> directoriesInTree(File directory) {
File[] files = directory.listFiles();
return new CompositeIterator<File>(directoriesIn(files), directoriesInTrees(directoriesIn(files)));
}
private static Iterator<File> directoriesInTrees(Iterator<File> directories) {
return new CompositeIterator<File>(
new TransformationIterator<File, Iterator<File>>(directories) {
@Override
protected Iterator<File> transform(File next) {
return FileTools.directoriesInTree(next);
}
}
);
}
// ********** short file name manipulation **********
/**
* Strip the extension from the specified file name
* and return the result. If the file name has no
* extension, it is returned unchanged
* File#basePath()
*/
public static String stripExtension(String fileName) {
int index = fileName.lastIndexOf('.');
if (index == -1) {
return fileName;
}
return fileName.substring(0, index);
}
/**
* Strip the extension from the specified file's name
* and return the result. If the file's name has no
* extension, it is returned unchanged
* File#basePath()
*/
public static String stripExtension(File file) {
return stripExtension(file.getPath());
}
/**
* Return the extension, including the dot, of the specified file name.
* If the file name has no extension, return an empty string.
* File#extension()
*/
public static String extension(String fileName) {
int index = fileName.lastIndexOf('.');
if (index == -1) {
return ""; //$NON-NLS-1$
}
return fileName.substring(index);
}
/**
* Return the extension, including the dot, of the specified file's name.
* If the file's name has no extension, return an empty string.
* File#extension()
*/
public static String extension(File file) {
return extension(file.getPath());
}
// ********** temporary directories **********
/**
* Build and return an empty temporary directory with the specified
* name. If the directory already exists, it will be cleared out.
* This directory will be a subdirectory of the Java temporary directory,
* as indicated by the System property "java.io.tmpdir".
*/
public static File emptyTemporaryDirectory(String name) {
File dir = new File(userTemporaryDirectory(), name);
if (dir.exists()) {
deleteDirectoryContents(dir);
} else {
mkdirs(dir);
}
return dir;
}
private static void mkdirs(File dir) {
if ( ! dir.mkdirs()) {
throw new RuntimeException("mkdirs() failed: " + dir); //$NON-NLS-1$
}
}
/**
* Build and return an empty temporary directory with a
* name of "tmpdir". If the directory already exists, it will be cleared out.
* This directory will be a subdirectory of the Java temporary directory,
* as indicated by the System property "java.io.tmpdir".
*/
public static File emptyTemporaryDirectory() {
return emptyTemporaryDirectory(DEFAULT_TEMPORARY_DIRECTORY_NAME);
}
/**
* Build and return a temporary directory with the specified
* name. If the directory already exists, it will be left unchanged;
* if it does not already exist, it will be created.
* This directory will be a subdirectory of the Java temporary directory,
* as indicated by the System property "java.io.tmpdir".
*/
public static File temporaryDirectory(String name) {
File dir = new File(userTemporaryDirectory(), name);
if ( ! dir.exists()) {
mkdirs(dir);
}
return dir;
}
/**
* Build and return a temporary directory with a name of
* "tmpdir". If the directory already exists, it will be left unchanged;
* if it does not already exist, it will be created.
* This directory will be a subdirectory of the Java temporary directory,
* as indicated by the System property "java.io.tmpdir".
*/
public static File temporaryDirectory() {
return temporaryDirectory(DEFAULT_TEMPORARY_DIRECTORY_NAME);
}
/**
* Build and return a *new* temporary directory with the specified
* prefix. The prefix will be appended with a number that
* is incremented, starting with 1, until a non-pre-existing directory
* is found and successfully created. This directory will be a
* subdirectory of the Java temporary directory, as indicated by
* the System property "java.io.tmpdir".
*/
public static File newTemporaryDirectory(String prefix) {
if ( ! prefix.endsWith(".")) { //$NON-NLS-1$
prefix = prefix + '.';
}
File dir;
int i = 0;
do {
i++;
dir = new File(userTemporaryDirectory(), prefix + i);
} while ( ! dir.mkdirs());
return dir;
}
/**
* Build and return a *new* temporary directory with a
* prefix of "tmpdir". This prefix will be appended with a number that
* is incremented, starting with 1, until a non-pre-existing directory
* is found and successfully created. This directory will be a
* subdirectory of the Java temporary directory, as indicated by
* the System property "java.io.tmpdir".
*/
public static File newTemporaryDirectory() {
return newTemporaryDirectory(DEFAULT_TEMPORARY_DIRECTORY_NAME);
}
// ********** resource files **********
/**
* Build and return a file for the specified resource.
* The resource name must be fully-qualified, i.e. it cannot be relative
* to the package name/directory.
* NB: There is a bug in jdk1.4.x the prevents us from getting
* a resource that has spaces (or other special characters) in
* its name.... (see Sun's Java bug 4466485)
*/
public static File resourceFile(String resourceName) throws URISyntaxException {
if ( ! resourceName.startsWith("/")) { //$NON-NLS-1$
throw new IllegalArgumentException(resourceName);
}
return resourceFile(resourceName, FileTools.class);
}
/**
* Build and return a file for the specified resource.
* NB: There is a bug in jdk1.4.x the prevents us from getting
* a resource that has spaces (or other special characters) in
* its name.... (see Sun's Java bug 4466485)
*/
public static File resourceFile(String resourceName, Class<?> javaClass) throws URISyntaxException {
URL url = javaClass.getResource(resourceName);
return buildFile(url);
}
/**
* Build and return a file for the specified URL.
* NB: There is a bug in jdk1.4.x the prevents us from getting
* a resource that has spaces (or other special characters) in
* its name.... (see Sun's Java bug 4466485)
*/
public static File buildFile(URL url) throws URISyntaxException {
return buildFile(url.getFile());
}
/**
* Build and return a file for the specified file name.
* NB: There is a bug in jdk1.4.x the prevents us from getting
* a resource that has spaces (or other special characters) in
* its name.... (see Sun's Java bug 4466485)
*/
public static File buildFile(String fileName) throws URISyntaxException {
URI uri = new URI(fileName);
File file = new File(uri.getPath());
return file;
}
// ********** "canonical" files **********
/**
* Convert the specified file into a "canonical" file.
*/
public static File canonicalFile(File file) {
try {
return file.getCanonicalFile();
} catch (IOException ioexception) {
// settle for the absolute file
return file.getAbsoluteFile();
}
}
/**
* Build an iterator that will convert the specified files
* into "canonical" files.
*/
public static Iterator<File> canonicalFiles(Iterator<File> files) {
return new TransformationIterator<File, File>(files) {
@Override
protected File transform(File next) {
return canonicalFile(next);
}
};
}
/**
* Build an iterator that will convert the specified files
* into "canonical" files.
*/
public static Iterator<File> canonicalFiles(Collection<File> files) {
return canonicalFiles(files.iterator());
}
/**
* Convert the specified file name into a "canonical" file name.
*/
public static String canonicalFileName(String fileName) {
return canonicalFile(new File(fileName)).getAbsolutePath();
}
/**
* Build an iterator that will convert the specified file names
* into "canonical" file names.
*/
public static Iterator<String> canonicalFileNames(Iterator<String> fileNames) {
return new TransformationIterator<String, String>(fileNames) {
@Override
protected String transform(String next) {
return canonicalFileName(next);
}
};
}
/**
* Build an iterator that will convert the specified file names
* into "canonical" file names.
*/
public static Iterator<String> canonicalFileNames(Collection<String> fileNames) {
return canonicalFileNames(fileNames.iterator());
}
// ********** file name validation **********
/**
* Return whether the specified file name is invalid.
*/
public static boolean fileNameIsInvalid(String filename) {
return ! fileNameIsValid(filename);
}
/**
* Return whether the specified file name is valid.
*/
public static boolean fileNameIsValid(String filename) {
int len = filename.length();
for (int i = 0; i < len; i++) {
char filenameChar = filename.charAt(i);
if (ArrayTools.contains(INVALID_FILENAME_CHARACTERS, filenameChar)) {
return false;
}
}
return true;
}
/**
* Convert the illegal characters in the specified file name to
* the specified character and return the result.
*/
public static String convertToValidFileName(String filename, char replacementChar) {
int len = filename.length();
StringBuilder sb = new StringBuilder(len);
for (int i = 0; i < len; i++) {
char filenameChar = filename.charAt(i);
if (ArrayTools.contains(INVALID_FILENAME_CHARACTERS, filenameChar)) {
sb.append(replacementChar);
} else {
sb.append(filenameChar);
}
}
return sb.toString();
}
/**
* Convert the illegal characters in the specified file name to
* periods ('.') and return the result.
*/
public static String convertToValidFileName(String filename) {
return convertToValidFileName(filename, '.');
}
/**
* Return whether the specified file name is "reserved"
* (i.e. it cannot be used for "user" files). Windows reserves
* a number of file names (e.g. CON, AUX, PRN).
*/
public static boolean fileNameIsReserved(String fileName) {
// Unix/Linux does not have any "reserved" file names (I think...)
return Tools.osIsWindows() && ArrayTools.contains(WINDOWS_RESERVED_FILE_NAMES, fileName.toLowerCase());
}
/**
* Return whether the specified file contains any "reserved"
* components.
* Windows reserves a number of file names (e.g. CON, AUX, PRN);
* and these file names cannot be used for either the names of
* files or directories.
*/
public static boolean fileHasAnyReservedComponents(File file) {
File temp = file;
while (temp != null) {
if (fileNameIsReserved(temp.getName())) {
return true;
}
temp = temp.getParentFile();
}
return false;
}
// ********** shortened file names **********
/**
* Return a shorter version of the absolute file name for the specified file.
* The shorter version will not be longer than the maximum length.
* The first directory (usually the drive letter) and the file name or the
* last directory will always be added to the generated string regardless of
* the maximum length allowed.
*/
public static String shortenFileName(URL url) {
return shortenFileName(url, MAXIMUM_SHORTENED_FILE_NAME_LENGTH);
}
/**
* Return a shorter version of the absolute file name for the specified file.
* The shorter version will not be longer than the maximum length.
* The first directory (usually the drive letter) and the file name or the
* last directory will always be added to the generated string regardless of
* the maximum length allowed.
*/
public static String shortenFileName(URL url, int maxLength) {
File file;
try {
file = buildFile(url);
} catch (URISyntaxException e) {
file = new File(url.getFile());
}
return shortenFileName(file, maxLength);
}
/**
* Return a shorter version of the absolute file name for the specified file.
* The shorter version will not be longer than the maximum length.
* The first directory (usually the drive letter) and the file name or the
* last directory will always be added to the generated string regardless of
* the maximum length allowed.
*/
public static String shortenFileName(File file) {
return shortenFileName(file, MAXIMUM_SHORTENED_FILE_NAME_LENGTH);
}
/**
* Return a shorter version of the absolute file name for the specified file.
* The shorter version will not be longer than the maximum length.
* The first directory (usually the drive letter) and the file name or the
* last directory will always be added to the generated string regardless of
* the maximum length allowed.
*/
public static String shortenFileName(File file, int maxLength) {
String absoluteFileName = canonicalFile(file).getAbsolutePath();
if (absoluteFileName.length() <= maxLength) {
// no need to shorten
return absoluteFileName;
}
// break down the path into its components
String fs = File.separator;
String[] paths = absoluteFileName.split('\\' + fs);
if (paths.length <= 1) {
// e.g. "C:\"
return paths[0];
}
if (paths.length == 2) {
// e.g. "C:\MyReallyLongFileName.ext" or "C:\MyReallyLongDirectoryName"
// return the complete file name since this is a minimum requirement,
// regardless of the maximum length allowed
return absoluteFileName;
}
StringBuilder sb = new StringBuilder();
sb.append(paths[0]); // always add the first directory, which is usually the drive letter
// Keep the index of insertion into the string buffer
int insertIndex = sb.length();
sb.append(fs);
sb.append(paths[paths.length - 1]); // append the file name or the last directory
maxLength -= 4; // -4 for "/..."
int currentLength = sb.length() - 4; // -4 for "/..."
int leftIndex = 1; // 1 to skip the root directory
int rightIndex = paths.length - 2; // -1 for the file name or the last directory
boolean canAddFromLeft = true;
boolean canAddFromRight = true;
// Add each directory, the insertion is going in both direction: left and
// right, once a side can't be added, the other side is still continuing
// until both can't add anymore
while (true) {
if (!canAddFromLeft && !canAddFromRight)
break;
if (canAddFromRight) {
String rightDirectory = paths[rightIndex];
int rightLength = rightDirectory.length();
// Add the directory on the right side of the loop
if (currentLength + rightLength + 1 <= maxLength) {
sb.insert(insertIndex, fs);
sb.insert(insertIndex + 1, rightDirectory);
currentLength += rightLength + 1;
rightIndex--;
// The right side is now overlapping the left side, that means
// we can't add from the right side anymore
if (leftIndex >= rightIndex) {
canAddFromRight = false;
}
} else {
canAddFromRight = false;
}
}
if (canAddFromLeft) {
String leftDirectory = paths[leftIndex];
int leftLength = leftDirectory.length();
// Add the directory on the left side of the loop
if (currentLength + leftLength + 1 <= maxLength) {
sb.insert(insertIndex, fs);
sb.insert(insertIndex + 1, leftDirectory);
insertIndex += leftLength + 1;
currentLength += leftLength + 1;
leftIndex++;
// The left side is now overlapping the right side, that means
// we can't add from the left side anymore
if (leftIndex >= rightIndex) {
canAddFromLeft = false;
}
} else {
canAddFromLeft = false;
}
}
}
if (leftIndex <= rightIndex) {
sb.insert(insertIndex, fs);
sb.insert(insertIndex + 1, "..."); //$NON-NLS-1$
}
return sb.toString();
}
// ********** system properties **********
/**
* Return a file representing the user's home directory.
*/
public static File userHomeDirectory() {
return new File(USER_HOME_DIRECTORY_NAME);
}
/**
* Return a file representing the user's temporary directory.
*/
public static File userTemporaryDirectory() {
return new File(USER_TEMPORARY_DIRECTORY_NAME);
}
/**
* Return a file representing the current working directory.
*/
public static File currentWorkingDirectory() {
return new File(CURRENT_WORKING_DIRECTORY_NAME);
}
// ********** miscellaneous **********
/**
* Return only the files that fit the filter.
* File#files(FileFilter fileFilter)
*/
public static Iterator<File> filter(Iterator<File> files, final FileFilter fileFilter) {
return new FilteringIterator<File>(files) {
@Override
protected boolean accept(File next) {
return fileFilter.accept(next);
}
};
}
/**
* Return a file that is a re-specification of the specified
* file, relative to the specified directory.
* Linux/Unix/Mac:
* convertToRelativeFile(/foo/bar/baz.java, /foo)
* => bar/baz.java
* Windows:
* convertToRelativeFile(C:\foo\bar\baz.java, C:\foo)
* => bar/baz.java
* The file can be either a file or a directory; the directory
* *should* be a directory.
* If the file is already relative or it cannot be made relative
* to the directory, it will be returned unchanged.
*
* NB: This method has been tested on Windows and Linux,
* but not Mac (but the Mac is Unix-based these days, so
* it shouldn't be a problem...).
*/
public static File convertToRelativeFile(final File file, final File dir) {
// check whether the file is already relative
if ( ! file.isAbsolute()) {
return file; // return unchanged
}
File cFile = canonicalFile(file);
File cDir = canonicalFile(dir);
// the two are the same directory
if (cFile.equals(cDir)) {
return new File("."); //$NON-NLS-1$
}
File[] filePathFiles = pathFiles(cFile);
File[] dirPathFiles = pathFiles(cDir);
// Windows only (?): the roots are different - e.g. D:\ vs. C:\
if ( ! dirPathFiles[0].equals(filePathFiles[0])) {
return file; // return unchanged
}
// at this point we know the root is the same, now find how much is in common
int i = 0; // this will point at the first miscompare
while ((i < dirPathFiles.length) && (i < filePathFiles.length)) {
if (dirPathFiles[i].equals(filePathFiles[i])) {
i++;
} else {
break;
}
}
// save our current position
int firstMismatch = i;
// check whether the file is ABOVE the directory: ../..
if (firstMismatch == filePathFiles.length) {
return relativeParentFile(dirPathFiles.length - firstMismatch);
}
// build a new file from the path beyond the matching portions
File diff = new File(filePathFiles[i].getName());
while (++i < filePathFiles.length) {
diff = new File(diff, filePathFiles[i].getName());
}
// check whether the file is BELOW the directory: subdir1/subdir2/file.ext
if (firstMismatch == dirPathFiles.length) {
return diff;
}
// the file must be a PEER of the directory: ../../subdir1/subdir2/file.ext
return new File(relativeParentFile(dirPathFiles.length - firstMismatch), diff.getPath());
}
/**
* Return a file that is a re-specification of the specified
* file, relative to the current working directory.
* Linux/Unix/Mac (CWD = /foo):
* convertToRelativeFile(/foo/bar/baz.java)
* => bar/baz.java
* Windows (CWD = C:\foo):
* convertToRelativeFile(C:\foo\bar\baz.java)
* => bar/baz.java
* The file can be either a file or a directory.
* If the file is already relative or it cannot be made relative
* to the directory, it will be returned unchanged.
*
* NB: This method has been tested on Windows and Linux,
* but not Mac (but the Mac is Unix-based these days, so
* it shouldn't be a problem...).
*/
public static File convertToRelativeFile(final File file) {
return convertToRelativeFile(file, currentWorkingDirectory());
}
/**
* Return an array of files representing the path to the specified
* file. For example:
* C:/foo/bar/baz.txt =>
* { C:/, C:/foo, C:/foo/bar, C:/foo/bar/baz.txt }
*/
private static File[] pathFiles(File file) {
List<File> path = new ArrayList<File>();
for (File f = file; f != null; f = f.getParentFile()) {
path.add(f);
}
Collections.reverse(path);
return path.toArray(new File[path.size()]);
}
/**
* Return a file with the specified (non-zero) number of relative
* file names, e.g. xxx(3) => ../../..
*/
private static File relativeParentFile(int len) {
if (len <= 0) {
throw new IllegalArgumentException("length must be greater than zero: " + len); //$NON-NLS-1$
}
File result = new File(".."); //$NON-NLS-1$
for (int i = len - 1; i-- > 0; ) {
result = new File(result, ".."); //$NON-NLS-1$
}
return result;
}
/**
* Return a file that is a re-specification of the specified
* file, absolute to the specified directory.
* Linux/Unix/Mac:
* convertToAbsoluteFile(bar/baz.java, /foo)
* => /foo/bar/baz.java
* Windows:
* convertToAbsoluteFile(bar/baz.java, C:\foo)
* => C:\foo\bar\baz.java
* The file can be either a file or a directory; the directory
* *should* be a directory.
* If the file is already absolute, it will be returned unchanged.
*
* NB: This method has been tested on Windows and Linux,
* but not Mac (but the Mac is Unix-based these days, so
* it shouldn't be a problem...).
*/
public static File convertToAbsoluteFile(final File file, final File dir) {
// check whether the file is already absolute
if (file.isAbsolute()) {
return file; // return unchanged
}
return canonicalFile(new File(dir, file.getPath()));
}
/**
* Return a file that is a re-specification of the specified
* file, absolute to the current working directory.
* Linux/Unix/Mac (CWD = /foo):
* convertToAbsoluteFile(bar/baz.java)
* => /foo/bar/baz.java
* Windows (CWD = C:\foo):
* convertToAbsoluteFile(bar/baz.java)
* => C:\foo\bar\baz.java
* The file can be either a file or a directory.
* If the file is already absolute, it will be returned unchanged.
*
* NB: This method has been tested on Windows and Linux,
* but not Mac (but the Mac is Unix-based these days, so
* it shouldn't be a problem...).
*/
public static File convertToAbsoluteFile(final File file) {
return convertToAbsoluteFile(file, currentWorkingDirectory());
}
// ********** constructor **********
/**
* Suppress default constructor, ensuring non-instantiability.
*/
private FileTools() {
super();
throw new UnsupportedOperationException();
}
}