KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > net > sourceforge > groboutils > codecoverage > v2 > ant > zip > ZipOutputStream


1 /*
2  * Copyright 2001-2004 The Apache Software Foundation
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  */

17
18 package net.sourceforge.groboutils.codecoverage.v2.ant.zip;
19
20 import java.io.File JavaDoc;
21 import java.io.FileOutputStream JavaDoc;
22 import java.io.FilterOutputStream JavaDoc;
23 import java.io.IOException JavaDoc;
24 import java.io.OutputStream JavaDoc;
25 import java.io.RandomAccessFile JavaDoc;
26 import java.io.UnsupportedEncodingException JavaDoc;
27 import java.util.Date JavaDoc;
28 import java.util.Hashtable JavaDoc;
29 import java.util.Vector JavaDoc;
30 import java.util.zip.CRC32 JavaDoc;
31 import java.util.zip.Deflater JavaDoc;
32 import java.util.zip.ZipException JavaDoc;
33
34 /**
35  * Reimplementation of {@link java.util.zip.ZipOutputStream
36  * java.util.zip.ZipOutputStream} that does handle the extended
37  * functionality of this package, especially internal/external file
38  * attributes and extra fields with different layouts for local file
39  * data and central directory entries.
40  *
41  * <p>This class will try to use {@link java.io.RandomAccessFile
42  * RandomAccessFile} when you know that the output is going to go to a
43  * file.</p>
44  *
45  * <p>If RandomAccessFile cannot be used, this implementation will use
46  * a Data Descriptor to store size and CRC information for {@link
47  * #DEFLATED DEFLATED} entries, this means, you don't need to
48  * calculate them yourself. Unfortunately this is not possible for
49  * the {@link #STORED STORED} method, here setting the CRC and
50  * uncompressed size information is required before {@link
51  * #putNextEntry putNextEntry} can be called.</p>
52  *
53  * @author Stefan Bodewig
54  * @author Richard Evans
55  * @version $Revision: 1.1 $
56  */

57 public class ZipOutputStream extends FilterOutputStream JavaDoc {
58
59     /**
60      * Current entry.
61      *
62      * @since 1.1
63      */

64     private ZipEntry entry;
65
66     /**
67      * The file comment.
68      *
69      * @since 1.1
70      */

71     private String JavaDoc comment = "";
72
73     /**
74      * Compression level for next entry.
75      *
76      * @since 1.1
77      */

78     private int level = Deflater.DEFAULT_COMPRESSION;
79
80     /**
81      * Has the compression level changed when compared to the last
82      * entry?
83      *
84      * @since 1.5
85      */

86     private boolean hasCompressionLevelChanged = false;
87
88     /**
89      * Default compression method for next entry.
90      *
91      * @since 1.1
92      */

93     private int method = DEFLATED;
94
95     /**
96      * List of ZipEntries written so far.
97      *
98      * @since 1.1
99      */

100     private Vector JavaDoc entries = new Vector JavaDoc();
101
102     /**
103      * CRC instance to avoid parsing DEFLATED data twice.
104      *
105      * @since 1.1
106      */

107     private CRC32 JavaDoc crc = new CRC32 JavaDoc();
108
109     /**
110      * Count the bytes written to out.
111      *
112      * @since 1.1
113      */

114     private long written = 0;
115
116     /**
117      * Data for local header data
118      *
119      * @since 1.1
120      */

121     private long dataStart = 0;
122
123     /**
124      * Offset for CRC entry in the local file header data for the
125      * current entry starts here.
126      *
127      * @since 1.15
128      */

129     private long localDataStart = 0;
130
131     /**
132      * Start of central directory.
133      *
134      * @since 1.1
135      */

136     private ZipLong cdOffset = new ZipLong(0);
137
138     /**
139      * Length of central directory.
140      *
141      * @since 1.1
142      */

143     private ZipLong cdLength = new ZipLong(0);
144
145     /**
146      * Helper, a 0 as ZipShort.
147      *
148      * @since 1.1
149      */

150     private static final byte[] ZERO = {0, 0};
151
152     /**
153      * Helper, a 0 as ZipLong.
154      *
155      * @since 1.1
156      */

157     private static final byte[] LZERO = {0, 0, 0, 0};
158
159     /**
160      * Holds the offsets of the LFH starts for each entry
161      *
162      * @since 1.1
163      */

164     private Hashtable JavaDoc offsets = new Hashtable JavaDoc();
165
166     /**
167      * The encoding to use for filenames and the file comment.
168      *
169      * <p>For a list of possible values see <a
170      * HREF="http://java.sun.com/products/jdk/1.2/docs/guide/internat/encoding.doc.html">http://java.sun.com/products/jdk/1.2/docs/guide/internat/encoding.doc.html</a>.
171      * Defaults to the platform's default character encoding.</p>
172      *
173      * @since 1.3
174      */

175     private String JavaDoc encoding = null;
176
177     /**
178      * Deflater object for output
179      *
180      * <p>This attribute is only protected to provide a level of API
181      * backwards compatibility. This class used to extend {@link
182      * java.util.zip.DeflaterOutputStream DeflaterOutputStream} up to
183      * Revision 1.13.</p>
184      *
185      * @since 1.14
186      */

187     protected Deflater JavaDoc def = new Deflater JavaDoc(Deflater.DEFAULT_COMPRESSION, true);
188
189     /**
190      * Deflater buffer
191      *
192      * <p>This attribute is only protected to provide a level of API
193      * backwards compatibility. This class used to extend {@link
194      * java.util.zip.DeflaterOutputStream DeflaterOutputStream} up to
195      * Revision 1.13.</p>
196      *
197      * @since 1.14
198      */

199     protected byte[] buf = new byte[512];
200
201     /**
202      * Optional random access output
203      *
204      * @since 1.14
205      */

206     private RandomAccessFile JavaDoc raf = null;
207
208     /**
209      * Compression method for deflated entries.
210      *
211      * @since 1.1
212      */

213     public static final int DEFLATED = ZipEntry.DEFLATED;
214
215     /**
216      * Compression method for deflated entries.
217      *
218      * @since 1.1
219      */

220     public static final int STORED = ZipEntry.STORED;
221
222     /**
223      * Creates a new ZIP OutputStream filtering the underlying stream.
224      *
225      * @since 1.1
226      */

227     public ZipOutputStream(OutputStream JavaDoc out) {
228         super(out);
229     }
230
231     /**
232      * Creates a new ZIP OutputStream writing to a File. Will use
233      * random access if possible.
234      *
235      * @since 1.14
236      */

237     public ZipOutputStream(File JavaDoc file) throws IOException JavaDoc {
238         super(null);
239
240         try {
241             raf = new RandomAccessFile JavaDoc(file, "rw");
242             raf.setLength(0);
243         } catch (IOException JavaDoc e) {
244             if (raf != null) {
245                 try {
246                     raf.close();
247                 } catch (IOException JavaDoc inner) {
248                     // ignore
249
}
250                 raf = null;
251             }
252             out = new FileOutputStream JavaDoc(file);
253         }
254     }
255
256     /**
257      * Is this archive writing to a seekable stream (i.e. a random
258      * access file)?
259      *
260      * <p>For seekable streams, you don't need to calculate the CRC or
261      * uncompressed size for {@link #STORED STORED} entries before
262      * invoking {@link #putEntry putEntry}.
263      *
264      * @since 1.17
265      */

266     public boolean isSeekable() {
267         return raf != null;
268     }
269
270     /**
271      * The encoding to use for filenames and the file comment.
272      *
273      * <p>For a list of possible values see <a
274      * HREF="http://java.sun.com/products/jdk/1.2/docs/guide/internat/encoding.doc.html">http://java.sun.com/products/jdk/1.2/docs/guide/internat/encoding.doc.html</a>.
275      * Defaults to the platform's default character encoding.</p>
276      *
277      * @since 1.3
278      */

279     public void setEncoding(String JavaDoc encoding) {
280         this.encoding = encoding;
281     }
282
283     /**
284      * The encoding to use for filenames and the file comment.
285      *
286      * @return null if using the platform's default character encoding.
287      *
288      * @since 1.3
289      */

290     public String JavaDoc getEncoding() {
291         return encoding;
292     }
293
294     /**
295      * Finishs writing the contents and closes this as well as the
296      * underlying stream.
297      *
298      * @since 1.1
299      */

300     public void finish() throws IOException JavaDoc {
301         closeEntry();
302         cdOffset = new ZipLong(written);
303         for (int i = 0; i < entries.size(); i++) {
304             writeCentralFileHeader((ZipEntry) entries.elementAt(i));
305         }
306         cdLength = new ZipLong(written - cdOffset.getValue());
307         writeCentralDirectoryEnd();
308         offsets.clear();
309         entries.removeAllElements();
310     }
311
312     /**
313      * Writes all necessary data for this entry.
314      *
315      * @since 1.1
316      */

317     public void closeEntry() throws IOException JavaDoc {
318         if (entry == null) {
319             return;
320         }
321
322         long realCrc = crc.getValue();
323         crc.reset();
324
325         if (entry.getMethod() == DEFLATED) {
326             def.finish();
327             while (!def.finished()) {
328                 deflate();
329             }
330
331             entry.setSize(def.getTotalIn());
332             entry.setComprSize(def.getTotalOut());
333             entry.setCrc(realCrc);
334
335             def.reset();
336
337             written += entry.getCompressedSize();
338         } else if (raf == null) {
339             if (entry.getCrc() != realCrc) {
340                 throw new ZipException JavaDoc("bad CRC checksum for entry "
341                                        + entry.getName() + ": "
342                                        + Long.toHexString(entry.getCrc())
343                                        + " instead of "
344                                        + Long.toHexString(realCrc));
345             }
346
347             if (entry.getSize() != written - dataStart) {
348                 throw new ZipException JavaDoc("bad size for entry "
349                                        + entry.getName() + ": "
350                                        + entry.getSize()
351                                        + " instead of "
352                                        + (written - dataStart));
353             }
354         } else { /* method is STORED and we used RandomAccessFile */
355             long size = written - dataStart;
356
357             entry.setSize(size);
358             entry.setComprSize(size);
359             entry.setCrc(realCrc);
360         }
361
362         // If random access output, write the local file header containing
363
// the correct CRC and compressed/uncompressed sizes
364
if (raf != null) {
365             long save = raf.getFilePointer();
366
367             raf.seek(localDataStart);
368             writeOut((new ZipLong(entry.getCrc())).getBytes());
369             writeOut((new ZipLong(entry.getCompressedSize())).getBytes());
370             writeOut((new ZipLong(entry.getSize())).getBytes());
371             raf.seek(save);
372         }
373
374         writeDataDescriptor(entry);
375         entry = null;
376     }
377
378     /**
379      * Begin writing next entry.
380      *
381      * @since 1.1
382      */

383     public void putNextEntry(ZipEntry ze) throws IOException JavaDoc {
384         closeEntry();
385
386         entry = ze;
387         entries.addElement(entry);
388
389         if (entry.getMethod() == -1) { // not specified
390
entry.setMethod(method);
391         }
392
393         if (entry.getTime() == -1) { // not specified
394
entry.setTime(System.currentTimeMillis());
395         }
396
397         // Size/CRC not required if RandomAccessFile is used
398
if (entry.getMethod() == STORED && raf == null) {
399             if (entry.getSize() == -1) {
400                 throw new ZipException JavaDoc("uncompressed size is required for"
401                                        + " STORED method when not writing to a"
402                                        + " file");
403             }
404             if (entry.getCrc() == -1) {
405                 throw new ZipException JavaDoc("crc checksum is required for STORED"
406                                        + " method when not writing to a file");
407             }
408             entry.setComprSize(entry.getSize());
409         }
410
411         if (entry.getMethod() == DEFLATED && hasCompressionLevelChanged) {
412             def.setLevel(level);
413             hasCompressionLevelChanged = false;
414         }
415         writeLocalFileHeader(entry);
416     }
417
418     /**
419      * Set the file comment.
420      *
421      * @since 1.1
422      */

423     public void setComment(String JavaDoc comment) {
424         this.comment = comment;
425     }
426
427     /**
428      * Sets the compression level for subsequent entries.
429      *
430      * <p>Default is Deflater.DEFAULT_COMPRESSION.</p>
431      *
432      * @since 1.1
433      */

434     public void setLevel(int level) {
435         hasCompressionLevelChanged = (this.level != level);
436         this.level = level;
437     }
438
439     /**
440      * Sets the default compression method for subsequent entries.
441      *
442      * <p>Default is DEFLATED.</p>
443      *
444      * @since 1.1
445      */

446     public void setMethod(int method) {
447         this.method = method;
448     }
449
450     /**
451      * Writes bytes to ZIP entry.
452      */

453     public void write(byte[] b, int offset, int length) throws IOException JavaDoc {
454         if (entry.getMethod() == DEFLATED) {
455             if (length > 0) {
456                 if (!def.finished()) {
457                     def.setInput(b, offset, length);
458                     while (!def.needsInput()) {
459                         deflate();
460                     }
461                 }
462             }
463         } else {
464             writeOut(b, offset, length);
465             written += length;
466         }
467         crc.update(b, offset, length);
468     }
469
470     /**
471      * Writes a single byte to ZIP entry.
472      *
473      * <p>Delegates to the three arg method.</p>
474      *
475      * @since 1.14
476      */

477     public void write(int b) throws IOException JavaDoc {
478         byte[] buf = new byte[1];
479         buf[0] = (byte) (b & 0xff);
480         write(buf, 0, 1);
481     }
482
483     /**
484      * Closes this output stream and releases any system resources
485      * associated with the stream.
486      *
487      * @exception IOException if an I/O error occurs.
488      * @since 1.14
489      */

490     public void close() throws IOException JavaDoc {
491         finish();
492
493         if (raf != null) {
494             raf.close();
495         }
496         if (out != null) {
497             out.close();
498         }
499     }
500
501     /**
502      * Flushes this output stream and forces any buffered output bytes
503      * to be written out to the stream.
504      *
505      * @exception IOException if an I/O error occurs.
506      * @since 1.14
507      */

508     public void flush() throws IOException JavaDoc {
509         if (out == null) {
510             out.flush();
511         }
512     }
513
514     /*
515      * Various ZIP constants
516      */

517     /**
518      * local file header signature
519      *
520      * @since 1.1
521      */

522     protected static final ZipLong LFH_SIG = new ZipLong(0X04034B50L);
523     /**
524      * data descriptor signature
525      *
526      * @since 1.1
527      */

528     protected static final ZipLong DD_SIG = new ZipLong(0X08074B50L);
529     /**
530      * central file header signature
531      *
532      * @since 1.1
533      */

534     protected static final ZipLong CFH_SIG = new ZipLong(0X02014B50L);
535     /**
536      * end of central dir signature
537      *
538      * @since 1.1
539      */

540     protected static final ZipLong EOCD_SIG = new ZipLong(0X06054B50L);
541
542     /**
543      * Writes next block of compressed data to the output stream.
544      *
545      * @since 1.14
546      */

547     protected final void deflate() throws IOException JavaDoc {
548         int len = def.deflate(buf, 0, buf.length);
549         if (len > 0) {
550             writeOut(buf, 0, len);
551         }
552     }
553
554     /**
555      * Writes the local file header entry
556      *
557      * @since 1.1
558      */

559     protected void writeLocalFileHeader(ZipEntry ze) throws IOException JavaDoc {
560         offsets.put(ze, new ZipLong(written));
561
562         writeOut(LFH_SIG.getBytes());
563         written += 4;
564
565         // version needed to extract
566
// general purpose bit flag
567
if (ze.getMethod() == DEFLATED && raf == null) {
568             // requires version 2 as we are going to store length info
569
// in the data descriptor
570
writeOut((new ZipShort(20)).getBytes());
571
572             // bit3 set to signal, we use a data descriptor
573
writeOut((new ZipShort(8)).getBytes());
574         } else {
575             writeOut((new ZipShort(10)).getBytes());
576             writeOut(ZERO);
577         }
578         written += 4;
579
580         // compression method
581
writeOut((new ZipShort(ze.getMethod())).getBytes());
582         written += 2;
583
584         // last mod. time and date
585
writeOut(toDosTime(new Date JavaDoc(ze.getTime())).getBytes());
586         written += 4;
587
588         // CRC
589
// compressed length
590
// uncompressed length
591
localDataStart = written;
592         if (ze.getMethod() == DEFLATED || raf != null) {
593             writeOut(LZERO);
594             writeOut(LZERO);
595             writeOut(LZERO);
596         } else {
597             writeOut((new ZipLong(ze.getCrc())).getBytes());
598             writeOut((new ZipLong(ze.getSize())).getBytes());
599             writeOut((new ZipLong(ze.getSize())).getBytes());
600         }
601         written += 12;
602
603         // file name length
604
byte[] name = getBytes(ze.getName());
605         writeOut((new ZipShort(name.length)).getBytes());
606         written += 2;
607
608         // extra field length
609
byte[] extra = ze.getLocalFileDataExtra();
610         writeOut((new ZipShort(extra.length)).getBytes());
611         written += 2;
612
613         // file name
614
writeOut(name);
615         written += name.length;
616
617         // extra field
618
writeOut(extra);
619         written += extra.length;
620
621         dataStart = written;
622     }
623
624     /**
625      * Writes the data descriptor entry
626      *
627      * @since 1.1
628      */

629     protected void writeDataDescriptor(ZipEntry ze) throws IOException JavaDoc {
630         if (ze.getMethod() != DEFLATED || raf != null) {
631             return;
632         }
633         writeOut(DD_SIG.getBytes());
634         writeOut((new ZipLong(entry.getCrc())).getBytes());
635         writeOut((new ZipLong(entry.getCompressedSize())).getBytes());
636         writeOut((new ZipLong(entry.getSize())).getBytes());
637         written += 16;
638     }
639
640     /**
641      * Writes the central file header entry
642      *
643      * @since 1.1
644      */

645     protected void writeCentralFileHeader(ZipEntry ze) throws IOException JavaDoc {
646         writeOut(CFH_SIG.getBytes());
647         written += 4;
648
649         // version made by
650
writeOut((new ZipShort((ze.getPlatform() << 8) | 20)).getBytes());
651         written += 2;
652
653         // version needed to extract
654
// general purpose bit flag
655
if (ze.getMethod() == DEFLATED && raf == null) {
656             // requires version 2 as we are going to store length info
657
// in the data descriptor
658
writeOut((new ZipShort(20)).getBytes());
659
660             // bit3 set to signal, we use a data descriptor
661
writeOut((new ZipShort(8)).getBytes());
662         } else {
663             writeOut((new ZipShort(10)).getBytes());
664             writeOut(ZERO);
665         }
666         written += 4;
667
668         // compression method
669
writeOut((new ZipShort(ze.getMethod())).getBytes());
670         written += 2;
671
672         // last mod. time and date
673
writeOut(toDosTime(new Date JavaDoc(ze.getTime())).getBytes());
674         written += 4;
675
676         // CRC
677
// compressed length
678
// uncompressed length
679
writeOut((new ZipLong(ze.getCrc())).getBytes());
680         writeOut((new ZipLong(ze.getCompressedSize())).getBytes());
681         writeOut((new ZipLong(ze.getSize())).getBytes());
682         written += 12;
683
684         // file name length
685
byte[] name = getBytes(ze.getName());
686         writeOut((new ZipShort(name.length)).getBytes());
687         written += 2;
688
689         // extra field length
690
byte[] extra = ze.getCentralDirectoryExtra();
691         writeOut((new ZipShort(extra.length)).getBytes());
692         written += 2;
693
694         // file comment length
695
String JavaDoc comm = ze.getComment();
696         if (comm == null) {
697             comm = "";
698         }
699         byte[] comment = getBytes(comm);
700         writeOut((new ZipShort(comment.length)).getBytes());
701         written += 2;
702
703         // disk number start
704
writeOut(ZERO);
705         written += 2;
706
707         // internal file attributes
708
writeOut((new ZipShort(ze.getInternalAttributes())).getBytes());
709         written += 2;
710
711         // external file attributes
712
writeOut((new ZipLong(ze.getExternalAttributes())).getBytes());
713         written += 4;
714
715         // relative offset of LFH
716
writeOut(((ZipLong) offsets.get(ze)).getBytes());
717         written += 4;
718
719         // file name
720
writeOut(name);
721         written += name.length;
722
723         // extra field
724
writeOut(extra);
725         written += extra.length;
726
727         // file comment
728
writeOut(comment);
729         written += comment.length;
730     }
731
732     /**
733      * Writes the &quot;End of central dir record&quot;
734      *
735      * @since 1.1
736      */

737     protected void writeCentralDirectoryEnd() throws IOException JavaDoc {
738         writeOut(EOCD_SIG.getBytes());
739
740         // disk numbers
741
writeOut(ZERO);
742         writeOut(ZERO);
743
744         // number of entries
745
byte[] num = (new ZipShort(entries.size())).getBytes();
746         writeOut(num);
747         writeOut(num);
748
749         // length and location of CD
750
writeOut(cdLength.getBytes());
751         writeOut(cdOffset.getBytes());
752
753         // ZIP file comment
754
byte[] data = getBytes(comment);
755         writeOut((new ZipShort(data.length)).getBytes());
756         writeOut(data);
757     }
758
759     /**
760      * Smallest date/time ZIP can handle.
761      *
762      * @since 1.1
763      */

764     private static final ZipLong DOS_TIME_MIN = new ZipLong(0x00002100L);
765
766     /**
767      * Convert a Date object to a DOS date/time field.
768      *
769      * <p>Stolen from InfoZip's <code>fileio.c</code></p>
770      *
771      * @since 1.1
772      */

773     protected static ZipLong toDosTime(Date JavaDoc time) {
774         int year = time.getYear() + 1900;
775         int month = time.getMonth() + 1;
776         if (year < 1980) {
777             return DOS_TIME_MIN;
778         }
779         long value = ((year - 1980) << 25)
780             | (month << 21)
781             | (time.getDate() << 16)
782             | (time.getHours() << 11)
783             | (time.getMinutes() << 5)
784             | (time.getSeconds() >> 1);
785
786         byte[] result = new byte[4];
787         result[0] = (byte) ((value & 0xFF));
788         result[1] = (byte) ((value & 0xFF00) >> 8);
789         result[2] = (byte) ((value & 0xFF0000) >> 16);
790         result[3] = (byte) ((value & 0xFF000000L) >> 24);
791         return new ZipLong(result);
792     }
793
794     /**
795      * Retrieve the bytes for the given String in the encoding set for
796      * this Stream.
797      *
798      * @since 1.3
799      */

800     protected byte[] getBytes(String JavaDoc name) throws ZipException JavaDoc {
801         if (encoding == null) {
802             return name.getBytes();
803         } else {
804             try {
805                 return name.getBytes(encoding);
806             } catch (UnsupportedEncodingException JavaDoc uee) {
807                 throw new ZipException JavaDoc(uee.getMessage());
808             }
809         }
810     }
811
812     /**
813      * Write bytes to output or random access file
814      *
815      * @since 1.14
816      */

817     protected final void writeOut(byte [] data) throws IOException JavaDoc {
818         writeOut(data, 0, data.length);
819     }
820
821     /**
822      * Write bytes to output or random access file
823      *
824      * @since 1.14
825      */

826     protected final void writeOut(byte [] data, int offset, int length)
827         throws IOException JavaDoc {
828         if (raf != null) {
829             raf.write(data, offset, length);
830         } else {
831             out.write(data, offset, length);
832         }
833     }
834 }
835
Popular Tags