2009-08-19 16 views
13

Tengo una ruta de archivo en forma de cadena. En Java, necesito determinar si ese archivo existe en el sistema de archivos (y nuestro código debe ser multiplataforma ya que se ejecuta en Windows, Linux y OS X).FileEquals insensible a archivos en el sistema de archivos con distinción entre mayúsculas y minúsculas

El problema es que el caso de la ruta del archivo y el archivo puede no coincidir, aunque representan el mismo archivo (presumiblemente porque se originó en Windows y no se notó la discrepancia).

Por ejemplo, tengo una ruta de archivo de "ABC.txt". Un archivo llamado "abc.txt" existe en el sistema de archivos. El siguiente código volverá cierto en Windows, pero falsa en Linux:

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

¿Cuál es la mejor manera de determinar si el archivo existe, y si existe para devolver un identificador para el archivo en el archivo ¿sistema?

Respuesta

13

Obtenga la lista de archivos del directorio (File.list()) y compare los nombres usando equalsIgnoreCase().

+0

Sí, eso es una posibilidad, pero el problema todavía existe si los directorios en la ruta son el caso incorrecto. La solución podría terminar siendo algún tipo de algoritmo recursivo que sube al árbol de directorios haciendo búsquedas insensibles a mayúsculas y minúsculas. ¡Pero espero una mejor solución! – jwaddell

+1

@jwaddell: no creo que haya una solución mejor, ya que el nombre de archivo/ruta puede estar en cualquier envoltura y el sistema operativo Linux lo trata en un modo sensible a mayúsculas y minúsculas. –

+0

Parece que implementaré de mala gana esta solución, además de la comprobación de ruta recursiva. – jwaddell

1

Si las discrepancias son aleatorias, para mí la solución de Shimi, incluida la comprobación recursiva de segmentos de ruta, es la mejor solución. Suena feo a primera vista, pero puede ocultar la magia en una clase separada e implementar una API simple para devolver el identificador del archivo para un nombre de archivo dado, por lo que solo verá algo así como una llamada Translator.translate(file).

Tal vez, las discrepancias son algo estáticas, predecibles. Entonces preferiría un diccionario que se pueda usar para traducir un nombre de archivo dado a nombres de archivo de Windows/Linux. Esto tiene una gran ventaja sobre el otro método: el riesgo de obtener un identificador de archivo incorrecto es menor.

Si el diccionario era realmente estático, podría crear y mantener un archivo de propiedades. Si fuera estático pero más complejo, digamos que un nombre de archivo determinado podría traducirse a más de un posible nombre de archivo de destino, respaldaría la clase dictonary con una estructura de datos Map<String, Set<String>> (Set como preferencia sobre List porque no hay duplicados alternativos).

+0

Lamentablemente, los nombres de archivo podrían ser cualquier cosa. – jwaddell

2

Como dijo jwaddell, parece que la comprobación de ruta recursiva MUY LENTA es (aparentemente) la única manera de hacerlo. Aquí está mi función escrita en java que acepta una Cadena que es una ruta de archivo. Si la representación de cadena de la ruta de archivo existe y tiene una sensibilidad de mayúsculas/minúsculas idéntica a la reportada por Windows, entonces devuelve verdadero, de lo contrario es falso.

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; 

} 
6

Este método le dirá si existe un archivo con el nombre exacto en cuestión (la parte de la ruta no distingue entre mayúsculas y minúsculas).

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

Eso seguirá siendo falso en Linux para mi ejemplo, ya que f.exists() devolverá falso. – jwaddell

+3

+1 No es exactamente la respuesta para la pregunta del tema, pero me ayudó a realizar la verificación de mayúsculas y minúsculas en Windows. – Dmitry

1

Aquí está mi solución Java 7, para situaciones donde se conoce una ruta principal y una ruta hija relativa puede tener un caso diferente a la ruta en el disco.

Por ejemplo, dado el archivo de /tmp/foo/biscuits, el método correctamente devolver un Path al archivo con la siguiente entrada:

  • /tmp y foo/biscuits
  • /tmp y foo/BISCUITS
  • /tmp y FOO/BISCUITS
  • /tmp y FOO/biscuits

Tenga en cuenta que esta solución tiene no ha sido probado de manera robusta, por lo que debe considerarse un punto de partida en lugar de un fragmento listo para la producción.

/** 
* 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; 
} 
0

En cuanto a la primera parte de la pregunta: utilizar Path.toRealPath. No solo maneja la distinción entre mayúsculas y minúsculas, sino también enlaces simbólicos (dependiendo de las opciones que proporciones como parámetros), etc. Esto requiere Java 7 o superior.

En cuanto a la segunda parte de la pregunta: no estoy seguro de lo que quiere decir con 'manejar'.

+0

Path.toRealPath me ha funcionado para convertir un archivo a su representación real. – Arne

+0

Bueno para Windows, pero aún así falla en Linux. – Matthieu

0

Puede hacer lo que busca con este código. Dado que el nombre del archivo canónico devuelve el nombre del archivo, distingue entre mayúsculas y minúsculas, si obtiene algo que no es igual, el archivo existe con el mismo nombre pero con un caso diferente.

En Windows, si el archivo existe, en cualquier caso, devolverá verdadero. Si el archivo no existe, el nombre canónico será el mismo, por lo que devolverá falso.

En Linux, si el archivo existe con un caso diferente, devolverá este nombre diferente, y el método devolverá verdadero. Si existe con el mismo caso, la primera prueba es verdadera.

En ambos casos, si el archivo no existe y el nombre y el nombre canónico son los mismos, el archivo realmente no existe.

public static boolean fileExistsCaseInsensitive(String path) { 
    try { 
     File file = new File(path); 
     return file.exists() || !file.getCanonicalFile().getName().equals(file.getName()); 
    } catch (IOException e) { 
     return false; 
    } 
} 
Cuestiones relacionadas