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 URI
s 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:
Paths.get(URI)
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?
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With