Save This Page
Home » openjdk-7 » java » util » zip » [javadoc | source]
    1   /*
    2    * Copyright 1996-2006 Sun Microsystems, Inc.  All Rights Reserved.
    3    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
    4    *
    5    * This code is free software; you can redistribute it and/or modify it
    6    * under the terms of the GNU General Public License version 2 only, as
    7    * published by the Free Software Foundation.  Sun designates this
    8    * particular file as subject to the "Classpath" exception as provided
    9    * by Sun in the LICENSE file that accompanied this code.
   10    *
   11    * This code is distributed in the hope that it will be useful, but WITHOUT
   12    * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
   13    * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
   14    * version 2 for more details (a copy is included in the LICENSE file that
   15    * accompanied this code).
   16    *
   17    * You should have received a copy of the GNU General Public License version
   18    * 2 along with this work; if not, write to the Free Software Foundation,
   19    * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
   20    *
   21    * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
   22    * CA 95054 USA or visit www.sun.com if you need additional information or
   23    * have any questions.
   24    */
   25   
   26   package java.util.zip;
   27   
   28   import java.io.OutputStream;
   29   import java.io.IOException;
   30   import java.util.Vector;
   31   import java.util.HashSet;
   32   
   33   /**
   34    * This class implements an output stream filter for writing files in the
   35    * ZIP file format. Includes support for both compressed and uncompressed
   36    * entries.
   37    *
   38    * @author      David Connelly
   39    */
   40   public
   41   class ZipOutputStream extends DeflaterOutputStream implements ZipConstants {
   42   
   43       private static class XEntry {
   44           public final ZipEntry entry;
   45           public final long offset;
   46           public final int flag;
   47           public XEntry(ZipEntry entry, long offset) {
   48               this.entry = entry;
   49               this.offset = offset;
   50               this.flag = (entry.method == DEFLATED &&
   51                            (entry.size  == -1 ||
   52                             entry.csize == -1 ||
   53                             entry.crc   == -1))
   54                   // store size, compressed size, and crc-32 in data descriptor
   55                   // immediately following the compressed entry data
   56                   ? 8
   57                   // store size, compressed size, and crc-32 in LOC header
   58                   : 0;
   59           }
   60       }
   61   
   62       private XEntry current;
   63       private Vector<XEntry> xentries = new Vector<XEntry>();
   64       private HashSet<String> names = new HashSet<String>();
   65       private CRC32 crc = new CRC32();
   66       private long written = 0;
   67       private long locoff = 0;
   68       private String comment;
   69       private int method = DEFLATED;
   70       private boolean finished;
   71   
   72       private boolean closed = false;
   73   
   74       private static int version(ZipEntry e) throws ZipException {
   75           switch (e.method) {
   76           case DEFLATED: return 20;
   77           case STORED:   return 10;
   78           default: throw new ZipException("unsupported compression method");
   79           }
   80       }
   81   
   82       /**
   83        * Checks to make sure that this stream has not been closed.
   84        */
   85       private void ensureOpen() throws IOException {
   86           if (closed) {
   87               throw new IOException("Stream closed");
   88           }
   89       }
   90       /**
   91        * Compression method for uncompressed (STORED) entries.
   92        */
   93       public static final int STORED = ZipEntry.STORED;
   94   
   95       /**
   96        * Compression method for compressed (DEFLATED) entries.
   97        */
   98       public static final int DEFLATED = ZipEntry.DEFLATED;
   99   
  100       /**
  101        * Creates a new ZIP output stream.
  102        * @param out the actual output stream
  103        */
  104       public ZipOutputStream(OutputStream out) {
  105           super(out, new Deflater(Deflater.DEFAULT_COMPRESSION, true));
  106           usesDefaultDeflater = true;
  107       }
  108   
  109       /**
  110        * Sets the ZIP file comment.
  111        * @param comment the comment string
  112        * @exception IllegalArgumentException if the length of the specified
  113        *            ZIP file comment is greater than 0xFFFF bytes
  114        */
  115       public void setComment(String comment) {
  116           if (comment != null && comment.length() > 0xffff/3
  117                                              && getUTF8Length(comment) > 0xffff) {
  118               throw new IllegalArgumentException("ZIP file comment too long.");
  119           }
  120           this.comment = comment;
  121       }
  122   
  123       /**
  124        * Sets the default compression method for subsequent entries. This
  125        * default will be used whenever the compression method is not specified
  126        * for an individual ZIP file entry, and is initially set to DEFLATED.
  127        * @param method the default compression method
  128        * @exception IllegalArgumentException if the specified compression method
  129        *            is invalid
  130        */
  131       public void setMethod(int method) {
  132           if (method != DEFLATED && method != STORED) {
  133               throw new IllegalArgumentException("invalid compression method");
  134           }
  135           this.method = method;
  136       }
  137   
  138       /**
  139        * Sets the compression level for subsequent entries which are DEFLATED.
  140        * The default setting is DEFAULT_COMPRESSION.
  141        * @param level the compression level (0-9)
  142        * @exception IllegalArgumentException if the compression level is invalid
  143        */
  144       public void setLevel(int level) {
  145           def.setLevel(level);
  146       }
  147   
  148       /**
  149        * Begins writing a new ZIP file entry and positions the stream to the
  150        * start of the entry data. Closes the current entry if still active.
  151        * The default compression method will be used if no compression method
  152        * was specified for the entry, and the current time will be used if
  153        * the entry has no set modification time.
  154        * @param e the ZIP entry to be written
  155        * @exception ZipException if a ZIP format error has occurred
  156        * @exception IOException if an I/O error has occurred
  157        */
  158       public void putNextEntry(ZipEntry e) throws IOException {
  159           ensureOpen();
  160           if (current != null) {
  161               closeEntry();       // close previous entry
  162           }
  163           if (e.time == -1) {
  164               e.setTime(System.currentTimeMillis());
  165           }
  166           if (e.method == -1) {
  167               e.method = method;  // use default method
  168           }
  169           switch (e.method) {
  170           case DEFLATED:
  171               break;
  172           case STORED:
  173               // compressed size, uncompressed size, and crc-32 must all be
  174               // set for entries using STORED compression method
  175               if (e.size == -1) {
  176                   e.size = e.csize;
  177               } else if (e.csize == -1) {
  178                   e.csize = e.size;
  179               } else if (e.size != e.csize) {
  180                   throw new ZipException(
  181                       "STORED entry where compressed != uncompressed size");
  182               }
  183               if (e.size == -1 || e.crc == -1) {
  184                   throw new ZipException(
  185                       "STORED entry missing size, compressed size, or crc-32");
  186               }
  187               break;
  188           default:
  189               throw new ZipException("unsupported compression method");
  190           }
  191           if (! names.add(e.name)) {
  192               throw new ZipException("duplicate entry: " + e.name);
  193           }
  194           current = new XEntry(e, written);
  195           xentries.add(current);
  196           writeLOC(current);
  197       }
  198   
  199       /**
  200        * Closes the current ZIP entry and positions the stream for writing
  201        * the next entry.
  202        * @exception ZipException if a ZIP format error has occurred
  203        * @exception IOException if an I/O error has occurred
  204        */
  205       public void closeEntry() throws IOException {
  206           ensureOpen();
  207           if (current != null) {
  208               ZipEntry e = current.entry;
  209               switch (e.method) {
  210               case DEFLATED:
  211                   def.finish();
  212                   while (!def.finished()) {
  213                       deflate();
  214                   }
  215                   if ((current.flag & 8) == 0) {
  216                       // verify size, compressed size, and crc-32 settings
  217                       if (e.size != def.getBytesRead()) {
  218                           throw new ZipException(
  219                               "invalid entry size (expected " + e.size +
  220                               " but got " + def.getBytesRead() + " bytes)");
  221                       }
  222                       if (e.csize != def.getBytesWritten()) {
  223                           throw new ZipException(
  224                               "invalid entry compressed size (expected " +
  225                               e.csize + " but got " + def.getBytesWritten() + " bytes)");
  226                       }
  227                       if (e.crc != crc.getValue()) {
  228                           throw new ZipException(
  229                               "invalid entry CRC-32 (expected 0x" +
  230                               Long.toHexString(e.crc) + " but got 0x" +
  231                               Long.toHexString(crc.getValue()) + ")");
  232                       }
  233                   } else {
  234                       e.size  = def.getBytesRead();
  235                       e.csize = def.getBytesWritten();
  236                       e.crc = crc.getValue();
  237                       writeEXT(e);
  238                   }
  239                   def.reset();
  240                   written += e.csize;
  241                   break;
  242               case STORED:
  243                   // we already know that both e.size and e.csize are the same
  244                   if (e.size != written - locoff) {
  245                       throw new ZipException(
  246                           "invalid entry size (expected " + e.size +
  247                           " but got " + (written - locoff) + " bytes)");
  248                   }
  249                   if (e.crc != crc.getValue()) {
  250                       throw new ZipException(
  251                            "invalid entry crc-32 (expected 0x" +
  252                            Long.toHexString(e.crc) + " but got 0x" +
  253                            Long.toHexString(crc.getValue()) + ")");
  254                   }
  255                   break;
  256               default:
  257                   throw new ZipException("invalid compression method");
  258               }
  259               crc.reset();
  260               current = null;
  261           }
  262       }
  263   
  264       /**
  265        * Writes an array of bytes to the current ZIP entry data. This method
  266        * will block until all the bytes are written.
  267        * @param b the data to be written
  268        * @param off the start offset in the data
  269        * @param len the number of bytes that are written
  270        * @exception ZipException if a ZIP file error has occurred
  271        * @exception IOException if an I/O error has occurred
  272        */
  273       public synchronized void write(byte[] b, int off, int len)
  274           throws IOException
  275       {
  276           ensureOpen();
  277           if (off < 0 || len < 0 || off > b.length - len) {
  278               throw new IndexOutOfBoundsException();
  279           } else if (len == 0) {
  280               return;
  281           }
  282   
  283           if (current == null) {
  284               throw new ZipException("no current ZIP entry");
  285           }
  286           ZipEntry entry = current.entry;
  287           switch (entry.method) {
  288           case DEFLATED:
  289               super.write(b, off, len);
  290               break;
  291           case STORED:
  292               written += len;
  293               if (written - locoff > entry.size) {
  294                   throw new ZipException(
  295                       "attempt to write past end of STORED entry");
  296               }
  297               out.write(b, off, len);
  298               break;
  299           default:
  300               throw new ZipException("invalid compression method");
  301           }
  302           crc.update(b, off, len);
  303       }
  304   
  305       /**
  306        * Finishes writing the contents of the ZIP output stream without closing
  307        * the underlying stream. Use this method when applying multiple filters
  308        * in succession to the same output stream.
  309        * @exception ZipException if a ZIP file error has occurred
  310        * @exception IOException if an I/O exception has occurred
  311        */
  312       public void finish() throws IOException {
  313           ensureOpen();
  314           if (finished) {
  315               return;
  316           }
  317           if (current != null) {
  318               closeEntry();
  319           }
  320           if (xentries.size() < 1) {
  321               throw new ZipException("ZIP file must have at least one entry");
  322           }
  323           // write central directory
  324           long off = written;
  325           for (XEntry xentry : xentries)
  326               writeCEN(xentry);
  327           writeEND(off, written - off);
  328           finished = true;
  329       }
  330   
  331       /**
  332        * Closes the ZIP output stream as well as the stream being filtered.
  333        * @exception ZipException if a ZIP file error has occurred
  334        * @exception IOException if an I/O error has occurred
  335        */
  336       public void close() throws IOException {
  337           if (!closed) {
  338               super.close();
  339               closed = true;
  340           }
  341       }
  342   
  343       /*
  344        * Writes local file (LOC) header for specified entry.
  345        */
  346       private void writeLOC(XEntry xentry) throws IOException {
  347           ZipEntry e = xentry.entry;
  348           int flag = xentry.flag;
  349           writeInt(LOCSIG);           // LOC header signature
  350           writeShort(version(e));     // version needed to extract
  351           writeShort(flag);           // general purpose bit flag
  352           writeShort(e.method);       // compression method
  353           writeInt(e.time);           // last modification time
  354           if ((flag & 8) == 8) {
  355               // store size, uncompressed size, and crc-32 in data descriptor
  356               // immediately following compressed entry data
  357               writeInt(0);
  358               writeInt(0);
  359               writeInt(0);
  360           } else {
  361               writeInt(e.crc);        // crc-32
  362               writeInt(e.csize);      // compressed size
  363               writeInt(e.size);       // uncompressed size
  364           }
  365           byte[] nameBytes = getUTF8Bytes(e.name);
  366           writeShort(nameBytes.length);
  367           writeShort(e.extra != null ? e.extra.length : 0);
  368           writeBytes(nameBytes, 0, nameBytes.length);
  369           if (e.extra != null) {
  370               writeBytes(e.extra, 0, e.extra.length);
  371           }
  372           locoff = written;
  373       }
  374   
  375       /*
  376        * Writes extra data descriptor (EXT) for specified entry.
  377        */
  378       private void writeEXT(ZipEntry e) throws IOException {
  379           writeInt(EXTSIG);           // EXT header signature
  380           writeInt(e.crc);            // crc-32
  381           writeInt(e.csize);          // compressed size
  382           writeInt(e.size);           // uncompressed size
  383       }
  384   
  385       /*
  386        * Write central directory (CEN) header for specified entry.
  387        * REMIND: add support for file attributes
  388        */
  389       private void writeCEN(XEntry xentry) throws IOException {
  390           ZipEntry e  = xentry.entry;
  391           int flag = xentry.flag;
  392           int version = version(e);
  393           writeInt(CENSIG);           // CEN header signature
  394           writeShort(version);        // version made by
  395           writeShort(version);        // version needed to extract
  396           writeShort(flag);           // general purpose bit flag
  397           writeShort(e.method);       // compression method
  398           writeInt(e.time);           // last modification time
  399           writeInt(e.crc);            // crc-32
  400           writeInt(e.csize);          // compressed size
  401           writeInt(e.size);           // uncompressed size
  402           byte[] nameBytes = getUTF8Bytes(e.name);
  403           writeShort(nameBytes.length);
  404           writeShort(e.extra != null ? e.extra.length : 0);
  405           byte[] commentBytes;
  406           if (e.comment != null) {
  407               commentBytes = getUTF8Bytes(e.comment);
  408               writeShort(commentBytes.length);
  409           } else {
  410               commentBytes = null;
  411               writeShort(0);
  412           }
  413           writeShort(0);              // starting disk number
  414           writeShort(0);              // internal file attributes (unused)
  415           writeInt(0);                // external file attributes (unused)
  416           writeInt(xentry.offset);    // relative offset of local header
  417           writeBytes(nameBytes, 0, nameBytes.length);
  418           if (e.extra != null) {
  419               writeBytes(e.extra, 0, e.extra.length);
  420           }
  421           if (commentBytes != null) {
  422               writeBytes(commentBytes, 0, commentBytes.length);
  423           }
  424       }
  425   
  426       /*
  427        * Writes end of central directory (END) header.
  428        */
  429       private void writeEND(long off, long len) throws IOException {
  430           int count = xentries.size();
  431           writeInt(ENDSIG);           // END record signature
  432           writeShort(0);              // number of this disk
  433           writeShort(0);              // central directory start disk
  434           writeShort(count);          // number of directory entries on disk
  435           writeShort(count);          // total number of directory entries
  436           writeInt(len);              // length of central directory
  437           writeInt(off);              // offset of central directory
  438           if (comment != null) {      // zip file comment
  439               byte[] b = getUTF8Bytes(comment);
  440               writeShort(b.length);
  441               writeBytes(b, 0, b.length);
  442           } else {
  443               writeShort(0);
  444           }
  445       }
  446   
  447       /*
  448        * Writes a 16-bit short to the output stream in little-endian byte order.
  449        */
  450       private void writeShort(int v) throws IOException {
  451           OutputStream out = this.out;
  452           out.write((v >>> 0) & 0xff);
  453           out.write((v >>> 8) & 0xff);
  454           written += 2;
  455       }
  456   
  457       /*
  458        * Writes a 32-bit int to the output stream in little-endian byte order.
  459        */
  460       private void writeInt(long v) throws IOException {
  461           OutputStream out = this.out;
  462           out.write((int)((v >>>  0) & 0xff));
  463           out.write((int)((v >>>  8) & 0xff));
  464           out.write((int)((v >>> 16) & 0xff));
  465           out.write((int)((v >>> 24) & 0xff));
  466           written += 4;
  467       }
  468   
  469       /*
  470        * Writes an array of bytes to the output stream.
  471        */
  472       private void writeBytes(byte[] b, int off, int len) throws IOException {
  473           super.out.write(b, off, len);
  474           written += len;
  475       }
  476   
  477       /*
  478        * Returns the length of String's UTF8 encoding.
  479        */
  480       static int getUTF8Length(String s) {
  481           int count = 0;
  482           for (int i = 0; i < s.length(); i++) {
  483               char ch = s.charAt(i);
  484               if (ch <= 0x7f) {
  485                   count++;
  486               } else if (ch <= 0x7ff) {
  487                   count += 2;
  488               } else {
  489                   count += 3;
  490               }
  491           }
  492           return count;
  493       }
  494   
  495       /*
  496        * Returns an array of bytes representing the UTF8 encoding
  497        * of the specified String.
  498        */
  499       private static byte[] getUTF8Bytes(String s) {
  500           char[] c = s.toCharArray();
  501           int len = c.length;
  502           // Count the number of encoded bytes...
  503           int count = 0;
  504           for (int i = 0; i < len; i++) {
  505               int ch = c[i];
  506               if (ch <= 0x7f) {
  507                   count++;
  508               } else if (ch <= 0x7ff) {
  509                   count += 2;
  510               } else {
  511                   count += 3;
  512               }
  513           }
  514           // Now return the encoded bytes...
  515           byte[] b = new byte[count];
  516           int off = 0;
  517           for (int i = 0; i < len; i++) {
  518               int ch = c[i];
  519               if (ch <= 0x7f) {
  520                   b[off++] = (byte)ch;
  521               } else if (ch <= 0x7ff) {
  522                   b[off++] = (byte)((ch >> 6) | 0xc0);
  523                   b[off++] = (byte)((ch & 0x3f) | 0x80);
  524               } else {
  525                   b[off++] = (byte)((ch >> 12) | 0xe0);
  526                   b[off++] = (byte)(((ch >> 6) & 0x3f) | 0x80);
  527                   b[off++] = (byte)((ch & 0x3f) | 0x80);
  528               }
  529           }
  530           return b;
  531       }
  532   }

Save This Page
Home » openjdk-7 » java » util » zip » [javadoc | source]