KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > java > util > zip > ZipOutputStream


1 /*
2  * @(#)ZipOutputStream.java 1.31 03/12/19
3  *
4  * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
5  * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
6  */

7
8 package java.util.zip;
9
10 import java.io.OutputStream JavaDoc;
11 import java.io.IOException JavaDoc;
12 import java.util.Vector JavaDoc;
13 import java.util.Hashtable JavaDoc;
14 import java.util.Enumeration JavaDoc;
15
16 /**
17  * This class implements an output stream filter for writing files in the
18  * ZIP file format. Includes support for both compressed and uncompressed
19  * entries.
20  *
21  * @author David Connelly
22  * @version 1.31, 12/19/03
23  */

24 public
25 class ZipOutputStream extends DeflaterOutputStream JavaDoc implements ZipConstants JavaDoc {
26     private ZipEntry JavaDoc entry;
27     private Vector JavaDoc entries = new Vector JavaDoc();
28     private Hashtable JavaDoc names = new Hashtable JavaDoc();
29     private CRC32 JavaDoc crc = new CRC32 JavaDoc();
30     private long written = 0;
31     private long locoff = 0;
32     private String JavaDoc comment;
33     private int method = DEFLATED;
34     private boolean finished;
35
36     private boolean closed = false;
37     
38     /**
39      * Check to make sure that this stream has not been closed
40      */

41     private void ensureOpen() throws IOException JavaDoc {
42     if (closed) {
43         throw new IOException JavaDoc("Stream closed");
44         }
45     }
46     /**
47      * Compression method for uncompressed (STORED) entries.
48      */

49     public static final int STORED = ZipEntry.STORED;
50
51     /**
52      * Compression method for compressed (DEFLATED) entries.
53      */

54     public static final int DEFLATED = ZipEntry.DEFLATED;
55
56     /**
57      * Creates a new ZIP output stream.
58      * @param out the actual output stream
59      */

60     public ZipOutputStream(OutputStream JavaDoc out) {
61     super(out, new Deflater JavaDoc(Deflater.DEFAULT_COMPRESSION, true));
62         usesDefaultDeflater = true;
63     }
64
65     /**
66      * Sets the ZIP file comment.
67      * @param comment the comment string
68      * @exception IllegalArgumentException if the length of the specified
69      * ZIP file comment is greater than 0xFFFF bytes
70      */

71     public void setComment(String JavaDoc comment) {
72         if (comment != null && comment.length() > 0xffff/3
73                                            && getUTF8Length(comment) > 0xffff) {
74         throw new IllegalArgumentException JavaDoc("ZIP file comment too long.");
75     }
76     this.comment = comment;
77     }
78
79     /**
80      * Sets the default compression method for subsequent entries. This
81      * default will be used whenever the compression method is not specified
82      * for an individual ZIP file entry, and is initially set to DEFLATED.
83      * @param method the default compression method
84      * @exception IllegalArgumentException if the specified compression method
85      * is invalid
86      */

87     public void setMethod(int method) {
88     if (method != DEFLATED && method != STORED) {
89         throw new IllegalArgumentException JavaDoc("invalid compression method");
90     }
91     this.method = method;
92     }
93
94     /**
95      * Sets the compression level for subsequent entries which are DEFLATED.
96      * The default setting is DEFAULT_COMPRESSION.
97      * @param level the compression level (0-9)
98      * @exception IllegalArgumentException if the compression level is invalid
99      */

100     public void setLevel(int level) {
101     def.setLevel(level);
102     }
103
104     /**
105      * Begins writing a new ZIP file entry and positions the stream to the
106      * start of the entry data. Closes the current entry if still active.
107      * The default compression method will be used if no compression method
108      * was specified for the entry, and the current time will be used if
109      * the entry has no set modification time.
110      * @param e the ZIP entry to be written
111      * @exception ZipException if a ZIP format error has occurred
112      * @exception IOException if an I/O error has occurred
113      */

114     public void putNextEntry(ZipEntry JavaDoc e) throws IOException JavaDoc {
115     ensureOpen();
116     if (entry != null) {
117         closeEntry(); // close previous entry
118
}
119     if (e.time == -1) {
120         e.setTime(System.currentTimeMillis());
121     }
122     if (e.method == -1) {
123         e.method = method; // use default method
124
}
125     switch (e.method) {
126     case DEFLATED:
127         if (e.size == -1 || e.csize == -1 || e.crc == -1) {
128         // store size, compressed size, and crc-32 in data descriptor
129
// immediately following the compressed entry data
130
e.flag = 8;
131         } else if (e.size != -1 && e.csize != -1 && e.crc != -1) {
132         // store size, compressed size, and crc-32 in LOC header
133
e.flag = 0;
134         } else {
135         throw new ZipException JavaDoc(
136             "DEFLATED entry missing size, compressed size, or crc-32");
137         }
138         e.version = 20;
139         break;
140     case STORED:
141         // compressed size, uncompressed size, and crc-32 must all be
142
// set for entries using STORED compression method
143
if (e.size == -1) {
144         e.size = e.csize;
145         } else if (e.csize == -1) {
146         e.csize = e.size;
147         } else if (e.size != e.csize) {
148         throw new ZipException JavaDoc(
149             "STORED entry where compressed != uncompressed size");
150         }
151         if (e.size == -1 || e.crc == -1) {
152         throw new ZipException JavaDoc(
153             "STORED entry missing size, compressed size, or crc-32");
154         }
155         e.version = 10;
156         e.flag = 0;
157         break;
158     default:
159         throw new ZipException JavaDoc("unsupported compression method");
160     }
161     e.offset = written;
162     if (names.put(e.name, e) != null) {
163         throw new ZipException JavaDoc("duplicate entry: " + e.name);
164     }
165         writeLOC(e);
166     entries.addElement(e);
167     entry = e;
168     }
169
170     /**
171      * Closes the current ZIP entry and positions the stream for writing
172      * the next entry.
173      * @exception ZipException if a ZIP format error has occurred
174      * @exception IOException if an I/O error has occurred
175      */

176     public void closeEntry() throws IOException JavaDoc {
177     ensureOpen();
178     ZipEntry JavaDoc e = entry;
179     if (e != null) {
180         switch (e.method) {
181         case DEFLATED:
182         def.finish();
183         while (!def.finished()) {
184             deflate();
185         }
186         if ((e.flag & 8) == 0) {
187             // verify size, compressed size, and crc-32 settings
188
if (e.size != def.getBytesRead()) {
189             throw new ZipException JavaDoc(
190                 "invalid entry size (expected " + e.size +
191                 " but got " + def.getBytesRead() + " bytes)");
192             }
193             if (e.csize != def.getBytesWritten()) {
194             throw new ZipException JavaDoc(
195                 "invalid entry compressed size (expected " +
196                 e.csize + " but got " + def.getBytesWritten() + " bytes)");
197             }
198             if (e.crc != crc.getValue()) {
199             throw new ZipException JavaDoc(
200                 "invalid entry CRC-32 (expected 0x" +
201                 Long.toHexString(e.crc) + " but got 0x" +
202                 Long.toHexString(crc.getValue()) + ")");
203             }
204         } else {
205             e.size = def.getBytesRead();
206             e.csize = def.getBytesWritten();
207             e.crc = crc.getValue();
208             writeEXT(e);
209         }
210         def.reset();
211         written += e.csize;
212         break;
213         case STORED:
214         // we already know that both e.size and e.csize are the same
215
if (e.size != written - locoff) {
216             throw new ZipException JavaDoc(
217             "invalid entry size (expected " + e.size +
218             " but got " + (written - locoff) + " bytes)");
219         }
220         if (e.crc != crc.getValue()) {
221             throw new ZipException JavaDoc(
222              "invalid entry crc-32 (expected 0x" +
223              Long.toHexString(e.crc) + " but got 0x" +
224              Long.toHexString(crc.getValue()) + ")");
225         }
226         break;
227         default:
228         throw new InternalError JavaDoc("invalid compression method");
229         }
230         crc.reset();
231         entry = null;
232     }
233     }
234
235     /**
236      * Writes an array of bytes to the current ZIP entry data. This method
237      * will block until all the bytes are written.
238      * @param b the data to be written
239      * @param off the start offset in the data
240      * @param len the number of bytes that are written
241      * @exception ZipException if a ZIP file error has occurred
242      * @exception IOException if an I/O error has occurred
243      */

244     public synchronized void write(byte[] b, int off, int len)
245     throws IOException JavaDoc
246     {
247     ensureOpen();
248         if (off < 0 || len < 0 || off > b.length - len) {
249         throw new IndexOutOfBoundsException JavaDoc();
250     } else if (len == 0) {
251         return;
252     }
253
254     if (entry == null) {
255         throw new ZipException JavaDoc("no current ZIP entry");
256     }
257     switch (entry.method) {
258     case DEFLATED:
259         super.write(b, off, len);
260         break;
261     case STORED:
262         written += len;
263         if (written - locoff > entry.size) {
264         throw new ZipException JavaDoc(
265             "attempt to write past end of STORED entry");
266         }
267         out.write(b, off, len);
268         break;
269     default:
270         throw new InternalError JavaDoc("invalid compression method");
271     }
272     crc.update(b, off, len);
273     }
274
275     /**
276      * Finishes writing the contents of the ZIP output stream without closing
277      * the underlying stream. Use this method when applying multiple filters
278      * in succession to the same output stream.
279      * @exception ZipException if a ZIP file error has occurred
280      * @exception IOException if an I/O exception has occurred
281      */

282     public void finish() throws IOException JavaDoc {
283     ensureOpen();
284     if (finished) {
285         return;
286     }
287     if (entry != null) {
288         closeEntry();
289     }
290     if (entries.size() < 1) {
291         throw new ZipException JavaDoc("ZIP file must have at least one entry");
292     }
293     // write central directory
294
long off = written;
295     Enumeration JavaDoc e = entries.elements();
296     while (e.hasMoreElements()) {
297         writeCEN((ZipEntry JavaDoc)e.nextElement());
298     }
299     writeEND(off, written - off);
300     finished = true;
301     }
302
303     /**
304      * Closes the ZIP output stream as well as the stream being filtered.
305      * @exception ZipException if a ZIP file error has occurred
306      * @exception IOException if an I/O error has occurred
307      */

308     public void close() throws IOException JavaDoc {
309         if (!closed) {
310             super.close();
311             closed = true;
312         }
313     }
314
315     /*
316      * Writes local file (LOC) header for specified entry.
317      */

318     private void writeLOC(ZipEntry JavaDoc e) throws IOException JavaDoc {
319     writeInt(LOCSIG); // LOC header signature
320
writeShort(e.version); // version needed to extract
321
writeShort(e.flag); // general purpose bit flag
322
writeShort(e.method); // compression method
323
writeInt(e.time); // last modification time
324
if ((e.flag & 8) == 8) {
325         // store size, uncompressed size, and crc-32 in data descriptor
326
// immediately following compressed entry data
327
writeInt(0);
328         writeInt(0);
329         writeInt(0);
330     } else {
331         writeInt(e.crc); // crc-32
332
writeInt(e.csize); // compressed size
333
writeInt(e.size); // uncompressed size
334
}
335     byte[] nameBytes = getUTF8Bytes(e.name);
336     writeShort(nameBytes.length);
337     writeShort(e.extra != null ? e.extra.length : 0);
338     writeBytes(nameBytes, 0, nameBytes.length);
339     if (e.extra != null) {
340         writeBytes(e.extra, 0, e.extra.length);
341     }
342     locoff = written;
343     }
344
345     /*
346      * Writes extra data descriptor (EXT) for specified entry.
347      */

348     private void writeEXT(ZipEntry JavaDoc e) throws IOException JavaDoc {
349     writeInt(EXTSIG); // EXT header signature
350
writeInt(e.crc); // crc-32
351
writeInt(e.csize); // compressed size
352
writeInt(e.size); // uncompressed size
353
}
354
355     /*
356      * Write central directory (CEN) header for specified entry.
357      * REMIND: add support for file attributes
358      */

359     private void writeCEN(ZipEntry JavaDoc e) throws IOException JavaDoc {
360     writeInt(CENSIG); // CEN header signature
361
writeShort(e.version); // version made by
362
writeShort(e.version); // version needed to extract
363
writeShort(e.flag); // general purpose bit flag
364
writeShort(e.method); // compression method
365
writeInt(e.time); // last modification time
366
writeInt(e.crc); // crc-32
367
writeInt(e.csize); // compressed size
368
writeInt(e.size); // uncompressed size
369
byte[] nameBytes = getUTF8Bytes(e.name);
370     writeShort(nameBytes.length);
371     writeShort(e.extra != null ? e.extra.length : 0);
372     byte[] commentBytes;
373     if (e.comment != null) {
374         commentBytes = getUTF8Bytes(e.comment);
375         writeShort(commentBytes.length);
376     } else {
377         commentBytes = null;
378         writeShort(0);
379     }
380     writeShort(0); // starting disk number
381
writeShort(0); // internal file attributes (unused)
382
writeInt(0); // external file attributes (unused)
383
writeInt(e.offset); // relative offset of local header
384
writeBytes(nameBytes, 0, nameBytes.length);
385     if (e.extra != null) {
386         writeBytes(e.extra, 0, e.extra.length);
387     }
388     if (commentBytes != null) {
389         writeBytes(commentBytes, 0, commentBytes.length);
390     }
391     }
392
393     /*
394      * Writes end of central directory (END) header.
395      */

396     private void writeEND(long off, long len) throws IOException JavaDoc {
397     writeInt(ENDSIG); // END record signature
398
writeShort(0); // number of this disk
399
writeShort(0); // central directory start disk
400
writeShort(entries.size()); // number of directory entries on disk
401
writeShort(entries.size()); // total number of directory entries
402
writeInt(len); // length of central directory
403
writeInt(off); // offset of central directory
404
if (comment != null) { // zip file comment
405
byte[] b = getUTF8Bytes(comment);
406         writeShort(b.length);
407         writeBytes(b, 0, b.length);
408     } else {
409         writeShort(0);
410     }
411     }
412
413     /*
414      * Writes a 16-bit short to the output stream in little-endian byte order.
415      */

416     private void writeShort(int v) throws IOException JavaDoc {
417     OutputStream JavaDoc out = this.out;
418     out.write((v >>> 0) & 0xff);
419     out.write((v >>> 8) & 0xff);
420     written += 2;
421     }
422
423     /*
424      * Writes a 32-bit int to the output stream in little-endian byte order.
425      */

426     private void writeInt(long v) throws IOException JavaDoc {
427     OutputStream JavaDoc out = this.out;
428     out.write((int)((v >>> 0) & 0xff));
429     out.write((int)((v >>> 8) & 0xff));
430     out.write((int)((v >>> 16) & 0xff));
431     out.write((int)((v >>> 24) & 0xff));
432     written += 4;
433     }
434
435     /*
436      * Writes an array of bytes to the output stream.
437      */

438     private void writeBytes(byte[] b, int off, int len) throws IOException JavaDoc {
439     super.out.write(b, off, len);
440     written += len;
441     }
442
443     /*
444      * Returns the length of String's UTF8 encoding.
445      */

446     static int getUTF8Length(String JavaDoc s) {
447         int count = 0;
448         for (int i = 0; i < s.length(); i++) {
449             char ch = s.charAt(i);
450             if (ch <= 0x7f) {
451                 count++;
452             } else if (ch <= 0x7ff) {
453                 count += 2;
454             } else {
455                 count += 3;
456             }
457         }
458         return count;
459     }
460
461     /*
462      * Returns an array of bytes representing the UTF8 encoding
463      * of the specified String.
464      */

465     private static byte[] getUTF8Bytes(String JavaDoc s) {
466     char[] c = s.toCharArray();
467     int len = c.length;
468     // Count the number of encoded bytes...
469
int count = 0;
470     for (int i = 0; i < len; i++) {
471         int ch = c[i];
472         if (ch <= 0x7f) {
473         count++;
474         } else if (ch <= 0x7ff) {
475         count += 2;
476         } else {
477         count += 3;
478         }
479     }
480     // Now return the encoded bytes...
481
byte[] b = new byte[count];
482     int off = 0;
483     for (int i = 0; i < len; i++) {
484         int ch = c[i];
485         if (ch <= 0x7f) {
486         b[off++] = (byte)ch;
487         } else if (ch <= 0x7ff) {
488         b[off++] = (byte)((ch >> 6) | 0xc0);
489         b[off++] = (byte)((ch & 0x3f) | 0x80);
490         } else {
491         b[off++] = (byte)((ch >> 12) | 0xe0);
492         b[off++] = (byte)(((ch >> 6) & 0x3f) | 0x80);
493         b[off++] = (byte)((ch & 0x3f) | 0x80);
494         }
495     }
496     return b;
497     }
498 }
499
Popular Tags