samblake
samblake

Reputation: 1578

Reading a zip file within a jar file

Previously we had some zip files within our web application. We would want to pares a specific text document within the zip file. This wasn't a problem:

URL url = getClass().getResource(zipfile);
ZipFile zip = new ZipFile(url.getFile().replaceAll("%20", " "));     
Entry entry = zip.getEntry("file.txt");

InputStream is = zip.getInputStream(entry);
BufferedReader reader = new BufferedReader(new InputStreamReader(is));

String line = reader.readLine();
while (line != null) {
    // do stuff
}

However we've moved these zip files into another module and want to package them within a jar. Unfortunately, creating the ZipFile now fails. I can get an InputStream for the zip: but I have no way of getting an input stream for the entry itself.

InputStream is = getClass().getResourceAsStream(zipfile);
ZipInputStream zis = new ZipInputStream(is);

ZipEntry entry = zis.getNextEntry();
while (entry != null && !entry.getName().equals("file.txt")) {
    entry = zis.getNextEntry();
}

but I have no way of getting an input stream for the entry itself. I tried finding the length of the entry and getting the next n bytes from the ZipInputStream but this didn't work for me. It seemed all bytes read were 0.

Is there a way round this or am I going to have to move the zip files back into the core project?

Upvotes: 8

Views: 11168

Answers (4)

Ben
Ben

Reputation: 401

it is also possible to parse the string and open an ZipInputStream on another ZipInputStream and set the entry to the file inside.

e.g. you have the String like above "path/to/some-jar.jar/internal/zip/file.zip/myfile.txt"

private static final String[] zipFiles = new String[] { ".zip", ".jar" };

public static InputStream getResourceAsStream(final String ref) throws IOException {
    String abstractPath = ref.replace("\\", "/");
    if (abstractPath.startsWith("/")) {
        abstractPath = abstractPath.substring(1);
    }
    final String[] pathElements = abstractPath.split("/");
    return getResourceAsStream(null, pathElements);
}

private static InputStream getResourceAsStream(final ZipInputStream parentStream, final String[] pathElements)
        throws IOException {

    if (pathElements.length == 0) return parentStream;

    final StringBuilder nextFile = new StringBuilder();
    for (int index = 0; index < pathElements.length; index++) {
        final String pathElement = pathElements[index];
        nextFile.append((index > 0 ? "/" : "") + pathElement);
        if (pathElement.contains(".")) {
            final String path = nextFile.toString();
            if (checkForZip(pathElement)) {
                final String[] restPath = new String[pathElements.length - index - 1];
                System.arraycopy(pathElements, index + 1, restPath, 0, restPath.length);
                if (parentStream != null) {
                    setZipToEntry(parentStream, path);
                    return getResourceAsStream(new ZipInputStream(parentStream), restPath);
                } else return getResourceAsStream(new ZipInputStream(new FileInputStream(path)), restPath);
            } else {
                if (parentStream != null) {
                    setZipToEntry(parentStream, path);
                    return parentStream;
                } else return new FileInputStream(path);
            }
        }
    }
    throw new FileNotFoundException("File not found: " + nextFile.toString());
}

private static void setZipToEntry(final ZipInputStream in, final String name) throws IOException {
    ZipEntry entry;
    while ((entry = in.getNextEntry()) != null) {
        if (entry.getName().equals(name)) return;
    }
    throw new FileNotFoundException("File not found: " + name);
}

private static boolean checkForZip(final String ref) {
    for (final String zipFile : zipFiles) {
        if (ref.endsWith(zipFile)) return true;
    }
    return false;
}

Upvotes: 0

Tilak Sharma
Tilak Sharma

Reputation: 11

I have modified the Sequential Zip access code provided above:

File destFile = new File(destDir, jarName);
JarOutputStream jos = new JarOutputStream(new FileOutputStream(destFile));

JarInputStream jis = new JarInputStream(is);
JarEntry jarEntry = jis.getNextJarEntry();
for (; jarEntry != null ; jarEntry = jis.getNextJarEntry()) {
    jos.putNextEntry(new JarEntry(jarEntry.getName()));
    if(jarEntry.isDirectory()) {
       continue;
    }

    int bytesRead = jis.read(buffer);
    while(bytesRead != -1) {
    jos.write(buffer, 0, bytesRead);
    bytesRead = jis.read(buffer);
    }

}
is.close();
jis.close();
jos.flush();
jos.closeEntry();
jos.close();

In the above code, I am trying to copy a Jar file inside another Jar file to a folder in file system. 'is' is the input stream to the jar file inside another jar file (jar.getInputStream("lib/abcd.jar"))

Upvotes: 0

Andy
Andy

Reputation: 5218

How about TrueZip? Using it you could simply open the zipped file as if it was located inside a directory.

new FileOutputStream("/path/to/some-jar.jar/internal/zip/file.zip/myfile.txt");

According to the docs, infinite nesting is also supported. I have yet to actually use this project, but it's been on my radar for a while and it seems applicable to your problem.

Project Site: http://truezip.java.net/ (edited)

Upvotes: 7

helios
helios

Reputation: 13841

entry can give you the inputstream of the inner-zip file.

InputStream innerzipstream = zip.getInputStream(entry);

So you can use

new ZipInputStream(innerzipstream);

and ask the ZipInputStream to retrieve the content of the inner-zip-file (in an ordered fashion, you don't have random access because it's a ZipInputStream)

Look at http://download.oracle.com/javase/1.4.2/docs/api/java/util/zip/ZipInputStream.html

Sequential zip access

As ZipInputStream is reading a zip file from an input stream it has to do things in order:

// DO THIS for each entry
ZipEntry e = zipInputStreamObj.getNextEntry();
e.getName // and all data
int size = e.getSize(); // the byte count
while (size > 0) {
   size -= zipInputStreamObj.read(...);
}
zipInputStreamObj.closeEntry();
// DO THIS END

zipInputStreamObj.close();

Note: I don't know if ZipInputStream.getNextEntry() returns null when end of zip file is reached or not. I hope so because I don't know other way to realize when there are no more entries.

Upvotes: 4

Related Questions