NIO2: how to generically map a URI to a Path?

落爺英雄遲暮 提交于 2019-12-10 20:16:45

问题


I'm trying to find an easy way to map a URI to a Path without writing code specific to any particular file system. The following seems to work but requires a questionable technique:

public void process(URI uri) throws IOException {
    try {
        // First try getting a path via existing file systems. (default fs)
        Path path = Paths.get(uri);
        doSomething(uri, path);
    }
    catch (FileSystemNotFoundException e) {
        // No existing file system, so try creating one. (jars, zips, etc.)
        Map<String, ?> env = Collections.emptyMap();
        try (FileSystem fs = FileSystems.newFileSystem(uri, env)) {
            Path path = fs.provider().getPath(uri);  // yuck :(
            // assert path.getFileSystem() == fs;
            doSomething(uri, path);
        }
    }
}

private void doSomething(URI uri, Path path) {
    FileSystem fs = path.getFileSystem();
    System.out.println(uri);
    System.out.println("[" + fs.getClass().getSimpleName() + "] " + path);
}

Running this code on a couple examples produces the following:

file:/C:/Users/cambecc/target/classes/org/foo
[WindowsFileSystem] C:\Users\cambecc\target\classes\org\foo

jar:file:/C:/Users/cambecc/bin/utils-1.0.jar!/org/foo
[ZipFileSystem] /org/foo

Notice how the URIs have been mapped to Path objects that have been "rooted" into the right kind of FileSystem, like the Path referring to the directory "/org/foo" inside a jar.

What bothers me about this code is that although NIO2 makes it easy to:

  • map a URI to a Path in existing file systems: Paths.get(URI)
  • map a URI to a new FileSystem instance: FileSystems.newFileSystem(uri, env)

... there is no nice way to map a URI to a Path in a new FileSystem instance.

The best I could find was, after creating a FileSystem, I can ask its FileSystemProvider to give me Path:

Path path = fs.provider().getPath(uri);

But this seems wrong as there is no guarantee it will return a Path that is bound to the FileSystem that I just instantiated (i.e., path.getFileSystem() == fs). It's pretty much relying on the internal state of FileSystemProvider to know what FileSystem instance I'm referring to. Is there no better way?


回答1:


Q: "I'm trying to find an easy way to map a URI to a Path without writing code specific to any particular file system"

A: There is no such way

The whole question is only interesting if the filesystem associated with the URI is not open yet, i.e. when getFileSystem (in Paths.get) throws FileSystemNotFoundException. But to call newFileSystem you need to know 2 things:

  • what (part of the) URI to use to create the new filesystem. The docu says that i.e. in the case of the default filesystem the path component of the URI must be a root. e.g. getFileSystem( URI.create( "file:///duda", Collections.EMPTY_MAP) fails.
  • what to set in the environment map, e.g. might be a password.

So to create a new filesystem from an URI you must have knowledge about the filesystem to create.




回答2:


You found a bug in the implementation/documentation of the zipfs.The documentation of the Path.get methods states:

* @throws  FileSystemNotFoundException
*          The file system, identified by the URI, does not exist and
*          cannot be created automatically

edit: In the case of FileSystems that need closing it might be better to require the programmer to call newFileSystem so that he can close it. The documentation should better read "if it should not be created" automatically.

ZipFs never tries to create a new filessystem. A failed get() is not caught but passed to be caller before an attempted newFileSystem call. See in the source:

public Path getPath(URI uri) {

    String spec = uri.getSchemeSpecificPart();
    int sep = spec.indexOf("!/");
    if (sep == -1)
        throw new IllegalArgumentException("URI: "
            + uri
            + " does not contain path info ex. jar:file:/c:/foo.zip!/BAR");
    return getFileSystem(uri).getPath(spec.substring(sep + 1));
}

In other words:

Paths.get()

should be enough for all FileSystems based on nio2. With the zipfs design.

Path path;
try {
   path = Paths.get( uri );
} catch ( FileSystemNotFoundException exp ) {
   try( FileSystem fs = FileSystems.newFileSystem( uri, Collections.EMPTY_MAP )) {;
       path = Paths.get( uri );
       ... use path ...
   }
}   

Is the short form of your workaround.

Note: The nio documentation states that the getFileSystem must use/return the FileSystems created by the matching newFileSystem.



来源:https://stackoverflow.com/questions/15195890/nio2-how-to-generically-map-a-uri-to-a-path

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!