/*
* net.sf.jazzlib.ZipOutputStream Copyright (C) 2001 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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 ilarkesto.io.zip;
import java.io.IOException;
import java.io.OutputStream;
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 final static int ZIP_STORED_VERSION = 10;
private final static int ZIP_DEFLATED_VERSION = 20;
/**
* Compression method. This method doesn't compress at all.
*/
public final static int STORED = 0;
/**
* Compression method. This method uses the Deflater.
*/
public final static 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;
commentBytes = comment.getBytes();
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 final void writeLeShort(int value) throws IOException {
out.write(value & 0xff);
out.write((value >> 8) & 0xff);
}
/**
* Write an int in little endian byte order.
*/
private final void writeLeInt(int value) throws IOException {
writeLeShort(value);
writeLeShort(value >> 16);
}
/**
* 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 = entry.getName().getBytes();
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 enume = entries.elements();
while (enume.hasMoreElements()) {
ZipEntry entry = (ZipEntry) enume.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 = entry.getName().getBytes();
if (name.length > 0xffff) throw new ZipException("Name too long.");
byte[] extra = entry.getExtra();
if (extra == null) extra = new byte[0];
String strComment = entry.getComment();
byte[] comment = strComment != null ? strComment.getBytes() : new byte[0];
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;
}
}