Most read:
Popular archives:
JavaWorld's new look is here!
We've upgraded the site with a fresh look-and-feel, improved topical navigation, better search, new features, and expanded
community platform. Learn more about the changes to JavaWorld.
| Oracle Compatibility Developer's Guide |
| The Explosion in DBMS Choice |
java.util.zip APIs weren't nearly as flexible or complete as I had thought, and I had to put a significant effort into a set of archive-maintenance
classes before I could proceed with my class loader. Consequently, this article and Part 2 will present those classes; I'll
return to the class loader afterwards.TEXTBOX:
TEXTBOX_HEAD: Modifying archives: Read the whole series!
:END_TEXTBOX
Java features a rich set of jar APIs. For example, you can read archives using a URL that takes the form jar:<url_of_jar>!<path_within_jar>. The following URL gets the source code for one of the classes in my threads package from an archive on my Website:
jar:http://www.holub.com/taming.java.threads.zip!/src/com/holub/asynch/Mutex.java
The code in Listing 1 demonstrates how to access a jar file with a URL.
|
Unfortunately, URL access to a jar file doesn't permit write operations, so I turned to the various classes in java.util.zip. The ZipFile, which seemed particularly promising, gets a list of ZipEntry objects that represent the files in the archive, and then asks the ZipEntry object for information about the file represented by the object. The following code demonstrates the process by printing
the names of all the files in my_file.zip:
zip_file = new ZipFile( "my_file.zip" );
for( Enumeration e = zip_file.entries(); e.hasMoreElements(); )
{
ZipEntry entry = (ZipEntry) e.nextElement();
System.out.println( entry.getName() );
}
You can request an InputStream to read the file associated with a given entry from the ZipFile:
InputStream in = zip_file.getInputStream(entry);
and then read from it in the normal way. So far so good; but, to my horror, I found that there is no getOutputStream() method available. It was back to the drawing board!
More digging unearthed ZipOutputStream, but this class is far from easy to use. There are examples in Chan, Lee, and Kramer's Java Class Libraries book (see Resources), but they prove hideously complicated.
It turns out that the only way to modify an archive is to make a new archive from scratch and copy the old one to the new one, making any changes along the way. In truth, you cannot in a simple way use Java's archive classes to modify, replace, or add a file in an existing archive.
With that in mind, the not-so-basic drill is as follows:
ZipEntry objects for the existing archive.
FileOutputStream.
ZipEntry objects made in step 1.
ZipEntry from the list of entries.
ZipEntry by copying relevant fields from the old one.
ZipEntry into the ZipOutputStream.
InputStream to the file in the original archive.
InputStream to the ZipOutputStream.
ZipOutputStream that you're done with the entry.
InputStream.
ZipEntry objects that remain in the list created in Step 1 (that is, the files you haven't deleted or replaced). Use the process described
earlier.
Ugh! (That's a technical term we Java programmers use.) To make matters worse, the requirements for writing a compressed (DEFLATED) file differ from those for writing an uncompressed (STORED) file. The ZipEntry for uncompressed files must be initialized with a CRC value and file size before it can be written to the ZipOutputStream. Since the ZipEntry must be written before the file contents, this means that you have to process the new data twice -- once to figure out the
CRC and once to copy the bytes to the ZipOutputStream. Fortunately, the process isn't so brain-dead for a compressed file; you can give the ZipOutputStream a ZipEntry with uninitialized size and CRC fields, and the ZipOutputStream will modify the fields for you as it does the compression.
The double processing of the uncompressed file gave me substantial grief. First, I didn't want to read the file twice. Second,
what if my program generated the file programmatically? I didn't want to generate the file contents twice. A temptingly easy
strategy is to use the ByteArrayOutputStream -- transfer the file to one of these, extract the resulting buffer, and then process the buffer twice. The problem with this
approach is the size of the runtime memory footprint. If I put a 1 MB file into my archive, I'll need 1 MB of memory for the
underlying byte array. Even if I have this much memory available, the program's memory footprint would probably get so large
that the operating system would start swapping the executable image to disk to allow other programs to run. A program can
slow down by an order of magnitude (or more) once the virtual memory manager starts swapping files to disk -- not a good outcome.
On the other hand, most of the files that I would be processing in the class-loader application would be small -- a typical
jar file comprises a couple KB or less. Nonetheless, it seemed to me that writing the class with the assumption that all files
would be small was a bad idea. I wanted to write this class once and be done with it.
java.util.zip