| /* ZipOutputStream.java -- | |
| Copyright (C) 2001, 2004, 2005, 2006 Free Software Foundation, Inc. | |
| This file is part of GNU Classpath. | |
| GNU Classpath is free software; you can redistribute it and/or modify | |
| it under the terms of the GNU General Public License as published by | |
| the Free Software Foundation; either version 2, or (at your option) | |
| any later version. | |
| GNU Classpath is distributed in the hope that it will be useful, but | |
| WITHOUT ANY WARRANTY; without even the implied warranty of | |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
| General Public License for more details. | |
| You should have received a copy of the GNU General Public License | |
| along with GNU Classpath; see the file COPYING. If not, write to the | |
| Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | |
| 02110-1301 USA. | |
| Linking this library statically or dynamically with other modules is | |
| making a combined work based on this library. Thus, the terms and | |
| conditions of the GNU General Public License cover the whole | |
| combination. | |
| As a special exception, the copyright holders of this library give you | |
| permission to link this library with independent modules to produce an | |
| executable, regardless of the license terms of these independent | |
| modules, and to copy and distribute the resulting executable under | |
| terms of your choice, provided that you also meet, for each linked | |
| independent module, the terms and conditions of the license of that | |
| module. An independent module is a module which is not derived from | |
| or based on this library. If you modify this library, you may extend | |
| this exception to your version of the library, but you are not | |
| obligated to do so. If you do not wish to do so, delete this | |
| exception statement from your version. */ | |
| package java.util.zip; | |
| import java.io.IOException; | |
| import java.io.OutputStream; | |
| import java.io.UnsupportedEncodingException; | |
| import java.util.Enumeration; | |
| import java.util.Vector; | |
| /** | |
| * This is a FilterOutputStream that writes the files into a zip | |
| * archive one after another. It has a special method to start a new | |
| * zip entry. The zip entries contains information about the file name | |
| * size, compressed size, CRC, etc. | |
| * | |
| * It includes support for STORED and DEFLATED entries. | |
| * | |
| * This class is not thread safe. | |
| * | |
| * @author Jochen Hoenicke | |
| */ | |
| public class ZipOutputStream extends DeflaterOutputStream implements ZipConstants | |
| { | |
| private Vector entries = new Vector(); | |
| private CRC32 crc = new CRC32(); | |
| private ZipEntry curEntry = null; | |
| private int curMethod; | |
| private int size; | |
| private int offset = 0; | |
| private byte[] zipComment = new byte[0]; | |
| private int defaultMethod = DEFLATED; | |
| /** | |
| * Our Zip version is hard coded to 1.0 resp. 2.0 | |
| */ | |
| private static final int ZIP_STORED_VERSION = 10; | |
| private static final int ZIP_DEFLATED_VERSION = 20; | |
| /** | |
| * Compression method. This method doesn't compress at all. | |
| */ | |
| public static final int STORED = 0; | |
| /** | |
| * Compression method. This method uses the Deflater. | |
| */ | |
| public static final int DEFLATED = 8; | |
| /** | |
| * Creates a new Zip output stream, writing a zip archive. | |
| * @param out the output stream to which the zip archive is written. | |
| */ | |
| public ZipOutputStream(OutputStream out) | |
| { | |
| super(out, new Deflater(Deflater.DEFAULT_COMPRESSION, true)); | |
| } | |
| /** | |
| * Set the zip file comment. | |
| * @param comment the comment. | |
| * @exception IllegalArgumentException if encoding of comment is | |
| * longer than 0xffff bytes. | |
| */ | |
| public void setComment(String comment) | |
| { | |
| byte[] commentBytes; | |
| try | |
| { | |
| commentBytes = comment.getBytes("UTF-8"); | |
| } | |
| catch (UnsupportedEncodingException uee) | |
| { | |
| throw new AssertionError(uee); | |
| } | |
| if (commentBytes.length > 0xffff) | |
| throw new IllegalArgumentException("Comment too long."); | |
| zipComment = commentBytes; | |
| } | |
| /** | |
| * Sets default compression method. If the Zip entry specifies | |
| * another method its method takes precedence. | |
| * @param method the method. | |
| * @exception IllegalArgumentException if method is not supported. | |
| * @see #STORED | |
| * @see #DEFLATED | |
| */ | |
| public void setMethod(int method) | |
| { | |
| if (method != STORED && method != DEFLATED) | |
| throw new IllegalArgumentException("Method not supported."); | |
| defaultMethod = method; | |
| } | |
| /** | |
| * Sets default compression level. The new level will be activated | |
| * immediately. | |
| * @exception IllegalArgumentException if level is not supported. | |
| * @see Deflater | |
| */ | |
| public void setLevel(int level) | |
| { | |
| def.setLevel(level); | |
| } | |
| /** | |
| * Write an unsigned short in little endian byte order. | |
| */ | |
| private void writeLeShort(int value) throws IOException | |
| { | |
| out.write(value & 0xff); | |
| out.write((value >> 8) & 0xff); | |
| } | |
| /** | |
| * Write an int in little endian byte order. | |
| */ | |
| private void writeLeInt(int value) throws IOException | |
| { | |
| writeLeShort(value); | |
| writeLeShort(value >> 16); | |
| } | |
| /** | |
| * Write a long value as an int. Some of the zip constants | |
| * are declared as longs even though they fit perfectly well | |
| * into integers. | |
| */ | |
| private void writeLeInt(long value) throws IOException | |
| { | |
| writeLeInt((int) value); | |
| } | |
| /** | |
| * Starts a new Zip entry. It automatically closes the previous | |
| * entry if present. If the compression method is stored, the entry | |
| * must have a valid size and crc, otherwise all elements (except | |
| * name) are optional, but must be correct if present. If the time | |
| * is not set in the entry, the current time is used. | |
| * @param entry the entry. | |
| * @exception IOException if an I/O error occured. | |
| * @exception ZipException if stream was finished. | |
| */ | |
| public void putNextEntry(ZipEntry entry) throws IOException | |
| { | |
| if (entries == null) | |
| throw new ZipException("ZipOutputStream was finished"); | |
| int method = entry.getMethod(); | |
| int flags = 0; | |
| if (method == -1) | |
| method = defaultMethod; | |
| if (method == STORED) | |
| { | |
| if (entry.getCompressedSize() >= 0) | |
| { | |
| if (entry.getSize() < 0) | |
| entry.setSize(entry.getCompressedSize()); | |
| else if (entry.getSize() != entry.getCompressedSize()) | |
| throw new ZipException | |
| ("Method STORED, but compressed size != size"); | |
| } | |
| else | |
| entry.setCompressedSize(entry.getSize()); | |
| if (entry.getSize() < 0) | |
| throw new ZipException("Method STORED, but size not set"); | |
| if (entry.getCrc() < 0) | |
| throw new ZipException("Method STORED, but crc not set"); | |
| } | |
| else if (method == DEFLATED) | |
| { | |
| if (entry.getCompressedSize() < 0 | |
| || entry.getSize() < 0 || entry.getCrc() < 0) | |
| flags |= 8; | |
| } | |
| if (curEntry != null) | |
| closeEntry(); | |
| if (entry.getTime() < 0) | |
| entry.setTime(System.currentTimeMillis()); | |
| entry.flags = flags; | |
| entry.offset = offset; | |
| entry.setMethod(method); | |
| curMethod = method; | |
| /* Write the local file header */ | |
| writeLeInt(LOCSIG); | |
| writeLeShort(method == STORED | |
| ? ZIP_STORED_VERSION : ZIP_DEFLATED_VERSION); | |
| writeLeShort(flags); | |
| writeLeShort(method); | |
| writeLeInt(entry.getDOSTime()); | |
| if ((flags & 8) == 0) | |
| { | |
| writeLeInt((int)entry.getCrc()); | |
| writeLeInt((int)entry.getCompressedSize()); | |
| writeLeInt((int)entry.getSize()); | |
| } | |
| else | |
| { | |
| writeLeInt(0); | |
| writeLeInt(0); | |
| writeLeInt(0); | |
| } | |
| byte[] name; | |
| try | |
| { | |
| name = entry.getName().getBytes("UTF-8"); | |
| } | |
| catch (UnsupportedEncodingException uee) | |
| { | |
| throw new AssertionError(uee); | |
| } | |
| if (name.length > 0xffff) | |
| throw new ZipException("Name too long."); | |
| byte[] extra = entry.getExtra(); | |
| if (extra == null) | |
| extra = new byte[0]; | |
| writeLeShort(name.length); | |
| writeLeShort(extra.length); | |
| out.write(name); | |
| out.write(extra); | |
| offset += LOCHDR + name.length + extra.length; | |
| /* Activate the entry. */ | |
| curEntry = entry; | |
| crc.reset(); | |
| if (method == DEFLATED) | |
| def.reset(); | |
| size = 0; | |
| } | |
| /** | |
| * Closes the current entry. | |
| * @exception IOException if an I/O error occured. | |
| * @exception ZipException if no entry is active. | |
| */ | |
| public void closeEntry() throws IOException | |
| { | |
| if (curEntry == null) | |
| throw new ZipException("No open entry"); | |
| /* First finish the deflater, if appropriate */ | |
| if (curMethod == DEFLATED) | |
| super.finish(); | |
| int csize = curMethod == DEFLATED ? def.getTotalOut() : size; | |
| if (curEntry.getSize() < 0) | |
| curEntry.setSize(size); | |
| else if (curEntry.getSize() != size) | |
| throw new ZipException("size was "+size | |
| +", but I expected "+curEntry.getSize()); | |
| if (curEntry.getCompressedSize() < 0) | |
| curEntry.setCompressedSize(csize); | |
| else if (curEntry.getCompressedSize() != csize) | |
| throw new ZipException("compressed size was "+csize | |
| +", but I expected "+curEntry.getSize()); | |
| if (curEntry.getCrc() < 0) | |
| curEntry.setCrc(crc.getValue()); | |
| else if (curEntry.getCrc() != crc.getValue()) | |
| throw new ZipException("crc was " + Long.toHexString(crc.getValue()) | |
| + ", but I expected " | |
| + Long.toHexString(curEntry.getCrc())); | |
| offset += csize; | |
| /* Now write the data descriptor entry if needed. */ | |
| if (curMethod == DEFLATED && (curEntry.flags & 8) != 0) | |
| { | |
| writeLeInt(EXTSIG); | |
| writeLeInt((int)curEntry.getCrc()); | |
| writeLeInt((int)curEntry.getCompressedSize()); | |
| writeLeInt((int)curEntry.getSize()); | |
| offset += EXTHDR; | |
| } | |
| entries.addElement(curEntry); | |
| curEntry = null; | |
| } | |
| /** | |
| * Writes the given buffer to the current entry. | |
| * @exception IOException if an I/O error occured. | |
| * @exception ZipException if no entry is active. | |
| */ | |
| public void write(byte[] b, int off, int len) throws IOException | |
| { | |
| if (curEntry == null) | |
| throw new ZipException("No open entry."); | |
| switch (curMethod) | |
| { | |
| case DEFLATED: | |
| super.write(b, off, len); | |
| break; | |
| case STORED: | |
| out.write(b, off, len); | |
| break; | |
| } | |
| crc.update(b, off, len); | |
| size += len; | |
| } | |
| /** | |
| * Finishes the stream. This will write the central directory at the | |
| * end of the zip file and flush the stream. | |
| * @exception IOException if an I/O error occured. | |
| */ | |
| public void finish() throws IOException | |
| { | |
| if (entries == null) | |
| return; | |
| if (curEntry != null) | |
| closeEntry(); | |
| int numEntries = 0; | |
| int sizeEntries = 0; | |
| Enumeration e = entries.elements(); | |
| while (e.hasMoreElements()) | |
| { | |
| ZipEntry entry = (ZipEntry) e.nextElement(); | |
| int method = entry.getMethod(); | |
| writeLeInt(CENSIG); | |
| writeLeShort(method == STORED | |
| ? ZIP_STORED_VERSION : ZIP_DEFLATED_VERSION); | |
| writeLeShort(method == STORED | |
| ? ZIP_STORED_VERSION : ZIP_DEFLATED_VERSION); | |
| writeLeShort(entry.flags); | |
| writeLeShort(method); | |
| writeLeInt(entry.getDOSTime()); | |
| writeLeInt((int)entry.getCrc()); | |
| writeLeInt((int)entry.getCompressedSize()); | |
| writeLeInt((int)entry.getSize()); | |
| byte[] name; | |
| try | |
| { | |
| name = entry.getName().getBytes("UTF-8"); | |
| } | |
| catch (UnsupportedEncodingException uee) | |
| { | |
| throw new AssertionError(uee); | |
| } | |
| if (name.length > 0xffff) | |
| throw new ZipException("Name too long."); | |
| byte[] extra = entry.getExtra(); | |
| if (extra == null) | |
| extra = new byte[0]; | |
| String str = entry.getComment(); | |
| byte[] comment; | |
| try | |
| { | |
| comment = str != null ? str.getBytes("UTF-8") : new byte[0]; | |
| } | |
| catch (UnsupportedEncodingException uee) | |
| { | |
| throw new AssertionError(uee); | |
| } | |
| if (comment.length > 0xffff) | |
| throw new ZipException("Comment too long."); | |
| writeLeShort(name.length); | |
| writeLeShort(extra.length); | |
| writeLeShort(comment.length); | |
| writeLeShort(0); /* disk number */ | |
| writeLeShort(0); /* internal file attr */ | |
| writeLeInt(0); /* external file attr */ | |
| writeLeInt(entry.offset); | |
| out.write(name); | |
| out.write(extra); | |
| out.write(comment); | |
| numEntries++; | |
| sizeEntries += CENHDR + name.length + extra.length + comment.length; | |
| } | |
| writeLeInt(ENDSIG); | |
| writeLeShort(0); /* disk number */ | |
| writeLeShort(0); /* disk with start of central dir */ | |
| writeLeShort(numEntries); | |
| writeLeShort(numEntries); | |
| writeLeInt(sizeEntries); | |
| writeLeInt(offset); | |
| writeLeShort(zipComment.length); | |
| out.write(zipComment); | |
| out.flush(); | |
| entries = null; | |
| } | |
| } |