开发者

Generating a canonical path

开发者 https://www.devze.com 2022-12-27 21:10 出处:网络
Does any one know of any Java libraries I could use to generate canonical paths (basically remove back-references).

Does any one know of any Java libraries I could use to generate canonical paths (basically remove back-references).

I need something that will do the following:

Raw Path -> Canonical Path

/../foo/       -> /foo
/foo/          -> /foo
/../../../     -> /
/./foo/./      -> /foo
//foo//bar     -> /foo/bar
//foo/../bar   -> /bar

etc...

At the moment I lazily rely on using:

 new File("/", path).getCanonicalPath();

But this resolves the path against the actual file system, and is synchronised.

   java.lang.Thread.State: BLOCKED (on object monitor)
        at java.io.ExpiringCache.get(ExpiringCache.java:55)
        - waiting to lock <0x93a0d180> (a java.io.ExpiringCache)
        at java.io.UnixFileSystem.canonicalize(UnixFileSystem.java:137)
        at java.io.File.getCanonicalPath(File.java:559)

The paths that I am canonicalising do not exist on my file system, so just the logic of the method will do me fine, thus not requiring any synchronisation. I'm hoping for a well te开发者_Go百科sted library rather than having to write my own.


I think you can use the URI class to do this; e.g. if the path contains no characters that need escaping in a URI path component, you can do this.

String normalized = new URI(path).normalize().getPath();

If the path contains (or might contain) characters that need escaping, the multi-argument constructors will escape the path argument, and you can provide null for the other arguments.

Notes:

  1. The above normalizes a file path by treating it as a relative URI. If you want to normalize an entire URI ... including the (optional) scheme, authority, and other components, don't call getPath()!

  2. URI normalization does not involve looking at the file system as File canonicalization does. But the flip side is that normalization behaves differently to canonicalization when there are symbolic links in the path.


Using Apache Commons IO (a well-known and well-tested library)

public static String normalize(String filename)

will do exactly what you're looking for.

Example:

String result = FilenameUtils.normalize(myFile.getAbsolutePath());


If you don't need path canonization but only normalization, in Java 7 you can use java.nio.file.Path.normalize method. According to http://docs.oracle.com/javase/7/docs/api/java/nio/file/Path.html:

This method does not access the file system; the path may not locate a file that exists.

If you work with File object you can use something like this:

file.toPath().normalize().toFile()


You could try an algorithm like this:

String collapsePath(String path) {
    /* Split into directory parts */
    String[] directories = path.split("/");
    String[] newDirectories = new String[directories.length];
    int i, j = 0;

    for (i=0; i<directories.length; i++) {
        /* Ignore the previous directory if it is a double dot */
        if (directories[i].equals("..") && j > 0)
            newDirectories[j--] = "";
        /* Completely ignore single dots */
        else if (! directories[i].equals("."))
            newDirectories[j++] = directories[i];
    }

    /* Ah, what I would give for String.join() */
    String newPath = new String();
    for (i=0; i < j; i++)
        newPath = newPath + "/" + newDirectories[i];
    return newPath;
}

It isn't perfect; it's linear over the number of directories but does make a copy in memory.


Which kind of path is qualified as a Canonical Path is OS dependent. That's why Java need to check it on the filesystem. So there's no simple logic to test the path without knowing the OS.


So, while normalizing can do the trick, here is a procedure that exposes a little more of the Java API than would simply calling Paths.normalize()

Say I want to find a file that is not in my current directory on the file system. My working code file is

myproject/src/JavaCode.java

Located in myproject/src/. My file is in

../../data/myfile.txt

I'm testing my program running my code from JavaCode.java

public static void main(String[] args) { 
    findFile("../../data","myfile.txt");
    System.out.println("Found it.");
}
public static File findFile(String inputPath, String inputFile) {
    File dataDir = new File("").getAbsoluteFile(); // points dataDir to working directory
    String delimiters = "" + '\\' + '/';           // dealing with different system separators
    StringTokenizer st = new StringTokenizer(inputPath, delimiters);
    while(st.hasMoreTokens()) {
        String s = st.nextToken();
        if(s.trim().isEmpty() || s.equals(".")) 
            continue;
        else if(s.equals("..")) 
            dataDir = dataDir.getParentFile();
        else {
            dataDir = new File(dataDir, s);
            if(!dataDir.exists())
                throw new RuntimeException("Data folder does not exist.");
        }
    }
    return new File(dataDir, inputFile);    
}

Having placed a file at the specified location, this should print "Found it."


I'm assuming you have strings and you want strings, and you have Java 7 available now, and your default file system uses '/' as a path separator, so try:

String output = FileSystems.getDefault().getPath(input).normalize().toString();

You can try this out with:

/**
 * Input           Output
 * /../foo/     -> /foo
 * /foo/        -> /foo
 * /../../../   -> /
 * /./foo/./    -> /foo
 * //foo//bar   -> /foo/bar
 * //foo/../bar -> /bar
 */
@Test
public void testNormalizedPath() throws URISyntaxException, IOException {
    String[] in = new String[]{"/../foo/", "/foo/", "/../../../", "/./foo/./",
            "//foo/bar", "//foo/../bar", "/", "/foo"};
    String[] ex = new String[]{"/foo", "/foo", "/", "/foo", "/foo/bar", "/bar", "/", "/foo"};
    FileSystem fs = FileSystems.getDefault();
    for (int i = 0; i < in.length; i++) {
        assertEquals(ex[i], fs.getPath(in[i]).normalize().toString());
    }
}
0

精彩评论

暂无评论...
验证码 换一张
取 消