Case-insensitive File.equals on case-sensitive file system

19,277

Solution 1

Get the list of files from the directory (File.list()) and compare the names using equalsIgnoreCase().

Solution 2

This method will tell you if a file with the exact name in question exists (the path portion is not case sensitive).

public static boolean caseSensitiveFileExists(String pathInQuestion) {
  File f = new File(pathInQuestion);
  return f.exists() && f.getCanonicalPath().endsWith(f.getName());
}

Solution 3

As jwaddell said, it looks like the VERY SLOW recursive path checking is (apparently) the only way to do this. Here is my function written in java that accepts a String which is a filepath. If the string representation of the filepath exists and has identical case sensitivity to the one reported by windows, then it returns true, else false.

public boolean file_exists_and_matches_case(
        String full_file_path) {

    //Returns true only if:
    //A. The file exists as reported by .exists() and
    //B. Your path string passed in matches (case-sensitivity) the entire
    //   file path stored on disk.

    //This java method was built for a windows file system only,
    //no guarantees for mac/linux/other.
    //It takes a String parameter like this:
    //"C:\\projects\\eric\\snalu\\filename.txt"
    //The double backslashes are needed to escape the one backslash.

    //This method has partial support for the following path:
    //"\\\\yourservername\\foo\\bar\\eleschinski\\baz.txt".
    //The problem is it stops recusing at directory 'foo'.
    //It ignores case at 'foo' and above.  So this function 
    //only detects case insensitivity after 'foo'.


    if (full_file_path == null) {
        return false;
    }

    //You are going to have to define these chars for your OS.  Backslash
    //is not specified here becuase if one is seen, it denotes a
    //directory delimiter:  C:\filename\fil\ename
    char[] ILLEGAL_CHARACTERS = {'/', '*', '?', '"', '<', '>', '>', '|'};
    for (char c : ILLEGAL_CHARACTERS) {
        if (full_file_path.contains(c + "")) {
            throw new RuntimeException("Invalid char passed in: "
                    + c + " in " + full_file_path);
        }
    }

    //If you don't trim, then spaces before a path will 
    //cause this: 'C:\default\ C:\mydirectory'
    full_file_path = full_file_path.trim();
    if (!full_file_path.equals(new File(full_file_path).getAbsolutePath()))
    {
        //If converting your string to a file changes the directory in any
        //way, then you didn't precisely convert your file to a string.
        //Programmer error, fix the input.
        throw new RuntimeException("Converting your string to a file has " +
            "caused a presumptous change in the the path.  " + full_file_path +
            " to " + new File(full_file_path).getAbsolutePath());
    }

    //If the file doesn't even exist then we care nothing about
    //uppercase lowercase.
    File f = new File(full_file_path);
    if (f.exists() == false) {
        return false;
    }

    return check_parent_directory_case_sensitivity(full_file_path);
}

public boolean check_parent_directory_case_sensitivity(
        String full_file_path) {
    //recursively checks if this directory name string passed in is
    //case-identical to the directory name reported by the system.
    //we don't check if the file exists because we've already done
    //that above.

    File f = new File(full_file_path);
    if (f.getParent() == null) {
        //This is the recursion base case.
        //If the filename passed in does not have a parent, then we have
        //reached the root directory.  We can't visit its parent like we
        //did the other directories and query its children so we have to
        //get a list of drive letters and make sure your passed in root
        //directory drive letter case matches the case reported
        //by the system.

        File[] roots = File.listRoots();
        for (File root : roots) {
            if (root.getAbsoluteFile().toString().equals(
                    full_file_path)) {
                return true;
            }
        }
        //If we got here, then it was because everything in the path is
        //case sensitive-identical except for the root drive letter:
        //"D:\" does not equal "d:\"
        return false;

    }

    //Visit the parent directory and list all the files underneath it.
    File[] list = new File(f.getParent()).listFiles();

    //It is possible you passed in an empty directory and it has no
    //children.  This is fine.
    if (list == null) {
        return true;
    }

    //Visit each one of the files and folders to get the filename which
    //informs us of the TRUE case of the file or folder.
    for (File file : list) {
        //if our specified case is in the list of child directories then
        //everything is good, our case matches what the system reports
        //as the correct case.

        if (full_file_path.trim().equals(file.getAbsolutePath().trim())) {
            //recursion that visits the parent directory
            //if this one is found.
            return check_parent_directory_case_sensitivity(
                    f.getParent().toString());
        }
    }

    return false;

}

Solution 4

If the discrepancies are random, then to me Shimi's solution including recursive path segment checking is the best solution. It sounds ugly at a first glance but you can hide the magic in a separate class and implement a simple API to return the file handle for a given file name, so you just see something like a Translator.translate(file) call.

Maybe, the discrepancies are kind of static, predictable. Then I would prefer a dictionary that can be used to translate a given file name to Windows/Linux file names. This has on big advantage over the other method: the risk to get a wrong file handle is smaller.

If the dictionary was really static, you could create and maintain a properties file. If it was static but more complex, say a given file name could be translated to more than one possible target file names, I'd back up the dictonary class with a Map<String, Set<String>> datastructure (Set preferred over List because there are no duplicate alternates).

Solution 5

Here's my Java 7 solution, for situations where a parent path is known and a relative child path may have different case to the path on disk.

For example, given the file /tmp/foo/biscuits, the method will correctly return a Path to the file with the following input:

  • /tmp and foo/biscuits
  • /tmp and foo/BISCUITS
  • /tmp and FOO/BISCUITS
  • /tmp and FOO/biscuits

Note that this solution has not been robustly tested so should be considered a starting point rather than a production-ready snippet.

/**
 * Returns an absolute path with a known parent path in a case-insensitive manner.
 * 
 * <p>
 * If the underlying filesystem is not case-sensitive or <code>relativeChild</code> has the same
 * case as the path on disk, this method is equivalent to returning
 * <code>parent.resolve(relativeChild)</code>
 * </p>
 * 
 * @param parent parent to search for child in
 * @param relativeChild relative child path of potentially mixed-case
 * @return resolved absolute path to file, or null if none found
 * @throws IOException
 */
public static Path getCaseInsensitivePath(Path parent, Path relativeChild) throws IOException {

    // If the path can be resolved, return it directly
    if (isReadable(parent.resolve(relativeChild))) {
        return parent.resolve(relativeChild);
    }

    // Recursively construct path
    return buildPath(parent, relativeChild);
}

private static Path buildPath(Path parent, Path relativeChild) throws IOException {
    return buildPath(parent, relativeChild, 0);
}

/**
 * Recursively searches for and constructs a case-insensitive path
 * 
 * @param parent path to search for child
 * @param relativeChild relative child path to search for
 * @param offset child name component
 * @return target path on disk, or null if none found
 * @throws IOException
 */
private static Path buildPath(Path parent, Path relativeChild, int offset) throws IOException {
    try (DirectoryStream<Path> stream = Files.newDirectoryStream(parent)) {
        for (Path entry : stream) {

            String entryFilename = entry.getFileName().toString();
            String childComponent = relativeChild.getName(offset).toString();

            /*
             * If the directory contains a file or folder corresponding to the current component of the
             * path, either return the full path (if the directory entry is a file and we have iterated
             * over all child path components), or recurse into the next child path component if the
             * match is on a directory.
             */
            if (entryFilename.equalsIgnoreCase(childComponent)) {
                if (offset == relativeChild.getNameCount() - 1 && Files.isRegularFile(entry)) {
                    return entry;
                }
                else if (Files.isDirectory(entry)) {
                    return buildPath(entry, relativeChild, offset + 1);
                }
            }
        }
    }

    // No matches found; path can't exist
    return null;
}
Share:
19,277
jwaddell
Author by

jwaddell

Updated on June 05, 2022

Comments

  • jwaddell
    jwaddell almost 2 years

    I have a file path in String form. In Java, I need to determine if that file exists on the file system (and our code needs to be cross-platform as it runs on Windows, Linux and OS X).

    The problem is that the case of the file path and the file itself may not match, even though they do represent the same file (presumably this is because they originated on Windows and the discrepancy was not noticed).

    For example, I have a file path of "ABC.txt". A file called "abc.txt" exists on the file system. The following code will return true on Windows but false on Linux:

    new File("ABC.txt").exists();
    

    What is the best way to determine if the file exists, and if it exists to return a handle to the file on the file system?

  • jwaddell
    jwaddell over 14 years
    Yes that's a possbility, but the problem still exists if directories in the path are the wrong case. The solution might end up being some kind of recursive algorithm that goes up the directory tree doing case-insensitive searches. But I'm hoping for a better solution!
  • Dave Thomas
    Dave Thomas over 14 years
    @jwaddell: i don't think there is a better solution since the filename/path can be in any casing and the linux os treats it in a case sensitive mode.
  • jwaddell
    jwaddell over 14 years
    Unfortunately the file names could be anything.
  • jwaddell
    jwaddell over 14 years
    Looks like I'll reluctantly implement this solution, plus recursive path checking.
  • jwaddell
    jwaddell over 12 years
    That will still return false on Linux for my example, as f.exists() will return false.
  • Dmitry
    Dmitry almost 12 years
    +1 Not exactly the answer for topic question, but helped me to perform case-sensitive check on Windows.
  • Arne Deutsch
    Arne Deutsch over 6 years
    Path.toRealPath has worked well for me to convert a file to its real representation.
  • Matthieu
    Matthieu over 6 years
    Good for Windows, but still fail on Linux though.