简体   繁体   中英

get FileSystems of a jar inside another jar

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!

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.

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

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM