How do I enumerate the content of a zipped folder in Java? - java

ZipeFile file = new ZipFile(filename);
ZipEntry folder = this.file.getEntry("some/path/in/zip/");
if (folder == null || !folder.isDirectory())
throw new Exception();
// now, how do I enumerate the contents of the zipped folder?

It doesn't look like there's a way to enumerate ZipEntry under a certain directory.
You'd have to go through all ZipFile.entries() and filter the ones you want based on the ZipEntry.getName() and see if it String.startsWith(String prefix).
String specificPath = "some/path/in/zip/";
ZipFile zipFile = new ZipFile(file);
Enumeration<? extends ZipEntry> entries = zipFile.entries();
while (entries.hasMoreElements()) {
ZipEntry ze = entries.nextElement();
if (ze.getName().startsWith(specificPath)) {
System.out.println(ze);
}
}

You don't - at least, not directly. ZIP files are not actually hierarchical. Enumerate all the entries (via ZipFile.entries() or ZipInputStream.getNextEntry()) and determine which are within the folder you want by examining the name.

Can you just use entries()? API link

Related

Enumerating FTPFile from FTP using ZipFile

My usual approach without an FTP to read ZipFiles was the following:
private void getLogFromZip(File logZip){
ZipFile zf = new ZipFile(logZip);
Enumeration<?> entries = zf.entries();
while (entries.hasMoreElements()) {
ZipEntry ze = (ZipEntry) entries.nextElement();
//do something with entry
}
Now that I have a connection to an FTP server, retrieving an FTPFile and using it makes things difficult:
private void getLogFromZip(FTPFile logZip){
ZipFile zf = new ZipFile(logZip.getName()); //here's the problem
Enumeration<?> entries = zf.entries();
while (entries.hasMoreElements()) {
ZipEntry ze = (ZipEntry) entries.nextElement();
//do something with entry
}
Directly in line 1 I am getting this:
java.io.FileNotFoundException: logger_150510_092333.zip (System cannot find the file? specified)
What is a workaround for that? How can I specify a path so that it knows where to look for the zip?
Many thanks in advance!
You probably have to get the file from the FTP server first, before you can access it. The FTPFile instance appears to be only a link to the actual file.
Have a look here for an example: http://www.mysamplecode.com/2012/03/apache-commons-ftpclient-java-example_16.html

getNextEntry() doesn't display folder as an entry?

Hi I'm new to android programming.
I'm trying to create a program to unzip a zipped file in my sd card and I noticed something when I debug.
public void testZipOrder() throws Exception {
File file = new File(_zipFile);
zis = new ZipInputStream(new FileInputStream(file));
ZipEntry entry = null;
while ( (entry = zis.getNextEntry()) != null ) {
System.out.println( entry.getName());
}
}
}
this give me an output of :
06-27 00:42:06.360: I/System.out(15402): weee.txt
06-27 00:42:06.360: I/System.out(15402): hi/bye.txt
06-27 00:42:06.360: I/System.out(15402): hi/hiwayne.txt
isn't it suppose to give
weee.txt
hi/
hi/bye.txt
hi/hiwayne.txt
or something that displays its folder instead?
I tried this on my own environment using a test zip file created with 7zip and the following method:
public void testZipOrder() throws Exception {
File file = new File("zip.zip");
ZipInputStream zis = new ZipInputStream(new FileInputStream(file));
ZipEntry entry = null;
while ( (entry = zis.getNextEntry()) != null ) {
System.out.println( entry.getName());
}
zis.close();
}
Note this method is effectively identical to yours.
The resulting output was:
file1.txt
folder1/
folder1/file2.txt
folder1/folder2/
folder1/folder2/file3.txt
Which is, I believe, what you are looking for. As such I expect the problem is with the zip file itself, not your code. It is likely that your zip file does not contain an entry for the directory "hi/".
See here for a basic description of how zip files are structured.
ZIP spec does not require the ordered "placement" of the file and its parent(s) directory in the zip file, and in fact the parent directory entries can be totally absent
See https://bugs.openjdk.java.net/browse/JDK-8054027

What is the idiomatic way to copy a ZipEntry into a new ZipFile?

I'm writing a tool to do some minor text replacement in a DOCX file, which is a zipped format. My method is to copy ZipEntry contents from entries in the original file into the modified file using a ZipOutputStream. For most DOCX files this works well, but occasionally I will encounter ZipExceptions regarding discrepancies between the contents I've written and the meta-information contained in the ZipEntry (usually a difference in compressed size).
Here's the code I'm using to copy over contents. I've stripped out error handling and document processing for brevity; I haven't had issues with the document entry so far.
ZipFile original = new ZipFile(INPUT_FILENAME);
ZipOutputStream outputStream = new ZipOutputStream(new FileOutputStream(OUTPUT_FILE));
Enumeration entries = original.entries();
byte[] buffer = new byte[512];
while (entries.hasMoreElements()) {
ZipEntry entry = (ZipEntry)entries.nextElement();
if ("word/document.xml".equalsIgnoreCase(entry.getName())) {
//perform special processing
}
else{
outputStream.putNextEntry(entry);
InputStream in = original.getInputStream(entry);
while (0 < in.available()){
int read = in.read(buffer);
outputStream.write(buffer,0,read);
}
in.close();
}
outputStream.closeEntry();
}
outputStream.close();
What is the proper or idiomatic way to directly copy ZipEntry objects from one ZipFile to another?
I have found a workaround that avoids the error. By creating a new ZipEntry with only the name field set I'm able to copy over contents without issue.
ZipFile original = new ZipFile(INPUT_FILENAME);
ZipOutputStream outputStream = new ZipOutputStream(new FileOutputStream(OUTPUT_FILE));
Enumeration entries = original.entries();
byte[] buffer = new byte[512];
while (entries.hasMoreElements()) {
ZipEntry entry = (ZipEntry)entries.nextElement();
if ("word/document.xml".equalsIgnoreCase(entry.getName())) {
//perform special processing
}
else{
// create a new empty ZipEntry
ZipEntry newEntry = new ZipEntry(entry.getName());
// outputStream.putNextEntry(entry);
outputStream.putNextEntry(newEntry);
InputStream in = original.getInputStream(entry);
while (0 < in.available()){
int read = in.read(buffer);
if (read > 0) {
outputStream.write(buffer,0,read);
}
}
in.close();
}
outputStream.closeEntry();
}
outputStream.close();
However, I this method loses any meta-information stored in the fields of original ZipEntry (e.g.: comment, extra). The API docs aren't clear on whether this is important.
To keep your meta-data for the zip entry, create it using ZipEntry's "copy constructor":
ZipEntry newEntry = new ZipEntry(entry);
You can then modify just the name or the comments etc. and everything else will be copied from the given entry.
You could also look at Docmosis which can populate DocX files from Java.

How does ZipInputStream.getNextEntry() work?

Say we have code like:
File file = new File("zip1.zip");
ZipInputStream zis = new ZipInputStream(new FileInputStream(file));
Let's assume you have a .zip file that contains the following:
zip1.zip
hello.c
world.java
folder1
foo.c
bar.java
foobar.c
How would zis.getNextEntry() iterate through that?
Would it return hello.c, world.java, folder1, foobar.c and completely ignore the files in folder1?
Or would it return hello.c, world.java, folder1, foo.c, bar.java, and then foobar.c?
Would it even return folder1 since it's technically a folder and not a file?
Thanks!
Well... Lets see:
ZipInputStream zis = new ZipInputStream(new FileInputStream("C:\\New Folder.zip"));
try
{
ZipEntry temp = null;
while ( (temp = zis.getNextEntry()) != null )
{
System.out.println( temp.getName());
}
}
Output:
New Folder/
New Folder/folder1/
New Folder/folder1/bar.java
New Folder/folder1/foo.c
New Folder/foobar.c
New Folder/hello.c
New Folder/world.java
Yes. It will print the folder name too, since it's also an entry within the zip. It will also print in the same order as it is displayed inside the zip. You can use below test to verify your output.
public class TestZipOrder {
#Test
public void testZipOrder() throws Exception {
File file = new File("/Project/test.zip");
ZipInputStream zis = new ZipInputStream(new FileInputStream(file));
ZipEntry entry = null;
while ( (entry = zis.getNextEntry()) != null ) {
System.out.println( entry.getName());
}
}
}
Excerpt from: https://blogs.oracle.com/CoreJavaTechTips/entry/creating_zip_and_jar_files
java.util.zip libraries offer some level of control for the added entries of the ZipOutputStream.
First, the order you add entries to the ZipOutputStream is the order they are physically located in the .zip file.
You can manipulate the enumeration of entries returned back by the entries() method of ZipFile to produce a list in alphabetical or size order, but the entries are still stored in the order they were written to the output stream.
So I would believe that you have to use the entries() method to see the order in which it will be iterated through.
ZipFile zf = new ZipFile("your file path with file name");
for (Enumeration<? extends ZipEntry> e = zf.entries();
e.hasMoreElements();) {
System.out.println(e.nextElement().getName());
}
The zip file internal directory is a "flat" list of all the files and directories in the zip. getNextEntry will iterate through the list and sequentially identify every file and directory in the zip file.
There is a variant of the zip file format that has no central directory, in which case (if it's handled at all) I suspect you'd iterate through all actual files in the zip, skipping directories (but not skipping files in directories).

Access external .jar resources from java

I am working on a multi module Java project, it consists of multiple projects in Eclipse that depend on each other, now to add GUI Theme support I have created a Java project that does not contain any code, just the icons and pictures needed for the GUI, I made it a Java project so Maven will build it into a .jar. Now there's a main Application that loads the multiple projects into a main GUI, which gets its icons from resources within the actual modules at the moment. All I want to do is load all these resources from the external dummy .jar that is included in the classpath of the main application's .jar. Every method I have found so far does not seem to work. The .jar contains no actual java .classes, so there is no ClassLoader to reference. Are there any other methods to load the pictures without extracting the .jar?
Three things you might try:
Create a dummy class in the resource jar, which you could use to get the ClassLoader reference.
Use URLClassLoader, if you know, where your jar-File resides.
You always have the possibility to create your own ClassLoader by extending java.lang.ClassLoader.
Treat the external jar file as a zip-archive and read the images/resource something like this:
public ZipStuff(String filename) throws IOException
{
ZipFile zf = new ZipFile(filename);
Enumeration<? extends ZipEntry> entries = zf.entries();
while (entries.hasMoreElements()) {
ZipEntry ze = entries.nextElement();
ImageIcon ii = new ImageIcon(ImageIO.read(zf.getInputStream(ze)));
JLabel l = new JLabel(ii);
getContentPane().add(l);
}
setVisible(true);
pack();
}
In this case I have a jar file with one single image which I load into a JLabel. The ZipStuff-class is a JFrame.
You could get all of the resources (all jar files on classpath should work even one without classes):
Enumeration<URL> resources = null;
try {
resources = Thread.currentThread().getContextClassLoader().getResources(someResource);
} catch (Exception ex) {
//no op
}
if (resources == null || !resources.hasMoreElements()) {
resources = ClasspathReader.class.getClassLoader().getResources(someResource);
}
Then check to see if the current resource is a file. Files you can deal with direct as files.
But your question was about jar files so I wont go there.
while (resources.hasMoreElements()) {
URL resource = resources.nextElement();
if (resource.getProtocol().equals("file")) {
//if it is a file then we can handle it the normal way.
handleFile(resource, namespace);
continue;
}
At this point you should have only jar:file resources so...
Split up the string that looks like this:
jar:file:/Users/rick/.m2/repository/invoke/invoke/1.0-SNAPSHOT/invoke-1.0-SNAPSHOT.jar!/org/node/
into this
/Users/rick/.m2/repository/invoke/invoke/1.0-SNAPSHOT/invoke-1.0-SNAPSHOT.jar
and this
/org/node/
Here is the code to do the above devoid of pesky error checking. :)
String[] split = resource.toString().split(":");
String[] split2 = split[2].split("!");
String zipFileName = split2[0];
String sresource = split2[1];
System.out.printf("After split zip file name = %s," +
" \nresource in zip %s \n", zipFileName, sresource);
Now we have the zip file name so we can read it:
ZipFile zipFile = new ZipFile(zipFileName);
Now we can iterate through its entries:
Enumeration<? extends ZipEntry> entries = zipFile.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
/* If it is a directory, then skip it. */
if (entry.isDirectory()) {
continue;
}
String entryName = entry.getName();
System.out.printf("zip entry name %s \n", entryName);
See if it starts with the resource we are looking for.
if (!entryName.startsWith(someResource)) {
continue;
}
There were two tricks I did earlier to see if it was a directory
boolean isDir = !someResource.endsWith(".txt");
This only works because I am looking for resources that ends with .txt and I assume if it does not end with .txt that it is an dir /foo/dir and /foo/dir/ both work.
The other trick was this:
if (someResource.startsWith("/")) {
someResource = someResource.substring(1);
}
Classpath resource can never really start with a starting slash. Logically they do, but in reality you have to strip it. This is a known behavior of classpath resources. It works with a slash unless the resource is in a jar file. Bottom line, by stripping it, it always works.
We need to get the actual fileName of the darn thing. The fileName part from the entry name.
where /foo/bar/foo/bee/bar.txt, and we want 'bar.txt' which is the fileName. Back inside of our while loop.
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
...
String entryName = entry.getName(); //entry is zipEntry
String fileName = entryName.substring(entryName.lastIndexOf("/") + 1);
/** See if the file starts with our namespace and ends with our extension. */
if (fileName.startsWith(namespace) && fileName.endsWith(".txt")) {
Next we see if these entry matches our criteria and if so read contents of the file to System.out.
try (Reader reader = new InputStreamReader(zipFile.getInputStream(entry))) {
StringBuilder builder = new StringBuilder();
int ch = 0;
while ((ch = reader.read()) != -1) {
builder.append((char) ch);
}
System.out.printf("zip fileName = %s\n\n####\n contents of file %s\n###\n",
entryName, builder);
} catch (Exception ex) {
ex.printStackTrace();//it is an example/proto :)
}
}
You can see the full example here: Sleepless in Pleasanton.

Categories

Resources