The most common solution is as follows:
interface IOConsumer<T> { void accept(T t) throws IOException; } public static void processRessource(URI uri, IOConsumer<Path> action) throws IOException { try { Path p=Paths.get(uri); action.accept(p); } catch(FileSystemNotFoundException ex) { try(FileSystem fs = FileSystems.newFileSystem( uri, Collections.<String,Object>emptyMap())) { Path p = fs.provider().getPath(uri); action.accept(p); } } }
The main obstacle is the use of two possibilities: either to have an existing file system that we must use but not close (for example, with a file URI or Java 9s module repository) or open and thus safely close the file system ourselves (like zip / files jar).
Thus, the solution above encapsulates the actual action in the interface , handles both cases, then closes securely in the second case and works with Java 7 in Java 10. It checks if there is an open file system before opening a new one, therefore it also works in that in case another component of your application has already opened the file system for the same zip / jar file.
It can be used in all versions of Java above, for example, to list the contents of a package ( java.lang in the example) as Path s, for example:
processRessource(Object.class.getResource("Object.class").toURI(), new IOConsumer<Path>() { public void accept(Path path) throws IOException { try(DirectoryStream<Path> ds = Files.newDirectoryStream(path.getParent())) { for(Path p: ds) System.out.println(p); } } });
With Java 8 or later, you can use lambda expressions or method references to represent the actual action, for example
processRessource(Object.class.getResource("Object.class").toURI(), path -> { try(Stream<Path> stream = Files.list(path.getParent())) { stream.forEach(System.out::println); } });
do the same thing.
The final release of the Java 9s modular system violated the above code example. The JRE inconsistently returns the path /java.base/java/lang/Object.class for Object.class.getResource("Object.class") whereas it should be /modules/java.base/java/lang/Object.class . This can be fixed by adding the missing /modules/ when the parent path is reported as non-existent:
processRessource(Object.class.getResource("Object.class").toURI(), path -> { Path p = path.getParent(); if(!Files.exists(p)) p = p.resolve("/modules").resolve(p.getRoot().relativize(p)); try(Stream<Path> stream = Files.list(p)) { stream.forEach(System.out::println); } });
Then it will work again with all versions and storage methods.