home *** CD-ROM | disk | FTP | other *** search
/ Java 1.2 How-To / JavaHowTo.iso / 3rdParty / jbuilder / unsupported / JDK1.2beta3 / SOURCE / SRC.ZIP / java / util / zip / ZipOutputStream.java < prev   
Encoding:
Java Source  |  1998-03-20  |  12.9 KB  |  425 lines

  1. /*
  2.  * @(#)ZipOutputStream.java    1.15 98/03/18
  3.  *
  4.  * Copyright 1996, 1997 by Sun Microsystems, Inc.,
  5.  * 901 San Antonio Road, Palo Alto, California, 94303, U.S.A.
  6.  * All rights reserved.
  7.  *
  8.  * This software is the confidential and proprietary information
  9.  * of Sun Microsystems, Inc. ("Confidential Information").  You
  10.  * shall not disclose such Confidential Information and shall use
  11.  * it only in accordance with the terms of the license agreement
  12.  * you entered into with Sun.
  13.  */
  14.  
  15. package java.util.zip;
  16.  
  17. import java.io.OutputStream;
  18. import java.io.IOException;
  19. import java.util.Vector;
  20. import java.util.Hashtable;
  21. import java.util.Enumeration;
  22.  
  23. /**
  24.  * This class implements an output stream filter for writing files in the
  25.  * ZIP file format. Includes support for both compressed and uncompressed
  26.  * entries.
  27.  *
  28.  * @author    David Connelly
  29.  * @version    1.15, 03/18/98
  30.  */
  31. public
  32. class ZipOutputStream extends DeflaterOutputStream implements ZipConstants {
  33.     private ZipEntry entry;
  34.     private Vector entries = new Vector();
  35.     private Hashtable names = new Hashtable();
  36.     private CRC32 crc = new CRC32();
  37.     private long written;
  38.     private long locoff = 0;
  39.     private String comment;
  40.     private int method = DEFLATED;
  41.     private boolean finished;
  42.  
  43.     /**
  44.      * Compression method for uncompressed (STORED) entries.
  45.      */
  46.     public static final int STORED = ZipEntry.STORED;
  47.  
  48.     /**
  49.      * Compression method for compressed (DEFLATED) entries.
  50.      */
  51.     public static final int DEFLATED = ZipEntry.DEFLATED;
  52.  
  53.     /**
  54.      * Creates a new ZIP output stream.
  55.      * @param out the actual output stream 
  56.      */
  57.     public ZipOutputStream(OutputStream out) {
  58.     super(out, new Deflater(Deflater.DEFAULT_COMPRESSION, true));
  59.     }
  60.  
  61.     /**
  62.      * Sets the ZIP file comment.
  63.      * @param comment the comment string
  64.      * @exception IllegalArgumentException if the length of the specified
  65.      *          ZIP file comment is greater than 0xFFFF bytes
  66.      */
  67.     public void setComment(String comment) {
  68.     if (comment.length() > 0xffff) {
  69.         throw new IllegalArgumentException("invalid ZIP file comment");
  70.     }
  71.     this.comment = comment;
  72.     }
  73.  
  74.     /**
  75.      * Sets the default compression method for subsequent entries. This
  76.      * default will be used whenever the compression method is not specified
  77.      * for an individual ZIP file entry, and is initially set to DEFLATED.
  78.      * @param method the default compression method
  79.      * @exception IllegalArgumentException if the specified compression method
  80.      *          is invalid
  81.      */
  82.     public void setMethod(int method) {
  83.     if (method != DEFLATED && method != STORED) {
  84.         throw new IllegalArgumentException("invalid compression method");
  85.     }
  86.     this.method = method;
  87.     }
  88.  
  89.     /**
  90.      * Sets the compression level for subsequent entries which are DEFLATED.
  91.      * The default setting is DEFAULT_COMPRESSION.
  92.      * @param level the compression level (0-9)
  93.      * @exception IllegalArgumentException if the compression level is invalid
  94.      */
  95.     public void setLevel(int level) {
  96.     def.setLevel(level);
  97.     }
  98.  
  99.     /**
  100.      * Begins writing a new ZIP file entry and positions the stream to the
  101.      * start of the entry data. Closes the current entry if still active.
  102.      * The default compression method will be used if no compression method
  103.      * was specified for the entry, and the current time will be used if
  104.      * the entry has no set modification time.
  105.      * @param e the ZIP entry to be written
  106.      * @exception ZipException if a ZIP format error has occurred
  107.      * @exception IOException if an I/O error has occurred
  108.      */
  109.     public void putNextEntry(ZipEntry e) throws IOException {
  110.     if (entry != null) {
  111.         closeEntry();    // close previous entry
  112.     }
  113.     if (e.time == -1) {
  114.         e.setTime(System.currentTimeMillis());
  115.     }
  116.     if (e.method == -1) {
  117.         e.method = method;    // use default method
  118.     }
  119.     switch (e.method) {
  120.     case DEFLATED:
  121.         if (e.size == -1 || e.csize == -1 || e.crc == -1) {
  122.         // store size, compressed size, and crc-32 in data descriptor
  123.         // immediately following the compressed entry data
  124.         e.flag = 8;
  125.         } else if (e.size != -1 && e.csize != -1 && e.crc != -1) {
  126.         // store size, compressed size, and crc-32 in LOC header
  127.         e.flag = 0;
  128.         } else {
  129.         throw new ZipException(
  130.             "DEFLATED entry missing size, compressed size, or crc-32");
  131.         }
  132.         e.version = 20;
  133.         break;
  134.     case STORED:
  135.         // compressed size, uncompressed size, and crc-32 must all be
  136.         // set for entries using STORED compression method
  137.         if (e.size == -1) {
  138.         e.size = e.csize;
  139.         } else if (e.csize == -1) {
  140.         e.csize = e.size;
  141.         } else if (e.size != e.csize) {
  142.         throw new ZipException(
  143.             "STORED entry where compressed != uncompressed size");
  144.         }
  145.         if (e.size == -1 || e.crc == -1) {
  146.         throw new ZipException(
  147.             "STORED entry missing size, compressed size, or crc-32");
  148.         }
  149.         e.version = 10;
  150.         e.flag = 0;
  151.         break;
  152.     default:
  153.         throw new ZipException("unsupported compression method");
  154.     }
  155.     e.offset = written;
  156.     writeLOC(e);
  157.     if (names.put(e.name, e) != null) {
  158.         throw new ZipException("duplicate entry: " + e.name);
  159.     }
  160.     entries.addElement(e);
  161.     entry = e;
  162.     }
  163.  
  164.     /**
  165.      * Closes the current ZIP entry and positions the stream for writing
  166.      * the next entry.
  167.      * @exception ZipException if a ZIP format error has occurred
  168.      * @exception IOException if an I/O error has occurred
  169.      */
  170.     public void closeEntry() throws IOException {
  171.     ZipEntry e = entry;
  172.     if (e != null) {
  173.         switch (e.method) {
  174.         case DEFLATED:
  175.         def.finish();
  176.         while (!def.finished()) {
  177.             deflate();
  178.         }
  179.         if ((e.flag & 8) == 0) {
  180.             // verify size, compressed size, and crc-32 settings
  181.             if (e.size != def.getTotalIn()) {
  182.             throw new ZipException(
  183.                 "invalid entry size (expected " + e.size +
  184.                 " but got " + def.getTotalIn() + " bytes)");
  185.             }
  186.             if (e.csize != def.getTotalOut()) {
  187.             throw new ZipException(
  188.                 "invalid entry compressed size (expected " +
  189.                 e.csize + " but got " + def.getTotalOut() +
  190.                 " bytes)");
  191.             }
  192.             if (e.crc != crc.getValue()) {
  193.             throw new ZipException(
  194.                 "invalid entry CRC-32 (expected 0x" +
  195.                 Long.toHexString(e.crc) + " but got 0x" +
  196.                 Long.toHexString(crc.getValue()) + ")");
  197.             }
  198.         } else {
  199.             e.size = def.getTotalIn();
  200.             e.csize = def.getTotalOut();
  201.             e.crc = crc.getValue();
  202.             writeEXT(e);
  203.         }
  204.         def.reset();
  205.         written += e.csize;
  206.         break;
  207.         case STORED:
  208.         // we already know that both e.size and e.csize are the same
  209.         if (e.size != written - locoff) {
  210.             throw new ZipException(
  211.             "invalid entry size (expected " + e.size +
  212.             " but got " + (written - locoff) + " bytes)");
  213.         }
  214.         if (e.crc != crc.getValue()) {
  215.             throw new ZipException(
  216.              "invalid entry crc-32 (expected 0x" +
  217.              Long.toHexString(e.size) + " but got 0x" +
  218.              Long.toHexString(crc.getValue()) + ")");
  219.         }
  220.         break;
  221.         default:
  222.         throw new InternalError("invalid compression method");
  223.         }
  224.         crc.reset();
  225.         entry = null;
  226.     }
  227.     }
  228.  
  229.     /**
  230.      * Writes an array of bytes to the current ZIP entry data. This method
  231.      * will block until all the bytes are written.
  232.      * @param b the data to be written
  233.      * @param off the start offset in the data
  234.      * @param len the number of bytes that are written
  235.      * @exception ZipException if a ZIP file error has occurred
  236.      * @exception IOException if an I/O error has occurred
  237.      */
  238.     public synchronized void write(byte[] b, int off, int len)
  239.     throws IOException
  240.     {
  241.     if (entry == null) {
  242.         throw new ZipException("no current ZIP entry");
  243.     }
  244.     switch (entry.method) {
  245.     case DEFLATED:
  246.         super.write(b, off, len);
  247.         break;
  248.     case STORED:
  249.         written += len;
  250.         if (written - locoff > entry.size) {
  251.         throw new ZipException(
  252.             "attempt to write past end of STORED entry");
  253.         }
  254.         out.write(b, off, len);
  255.         break;
  256.     default:
  257.         throw new InternalError("invalid compression method");
  258.     }
  259.     crc.update(b, off, len);
  260.     }
  261.  
  262.     /**
  263.      * Finishes writing the contents of the ZIP output stream without closing
  264.      * the underlying stream. Use this method when applying multiple filters
  265.      * in succession to the same output stream.
  266.      * @exception ZipException if a ZIP file error has occurred
  267.      * @exception IOException if an I/O exception has occurred
  268.      */
  269.     public void finish() throws IOException {
  270.     if (finished) {
  271.         return;
  272.     }
  273.     if (entry != null) {
  274.         closeEntry();
  275.     }
  276.     if (entries.size() < 1) {
  277.         throw new ZipException("ZIP file must have at least one entry");
  278.     }
  279.     // write central directory
  280.     long off = written;
  281.     Enumeration e = entries.elements();
  282.     while (e.hasMoreElements()) {
  283.         writeCEN((ZipEntry)e.nextElement());
  284.     }
  285.     writeEND(off, written - off);
  286.     finished = true;
  287.     }
  288.  
  289.     /**
  290.      * Closes the ZIP output stream as well as the stream being filtered.
  291.      * @exception ZipException if a ZIP file error has occurred
  292.      * @exception IOException if an I/O error has occurred
  293.      */
  294.     public void close() throws IOException {
  295.     finish();
  296.     out.close();
  297.     }
  298.  
  299.     /*
  300.      * Writes local file (LOC) header for specified entry.
  301.      */
  302.     private void writeLOC(ZipEntry e) throws IOException {
  303.     writeInt(LOCSIG);        // LOC header signature
  304.     writeShort(e.version);      // version needed to extract
  305.     writeShort(e.flag);         // general purpose bit flag
  306.     writeShort(e.method);       // compression method
  307.     writeInt(e.time);           // last modification time
  308.     if ((e.flag & 8) == 8) {
  309.         // store size, uncompressed size, and crc-32 in data descriptor
  310.         // immediately following compressed entry data
  311.         writeInt(0);
  312.         writeInt(0);
  313.         writeInt(0);
  314.     } else {
  315.         writeInt(e.crc);        // crc-32
  316.         writeInt(e.csize);      // compressed size
  317.         writeInt(e.size);       // uncompressed size
  318.     }
  319.     writeShort(e.name.length());
  320.     writeShort(e.extra != null ? e.extra.length : 0);
  321.     writeAscii(e.name);
  322.     if (e.extra != null) {
  323.         writeBytes(e.extra, 0, e.extra.length);
  324.     }
  325.     locoff = written;
  326.     }
  327.  
  328.     /*
  329.      * Writes extra data descriptor (EXT) for specified entry.
  330.      */
  331.     private void writeEXT(ZipEntry e) throws IOException {
  332.     writeInt(EXTSIG);        // EXT header signature
  333.     writeInt(e.crc);        // crc-32
  334.     writeInt(e.csize);        // compressed size
  335.     writeInt(e.size);        // uncompressed size
  336.     }
  337.  
  338.     /*
  339.      * Write central directory (CEN) header for specified entry.
  340.      * REMIND: add support for file attributes
  341.      */
  342.     private void writeCEN(ZipEntry e) throws IOException {
  343.     writeInt(CENSIG);        // CEN header signature
  344.     writeShort(e.version);        // version made by
  345.     writeShort(e.version);        // version needed to extract
  346.     writeShort(e.flag);        // general purpose bit flag
  347.     writeShort(e.method);        // compression method
  348.     writeInt(e.time);        // last modification time
  349.     writeInt(e.crc);        // crc-32
  350.     writeInt(e.csize);        // compressed size
  351.     writeInt(e.size);        // uncompressed size
  352.     writeShort(e.name.length());
  353.     writeShort(e.extra != null ? e.extra.length : 0);
  354.     writeShort(e.comment != null ? e.comment.length() : 0);
  355.     writeShort(0);            // starting disk number
  356.     writeShort(0);            // internal file attributes (unused)
  357.     writeInt(0);            // external file attributes (unused)
  358.     writeInt(e.offset);        // relative offset of local header
  359.     writeAscii(e.name);
  360.     if (e.extra != null) {
  361.         writeBytes(e.extra, 0, e.extra.length);
  362.     }
  363.     if (e.comment != null) {
  364.         writeAscii(e.comment);
  365.     }
  366.     }
  367.  
  368.     /*
  369.      * Writes end of central directory (END) header.
  370.      */
  371.     private void writeEND(long off, long len) throws IOException {
  372.     writeInt(ENDSIG);        // END record signature
  373.     writeShort(0);            // number of this disk
  374.     writeShort(0);            // central directory start disk
  375.     writeShort(entries.size()); // number of directory entries on disk
  376.     writeShort(entries.size()); // total number of directory entries
  377.     writeInt(len);            // length of central directory
  378.     writeInt(off);            // offset of central directory
  379.     writeShort(comment != null ? comment.length() : 0);
  380.     if (comment != null) {
  381.         writeAscii(comment);    // ZIP file comment
  382.     }
  383.     }
  384.  
  385.     /*
  386.      * Writes a 16-bit short to the output stream in little-endian byte order.
  387.      */
  388.     private void writeShort(int v) throws IOException {
  389.     OutputStream out = this.out;
  390.     out.write((v >>> 0) & 0xff);
  391.     out.write((v >>> 8) & 0xff);
  392.     written += 2;
  393.     }
  394.  
  395.     /*
  396.      * Writes a 32-bit int to the output stream in little-endian byte order.
  397.      */
  398.     private void writeInt(long v) throws IOException {
  399.     OutputStream out = this.out;
  400.     out.write((int)((v >>>  0) & 0xff));
  401.     out.write((int)((v >>>  8) & 0xff));
  402.     out.write((int)((v >>> 16) & 0xff));
  403.     out.write((int)((v >>> 24) & 0xff));
  404.     written += 4;
  405.     }
  406.  
  407.     /*
  408.      * Writes an ASCII string to the output stream.
  409.      */
  410.     private void writeAscii(String s) throws IOException {
  411.     OutputStream out = this.out;
  412.     byte[] b = new byte[s.length()];
  413.     s.getBytes(0, b.length, b, 0);
  414.     writeBytes(b, 0, b.length);
  415.     }
  416.  
  417.     /*
  418.      * Writes an array of bytes to the output stream.
  419.      */
  420.     private void writeBytes(byte[] b, int off, int len) throws IOException {
  421.     super.out.write(b, off, len);
  422.     written += len;
  423.     }
  424. }
  425.