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