5

This is what I'm trying to do:

FileSystem fs1 = FileSystems.newFileSystem(Paths.get("f1.jar"), null);
FileSystem fs2 = FileSystems.newFileSystem(fs1.getPath("/f2.jar"), null);

but I get a java.nio.file.ProviderNotFoundException thrown by FileSystems.newFileSystem() on the second line.

What am I doing wrong?

Thanks!

jamp
  • 2,159
  • 1
  • 17
  • 28
  • 1
    So... both Java 7 and Java 8? Which one are you actually using? – bcsb1001 Oct 06 '14 at 15:28
  • @bcsb1001 forgot to remove the tag, fixed – jamp Oct 06 '14 at 15:35
  • Trying to open the zip file system directly using `new ZipFileSystemProvider().newFileSystem(` *path inside another zip* `, emptyMap())` you will get an `UnsupportedOperationException`. – Holger Oct 07 '14 at 18:47

3 Answers3

1

You have to extract nested jar first.

Edit: Answers on oracle forum don't give clear reason why you have to extract jar first.

Here is quote from Rajendra Gutupalli's blog (author of com.sun.nio.zipfs):

Let's assume that we have a Jar file nested inside a Zip. The following program prints the contents of the MANIFEST.MF file which is inside nested jarCompress1.jar file.

import java.io.BufferedInputStream; 
import java.nio.file.*; 
import java.util.HashMap; 
import java.util.Map;

public class ReadEntry {

    public static void main(String... args) throws Exception {
        Path zipfile = Path.get("c:/zips/zip1.zip");
        Map<String, String> env = new HashMap();
        FileSystem manager = FileSystems.newFileSystem(zipfile, env,null);
        Path path = manager.getPath("/jarCompress1.jar/META-INF/MANIFEST.MF");
        System.out.println("Reading input stream");
        BufferedInputStream bis = new BufferedInputStream(path.newInputStream());
        int ch = -1;
        while ((ch = bis.read()) != -1) {
            System.out.print((char) ch);
        }
    } 
}

And another one:

Important point to note here is, zip file path can expand to nested zips or jars in the file's path name. For example, /home/userA/zipfile.zip/DirA/dirB/jarFile.jar/META-INF/MANIFEST.MF accesses the jar file “jarFile.jar” inside Zip file “/home/userA/zipfile.zip”.

I couldn't reproduce claimed behavior. Next code:

try (FileSystem fs1 = FileSystems.newFileSystem(Paths.get("f1.zip"), null)) {
    Path path = fs1.getPath("/f2.zip/test.txt");
    Files.lines(path).forEach(System.out::println);
}

Gives exception

Exception in thread "main" java.nio.file.NoSuchFileException: f2.zip/test.txt
  at com.sun.nio.zipfs.ZipFileSystem.newInputStream(ZipFileSystem.java:544)
  at com.sun.nio.zipfs.ZipPath.newInputStream(ZipPath.java:645)
  at com.sun.nio.zipfs.ZipFileSystemProvider.newInputStream(ZipFileSystemProvider.java:278)
  at java.nio.file.Files.newInputStream(Files.java:152)
  at java.nio.file.Files.newBufferedReader(Files.java:2781)
  at java.nio.file.Files.lines(Files.java:3741)
  at java.nio.file.Files.lines(Files.java:3782)

May be someone would confirm that it's a bug or point to the error in my code.

Meanwhile returning to your original question. You cannot create FileSystem inside zip(jar) because there is no FileSystemProvider (look source code of newFileSystem method) which can create FileSystem instance from ZipPath. Hence you have to options extract the from outer zip or write your own FileSystemProvider implementation.

Community
  • 1
  • 1
user2418306
  • 2,352
  • 1
  • 22
  • 33
  • Please summarize the content of the link in your answer. If the link goes dead, this answer will not be helpful. – Thorn G Oct 06 '14 at 16:26
  • 4
    The blog you are referring to is way out-of-date. It was written years before Java 7 came out and is wrong regarding several issues, e.g. the Scheme is `Jar` rather than `Zip` and the zipfs is not read-only. So it’s like the blog is describing an entirely different software… – Holger Oct 07 '14 at 18:46
  • Firstly `ZipFileSystemProvider` extends `FileSystemProvider` which `@since 1.7`. Secondly look at stages dates of [JSR203](https://jcp.org/en/jsr/detail?id=203). Thirdly zip and jar essentially the same. – user2418306 Oct 07 '14 at 19:01
0

Given a jarfile URI like: jar:file:/console/console.jar!/BOOT-INF/lib/generator.jar!/generator/static/

I wrote the following method to extract the jars recursively and return all of the file systems:

private FileSystem getNestedFileSystems(URI generatorAssets) throws IOException {
    String[] jarFiles = generatorAssets.toString().split("!");
    URI rootJarUri = URI.create(jarFiles[0]);
    FileSystem currentFs;
    try {
        currentFs = FileSystems.getFileSystem(rootJarUri);
    } catch (FileSystemNotFoundException fsnf) {
        currentFs = FileSystems.newFileSystem(rootJarUri, Map.of("create", "true"));
    }
    Path currentJar = null;
    if (jarFiles.length > 2) {
        for (int i = 1; i < (jarFiles.length - 1); i++) {
            Path nestedJar = currentFs.getPath(jarFiles[i]);
            Path extractedJar = Files.createTempFile("jar-" + i, ".jar");
            Files.copy(nestedJar, extractedJar, StandardCopyOption.REPLACE_EXISTING);
            currentFs.close();
            if (currentJar != null) {
                Files.delete(currentJar);
            }
            currentJar = extractedJar;
            currentFs = FileSystems.newFileSystem(URI.create("jar:file:" + extractedJar),
                    Map.of("create", "true"));
        }
    }
    return currentFs;
}

Returning a list of filesystems so you can close them all after use and not leak FD's. The last FileSystem will be able to get your desired directory/object/etc

Dan
  • 51
  • 1
  • 3
  • You copy the file contents. The OP wants to access the file in a nested JAR without extracting it (using only FileSystem API). – vadipp Feb 18 '22 at 09:44
0

The problem is solved at least in Java 14 (maybe earlier). OP's code works there with one change: you need to add an explicit cast to ClassLoader for the second argument of newFileSystem.

For a more complete example, this code prints the contents of README.md which is inside the internal.zip archive which itself is inside the archive.zip archive:

import java.io.IOException;
import java.nio.file.*;

public class Test {
    public static void main(String[] args) throws IOException {
        Path zipfile = Paths.get("archive.zip");
        FileSystem manager = FileSystems.newFileSystem(zipfile, (ClassLoader) null);
        Path internal = manager.getPath("internal.zip");
        FileSystem internalManager = FileSystems.newFileSystem(internal, (ClassLoader) null);
        Path internalFile = internalManager.getPath("README.md");
        Files.lines(internalFile).forEach(System.out::println);
    }
}

Note that file names inside archives are case-sensitive.

vadipp
  • 877
  • 1
  • 12
  • 22