OSDN Git Service

dmg作成バージョンの取得方法の修正
[charactermanaj/CharacterManaJ.git] / src / main / java / org / apache / tools / zip / ZipOutputStream.java
1 /*\r
2  *  Licensed to the Apache Software Foundation (ASF) under one or more\r
3  *  contributor license agreements.  See the NOTICE file distributed with\r
4  *  this work for additional information regarding copyright ownership.\r
5  *  The ASF licenses this file to You under the Apache License, Version 2.0\r
6  *  (the "License"); you may not use this file except in compliance with\r
7  *  the License.  You may obtain a copy of the License at\r
8  *\r
9  *      http://www.apache.org/licenses/LICENSE-2.0\r
10  *\r
11  *  Unless required by applicable law or agreed to in writing, software\r
12  *  distributed under the License is distributed on an "AS IS" BASIS,\r
13  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
14  *  See the License for the specific language governing permissions and\r
15  *  limitations under the License.\r
16  *\r
17  */\r
18 \r
19 package org.apache.tools.zip;\r
20 \r
21 import java.io.File;\r
22 import java.io.FileOutputStream;\r
23 import java.io.FilterOutputStream;\r
24 import java.io.IOException;\r
25 import java.io.OutputStream;\r
26 import java.io.RandomAccessFile;\r
27 import java.nio.ByteBuffer;\r
28 import java.util.Date;\r
29 import java.util.HashMap;\r
30 import java.util.Iterator;\r
31 import java.util.LinkedList;\r
32 import java.util.List;\r
33 import java.util.Map;\r
34 import java.util.zip.CRC32;\r
35 import java.util.zip.Deflater;\r
36 import java.util.zip.ZipException;\r
37 \r
38 /**\r
39  * Reimplementation of {@link java.util.zip.ZipOutputStream\r
40  * java.util.zip.ZipOutputStream} that does handle the extended\r
41  * functionality of this package, especially internal/external file\r
42  * attributes and extra fields with different layouts for local file\r
43  * data and central directory entries.\r
44  *\r
45  * <p>This class will try to use {@link java.io.RandomAccessFile\r
46  * RandomAccessFile} when you know that the output is going to go to a\r
47  * file.</p>\r
48  *\r
49  * <p>If RandomAccessFile cannot be used, this implementation will use\r
50  * a Data Descriptor to store size and CRC information for {@link\r
51  * #DEFLATED DEFLATED} entries, this means, you don't need to\r
52  * calculate them yourself.  Unfortunately this is not possible for\r
53  * the {@link #STORED STORED} method, here setting the CRC and\r
54  * uncompressed size information is required before {@link\r
55  * #putNextEntry putNextEntry} can be called.</p>\r
56  *\r
57  */\r
58 @SuppressWarnings({"unchecked", "rawtypes"})\r
59 public class ZipOutputStream extends FilterOutputStream {\r
60 \r
61     private static final int BYTE_MASK = 0xFF;\r
62     private static final int SHORT = 2;\r
63     private static final int WORD = 4;\r
64     private static final int BUFFER_SIZE = 512;\r
65     /* \r
66      * Apparently Deflater.setInput gets slowed down a lot on Sun JVMs\r
67      * when it gets handed a really big buffer.  See\r
68      * https://issues.apache.org/bugzilla/show_bug.cgi?id=45396\r
69      *\r
70      * Using a buffer size of 8 kB proved to be a good compromise\r
71      */\r
72     private static final int DEFLATER_BLOCK_SIZE = 8192;\r
73 \r
74     /**\r
75      * Compression method for deflated entries.\r
76      *\r
77      * @since 1.1\r
78      */\r
79     public static final int DEFLATED = java.util.zip.ZipEntry.DEFLATED;\r
80 \r
81     /**\r
82      * Default compression level for deflated entries.\r
83      *\r
84      * @since Ant 1.7\r
85      */\r
86     public static final int DEFAULT_COMPRESSION = Deflater.DEFAULT_COMPRESSION;\r
87 \r
88     /**\r
89      * Compression method for stored entries.\r
90      *\r
91      * @since 1.1\r
92      */\r
93     public static final int STORED = java.util.zip.ZipEntry.STORED;\r
94 \r
95     /**\r
96      * default encoding for file names and comment.\r
97      */\r
98     static final String DEFAULT_ENCODING = null;\r
99 \r
100     /**\r
101      * General purpose flag, which indicates that filenames are\r
102      * written in utf-8.\r
103      */\r
104     public static final int UFT8_NAMES_FLAG = 1 << 11;\r
105 \r
106     /**\r
107      * General purpose flag, which indicates that filenames are\r
108      * written in utf-8.\r
109      * @deprecated use {@link #UFT8_NAMES_FLAG} instead\r
110      */\r
111     public static final int EFS_FLAG = UFT8_NAMES_FLAG;\r
112 \r
113     /**\r
114      * Current entry.\r
115      *\r
116      * @since 1.1\r
117      */\r
118     private ZipEntry entry;\r
119 \r
120     /**\r
121      * The file comment.\r
122      *\r
123      * @since 1.1\r
124      */\r
125     private String comment = "";\r
126 \r
127     /**\r
128      * Compression level for next entry.\r
129      *\r
130      * @since 1.1\r
131      */\r
132     private int level = DEFAULT_COMPRESSION;\r
133 \r
134     /**\r
135      * Has the compression level changed when compared to the last\r
136      * entry?\r
137      *\r
138      * @since 1.5\r
139      */\r
140     private boolean hasCompressionLevelChanged = false;\r
141 \r
142     /**\r
143      * Default compression method for next entry.\r
144      *\r
145      * @since 1.1\r
146      */\r
147     private int method = java.util.zip.ZipEntry.DEFLATED;\r
148 \r
149     /**\r
150      * List of ZipEntries written so far.\r
151      *\r
152      * @since 1.1\r
153      */\r
154     private final List entries = new LinkedList();\r
155 \r
156     /**\r
157      * CRC instance to avoid parsing DEFLATED data twice.\r
158      *\r
159      * @since 1.1\r
160      */\r
161     private final CRC32 crc = new CRC32();\r
162 \r
163     /**\r
164      * Count the bytes written to out.\r
165      *\r
166      * @since 1.1\r
167      */\r
168     private long written = 0;\r
169 \r
170     /**\r
171      * Data for local header data\r
172      *\r
173      * @since 1.1\r
174      */\r
175     private long dataStart = 0;\r
176 \r
177     /**\r
178      * Offset for CRC entry in the local file header data for the\r
179      * current entry starts here.\r
180      *\r
181      * @since 1.15\r
182      */\r
183     private long localDataStart = 0;\r
184 \r
185     /**\r
186      * Start of central directory.\r
187      *\r
188      * @since 1.1\r
189      */\r
190     private long cdOffset = 0;\r
191 \r
192     /**\r
193      * Length of central directory.\r
194      *\r
195      * @since 1.1\r
196      */\r
197     private long cdLength = 0;\r
198 \r
199     /**\r
200      * Helper, a 0 as ZipShort.\r
201      *\r
202      * @since 1.1\r
203      */\r
204     private static final byte[] ZERO = {0, 0};\r
205 \r
206     /**\r
207      * Helper, a 0 as ZipLong.\r
208      *\r
209      * @since 1.1\r
210      */\r
211     private static final byte[] LZERO = {0, 0, 0, 0};\r
212 \r
213     /**\r
214      * Holds the offsets of the LFH starts for each entry.\r
215      *\r
216      * @since 1.1\r
217      */\r
218     private final Map offsets = new HashMap();\r
219 \r
220     /**\r
221      * The encoding to use for filenames and the file comment.\r
222      *\r
223      * <p>For a list of possible values see <a\r
224      * href="http://java.sun.com/j2se/1.5.0/docs/guide/intl/encoding.doc.html">http://java.sun.com/j2se/1.5.0/docs/guide/intl/encoding.doc.html</a>.\r
225      * Defaults to the platform's default character encoding.</p>\r
226      *\r
227      * @since 1.3\r
228      */\r
229     private String encoding = null;\r
230 \r
231     /**\r
232      * The zip encoding to use for filenames and the file comment.\r
233      *\r
234      * This field is of internal use and will be set in {@link\r
235      * #setEncoding(String)}.\r
236      */\r
237     private ZipEncoding zipEncoding =\r
238         ZipEncodingHelper.getZipEncoding(DEFAULT_ENCODING);\r
239 \r
240    // CheckStyle:VisibilityModifier OFF - bc\r
241 \r
242     /**\r
243      * This Deflater object is used for output.\r
244      *\r
245      * <p>This attribute is only protected to provide a level of API\r
246      * backwards compatibility.  This class used to extend {@link\r
247      * java.util.zip.DeflaterOutputStream DeflaterOutputStream} up to\r
248      * Revision 1.13.</p>\r
249      *\r
250      * @since 1.14\r
251      */\r
252     protected Deflater def = new Deflater(level, true);\r
253 \r
254     /**\r
255      * This buffer servers as a Deflater.\r
256      *\r
257      * <p>This attribute is only protected to provide a level of API\r
258      * backwards compatibility.  This class used to extend {@link\r
259      * java.util.zip.DeflaterOutputStream DeflaterOutputStream} up to\r
260      * Revision 1.13.</p>\r
261      *\r
262      * @since 1.14\r
263      */\r
264     protected byte[] buf = new byte[BUFFER_SIZE];\r
265 \r
266     // CheckStyle:VisibilityModifier ON\r
267 \r
268     /**\r
269      * Optional random access output.\r
270      *\r
271      * @since 1.14\r
272      */\r
273     private RandomAccessFile raf = null;\r
274 \r
275     /**\r
276      * whether to use the general purpose bit flag when writing UTF-8\r
277      * filenames or not.\r
278      */\r
279     private boolean useUTF8Flag = true; \r
280 \r
281     /**\r
282      * Whether to encode non-encodable file names as UTF-8.\r
283      */\r
284     private boolean fallbackToUTF8 = false;\r
285 \r
286     /**\r
287      * whether to create UnicodePathExtraField-s for each entry.\r
288      */\r
289     private UnicodeExtraFieldPolicy createUnicodeExtraFields =\r
290         UnicodeExtraFieldPolicy.NEVER;\r
291 \r
292     /**\r
293      * Creates a new ZIP OutputStream filtering the underlying stream.\r
294      * @param out the outputstream to zip\r
295      * @since 1.1\r
296      */\r
297     public ZipOutputStream(OutputStream out) {\r
298         super(out);\r
299     }\r
300 \r
301     /**\r
302      * Creates a new ZIP OutputStream writing to a File.  Will use\r
303      * random access if possible.\r
304      * @param file the file to zip to\r
305      * @since 1.14\r
306      * @throws IOException on error\r
307      */\r
308     public ZipOutputStream(File file) throws IOException {\r
309         super(null);\r
310 \r
311         try {\r
312             raf = new RandomAccessFile(file, "rw");\r
313             raf.setLength(0);\r
314         } catch (IOException e) {\r
315             if (raf != null) {\r
316                 try {\r
317                     raf.close();\r
318                 } catch (IOException inner) {\r
319                     // ignore\r
320                 }\r
321                 raf = null;\r
322             }\r
323             out = new FileOutputStream(file);\r
324         }\r
325     }\r
326 \r
327     /**\r
328      * This method indicates whether this archive is writing to a\r
329      * seekable stream (i.e., to a random access file).\r
330      *\r
331      * <p>For seekable streams, you don't need to calculate the CRC or\r
332      * uncompressed size for {@link #STORED} entries before\r
333      * invoking {@link #putNextEntry}.\r
334      * @return true if seekable\r
335      * @since 1.17\r
336      */\r
337     public boolean isSeekable() {\r
338         return raf != null;\r
339     }\r
340 \r
341     /**\r
342      * The encoding to use for filenames and the file comment.\r
343      *\r
344      * <p>For a list of possible values see <a\r
345      * href="http://java.sun.com/j2se/1.5.0/docs/guide/intl/encoding.doc.html">http://java.sun.com/j2se/1.5.0/docs/guide/intl/encoding.doc.html</a>.\r
346      * Defaults to the platform's default character encoding.</p>\r
347      * @param encoding the encoding value\r
348      * @since 1.3\r
349      */\r
350     public void setEncoding(final String encoding) {\r
351         this.encoding = encoding;\r
352         this.zipEncoding = ZipEncodingHelper.getZipEncoding(encoding);\r
353         useUTF8Flag &= ZipEncodingHelper.isUTF8(encoding);\r
354     }\r
355 \r
356     /**\r
357      * The encoding to use for filenames and the file comment.\r
358      *\r
359      * @return null if using the platform's default character encoding.\r
360      *\r
361      * @since 1.3\r
362      */\r
363     public String getEncoding() {\r
364         return encoding;\r
365     }\r
366 \r
367     /**\r
368      * Whether to set the language encoding flag if the file name\r
369      * encoding is UTF-8.\r
370      *\r
371      * <p>Defaults to true.</p>\r
372      */\r
373     public void setUseLanguageEncodingFlag(boolean b) {\r
374         useUTF8Flag = b && ZipEncodingHelper.isUTF8(encoding);\r
375     }\r
376 \r
377     /**\r
378      * Whether to create Unicode Extra Fields.\r
379      *\r
380      * <p>Defaults to NEVER.</p>\r
381      */\r
382     public void setCreateUnicodeExtraFields(UnicodeExtraFieldPolicy b) {\r
383         createUnicodeExtraFields = b;\r
384     }\r
385 \r
386     /**\r
387      * Whether to fall back to UTF and the language encoding flag if\r
388      * the file name cannot be encoded using the specified encoding.\r
389      *\r
390      * <p>Defaults to false.</p>\r
391      */\r
392     public void setFallbackToUTF8(boolean b) {\r
393         fallbackToUTF8 = b;\r
394     }\r
395 \r
396     /**\r
397      * Finishs writing the contents and closes this as well as the\r
398      * underlying stream.\r
399      *\r
400      * @since 1.1\r
401      * @throws IOException on error\r
402      */\r
403     public void finish() throws IOException {\r
404         closeEntry();\r
405         cdOffset = written;\r
406         for (Iterator i = entries.iterator(); i.hasNext(); ) {\r
407             writeCentralFileHeader((ZipEntry) i.next());\r
408         }\r
409         cdLength = written - cdOffset;\r
410         writeCentralDirectoryEnd();\r
411         offsets.clear();\r
412         entries.clear();\r
413     }\r
414 \r
415     /**\r
416      * Writes all necessary data for this entry.\r
417      *\r
418      * @since 1.1\r
419      * @throws IOException on error\r
420      */\r
421     public void closeEntry() throws IOException {\r
422         if (entry == null) {\r
423             return;\r
424         }\r
425 \r
426         long realCrc = crc.getValue();\r
427         crc.reset();\r
428 \r
429         if (entry.getMethod() == DEFLATED) {\r
430             def.finish();\r
431             while (!def.finished()) {\r
432                 deflate();\r
433             }\r
434 \r
435             entry.setSize(adjustToLong(def.getTotalIn()));\r
436             entry.setCompressedSize(adjustToLong(def.getTotalOut()));\r
437             entry.setCrc(realCrc);\r
438 \r
439             def.reset();\r
440 \r
441             written += entry.getCompressedSize();\r
442         } else if (raf == null) {\r
443             if (entry.getCrc() != realCrc) {\r
444                 throw new ZipException("bad CRC checksum for entry "\r
445                                        + entry.getName() + ": "\r
446                                        + Long.toHexString(entry.getCrc())\r
447                                        + " instead of "\r
448                                        + Long.toHexString(realCrc));\r
449             }\r
450 \r
451             if (entry.getSize() != written - dataStart) {\r
452                 throw new ZipException("bad size for entry "\r
453                                        + entry.getName() + ": "\r
454                                        + entry.getSize()\r
455                                        + " instead of "\r
456                                        + (written - dataStart));\r
457             }\r
458         } else { /* method is STORED and we used RandomAccessFile */\r
459             long size = written - dataStart;\r
460 \r
461             entry.setSize(size);\r
462             entry.setCompressedSize(size);\r
463             entry.setCrc(realCrc);\r
464         }\r
465 \r
466         // If random access output, write the local file header containing\r
467         // the correct CRC and compressed/uncompressed sizes\r
468         if (raf != null) {\r
469             long save = raf.getFilePointer();\r
470 \r
471             raf.seek(localDataStart);\r
472             writeOut(ZipLong.getBytes(entry.getCrc()));\r
473             writeOut(ZipLong.getBytes(entry.getCompressedSize()));\r
474             writeOut(ZipLong.getBytes(entry.getSize()));\r
475             raf.seek(save);\r
476         }\r
477 \r
478         writeDataDescriptor(entry);\r
479         entry = null;\r
480     }\r
481 \r
482     /**\r
483      * Begin writing next entry.\r
484      * @param ze the entry to write\r
485      * @since 1.1\r
486      * @throws IOException on error\r
487      */\r
488     public void putNextEntry(ZipEntry ze) throws IOException {\r
489         closeEntry();\r
490 \r
491         entry = ze;\r
492         entries.add(entry);\r
493 \r
494         if (entry.getMethod() == -1) { // not specified\r
495             entry.setMethod(method);\r
496         }\r
497 \r
498         if (entry.getTime() == -1) { // not specified\r
499             entry.setTime(System.currentTimeMillis());\r
500         }\r
501 \r
502         // Size/CRC not required if RandomAccessFile is used\r
503         if (entry.getMethod() == STORED && raf == null) {\r
504             if (entry.getSize() == -1) {\r
505                 throw new ZipException("uncompressed size is required for"\r
506                                        + " STORED method when not writing to a"\r
507                                        + " file");\r
508             }\r
509             if (entry.getCrc() == -1) {\r
510                 throw new ZipException("crc checksum is required for STORED"\r
511                                        + " method when not writing to a file");\r
512             }\r
513             entry.setCompressedSize(entry.getSize());\r
514         }\r
515 \r
516         if (entry.getMethod() == DEFLATED && hasCompressionLevelChanged) {\r
517             def.setLevel(level);\r
518             hasCompressionLevelChanged = false;\r
519         }\r
520         writeLocalFileHeader(entry);\r
521     }\r
522 \r
523     /**\r
524      * Set the file comment.\r
525      * @param comment the comment\r
526      * @since 1.1\r
527      */\r
528     public void setComment(String comment) {\r
529         this.comment = comment;\r
530     }\r
531 \r
532     /**\r
533      * Sets the compression level for subsequent entries.\r
534      *\r
535      * <p>Default is Deflater.DEFAULT_COMPRESSION.</p>\r
536      * @param level the compression level.\r
537      * @throws IllegalArgumentException if an invalid compression\r
538      * level is specified.\r
539      * @since 1.1\r
540      */\r
541     public void setLevel(int level) {\r
542         if (level < Deflater.DEFAULT_COMPRESSION\r
543             || level > Deflater.BEST_COMPRESSION) {\r
544             throw new IllegalArgumentException("Invalid compression level: "\r
545                                                + level);\r
546         }\r
547         hasCompressionLevelChanged = (this.level != level);\r
548         this.level = level;\r
549     }\r
550 \r
551     /**\r
552      * Sets the default compression method for subsequent entries.\r
553      *\r
554      * <p>Default is DEFLATED.</p>\r
555      * @param method an <code>int</code> from java.util.zip.ZipEntry\r
556      * @since 1.1\r
557      */\r
558     public void setMethod(int method) {\r
559         this.method = method;\r
560     }\r
561 \r
562     /**\r
563      * Writes bytes to ZIP entry.\r
564      * @param b the byte array to write\r
565      * @param offset the start position to write from\r
566      * @param length the number of bytes to write\r
567      * @throws IOException on error\r
568      */\r
569     public void write(byte[] b, int offset, int length) throws IOException {\r
570         if (entry.getMethod() == DEFLATED) {\r
571             if (length > 0) {\r
572                 if (!def.finished()) {\r
573                     if (length <= DEFLATER_BLOCK_SIZE) {\r
574                         def.setInput(b, offset, length);\r
575                         deflateUntilInputIsNeeded();\r
576                     } else {\r
577                         final int fullblocks = length / DEFLATER_BLOCK_SIZE;\r
578                         for (int i = 0; i < fullblocks; i++) {\r
579                             def.setInput(b, offset + i * DEFLATER_BLOCK_SIZE,\r
580                                          DEFLATER_BLOCK_SIZE);\r
581                             deflateUntilInputIsNeeded();\r
582                         }\r
583                         final int done = fullblocks * DEFLATER_BLOCK_SIZE;\r
584                         if (done < length) {\r
585                             def.setInput(b, offset + done, length - done);\r
586                             deflateUntilInputIsNeeded();\r
587                         }\r
588                     }\r
589                 }\r
590             }\r
591         } else {\r
592             writeOut(b, offset, length);\r
593             written += length;\r
594         }\r
595         crc.update(b, offset, length);\r
596     }\r
597 \r
598     /**\r
599      * Writes a single byte to ZIP entry.\r
600      *\r
601      * <p>Delegates to the three arg method.</p>\r
602      * @param b the byte to write\r
603      * @since 1.14\r
604      * @throws IOException on error\r
605      */\r
606     public void write(int b) throws IOException {\r
607         byte[] buff = new byte[1];\r
608         buff[0] = (byte) (b & BYTE_MASK);\r
609         write(buff, 0, 1);\r
610     }\r
611 \r
612     /**\r
613      * Closes this output stream and releases any system resources\r
614      * associated with the stream.\r
615      *\r
616      * @exception  IOException  if an I/O error occurs.\r
617      * @since 1.14\r
618      */\r
619     public void close() throws IOException {\r
620         finish();\r
621 \r
622         if (raf != null) {\r
623             raf.close();\r
624         }\r
625         if (out != null) {\r
626             out.close();\r
627         }\r
628     }\r
629 \r
630     /**\r
631      * Flushes this output stream and forces any buffered output bytes\r
632      * to be written out to the stream.\r
633      *\r
634      * @exception  IOException  if an I/O error occurs.\r
635      * @since 1.14\r
636      */\r
637     public void flush() throws IOException {\r
638         if (out != null) {\r
639             out.flush();\r
640         }\r
641     }\r
642 \r
643     /*\r
644      * Various ZIP constants\r
645      */\r
646     /**\r
647      * local file header signature\r
648      *\r
649      * @since 1.1\r
650      */\r
651     protected static final byte[] LFH_SIG = ZipLong.getBytes(0X04034B50L);\r
652     /**\r
653      * data descriptor signature\r
654      *\r
655      * @since 1.1\r
656      */\r
657     protected static final byte[] DD_SIG = ZipLong.getBytes(0X08074B50L);\r
658     /**\r
659      * central file header signature\r
660      *\r
661      * @since 1.1\r
662      */\r
663     protected static final byte[] CFH_SIG = ZipLong.getBytes(0X02014B50L);\r
664     /**\r
665      * end of central dir signature\r
666      *\r
667      * @since 1.1\r
668      */\r
669     protected static final byte[] EOCD_SIG = ZipLong.getBytes(0X06054B50L);\r
670 \r
671     /**\r
672      * Writes next block of compressed data to the output stream.\r
673      * @throws IOException on error\r
674      *\r
675      * @since 1.14\r
676      */\r
677     protected final void deflate() throws IOException {\r
678         int len = def.deflate(buf, 0, buf.length);\r
679         if (len > 0) {\r
680             writeOut(buf, 0, len);\r
681         }\r
682     }\r
683 \r
684     /**\r
685      * Writes the local file header entry\r
686      * @param ze the entry to write\r
687      * @throws IOException on error\r
688      *\r
689      * @since 1.1\r
690      */\r
691     protected void writeLocalFileHeader(ZipEntry ze) throws IOException {\r
692 \r
693         boolean encodable = zipEncoding.canEncode(ze.getName());\r
694         \r
695         final ZipEncoding entryEncoding;\r
696         \r
697         if (!encodable && fallbackToUTF8) {\r
698             entryEncoding = ZipEncodingHelper.UTF8_ZIP_ENCODING;\r
699         } else {\r
700             entryEncoding = zipEncoding;\r
701         }\r
702         \r
703         ByteBuffer name = entryEncoding.encode(ze.getName());        \r
704 \r
705         if (createUnicodeExtraFields != UnicodeExtraFieldPolicy.NEVER) {\r
706 \r
707             if (createUnicodeExtraFields == UnicodeExtraFieldPolicy.ALWAYS\r
708                 || !encodable) {\r
709                 ze.addExtraField(new UnicodePathExtraField(ze.getName(),\r
710                                                            name.array(),\r
711                                                            name.arrayOffset(),\r
712                                                            name.limit()));\r
713             }\r
714 \r
715             String comm = ze.getComment();\r
716             if (comm != null && !"".equals(comm)) {\r
717 \r
718                 boolean commentEncodable = this.zipEncoding.canEncode(comm);\r
719 \r
720                 if (createUnicodeExtraFields == UnicodeExtraFieldPolicy.ALWAYS\r
721                     || !commentEncodable) {\r
722                     ByteBuffer commentB = entryEncoding.encode(comm);\r
723                     ze.addExtraField(new UnicodeCommentExtraField(comm,\r
724                                                                   commentB.array(),\r
725                                                                   commentB.arrayOffset(),\r
726                                                                   commentB.limit())\r
727                                      );\r
728                 }\r
729             }\r
730         }\r
731 \r
732         offsets.put(ze, ZipLong.getBytes(written));\r
733 \r
734         writeOut(LFH_SIG);\r
735         written += WORD;\r
736 \r
737         //store method in local variable to prevent multiple method calls\r
738         final int zipMethod = ze.getMethod();\r
739 \r
740         writeVersionNeededToExtractAndGeneralPurposeBits(zipMethod,\r
741                                                          !encodable\r
742                                                          && fallbackToUTF8);\r
743         written += WORD;\r
744 \r
745         // compression method\r
746         writeOut(ZipShort.getBytes(zipMethod));\r
747         written += SHORT;\r
748 \r
749         // last mod. time and date\r
750         writeOut(toDosTime(ze.getTime()));\r
751         written += WORD;\r
752 \r
753         // CRC\r
754         // compressed length\r
755         // uncompressed length\r
756         localDataStart = written;\r
757         if (zipMethod == DEFLATED || raf != null) {\r
758             writeOut(LZERO);\r
759             writeOut(LZERO);\r
760             writeOut(LZERO);\r
761         } else {\r
762             writeOut(ZipLong.getBytes(ze.getCrc()));\r
763             writeOut(ZipLong.getBytes(ze.getSize()));\r
764             writeOut(ZipLong.getBytes(ze.getSize()));\r
765         }\r
766         // CheckStyle:MagicNumber OFF\r
767         written += 12;\r
768         // CheckStyle:MagicNumber ON\r
769 \r
770         // file name length\r
771         writeOut(ZipShort.getBytes(name.limit()));\r
772         written += SHORT;\r
773 \r
774         // extra field length\r
775         byte[] extra = ze.getLocalFileDataExtra();\r
776         writeOut(ZipShort.getBytes(extra.length));\r
777         written += SHORT;\r
778 \r
779         // file name\r
780         writeOut(name.array(), name.arrayOffset(), name.limit());\r
781         written += name.limit();\r
782 \r
783         // extra field\r
784         writeOut(extra);\r
785         written += extra.length;\r
786 \r
787         dataStart = written;\r
788     }\r
789 \r
790     /**\r
791      * Writes the data descriptor entry.\r
792      * @param ze the entry to write\r
793      * @throws IOException on error\r
794      *\r
795      * @since 1.1\r
796      */\r
797     protected void writeDataDescriptor(ZipEntry ze) throws IOException {\r
798         if (ze.getMethod() != DEFLATED || raf != null) {\r
799             return;\r
800         }\r
801         writeOut(DD_SIG);\r
802         writeOut(ZipLong.getBytes(entry.getCrc()));\r
803         writeOut(ZipLong.getBytes(entry.getCompressedSize()));\r
804         writeOut(ZipLong.getBytes(entry.getSize()));\r
805         // CheckStyle:MagicNumber OFF\r
806         written += 16;\r
807         // CheckStyle:MagicNumber ON\r
808     }\r
809 \r
810     /**\r
811      * Writes the central file header entry.\r
812      * @param ze the entry to write\r
813      * @throws IOException on error\r
814      *\r
815      * @since 1.1\r
816      */\r
817     protected void writeCentralFileHeader(ZipEntry ze) throws IOException {\r
818         writeOut(CFH_SIG);\r
819         written += WORD;\r
820 \r
821         // version made by\r
822         // CheckStyle:MagicNumber OFF\r
823         writeOut(ZipShort.getBytes((ze.getPlatform() << 8) | 20));\r
824         written += SHORT;\r
825 \r
826         final int zipMethod = ze.getMethod();\r
827         final boolean encodable = zipEncoding.canEncode(ze.getName());\r
828         writeVersionNeededToExtractAndGeneralPurposeBits(zipMethod,\r
829                                                          !encodable\r
830                                                          && fallbackToUTF8);\r
831         written += WORD;\r
832 \r
833         // compression method\r
834         writeOut(ZipShort.getBytes(zipMethod));\r
835         written += SHORT;\r
836 \r
837         // last mod. time and date\r
838         writeOut(toDosTime(ze.getTime()));\r
839         written += WORD;\r
840 \r
841         // CRC\r
842         // compressed length\r
843         // uncompressed length\r
844         writeOut(ZipLong.getBytes(ze.getCrc()));\r
845         writeOut(ZipLong.getBytes(ze.getCompressedSize()));\r
846         writeOut(ZipLong.getBytes(ze.getSize()));\r
847         // CheckStyle:MagicNumber OFF\r
848         written += 12;\r
849         // CheckStyle:MagicNumber ON\r
850 \r
851         // file name length\r
852         final ZipEncoding entryEncoding;\r
853         \r
854         if (!encodable && fallbackToUTF8) {\r
855             entryEncoding = ZipEncodingHelper.UTF8_ZIP_ENCODING;\r
856         } else {\r
857             entryEncoding = zipEncoding;\r
858         }\r
859         \r
860         ByteBuffer name = entryEncoding.encode(ze.getName());        \r
861 \r
862         writeOut(ZipShort.getBytes(name.limit()));\r
863         written += SHORT;\r
864 \r
865         // extra field length\r
866         byte[] extra = ze.getCentralDirectoryExtra();\r
867         writeOut(ZipShort.getBytes(extra.length));\r
868         written += SHORT;\r
869 \r
870         // file comment length\r
871         String comm = ze.getComment();\r
872         if (comm == null) {\r
873             comm = "";\r
874         }\r
875         \r
876         ByteBuffer commentB = entryEncoding.encode(comm);\r
877         \r
878         writeOut(ZipShort.getBytes(commentB.limit()));\r
879         written += SHORT;\r
880 \r
881         // disk number start\r
882         writeOut(ZERO);\r
883         written += SHORT;\r
884 \r
885         // internal file attributes\r
886         writeOut(ZipShort.getBytes(ze.getInternalAttributes()));\r
887         written += SHORT;\r
888 \r
889         // external file attributes\r
890         writeOut(ZipLong.getBytes(ze.getExternalAttributes()));\r
891         written += WORD;\r
892 \r
893         // relative offset of LFH\r
894         writeOut((byte[]) offsets.get(ze));\r
895         written += WORD;\r
896 \r
897         // file name\r
898         writeOut(name.array(), name.arrayOffset(), name.limit());\r
899         written += name.limit();\r
900 \r
901         // extra field\r
902         writeOut(extra);\r
903         written += extra.length;\r
904 \r
905         // file comment\r
906         writeOut(commentB.array(), commentB.arrayOffset(), commentB.limit());\r
907         written += commentB.limit();\r
908     }\r
909 \r
910     /**\r
911      * Writes the &quot;End of central dir record&quot;.\r
912      * @throws IOException on error\r
913      *\r
914      * @since 1.1\r
915      */\r
916     protected void writeCentralDirectoryEnd() throws IOException {\r
917         writeOut(EOCD_SIG);\r
918 \r
919         // disk numbers\r
920         writeOut(ZERO);\r
921         writeOut(ZERO);\r
922 \r
923         // number of entries\r
924         byte[] num = ZipShort.getBytes(entries.size());\r
925         writeOut(num);\r
926         writeOut(num);\r
927 \r
928         // length and location of CD\r
929         writeOut(ZipLong.getBytes(cdLength));\r
930         writeOut(ZipLong.getBytes(cdOffset));\r
931 \r
932         // ZIP file comment\r
933         ByteBuffer data = this.zipEncoding.encode(comment);\r
934         writeOut(ZipShort.getBytes(data.limit()));\r
935         writeOut(data.array(), data.arrayOffset(), data.limit());\r
936     }\r
937 \r
938     /**\r
939      * Smallest date/time ZIP can handle.\r
940      *\r
941      * @since 1.1\r
942      */\r
943     private static final byte[] DOS_TIME_MIN = ZipLong.getBytes(0x00002100L);\r
944 \r
945     /**\r
946      * Convert a Date object to a DOS date/time field.\r
947      * @param time the <code>Date</code> to convert\r
948      * @return the date as a <code>ZipLong</code>\r
949      * @since 1.1\r
950      */\r
951     protected static ZipLong toDosTime(Date time) {\r
952         return new ZipLong(toDosTime(time.getTime()));\r
953     }\r
954 \r
955     /**\r
956      * Convert a Date object to a DOS date/time field.\r
957      *\r
958      * <p>Stolen from InfoZip's <code>fileio.c</code></p>\r
959      * @param t number of milliseconds since the epoch\r
960      * @return the date as a byte array\r
961      * @since 1.26\r
962      */\r
963     @SuppressWarnings("deprecation")\r
964         protected static byte[] toDosTime(long t) {\r
965         Date time = new Date(t);\r
966         // CheckStyle:MagicNumberCheck OFF - I do not think that using constants\r
967         //                                   here will improve the readablity\r
968         int year = time.getYear() + 1900;\r
969         if (year < 1980) {\r
970             return DOS_TIME_MIN;\r
971         }\r
972         int month = time.getMonth() + 1;\r
973         long value =  ((year - 1980) << 25)\r
974             |         (month << 21)\r
975             |         (time.getDate() << 16)\r
976             |         (time.getHours() << 11)\r
977             |         (time.getMinutes() << 5)\r
978             |         (time.getSeconds() >> 1);\r
979         return ZipLong.getBytes(value);\r
980         // CheckStyle:MagicNumberCheck ON\r
981     }\r
982 \r
983     /**\r
984      * Retrieve the bytes for the given String in the encoding set for\r
985      * this Stream.\r
986      * @param name the string to get bytes from\r
987      * @return the bytes as a byte array\r
988      * @throws ZipException on error\r
989      *\r
990      * @since 1.3\r
991      */\r
992     protected byte[] getBytes(String name) throws ZipException {\r
993         try {\r
994             ByteBuffer b =\r
995                 ZipEncodingHelper.getZipEncoding(encoding).encode(name);\r
996             byte[] result = new byte[b.limit()];\r
997             System.arraycopy(b.array(), b.arrayOffset(), result, 0,\r
998                              result.length);\r
999             return result;\r
1000         } catch (IOException ex) {\r
1001             throw new ZipException("Failed to encode name: " + ex.getMessage());\r
1002         }\r
1003     }\r
1004 \r
1005     /**\r
1006      * Write bytes to output or random access file.\r
1007      * @param data the byte array to write\r
1008      * @throws IOException on error\r
1009      *\r
1010      * @since 1.14\r
1011      */\r
1012     protected final void writeOut(byte[] data) throws IOException {\r
1013         writeOut(data, 0, data.length);\r
1014     }\r
1015 \r
1016     /**\r
1017      * Write bytes to output or random access file.\r
1018      * @param data the byte array to write\r
1019      * @param offset the start position to write from\r
1020      * @param length the number of bytes to write\r
1021      * @throws IOException on error\r
1022      *\r
1023      * @since 1.14\r
1024      */\r
1025     protected final void writeOut(byte[] data, int offset, int length)\r
1026         throws IOException {\r
1027         if (raf != null) {\r
1028             raf.write(data, offset, length);\r
1029         } else {\r
1030             out.write(data, offset, length);\r
1031         }\r
1032     }\r
1033 \r
1034     /**\r
1035      * Assumes a negative integer really is a positive integer that\r
1036      * has wrapped around and re-creates the original value.\r
1037      * @param i the value to treat as unsigned int.\r
1038      * @return the unsigned int as a long.\r
1039      * @since 1.34\r
1040      */\r
1041     protected static long adjustToLong(int i) {\r
1042         if (i < 0) {\r
1043             return 2 * ((long) Integer.MAX_VALUE) + 2 + i;\r
1044         } else {\r
1045             return i;\r
1046         }\r
1047     }\r
1048 \r
1049     private void deflateUntilInputIsNeeded() throws IOException {\r
1050         while (!def.needsInput()) {\r
1051             deflate();\r
1052         }\r
1053     }\r
1054 \r
1055     private void writeVersionNeededToExtractAndGeneralPurposeBits(final int\r
1056                                                                   zipMethod,\r
1057                                                                   final boolean\r
1058                                                                   utfFallback)\r
1059         throws IOException {\r
1060 \r
1061         // CheckStyle:MagicNumber OFF\r
1062         int versionNeededToExtract = 10;\r
1063         int generalPurposeFlag = (useUTF8Flag || utfFallback) ? UFT8_NAMES_FLAG : 0;\r
1064         if (zipMethod == DEFLATED && raf == null) {\r
1065             // requires version 2 as we are going to store length info\r
1066             // in the data descriptor\r
1067             versionNeededToExtract =  20;\r
1068             // bit3 set to signal, we use a data descriptor\r
1069             generalPurposeFlag |= 8;\r
1070         }\r
1071         // CheckStyle:MagicNumber ON\r
1072 \r
1073         // version needed to extract\r
1074         writeOut(ZipShort.getBytes(versionNeededToExtract));\r
1075         // general purpose bit flag\r
1076         writeOut(ZipShort.getBytes(generalPurposeFlag));\r
1077     }\r
1078 \r
1079     /**\r
1080      * enum that represents the possible policies for creating Unicode\r
1081      * extra fields.\r
1082      */\r
1083     public static final class UnicodeExtraFieldPolicy {\r
1084         /**\r
1085          * Always create Unicode extra fields.\r
1086          */\r
1087         public static final UnicodeExtraFieldPolicy ALWAYS =\r
1088             new UnicodeExtraFieldPolicy("always");\r
1089         /**\r
1090          * Never create Unicode extra fields.\r
1091          */\r
1092         public static final UnicodeExtraFieldPolicy NEVER =\r
1093             new UnicodeExtraFieldPolicy("never");\r
1094         /**\r
1095          * Create Unicode extra fields for filenames that cannot be\r
1096          * encoded using the specified encoding.\r
1097          */\r
1098         public static final UnicodeExtraFieldPolicy NOT_ENCODEABLE =\r
1099             new UnicodeExtraFieldPolicy("not encodeable");\r
1100 \r
1101         private final String name;\r
1102         private UnicodeExtraFieldPolicy(String n) {\r
1103             name = n;\r
1104         }\r
1105         public String toString() {\r
1106             return name;\r
1107         }\r
1108     }\r
1109 }\r