• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  * Copyright (c) 1996, 2015, Oracle and/or its affiliates. All rights reserved.
4  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
5  *
6  * This code is free software; you can redistribute it and/or modify it
7  * under the terms of the GNU General Public License version 2 only, as
8  * published by the Free Software Foundation.  Oracle designates this
9  * particular file as subject to the "Classpath" exception as provided
10  * by Oracle in the LICENSE file that accompanied this code.
11  *
12  * This code is distributed in the hope that it will be useful, but WITHOUT
13  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
15  * version 2 for more details (a copy is included in the LICENSE file that
16  * accompanied this code).
17  *
18  * You should have received a copy of the GNU General Public License version
19  * 2 along with this work; if not, write to the Free Software Foundation,
20  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
21  *
22  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
23  * or visit www.oracle.com if you need additional information or have any
24  * questions.
25  */
26 
27 package java.util.zip;
28 
29 import java.io.OutputStream;
30 import java.io.IOException;
31 import java.nio.charset.Charset;
32 import java.nio.charset.StandardCharsets;
33 import java.util.Vector;
34 import java.util.HashSet;
35 import static java.util.zip.ZipConstants64.*;
36 import static java.util.zip.ZipUtils.*;
37 
38 /**
39  * This class implements an output stream filter for writing files in the
40  * ZIP file format. Includes support for both compressed and uncompressed
41  * entries.
42  *
43  * @author      David Connelly
44  */
45 public
46 class ZipOutputStream extends DeflaterOutputStream implements ZipConstants {
47 
48     /**
49      * Whether to use ZIP64 for zip files with more than 64k entries.
50      * Until ZIP64 support in zip implementations is ubiquitous, this
51      * system property allows the creation of zip files which can be
52      * read by legacy zip implementations which tolerate "incorrect"
53      * total entry count fields, such as the ones in jdk6, and even
54      * some in jdk7.
55      */
56     // Android-changed: Always allow use of Zip64.
57     private static final boolean inhibitZip64 = false;
58     //  Boolean.parseBoolean(
59     //      java.security.AccessController.doPrivileged(
60     //          new sun.security.action.GetPropertyAction(
61     //              "jdk.util.zip.inhibitZip64", "false")));
62 
63     private static class XEntry {
64         final ZipEntry entry;
65         final long offset;
XEntry(ZipEntry entry, long offset)66         public XEntry(ZipEntry entry, long offset) {
67             this.entry = entry;
68             this.offset = offset;
69         }
70     }
71 
72     private XEntry current;
73     private Vector<XEntry> xentries = new Vector<>();
74     private HashSet<String> names = new HashSet<>();
75     private CRC32 crc = new CRC32();
76     private long written = 0;
77     private long locoff = 0;
78     private byte[] comment;
79     private int method = DEFLATED;
80     private boolean finished;
81 
82     private boolean closed = false;
83 
84     private final ZipCoder zc;
85 
version(ZipEntry e)86     private static int version(ZipEntry e) throws ZipException {
87         switch (e.method) {
88         case DEFLATED: return 20;
89         case STORED:   return 10;
90         default: throw new ZipException("unsupported compression method");
91         }
92     }
93 
94     /**
95      * Checks to make sure that this stream has not been closed.
96      */
ensureOpen()97     private void ensureOpen() throws IOException {
98         if (closed) {
99             throw new IOException("Stream closed");
100         }
101     }
102     /**
103      * Compression method for uncompressed (STORED) entries.
104      */
105     public static final int STORED = ZipEntry.STORED;
106 
107     /**
108      * Compression method for compressed (DEFLATED) entries.
109      */
110     public static final int DEFLATED = ZipEntry.DEFLATED;
111 
112     /**
113      * Creates a new ZIP output stream.
114      *
115      * <p>The UTF-8 {@link java.nio.charset.Charset charset} is used
116      * to encode the entry names and comments.
117      *
118      * @param out the actual output stream
119      */
ZipOutputStream(OutputStream out)120     public ZipOutputStream(OutputStream out) {
121         this(out, StandardCharsets.UTF_8);
122     }
123 
124     /**
125      * Creates a new ZIP output stream.
126      *
127      * @param out the actual output stream
128      *
129      * @param charset the {@linkplain java.nio.charset.Charset charset}
130      *                to be used to encode the entry names and comments
131      *
132      * @since 1.7
133      */
ZipOutputStream(OutputStream out, Charset charset)134     public ZipOutputStream(OutputStream out, Charset charset) {
135         super(out, new Deflater(Deflater.DEFAULT_COMPRESSION, true));
136         if (charset == null)
137             throw new NullPointerException("charset is null");
138         this.zc = ZipCoder.get(charset);
139         usesDefaultDeflater = true;
140     }
141 
142     /**
143      * Sets the ZIP file comment.
144      * @param comment the comment string
145      * @exception IllegalArgumentException if the length of the specified
146      *            ZIP file comment is greater than 0xFFFF bytes
147      */
setComment(String comment)148     public void setComment(String comment) {
149         if (comment != null) {
150             this.comment = zc.getBytes(comment);
151             if (this.comment.length > 0xffff)
152                 throw new IllegalArgumentException("ZIP file comment too long.");
153         }
154     }
155 
156     /**
157      * Sets the default compression method for subsequent entries. This
158      * default will be used whenever the compression method is not specified
159      * for an individual ZIP file entry, and is initially set to DEFLATED.
160      * @param method the default compression method
161      * @exception IllegalArgumentException if the specified compression method
162      *            is invalid
163      */
setMethod(int method)164     public void setMethod(int method) {
165         if (method != DEFLATED && method != STORED) {
166             throw new IllegalArgumentException("invalid compression method");
167         }
168         this.method = method;
169     }
170 
171     /**
172      * Sets the compression level for subsequent entries which are DEFLATED.
173      * The default setting is DEFAULT_COMPRESSION.
174      * @param level the compression level (0-9)
175      * @exception IllegalArgumentException if the compression level is invalid
176      */
setLevel(int level)177     public void setLevel(int level) {
178         def.setLevel(level);
179     }
180 
181     /**
182      * Begins writing a new ZIP file entry and positions the stream to the
183      * start of the entry data. Closes the current entry if still active.
184      * The default compression method will be used if no compression method
185      * was specified for the entry, and the current time will be used if
186      * the entry has no set modification time.
187      * @param e the ZIP entry to be written
188      * @exception ZipException if a ZIP format error has occurred
189      * @exception IOException if an I/O error has occurred
190      */
putNextEntry(ZipEntry e)191     public void putNextEntry(ZipEntry e) throws IOException {
192         ensureOpen();
193         if (current != null) {
194             closeEntry();       // close previous entry
195         }
196         if (e.xdostime == -1) {
197             // by default, do NOT use extended timestamps in extra
198             // data, for now.
199             e.setTime(System.currentTimeMillis());
200         }
201         if (e.method == -1) {
202             e.method = method;  // use default method
203         }
204         // store size, compressed size, and crc-32 in LOC header
205         e.flag = 0;
206         switch (e.method) {
207         case DEFLATED:
208             // store size, compressed size, and crc-32 in data descriptor
209             // immediately following the compressed entry data
210             if (e.size  == -1 || e.csize == -1 || e.crc   == -1)
211                 e.flag = 8;
212 
213             break;
214         case STORED:
215             // compressed size, uncompressed size, and crc-32 must all be
216             // set for entries using STORED compression method
217             if (e.size == -1) {
218                 e.size = e.csize;
219             } else if (e.csize == -1) {
220                 e.csize = e.size;
221             } else if (e.size != e.csize) {
222                 throw new ZipException(
223                     "STORED entry where compressed != uncompressed size");
224             }
225             if (e.size == -1 || e.crc == -1) {
226                 throw new ZipException(
227                     "STORED entry missing size, compressed size, or crc-32");
228             }
229             break;
230         default:
231             throw new ZipException("unsupported compression method");
232         }
233         if (! names.add(e.name)) {
234             throw new ZipException("duplicate entry: " + e.name);
235         }
236         if (zc.isUTF8())
237             e.flag |= USE_UTF8;
238         current = new XEntry(e, written);
239         xentries.add(current);
240         writeLOC(current);
241     }
242 
243     /**
244      * Closes the current ZIP entry and positions the stream for writing
245      * the next entry.
246      * @exception ZipException if a ZIP format error has occurred
247      * @exception IOException if an I/O error has occurred
248      */
closeEntry()249     public void closeEntry() throws IOException {
250         ensureOpen();
251         if (current != null) {
252             ZipEntry e = current.entry;
253             switch (e.method) {
254             case DEFLATED:
255                 def.finish();
256                 while (!def.finished()) {
257                     deflate();
258                 }
259                 if ((e.flag & 8) == 0) {
260                     // verify size, compressed size, and crc-32 settings
261                     if (e.size != def.getBytesRead()) {
262                         throw new ZipException(
263                             "invalid entry size (expected " + e.size +
264                             " but got " + def.getBytesRead() + " bytes)");
265                     }
266                     if (e.csize != def.getBytesWritten()) {
267                         throw new ZipException(
268                             "invalid entry compressed size (expected " +
269                             e.csize + " but got " + def.getBytesWritten() + " bytes)");
270                     }
271                     if (e.crc != crc.getValue()) {
272                         throw new ZipException(
273                             "invalid entry CRC-32 (expected 0x" +
274                             Long.toHexString(e.crc) + " but got 0x" +
275                             Long.toHexString(crc.getValue()) + ")");
276                     }
277                 } else {
278                     e.size  = def.getBytesRead();
279                     e.csize = def.getBytesWritten();
280                     e.crc = crc.getValue();
281                     writeEXT(e);
282                 }
283                 def.reset();
284                 written += e.csize;
285                 break;
286             case STORED:
287                 // we already know that both e.size and e.csize are the same
288                 if (e.size != written - locoff) {
289                     throw new ZipException(
290                         "invalid entry size (expected " + e.size +
291                         " but got " + (written - locoff) + " bytes)");
292                 }
293                 if (e.crc != crc.getValue()) {
294                     throw new ZipException(
295                          "invalid entry crc-32 (expected 0x" +
296                          Long.toHexString(e.crc) + " but got 0x" +
297                          Long.toHexString(crc.getValue()) + ")");
298                 }
299                 break;
300             default:
301                 throw new ZipException("invalid compression method");
302             }
303             crc.reset();
304             current = null;
305         }
306     }
307 
308     /**
309      * Writes an array of bytes to the current ZIP entry data. This method
310      * will block until all the bytes are written.
311      * @param b the data to be written
312      * @param off the start offset in the data
313      * @param len the number of bytes that are written
314      * @exception ZipException if a ZIP file error has occurred
315      * @exception IOException if an I/O error has occurred
316      */
write(byte[] b, int off, int len)317     public synchronized void write(byte[] b, int off, int len)
318         throws IOException
319     {
320         ensureOpen();
321         if (off < 0 || len < 0 || off > b.length - len) {
322             throw new IndexOutOfBoundsException();
323         } else if (len == 0) {
324             return;
325         }
326 
327         if (current == null) {
328             throw new ZipException("no current ZIP entry");
329         }
330         ZipEntry entry = current.entry;
331         switch (entry.method) {
332         case DEFLATED:
333             super.write(b, off, len);
334             break;
335         case STORED:
336             written += len;
337             if (written - locoff > entry.size) {
338                 throw new ZipException(
339                     "attempt to write past end of STORED entry");
340             }
341             out.write(b, off, len);
342             break;
343         default:
344             throw new ZipException("invalid compression method");
345         }
346         crc.update(b, off, len);
347     }
348 
349     /**
350      * Finishes writing the contents of the ZIP output stream without closing
351      * the underlying stream. Use this method when applying multiple filters
352      * in succession to the same output stream.
353      * @exception ZipException if a ZIP file error has occurred
354      * @exception IOException if an I/O exception has occurred
355      */
finish()356     public void finish() throws IOException {
357         ensureOpen();
358         if (finished) {
359             return;
360         }
361         if (current != null) {
362             closeEntry();
363         }
364         // write central directory
365         long off = written;
366         for (XEntry xentry : xentries)
367             writeCEN(xentry);
368         writeEND(off, written - off);
369         finished = true;
370     }
371 
372     /**
373      * Closes the ZIP output stream as well as the stream being filtered.
374      * @exception ZipException if a ZIP file error has occurred
375      * @exception IOException if an I/O error has occurred
376      */
close()377     public void close() throws IOException {
378         if (!closed) {
379             super.close();
380             closed = true;
381         }
382     }
383 
384     /*
385      * Writes local file (LOC) header for specified entry.
386      */
writeLOC(XEntry xentry)387     private void writeLOC(XEntry xentry) throws IOException {
388         ZipEntry e = xentry.entry;
389         int flag = e.flag;
390         boolean hasZip64 = false;
391         int elen = getExtraLen(e.extra);
392 
393         writeInt(LOCSIG);               // LOC header signature
394         if ((flag & 8) == 8) {
395             writeShort(version(e));     // version needed to extract
396             writeShort(flag);           // general purpose bit flag
397             writeShort(e.method);       // compression method
398             writeInt(e.xdostime);       // last modification time
399             // store size, uncompressed size, and crc-32 in data descriptor
400             // immediately following compressed entry data
401             writeInt(0);
402             writeInt(0);
403             writeInt(0);
404         } else {
405             if (e.csize >= ZIP64_MAGICVAL || e.size >= ZIP64_MAGICVAL) {
406                 hasZip64 = true;
407                 writeShort(45);         // ver 4.5 for zip64
408             } else {
409                 writeShort(version(e)); // version needed to extract
410             }
411             writeShort(flag);           // general purpose bit flag
412             writeShort(e.method);       // compression method
413             writeInt(e.xdostime);       // last modification time
414             writeInt(e.crc);            // crc-32
415             if (hasZip64) {
416                 writeInt(ZIP64_MAGICVAL);
417                 writeInt(ZIP64_MAGICVAL);
418                 elen += 20;        //headid(2) + size(2) + size(8) + csize(8)
419             } else {
420                 writeInt(e.csize);  // compressed size
421                 writeInt(e.size);   // uncompressed size
422             }
423         }
424         byte[] nameBytes = zc.getBytes(e.name);
425         writeShort(nameBytes.length);
426 
427         int elenEXTT = 0;               // info-zip extended timestamp
428         int flagEXTT = 0;
429         if (e.mtime != null) {
430             elenEXTT += 4;
431             flagEXTT |= EXTT_FLAG_LMT;
432         }
433         if (e.atime != null) {
434             elenEXTT += 4;
435             flagEXTT |= EXTT_FLAG_LAT;
436         }
437         if (e.ctime != null) {
438             elenEXTT += 4;
439             flagEXTT |= EXTT_FLAT_CT;
440         }
441         if (flagEXTT != 0)
442             elen += (elenEXTT + 5);    // headid(2) + size(2) + flag(1) + data
443         writeShort(elen);
444         writeBytes(nameBytes, 0, nameBytes.length);
445         if (hasZip64) {
446             writeShort(ZIP64_EXTID);
447             writeShort(16);
448             writeLong(e.size);
449             writeLong(e.csize);
450         }
451         if (flagEXTT != 0) {
452             writeShort(EXTID_EXTT);
453             writeShort(elenEXTT + 1);      // flag + data
454             writeByte(flagEXTT);
455             if (e.mtime != null)
456                 writeInt(fileTimeToUnixTime(e.mtime));
457             if (e.atime != null)
458                 writeInt(fileTimeToUnixTime(e.atime));
459             if (e.ctime != null)
460                 writeInt(fileTimeToUnixTime(e.ctime));
461         }
462         writeExtra(e.extra);
463         locoff = written;
464     }
465 
466     /*
467      * Writes extra data descriptor (EXT) for specified entry.
468      */
writeEXT(ZipEntry e)469     private void writeEXT(ZipEntry e) throws IOException {
470         writeInt(EXTSIG);           // EXT header signature
471         writeInt(e.crc);            // crc-32
472         if (e.csize >= ZIP64_MAGICVAL || e.size >= ZIP64_MAGICVAL) {
473             writeLong(e.csize);
474             writeLong(e.size);
475         } else {
476             writeInt(e.csize);          // compressed size
477             writeInt(e.size);           // uncompressed size
478         }
479     }
480 
481     /*
482      * Write central directory (CEN) header for specified entry.
483      * REMIND: add support for file attributes
484      */
writeCEN(XEntry xentry)485     private void writeCEN(XEntry xentry) throws IOException {
486         ZipEntry e  = xentry.entry;
487         int flag = e.flag;
488         int version = version(e);
489         long csize = e.csize;
490         long size = e.size;
491         long offset = xentry.offset;
492         int elenZIP64 = 0;
493         boolean hasZip64 = false;
494 
495         if (e.csize >= ZIP64_MAGICVAL) {
496             csize = ZIP64_MAGICVAL;
497             elenZIP64 += 8;              // csize(8)
498             hasZip64 = true;
499         }
500         if (e.size >= ZIP64_MAGICVAL) {
501             size = ZIP64_MAGICVAL;    // size(8)
502             elenZIP64 += 8;
503             hasZip64 = true;
504         }
505         if (xentry.offset >= ZIP64_MAGICVAL) {
506             offset = ZIP64_MAGICVAL;
507             elenZIP64 += 8;              // offset(8)
508             hasZip64 = true;
509         }
510         writeInt(CENSIG);           // CEN header signature
511         if (hasZip64) {
512             writeShort(45);         // ver 4.5 for zip64
513             writeShort(45);
514         } else {
515             writeShort(version);    // version made by
516             writeShort(version);    // version needed to extract
517         }
518         writeShort(flag);           // general purpose bit flag
519         writeShort(e.method);       // compression method
520         writeInt(e.xdostime);       // last modification time
521         writeInt(e.crc);            // crc-32
522         writeInt(csize);            // compressed size
523         writeInt(size);             // uncompressed size
524         byte[] nameBytes = zc.getBytes(e.name);
525         writeShort(nameBytes.length);
526 
527         int elen = getExtraLen(e.extra);
528         if (hasZip64) {
529             elen += (elenZIP64 + 4);// + headid(2) + datasize(2)
530         }
531         // cen info-zip extended timestamp only outputs mtime
532         // but set the flag for a/ctime, if present in loc
533         int flagEXTT = 0;
534         if (e.mtime != null) {
535             elen += 4;              // + mtime(4)
536             flagEXTT |= EXTT_FLAG_LMT;
537         }
538         if (e.atime != null) {
539             flagEXTT |= EXTT_FLAG_LAT;
540         }
541         if (e.ctime != null) {
542             flagEXTT |= EXTT_FLAT_CT;
543         }
544         if (flagEXTT != 0) {
545             elen += 5;             // headid + sz + flag
546         }
547         writeShort(elen);
548         byte[] commentBytes;
549         if (e.comment != null) {
550             commentBytes = zc.getBytes(e.comment);
551             writeShort(Math.min(commentBytes.length, 0xffff));
552         } else {
553             commentBytes = null;
554             writeShort(0);
555         }
556         writeShort(0);              // starting disk number
557         writeShort(0);              // internal file attributes (unused)
558         writeInt(0);                // external file attributes (unused)
559         writeInt(offset);           // relative offset of local header
560         writeBytes(nameBytes, 0, nameBytes.length);
561 
562         // take care of EXTID_ZIP64 and EXTID_EXTT
563         if (hasZip64) {
564             writeShort(ZIP64_EXTID);// Zip64 extra
565             writeShort(elenZIP64);
566             if (size == ZIP64_MAGICVAL)
567                 writeLong(e.size);
568             if (csize == ZIP64_MAGICVAL)
569                 writeLong(e.csize);
570             if (offset == ZIP64_MAGICVAL)
571                 writeLong(xentry.offset);
572         }
573         if (flagEXTT != 0) {
574             writeShort(EXTID_EXTT);
575             if (e.mtime != null) {
576                 writeShort(5);      // flag + mtime
577                 writeByte(flagEXTT);
578                 writeInt(fileTimeToUnixTime(e.mtime));
579             } else {
580                 writeShort(1);      // flag only
581                 writeByte(flagEXTT);
582             }
583         }
584         writeExtra(e.extra);
585         if (commentBytes != null) {
586             writeBytes(commentBytes, 0, Math.min(commentBytes.length, 0xffff));
587         }
588     }
589 
590     /*
591      * Writes end of central directory (END) header.
592      */
writeEND(long off, long len)593     private void writeEND(long off, long len) throws IOException {
594         boolean hasZip64 = false;
595         long xlen = len;
596         long xoff = off;
597         if (xlen >= ZIP64_MAGICVAL) {
598             xlen = ZIP64_MAGICVAL;
599             hasZip64 = true;
600         }
601         if (xoff >= ZIP64_MAGICVAL) {
602             xoff = ZIP64_MAGICVAL;
603             hasZip64 = true;
604         }
605         int count = xentries.size();
606         if (count >= ZIP64_MAGICCOUNT) {
607             hasZip64 |= !inhibitZip64;
608             if (hasZip64) {
609                 count = ZIP64_MAGICCOUNT;
610             }
611         }
612         if (hasZip64) {
613             long off64 = written;
614             //zip64 end of central directory record
615             writeInt(ZIP64_ENDSIG);        // zip64 END record signature
616             writeLong(ZIP64_ENDHDR - 12);  // size of zip64 end
617             writeShort(45);                // version made by
618             writeShort(45);                // version needed to extract
619             writeInt(0);                   // number of this disk
620             writeInt(0);                   // central directory start disk
621             writeLong(xentries.size());    // number of directory entires on disk
622             writeLong(xentries.size());    // number of directory entires
623             writeLong(len);                // length of central directory
624             writeLong(off);                // offset of central directory
625 
626             //zip64 end of central directory locator
627             writeInt(ZIP64_LOCSIG);        // zip64 END locator signature
628             writeInt(0);                   // zip64 END start disk
629             writeLong(off64);              // offset of zip64 END
630             writeInt(1);                   // total number of disks (?)
631         }
632         writeInt(ENDSIG);                 // END record signature
633         writeShort(0);                    // number of this disk
634         writeShort(0);                    // central directory start disk
635         writeShort(count);                // number of directory entries on disk
636         writeShort(count);                // total number of directory entries
637         writeInt(xlen);                   // length of central directory
638         writeInt(xoff);                   // offset of central directory
639         if (comment != null) {            // zip file comment
640             writeShort(comment.length);
641             writeBytes(comment, 0, comment.length);
642         } else {
643             writeShort(0);
644         }
645     }
646 
647     /*
648      * Returns the length of extra data without EXTT and ZIP64.
649      */
getExtraLen(byte[] extra)650     private int getExtraLen(byte[] extra) {
651         if (extra == null)
652             return 0;
653         int skipped = 0;
654         int len = extra.length;
655         int off = 0;
656         while (off + 4 <= len) {
657             int tag = get16(extra, off);
658             int sz = get16(extra, off + 2);
659             if (sz < 0 || (off + 4 + sz) > len) {
660                 break;
661             }
662             if (tag == EXTID_EXTT || tag == EXTID_ZIP64) {
663                 skipped += (sz + 4);
664             }
665             off += (sz + 4);
666         }
667         return len - skipped;
668     }
669 
670     /*
671      * Writes extra data without EXTT and ZIP64.
672      *
673      * Extra timestamp and ZIP64 data is handled/output separately
674      * in writeLOC and writeCEN.
675      */
writeExtra(byte[] extra)676     private void writeExtra(byte[] extra) throws IOException {
677         if (extra != null) {
678             int len = extra.length;
679             int off = 0;
680             while (off + 4 <= len) {
681                 int tag = get16(extra, off);
682                 int sz = get16(extra, off + 2);
683                 if (sz < 0 || (off + 4 + sz) > len) {
684                     writeBytes(extra, off, len - off);
685                     return;
686                 }
687                 if (tag != EXTID_EXTT && tag != EXTID_ZIP64) {
688                     writeBytes(extra, off, sz + 4);
689                 }
690                 off += (sz + 4);
691             }
692             if (off < len) {
693                 writeBytes(extra, off, len - off);
694             }
695         }
696     }
697 
698     /*
699      * Writes a 8-bit byte to the output stream.
700      */
writeByte(int v)701     private void writeByte(int v) throws IOException {
702         OutputStream out = this.out;
703         out.write(v & 0xff);
704         written += 1;
705     }
706 
707     /*
708      * Writes a 16-bit short to the output stream in little-endian byte order.
709      */
writeShort(int v)710     private void writeShort(int v) throws IOException {
711         OutputStream out = this.out;
712         out.write((v >>> 0) & 0xff);
713         out.write((v >>> 8) & 0xff);
714         written += 2;
715     }
716 
717     /*
718      * Writes a 32-bit int to the output stream in little-endian byte order.
719      */
writeInt(long v)720     private void writeInt(long v) throws IOException {
721         OutputStream out = this.out;
722         out.write((int)((v >>>  0) & 0xff));
723         out.write((int)((v >>>  8) & 0xff));
724         out.write((int)((v >>> 16) & 0xff));
725         out.write((int)((v >>> 24) & 0xff));
726         written += 4;
727     }
728 
729     /*
730      * Writes a 64-bit int to the output stream in little-endian byte order.
731      */
writeLong(long v)732     private void writeLong(long v) throws IOException {
733         OutputStream out = this.out;
734         out.write((int)((v >>>  0) & 0xff));
735         out.write((int)((v >>>  8) & 0xff));
736         out.write((int)((v >>> 16) & 0xff));
737         out.write((int)((v >>> 24) & 0xff));
738         out.write((int)((v >>> 32) & 0xff));
739         out.write((int)((v >>> 40) & 0xff));
740         out.write((int)((v >>> 48) & 0xff));
741         out.write((int)((v >>> 56) & 0xff));
742         written += 8;
743     }
744 
745     /*
746      * Writes an array of bytes to the output stream.
747      */
writeBytes(byte[] b, int off, int len)748     private void writeBytes(byte[] b, int off, int len) throws IOException {
749         super.out.write(b, off, len);
750         written += len;
751     }
752 }
753