Reputation: 1578
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
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
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
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
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