Author's note: Before we get started on this month's article, I'd like to mention that my new book on Java threading, Taming Java Threads (APress, June 2000 (see Resources)), is finally out. The book shows you how to create production-quality multithreaded programs; it presents a full-blown industrial-strength threading library along with a lot of advice about threading pitfalls and good architecture. Much of the material in this book first appeared in JavaWorld as a nine-part series on threading (see Resources), though the material has been expanded considerably and the code has been cleaned up and expanded as well.
TEXTBOX:
TEXTBOX_HEAD: Modifying archives: Read the whole series!
:END_TEXTBOX
As I discussed in Part 1 of this series, the built-in Java archive classes contain no support for modifying an existing archive. They only let you build one from scratch. To modify an archive, you must copy it to another archive, performing the modifications along the way. Three classes are involved in the transfer:
ZipFile: Represents the file as a whole; you get ZipEntry objects that represent the archive's contents from here. The constructor takes the full path name of the .zip or .jar file as an argument.
ZipEntry: Essentially the directory entry for a file within the archive. You get an InputStream for a particular file within the archive by calling a_ZipFile_object.getInputStream(a_ZipEntry).
ZipOutputStream: An output stream that builds an archive. You can write ZipEntry objects onto this stream as well as the actual data (the ZipEntry object has to be written first, then the data). A ZipOutputStream is a standard java.io-style decorator used along the same lines as BufferedOutputStream. You pass an OutputStream representing the physical archive file to the ZipOutputStream as a constructor argument, and you write to the ZipOutputStream wrapper.
Next, we see the general (but not so easy) process for modifying an archive:
ZipEntry objects for the existing archive.
ZipOutputStream.
ZipEntry objects made in Step 1.
ZipEntry from the list of entries made in Step 1.
ZipEntry by copying relevant fields from the old one.
ZipEntry into the ZipOutputStream.
ZipOutputStream.
ZipOutputStream that you're done with the entry.
InputStream.
ZipEntry in the old archive, so you have to create one from scratch.
ZipEntry objects that remain in the list created in Step 1 (that is, the files you haven't deleted or replaced). To do this, you'll
have to open an InputStream for each of the entries remaining in the list (by asking the ZipFile for an InputStream for a particular ZipEntry), then transfer bytes from that stream to the ZipOutputStream using the process described earlier.
To make matters worse, the requirements for writing a compressed (ZipEntry.DEFLATED) file differ from those for writing an uncompressed (ZipEntry.STORED) file. The ZipEntry for uncompressed files must be initialized with a CRC value (a checksum) and file size before it can be written to the ZipOutputStream. The checksum can be built using Java's CRC32 class (which is passed the bytes that comprise the file and provides a checksum when all the bytes have been imported). The
ZipEntry must be written before the file contents, however, so you have to process the data twice -- once to figure out the CRC and
once again 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.
Mutex class was covered in "Programming Java Threads in the Real World, Part 4," Allen Holub (JavaWorld, December 1998)Mutex class in Allen Holub's new book, Taming Java Threads (APress, June 2000)