Java – How do I create a FileSystem object for *directory* using absolute paths?

How do I create a FileSystem object for *directory* using absolute paths?… here is a solution to the problem.

How do I create a FileSystem object for *directory* using absolute paths?

I’m creating a command-line application that needs to output some files (multiple) to a ZIP file or a normal folder depending on the given arguments

My approach is to wrap the target (normal folder/ZIP file) with FileSystem.

My problem is that I can’t successfully create a FileSystem object for the directory instead of the current working directory representing the absolute path on my hard drive:

public class FileSystemWriteTest {
    public static void main(String[] args) throws IOException {
        Path absolutePath = Paths.get("target", "testpath").toAbsolutePath();
        System.out.println(String.format("user.dir before change:\n %s", System.getProperty("user.dir")));

System.setProperty("user.dir", absolutePath.toString());
        System.out.println(String.format("changed user.dir:\n %s", System.getProperty("user.dir")));
        FileSystem defaultSystem = FileSystems.getDefault();
        Path testFilePath = defaultSystem.getPath("test.file");
        System.out.println(String.format("expected to be in changed user.dir:\n %s", testFilePath.toAbsolutePath()));

URI uri = absolutePath.toUri();
        System.out.println(String.format("URI: %s", uri));
        FileSystem localFileSystem =
                FileSystems.newFileSystem(uri, Collections.emptyMap());
        Path file = localFileSystem.getPath("test.txt");
        System.out.println(file.toAbsolutePath());
    }
}

The output is:

user.dir before change:
 D:\data\scm-workspace\anderes\Test
changed user.dir:
 D:\data\scm-workspace\anderes\Test\target\testpath
expected to be in changed user.dir:
 D:\data\scm-workspace\anderes\Test\test.file
URI: file:///D:/data/scm-workspace/anderes/Test/target/testpath/
Exception in thread "main" java.lang.IllegalArgumentException: Path component should be '/'
    at sun.nio.fs.WindowsFileSystemProvider.checkUri(Unknown Source)
    at sun.nio.fs.WindowsFileSystemProvider.newFileSystem(Unknown Source)
    at java.nio.file.FileSystems.newFileSystem(Unknown Source)
    at java.nio.file.FileSystems.newFileSystem(Unknown Source)
    at com.oc.test.filesystem.FileSystemWriteTest.main(FileSystemWriteTest.java:27)

If I change to FileSystems.newFileSystem(Path, Classloader), the exception changes to:

Exception in thread "main" java.nio.file.ProviderNotFoundException: Provider not found
    at java.nio.file.FileSystems.newFileSystem(Unknown Source)
    at com.oc.test.filesystem.FileSystemWriteTest.main(FileSystemWriteTest.java:27)

It looks like this only works for regular files, not directories.

So how do I create a FileSystem object for a directory other than pwd?

Solution

There are no built-in tools for creating FileSystem with chroot-like semantics. The default file system only supports file:/// as a URI and does not allow multiple instantiations.

In this respect, FileSystems.getDefault().getPath(“test.file”) creates a relative path, just like Paths.get("test.file"). The difference between relative paths created for the default file system and other file systems is the parsing behavior when no other base path is specified (for example, when toAbsolutePath() is called or simply trying to open them). But parsing against the current working directory does not make them root.

The best solution for file system agnostic operations is to have your code receive a base Path object to resolve relative paths.

For example, a simple tree copy routine might look like this:

static void copyTree(Path sourceBase, Path targetBase) throws IOException {
    try {
        Files.walk(sourceBase).forEach(path -> {
            if(Files.isRegularFile(path)) try {
                Path target = targetBase.resolve(sourceBase.relativize(path).toString());
                if(! Files.isDirectory(target.getParent()))
                    Files.createDirectories(target.getParent());
                Files.copy(path, target, StandardCopyOption.COPY_ATTRIBUTES);
            } catch(IOException ex) {
                throw new UncheckedIOException(ex);
            }
        });
    } catch(UncheckedIOException ex) {
        throw ex.getCause();
    }
}

For this method, it doesn’t matter whether you copy from one hard drive directory to another, or to a zip file system, or from one zip file

system to a hard drive, or from one zip file to another, and so on

The most interesting part is calling sourceBase.relativize(path), which gets the relative path from the source base path to the actual file subpath. Since relative Path instances are still bound (bind) to a particular file system, the code calls toString() before passing it to targetBase.resolve(...) to ensure that it works across different file systems. Note that path.resolve(string) is equivalent to path.resolve(path.getFileSystem().getPath(string)). It would be legal if the method first checked whether two Path instances belong to the same file system, in which case skipping the String detour.

Related Problems and Solutions