source: other-projects/trunk/gs3-release-maker/apache-ant-1.6.5/src/main/org/apache/tools/zip/ZipOutputStream.java@ 14627

Last change on this file since 14627 was 14627, checked in by oranfry, 17 years ago

initial import of the gs3-release-maker

File size: 23.5 KB
Line 
1/*
2 * Copyright 2001-2005 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
18package org.apache.tools.zip;
19
20import java.io.File;
21import java.io.FileOutputStream;
22import java.io.FilterOutputStream;
23import java.io.IOException;
24import java.io.OutputStream;
25import java.io.RandomAccessFile;
26import java.io.UnsupportedEncodingException;
27import java.util.Date;
28import java.util.Hashtable;
29import java.util.Vector;
30import java.util.zip.CRC32;
31import java.util.zip.Deflater;
32import java.util.zip.ZipException;
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 */
54public class ZipOutputStream extends FilterOutputStream {
55
56 /**
57 * Current entry.
58 *
59 * @since 1.1
60 */
61 private ZipEntry entry;
62
63 /**
64 * The file comment.
65 *
66 * @since 1.1
67 */
68 private String comment = "";
69
70 /**
71 * Compression level for next entry.
72 *
73 * @since 1.1
74 */
75 private int level = Deflater.DEFAULT_COMPRESSION;
76
77 /**
78 * Has the compression level changed when compared to the last
79 * entry?
80 *
81 * @since 1.5
82 */
83 private boolean hasCompressionLevelChanged = false;
84
85 /**
86 * Default compression method for next entry.
87 *
88 * @since 1.1
89 */
90 private int method = DEFLATED;
91
92 /**
93 * List of ZipEntries written so far.
94 *
95 * @since 1.1
96 */
97 private Vector entries = new Vector();
98
99 /**
100 * CRC instance to avoid parsing DEFLATED data twice.
101 *
102 * @since 1.1
103 */
104 private CRC32 crc = new CRC32();
105
106 /**
107 * Count the bytes written to out.
108 *
109 * @since 1.1
110 */
111 private long written = 0;
112
113 /**
114 * Data for local header data
115 *
116 * @since 1.1
117 */
118 private long dataStart = 0;
119
120 /**
121 * Offset for CRC entry in the local file header data for the
122 * current entry starts here.
123 *
124 * @since 1.15
125 */
126 private long localDataStart = 0;
127
128 /**
129 * Start of central directory.
130 *
131 * @since 1.1
132 */
133 private ZipLong cdOffset = new ZipLong(0);
134
135 /**
136 * Length of central directory.
137 *
138 * @since 1.1
139 */
140 private ZipLong cdLength = new ZipLong(0);
141
142 /**
143 * Helper, a 0 as ZipShort.
144 *
145 * @since 1.1
146 */
147 private static final byte[] ZERO = {0, 0};
148
149 /**
150 * Helper, a 0 as ZipLong.
151 *
152 * @since 1.1
153 */
154 private static final byte[] LZERO = {0, 0, 0, 0};
155
156 /**
157 * Holds the offsets of the LFH starts for each entry.
158 *
159 * @since 1.1
160 */
161 private Hashtable offsets = new Hashtable();
162
163 /**
164 * The encoding to use for filenames and the file comment.
165 *
166 * <p>For a list of possible values see <a
167 * 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>.
168 * Defaults to the platform's default character encoding.</p>
169 *
170 * @since 1.3
171 */
172 private String encoding = null;
173
174 /**
175 * This Deflater object is used for output.
176 *
177 * <p>This attribute is only protected to provide a level of API
178 * backwards compatibility. This class used to extend {@link
179 * java.util.zip.DeflaterOutputStream DeflaterOutputStream} up to
180 * Revision 1.13.</p>
181 *
182 * @since 1.14
183 */
184 protected Deflater def = new Deflater(Deflater.DEFAULT_COMPRESSION, true);
185
186 /**
187 * This buffer servers as a Deflater.
188 *
189 * <p>This attribute is only protected to provide a level of API
190 * backwards compatibility. This class used to extend {@link
191 * java.util.zip.DeflaterOutputStream DeflaterOutputStream} up to
192 * Revision 1.13.</p>
193 *
194 * @since 1.14
195 */
196 protected byte[] buf = new byte[512];
197
198 /**
199 * Optional random access output.
200 *
201 * @since 1.14
202 */
203 private RandomAccessFile raf = null;
204
205 /**
206 * Compression method for deflated entries.
207 *
208 * @since 1.1
209 */
210 public static final int DEFLATED = ZipEntry.DEFLATED;
211
212 /**
213 * Compression method for deflated entries.
214 *
215 * @since 1.1
216 */
217 public static final int STORED = ZipEntry.STORED;
218
219 /**
220 * Creates a new ZIP OutputStream filtering the underlying stream.
221 *
222 * @since 1.1
223 */
224 public ZipOutputStream(OutputStream out) {
225 super(out);
226 }
227
228 /**
229 * Creates a new ZIP OutputStream writing to a File. Will use
230 * random access if possible.
231 *
232 * @since 1.14
233 */
234 public ZipOutputStream(File file) throws IOException {
235 super(null);
236
237 try {
238 raf = new RandomAccessFile(file, "rw");
239 raf.setLength(0);
240 } catch (IOException e) {
241 if (raf != null) {
242 try {
243 raf.close();
244 } catch (IOException inner) {
245 // ignore
246 }
247 raf = null;
248 }
249 out = new FileOutputStream(file);
250 }
251 }
252
253 /**
254 * This method indicates whether this archive is writing to a seekable stream (i.e., to a random
255 * access file).
256 *
257 * <p>For seekable streams, you don't need to calculate the CRC or
258 * uncompressed size for {@link #STORED} entries before
259 * invoking {@link #putNextEntry}.
260 *
261 * @since 1.17
262 */
263 public boolean isSeekable() {
264 return raf != null;
265 }
266
267 /**
268 * The encoding to use for filenames and the file comment.
269 *
270 * <p>For a list of possible values see <a
271 * 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>.
272 * Defaults to the platform's default character encoding.</p>
273 *
274 * @since 1.3
275 */
276 public void setEncoding(String encoding) {
277 this.encoding = encoding;
278 }
279
280 /**
281 * The encoding to use for filenames and the file comment.
282 *
283 * @return null if using the platform's default character encoding.
284 *
285 * @since 1.3
286 */
287 public String getEncoding() {
288 return encoding;
289 }
290
291 /**
292 * Finishs writing the contents and closes this as well as the
293 * underlying stream.
294 *
295 * @since 1.1
296 */
297 public void finish() throws IOException {
298 closeEntry();
299 cdOffset = new ZipLong(written);
300 for (int i = 0; i < entries.size(); i++) {
301 writeCentralFileHeader((ZipEntry) entries.elementAt(i));
302 }
303 cdLength = new ZipLong(written - cdOffset.getValue());
304 writeCentralDirectoryEnd();
305 offsets.clear();
306 entries.removeAllElements();
307 }
308
309 /**
310 * Writes all necessary data for this entry.
311 *
312 * @since 1.1
313 */
314 public void closeEntry() throws IOException {
315 if (entry == null) {
316 return;
317 }
318
319 long realCrc = crc.getValue();
320 crc.reset();
321
322 if (entry.getMethod() == DEFLATED) {
323 def.finish();
324 while (!def.finished()) {
325 deflate();
326 }
327
328 entry.setSize(adjustToLong(def.getTotalIn()));
329 entry.setComprSize(adjustToLong(def.getTotalOut()));
330 entry.setCrc(realCrc);
331
332 def.reset();
333
334 written += entry.getCompressedSize();
335 } else if (raf == null) {
336 if (entry.getCrc() != realCrc) {
337 throw new ZipException("bad CRC checksum for entry "
338 + entry.getName() + ": "
339 + Long.toHexString(entry.getCrc())
340 + " instead of "
341 + Long.toHexString(realCrc));
342 }
343
344 if (entry.getSize() != written - dataStart) {
345 throw new ZipException("bad size for entry "
346 + entry.getName() + ": "
347 + entry.getSize()
348 + " instead of "
349 + (written - dataStart));
350 }
351 } else { /* method is STORED and we used RandomAccessFile */
352 long size = written - dataStart;
353
354 entry.setSize(size);
355 entry.setComprSize(size);
356 entry.setCrc(realCrc);
357 }
358
359 // If random access output, write the local file header containing
360 // the correct CRC and compressed/uncompressed sizes
361 if (raf != null) {
362 long save = raf.getFilePointer();
363
364 raf.seek(localDataStart);
365 writeOut((new ZipLong(entry.getCrc())).getBytes());
366 writeOut((new ZipLong(entry.getCompressedSize())).getBytes());
367 writeOut((new ZipLong(entry.getSize())).getBytes());
368 raf.seek(save);
369 }
370
371 writeDataDescriptor(entry);
372 entry = null;
373 }
374
375 /**
376 * Begin writing next entry.
377 *
378 * @since 1.1
379 */
380 public void putNextEntry(ZipEntry ze) throws IOException {
381 closeEntry();
382
383 entry = ze;
384 entries.addElement(entry);
385
386 if (entry.getMethod() == -1) { // not specified
387 entry.setMethod(method);
388 }
389
390 if (entry.getTime() == -1) { // not specified
391 entry.setTime(System.currentTimeMillis());
392 }
393
394 // Size/CRC not required if RandomAccessFile is used
395 if (entry.getMethod() == STORED && raf == null) {
396 if (entry.getSize() == -1) {
397 throw new ZipException("uncompressed size is required for"
398 + " STORED method when not writing to a"
399 + " file");
400 }
401 if (entry.getCrc() == -1) {
402 throw new ZipException("crc checksum is required for STORED"
403 + " method when not writing to a file");
404 }
405 entry.setComprSize(entry.getSize());
406 }
407
408 if (entry.getMethod() == DEFLATED && hasCompressionLevelChanged) {
409 def.setLevel(level);
410 hasCompressionLevelChanged = false;
411 }
412 writeLocalFileHeader(entry);
413 }
414
415 /**
416 * Set the file comment.
417 *
418 * @since 1.1
419 */
420 public void setComment(String comment) {
421 this.comment = comment;
422 }
423
424 /**
425 * Sets the compression level for subsequent entries.
426 *
427 * <p>Default is Deflater.DEFAULT_COMPRESSION.</p>
428 *
429 * @since 1.1
430 */
431 public void setLevel(int level) {
432 hasCompressionLevelChanged = (this.level != level);
433 this.level = level;
434 }
435
436 /**
437 * Sets the default compression method for subsequent entries.
438 *
439 * <p>Default is DEFLATED.</p>
440 *
441 * @since 1.1
442 */
443 public void setMethod(int method) {
444 this.method = method;
445 }
446
447 /**
448 * Writes bytes to ZIP entry.
449 */
450 public void write(byte[] b, int offset, int length) throws IOException {
451 if (entry.getMethod() == DEFLATED) {
452 if (length > 0) {
453 if (!def.finished()) {
454 def.setInput(b, offset, length);
455 while (!def.needsInput()) {
456 deflate();
457 }
458 }
459 }
460 } else {
461 writeOut(b, offset, length);
462 written += length;
463 }
464 crc.update(b, offset, length);
465 }
466
467 /**
468 * Writes a single byte to ZIP entry.
469 *
470 * <p>Delegates to the three arg method.</p>
471 *
472 * @since 1.14
473 */
474 public void write(int b) throws IOException {
475 byte[] buf = new byte[1];
476 buf[0] = (byte) (b & 0xff);
477 write(buf, 0, 1);
478 }
479
480 /**
481 * Closes this output stream and releases any system resources
482 * associated with the stream.
483 *
484 * @exception IOException if an I/O error occurs.
485 * @since 1.14
486 */
487 public void close() throws IOException {
488 finish();
489
490 if (raf != null) {
491 raf.close();
492 }
493 if (out != null) {
494 out.close();
495 }
496 }
497
498 /**
499 * Flushes this output stream and forces any buffered output bytes
500 * to be written out to the stream.
501 *
502 * @exception IOException if an I/O error occurs.
503 * @since 1.14
504 */
505 public void flush() throws IOException {
506 if (out != null) {
507 out.flush();
508 }
509 }
510
511 /*
512 * Various ZIP constants
513 */
514 /**
515 * local file header signature
516 *
517 * @since 1.1
518 */
519 protected static final ZipLong LFH_SIG = new ZipLong(0X04034B50L);
520 /**
521 * data descriptor signature
522 *
523 * @since 1.1
524 */
525 protected static final ZipLong DD_SIG = new ZipLong(0X08074B50L);
526 /**
527 * central file header signature
528 *
529 * @since 1.1
530 */
531 protected static final ZipLong CFH_SIG = new ZipLong(0X02014B50L);
532 /**
533 * end of central dir signature
534 *
535 * @since 1.1
536 */
537 protected static final ZipLong EOCD_SIG = new ZipLong(0X06054B50L);
538
539 /**
540 * Writes next block of compressed data to the output stream.
541 *
542 * @since 1.14
543 */
544 protected final void deflate() throws IOException {
545 int len = def.deflate(buf, 0, buf.length);
546 if (len > 0) {
547 writeOut(buf, 0, len);
548 }
549 }
550
551 /**
552 * Writes the local file header entry
553 *
554 * @since 1.1
555 */
556 protected void writeLocalFileHeader(ZipEntry ze) throws IOException {
557 offsets.put(ze, new ZipLong(written));
558
559 writeOut(LFH_SIG.getBytes());
560 written += 4;
561
562 // version needed to extract
563 // general purpose bit flag
564 if (ze.getMethod() == DEFLATED && raf == null) {
565 // requires version 2 as we are going to store length info
566 // in the data descriptor
567 writeOut((new ZipShort(20)).getBytes());
568
569 // bit3 set to signal, we use a data descriptor
570 writeOut((new ZipShort(8)).getBytes());
571 } else {
572 writeOut((new ZipShort(10)).getBytes());
573 writeOut(ZERO);
574 }
575 written += 4;
576
577 // compression method
578 writeOut((new ZipShort(ze.getMethod())).getBytes());
579 written += 2;
580
581 // last mod. time and date
582 writeOut(toDosTime(new Date(ze.getTime())).getBytes());
583 written += 4;
584
585 // CRC
586 // compressed length
587 // uncompressed length
588 localDataStart = written;
589 if (ze.getMethod() == DEFLATED || raf != null) {
590 writeOut(LZERO);
591 writeOut(LZERO);
592 writeOut(LZERO);
593 } else {
594 writeOut((new ZipLong(ze.getCrc())).getBytes());
595 writeOut((new ZipLong(ze.getSize())).getBytes());
596 writeOut((new ZipLong(ze.getSize())).getBytes());
597 }
598 written += 12;
599
600 // file name length
601 byte[] name = getBytes(ze.getName());
602 writeOut((new ZipShort(name.length)).getBytes());
603 written += 2;
604
605 // extra field length
606 byte[] extra = ze.getLocalFileDataExtra();
607 writeOut((new ZipShort(extra.length)).getBytes());
608 written += 2;
609
610 // file name
611 writeOut(name);
612 written += name.length;
613
614 // extra field
615 writeOut(extra);
616 written += extra.length;
617
618 dataStart = written;
619 }
620
621 /**
622 * Writes the data descriptor entry
623 *
624 * @since 1.1
625 */
626 protected void writeDataDescriptor(ZipEntry ze) throws IOException {
627 if (ze.getMethod() != DEFLATED || raf != null) {
628 return;
629 }
630 writeOut(DD_SIG.getBytes());
631 writeOut((new ZipLong(entry.getCrc())).getBytes());
632 writeOut((new ZipLong(entry.getCompressedSize())).getBytes());
633 writeOut((new ZipLong(entry.getSize())).getBytes());
634 written += 16;
635 }
636
637 /**
638 * Writes the central file header entry
639 *
640 * @since 1.1
641 */
642 protected void writeCentralFileHeader(ZipEntry ze) throws IOException {
643 writeOut(CFH_SIG.getBytes());
644 written += 4;
645
646 // version made by
647 writeOut((new ZipShort((ze.getPlatform() << 8) | 20)).getBytes());
648 written += 2;
649
650 // version needed to extract
651 // general purpose bit flag
652 if (ze.getMethod() == DEFLATED && raf == null) {
653 // requires version 2 as we are going to store length info
654 // in the data descriptor
655 writeOut((new ZipShort(20)).getBytes());
656
657 // bit3 set to signal, we use a data descriptor
658 writeOut((new ZipShort(8)).getBytes());
659 } else {
660 writeOut((new ZipShort(10)).getBytes());
661 writeOut(ZERO);
662 }
663 written += 4;
664
665 // compression method
666 writeOut((new ZipShort(ze.getMethod())).getBytes());
667 written += 2;
668
669 // last mod. time and date
670 writeOut(toDosTime(new Date(ze.getTime())).getBytes());
671 written += 4;
672
673 // CRC
674 // compressed length
675 // uncompressed length
676 writeOut((new ZipLong(ze.getCrc())).getBytes());
677 writeOut((new ZipLong(ze.getCompressedSize())).getBytes());
678 writeOut((new ZipLong(ze.getSize())).getBytes());
679 written += 12;
680
681 // file name length
682 byte[] name = getBytes(ze.getName());
683 writeOut((new ZipShort(name.length)).getBytes());
684 written += 2;
685
686 // extra field length
687 byte[] extra = ze.getCentralDirectoryExtra();
688 writeOut((new ZipShort(extra.length)).getBytes());
689 written += 2;
690
691 // file comment length
692 String comm = ze.getComment();
693 if (comm == null) {
694 comm = "";
695 }
696 byte[] comment = getBytes(comm);
697 writeOut((new ZipShort(comment.length)).getBytes());
698 written += 2;
699
700 // disk number start
701 writeOut(ZERO);
702 written += 2;
703
704 // internal file attributes
705 writeOut((new ZipShort(ze.getInternalAttributes())).getBytes());
706 written += 2;
707
708 // external file attributes
709 writeOut((new ZipLong(ze.getExternalAttributes())).getBytes());
710 written += 4;
711
712 // relative offset of LFH
713 writeOut(((ZipLong) offsets.get(ze)).getBytes());
714 written += 4;
715
716 // file name
717 writeOut(name);
718 written += name.length;
719
720 // extra field
721 writeOut(extra);
722 written += extra.length;
723
724 // file comment
725 writeOut(comment);
726 written += comment.length;
727 }
728
729 /**
730 * Writes the &quot;End of central dir record&quot;
731 *
732 * @since 1.1
733 */
734 protected void writeCentralDirectoryEnd() throws IOException {
735 writeOut(EOCD_SIG.getBytes());
736
737 // disk numbers
738 writeOut(ZERO);
739 writeOut(ZERO);
740
741 // number of entries
742 byte[] num = (new ZipShort(entries.size())).getBytes();
743 writeOut(num);
744 writeOut(num);
745
746 // length and location of CD
747 writeOut(cdLength.getBytes());
748 writeOut(cdOffset.getBytes());
749
750 // ZIP file comment
751 byte[] data = getBytes(comment);
752 writeOut((new ZipShort(data.length)).getBytes());
753 writeOut(data);
754 }
755
756 /**
757 * Smallest date/time ZIP can handle.
758 *
759 * @since 1.1
760 */
761 private static final ZipLong DOS_TIME_MIN = new ZipLong(0x00002100L);
762
763 /**
764 * Convert a Date object to a DOS date/time field.
765 *
766 * <p>Stolen from InfoZip's <code>fileio.c</code></p>
767 *
768 * @since 1.1
769 */
770 protected static ZipLong toDosTime(Date time) {
771 int year = time.getYear() + 1900;
772 int month = time.getMonth() + 1;
773 if (year < 1980) {
774 return DOS_TIME_MIN;
775 }
776 long value = ((year - 1980) << 25)
777 | (month << 21)
778 | (time.getDate() << 16)
779 | (time.getHours() << 11)
780 | (time.getMinutes() << 5)
781 | (time.getSeconds() >> 1);
782
783 byte[] result = new byte[4];
784 result[0] = (byte) ((value & 0xFF));
785 result[1] = (byte) ((value & 0xFF00) >> 8);
786 result[2] = (byte) ((value & 0xFF0000) >> 16);
787 result[3] = (byte) ((value & 0xFF000000L) >> 24);
788 return new ZipLong(result);
789 }
790
791 /**
792 * Retrieve the bytes for the given String in the encoding set for
793 * this Stream.
794 *
795 * @since 1.3
796 */
797 protected byte[] getBytes(String name) throws ZipException {
798 if (encoding == null) {
799 return name.getBytes();
800 } else {
801 try {
802 return name.getBytes(encoding);
803 } catch (UnsupportedEncodingException uee) {
804 throw new ZipException(uee.getMessage());
805 }
806 }
807 }
808
809 /**
810 * Write bytes to output or random access file
811 *
812 * @since 1.14
813 */
814 protected final void writeOut(byte [] data) throws IOException {
815 writeOut(data, 0, data.length);
816 }
817
818 /**
819 * Write bytes to output or random access file
820 *
821 * @since 1.14
822 */
823 protected final void writeOut(byte [] data, int offset, int length)
824 throws IOException {
825 if (raf != null) {
826 raf.write(data, offset, length);
827 } else {
828 out.write(data, offset, length);
829 }
830 }
831
832 /**
833 * Assumes a negative integer really is a positive integer that
834 * has wrapped around and re-creates the original value.
835 *
836 * @since 1.17.2.8
837 */
838 protected static long adjustToLong(int i) {
839 if (i < 0) {
840 return 2 * ((long) Integer.MAX_VALUE) + 2 + i;
841 } else {
842 return i;
843 }
844 }
845
846}
Note: See TracBrowser for help on using the repository browser.