FileDocCategorySizeDatePackage
ZipOutputStream.javaAPI DocApache Ant 1.7025348Wed Dec 13 06:16:20 GMT 2006org.apache.tools.zip

ZipOutputStream

public class ZipOutputStream extends FilterOutputStream
Reimplementation of {@link java.util.zip.ZipOutputStream java.util.zip.ZipOutputStream} that does handle the extended functionality of this package, especially internal/external file attributes and extra fields with different layouts for local file data and central directory entries.

This class will try to use {@link java.io.RandomAccessFile RandomAccessFile} when you know that the output is going to go to a file.

If RandomAccessFile cannot be used, this implementation will use a Data Descriptor to store size and CRC information for {@link #DEFLATED DEFLATED} entries, this means, you don't need to calculate them yourself. Unfortunately this is not possible for the {@link #STORED STORED} method, here setting the CRC and uncompressed size information is required before {@link #putNextEntry putNextEntry} can be called.

Fields Summary
public static final int
DEFLATED
Compression method for deflated entries.
public static final int
DEFAULT_COMPRESSION
Default compression level for deflated entries.
public static final int
STORED
Compression method for stored entries.
private ZipEntry
entry
Current entry.
private String
comment
The file comment.
private int
level
Compression level for next entry.
private boolean
hasCompressionLevelChanged
Has the compression level changed when compared to the last entry?
private int
method
Default compression method for next entry.
private Vector
entries
List of ZipEntries written so far.
private CRC32
crc
CRC instance to avoid parsing DEFLATED data twice.
private long
written
Count the bytes written to out.
private long
dataStart
Data for local header data
private long
localDataStart
Offset for CRC entry in the local file header data for the current entry starts here.
private long
cdOffset
Start of central directory.
private long
cdLength
Length of central directory.
private static final byte[]
ZERO
Helper, a 0 as ZipShort.
private static final byte[]
LZERO
Helper, a 0 as ZipLong.
private Hashtable
offsets
Holds the offsets of the LFH starts for each entry.
private String
encoding
The encoding to use for filenames and the file comment.

For a list of possible values see http://java.sun.com/j2se/1.5.0/docs/guide/intl/encoding.doc.html. Defaults to the platform's default character encoding.

protected Deflater
def
This Deflater object is used for output.

This attribute is only protected to provide a level of API backwards compatibility. This class used to extend {@link java.util.zip.DeflaterOutputStream DeflaterOutputStream} up to Revision 1.13.

protected byte[]
buf
This buffer servers as a Deflater.

This attribute is only protected to provide a level of API backwards compatibility. This class used to extend {@link java.util.zip.DeflaterOutputStream DeflaterOutputStream} up to Revision 1.13.

private RandomAccessFile
raf
Optional random access output.
protected static final byte[]
LFH_SIG
local file header signature
protected static final byte[]
DD_SIG
data descriptor signature
protected static final byte[]
CFH_SIG
central file header signature
protected static final byte[]
EOCD_SIG
end of central dir signature
private static final byte[]
DOS_TIME_MIN
Smallest date/time ZIP can handle.
Constructors Summary
public ZipOutputStream(OutputStream out)
Creates a new ZIP OutputStream filtering the underlying stream.

param
out the outputstream to zip
since
1.1


                          
       
        super(out);
    
public ZipOutputStream(File file)
Creates a new ZIP OutputStream writing to a File. Will use random access if possible.

param
file the file to zip to
since
1.14
throws
IOException on error

        super(null);

        try {
            raf = new RandomAccessFile(file, "rw");
            raf.setLength(0);
        } catch (IOException e) {
            if (raf != null) {
                try {
                    raf.close();
                } catch (IOException inner) {
                    // ignore
                }
                raf = null;
            }
            out = new FileOutputStream(file);
        }
    
Methods Summary
protected static longadjustToLong(int i)
Assumes a negative integer really is a positive integer that has wrapped around and re-creates the original value.

param
i the value to treat as unsigned int.
return
the unsigned int as a long.
since
1.34

        if (i < 0) {
            return 2 * ((long) Integer.MAX_VALUE) + 2 + i;
        } else {
            return i;
        }
    
public voidclose()
Closes this output stream and releases any system resources associated with the stream.

exception
IOException if an I/O error occurs.
since
1.14

        finish();

        if (raf != null) {
            raf.close();
        }
        if (out != null) {
            out.close();
        }
    
public voidcloseEntry()
Writes all necessary data for this entry.

since
1.1
throws
IOException on error

        if (entry == null) {
            return;
        }

        long realCrc = crc.getValue();
        crc.reset();

        if (entry.getMethod() == DEFLATED) {
            def.finish();
            while (!def.finished()) {
                deflate();
            }

            entry.setSize(adjustToLong(def.getTotalIn()));
            entry.setCompressedSize(adjustToLong(def.getTotalOut()));
            entry.setCrc(realCrc);

            def.reset();

            written += entry.getCompressedSize();
        } else if (raf == null) {
            if (entry.getCrc() != realCrc) {
                throw new ZipException("bad CRC checksum for entry "
                                       + entry.getName() + ": "
                                       + Long.toHexString(entry.getCrc())
                                       + " instead of "
                                       + Long.toHexString(realCrc));
            }

            if (entry.getSize() != written - dataStart) {
                throw new ZipException("bad size for entry "
                                       + entry.getName() + ": "
                                       + entry.getSize()
                                       + " instead of "
                                       + (written - dataStart));
            }
        } else { /* method is STORED and we used RandomAccessFile */
            long size = written - dataStart;

            entry.setSize(size);
            entry.setCompressedSize(size);
            entry.setCrc(realCrc);
        }

        // If random access output, write the local file header containing
        // the correct CRC and compressed/uncompressed sizes
        if (raf != null) {
            long save = raf.getFilePointer();

            raf.seek(localDataStart);
            writeOut(ZipLong.getBytes(entry.getCrc()));
            writeOut(ZipLong.getBytes(entry.getCompressedSize()));
            writeOut(ZipLong.getBytes(entry.getSize()));
            raf.seek(save);
        }

        writeDataDescriptor(entry);
        entry = null;
    
protected final voiddeflate()
Writes next block of compressed data to the output stream.

throws
IOException on error
since
1.14


                         
          
        int len = def.deflate(buf, 0, buf.length);
        if (len > 0) {
            writeOut(buf, 0, len);
        }
    
public voidfinish()
Finishs writing the contents and closes this as well as the underlying stream.

since
1.1
throws
IOException on error

        closeEntry();
        cdOffset = written;
        for (int i = 0, entriesSize = entries.size(); i < entriesSize; i++) {
            writeCentralFileHeader((ZipEntry) entries.elementAt(i));
        }
        cdLength = written - cdOffset;
        writeCentralDirectoryEnd();
        offsets.clear();
        entries.removeAllElements();
    
public voidflush()
Flushes this output stream and forces any buffered output bytes to be written out to the stream.

exception
IOException if an I/O error occurs.
since
1.14

        if (out != null) {
            out.flush();
        }
    
protected byte[]getBytes(java.lang.String name)
Retrieve the bytes for the given String in the encoding set for this Stream.

param
name the string to get bytes from
return
the bytes as a byte array
throws
ZipException on error
since
1.3

        if (encoding == null) {
            return name.getBytes();
        } else {
            try {
                return name.getBytes(encoding);
            } catch (UnsupportedEncodingException uee) {
                throw new ZipException(uee.getMessage());
            }
        }
    
public java.lang.StringgetEncoding()
The encoding to use for filenames and the file comment.

return
null if using the platform's default character encoding.
since
1.3

        return encoding;
    
public booleanisSeekable()
This method indicates whether this archive is writing to a seekable stream (i.e., to a random access file).

For seekable streams, you don't need to calculate the CRC or uncompressed size for {@link #STORED} entries before invoking {@link #putNextEntry}.

return
true if seekable
since
1.17

        return raf != null;
    
public voidputNextEntry(ZipEntry ze)
Begin writing next entry.

param
ze the entry to write
since
1.1
throws
IOException on error

        closeEntry();

        entry = ze;
        entries.addElement(entry);

        if (entry.getMethod() == -1) { // not specified
            entry.setMethod(method);
        }

        if (entry.getTime() == -1) { // not specified
            entry.setTime(System.currentTimeMillis());
        }

        // Size/CRC not required if RandomAccessFile is used
        if (entry.getMethod() == STORED && raf == null) {
            if (entry.getSize() == -1) {
                throw new ZipException("uncompressed size is required for"
                                       + " STORED method when not writing to a"
                                       + " file");
            }
            if (entry.getCrc() == -1) {
                throw new ZipException("crc checksum is required for STORED"
                                       + " method when not writing to a file");
            }
            entry.setCompressedSize(entry.getSize());
        }

        if (entry.getMethod() == DEFLATED && hasCompressionLevelChanged) {
            def.setLevel(level);
            hasCompressionLevelChanged = false;
        }
        writeLocalFileHeader(entry);
    
public voidsetComment(java.lang.String comment)
Set the file comment.

param
comment the comment
since
1.1

        this.comment = comment;
    
public voidsetEncoding(java.lang.String encoding)
The encoding to use for filenames and the file comment.

For a list of possible values see http://java.sun.com/j2se/1.5.0/docs/guide/intl/encoding.doc.html. Defaults to the platform's default character encoding.

param
encoding the encoding value
since
1.3

        this.encoding = encoding;
    
public voidsetLevel(int level)
Sets the compression level for subsequent entries.

Default is Deflater.DEFAULT_COMPRESSION.

param
level the compression level.
throws
IllegalArgumentException if an invalid compression level is specified.
since
1.1

        if (level < Deflater.DEFAULT_COMPRESSION
            || level > Deflater.BEST_COMPRESSION) {
            throw new IllegalArgumentException(
                "Invalid compression level: " + level);
        }
        hasCompressionLevelChanged = (this.level != level);
        this.level = level;
    
public voidsetMethod(int method)
Sets the default compression method for subsequent entries.

Default is DEFLATED.

param
method an int from java.util.zip.ZipEntry
since
1.1

        this.method = method;
    
protected static ZipLongtoDosTime(java.util.Date time)
Convert a Date object to a DOS date/time field.

param
time the Date to convert
return
the date as a ZipLong
since
1.1


                                
         
        return new ZipLong(toDosTime(time.getTime()));
    
protected static byte[]toDosTime(long t)
Convert a Date object to a DOS date/time field.

Stolen from InfoZip's fileio.c

param
t number of milliseconds since the epoch
return
the date as a byte array
since
1.26

        Date time = new Date(t);
        int year = time.getYear() + 1900;
        if (year < 1980) {
            return DOS_TIME_MIN;
        }
        int month = time.getMonth() + 1;
        long value =  ((year - 1980) << 25)
            |         (month << 21)
            |         (time.getDate() << 16)
            |         (time.getHours() << 11)
            |         (time.getMinutes() << 5)
            |         (time.getSeconds() >> 1);
        return ZipLong.getBytes(value);
    
public voidwrite(byte[] b, int offset, int length)
Writes bytes to ZIP entry.

param
b the byte array to write
param
offset the start position to write from
param
length the number of bytes to write
throws
IOException on error

        if (entry.getMethod() == DEFLATED) {
            if (length > 0) {
                if (!def.finished()) {
                    def.setInput(b, offset, length);
                    while (!def.needsInput()) {
                        deflate();
                    }
                }
            }
        } else {
            writeOut(b, offset, length);
            written += length;
        }
        crc.update(b, offset, length);
    
public voidwrite(int b)
Writes a single byte to ZIP entry.

Delegates to the three arg method.

param
b the byte to write
since
1.14
throws
IOException on error

        byte[] buff = new byte[1];
        buff[0] = (byte) (b & 0xff);
        write(buff, 0, 1);
    
protected voidwriteCentralDirectoryEnd()
Writes the "End of central dir record".

throws
IOException on error
since
1.1

        writeOut(EOCD_SIG);

        // disk numbers
        writeOut(ZERO);
        writeOut(ZERO);

        // number of entries
        byte[] num = ZipShort.getBytes(entries.size());
        writeOut(num);
        writeOut(num);

        // length and location of CD
        writeOut(ZipLong.getBytes(cdLength));
        writeOut(ZipLong.getBytes(cdOffset));

        // ZIP file comment
        byte[] data = getBytes(comment);
        writeOut(ZipShort.getBytes(data.length));
        writeOut(data);
    
protected voidwriteCentralFileHeader(ZipEntry ze)
Writes the central file header entry.

param
ze the entry to write
throws
IOException on error
since
1.1

        writeOut(CFH_SIG);
        written += 4;

        // version made by
        writeOut(ZipShort.getBytes((ze.getPlatform() << 8) | 20));
        written += 2;

        // version needed to extract
        // general purpose bit flag
        if (ze.getMethod() == DEFLATED && raf == null) {
            // requires version 2 as we are going to store length info
            // in the data descriptor
            writeOut(ZipShort.getBytes(20));

            // bit3 set to signal, we use a data descriptor
            writeOut(ZipShort.getBytes(8));
        } else {
            writeOut(ZipShort.getBytes(10));
            writeOut(ZERO);
        }
        written += 4;

        // compression method
        writeOut(ZipShort.getBytes(ze.getMethod()));
        written += 2;

        // last mod. time and date
        writeOut(toDosTime(ze.getTime()));
        written += 4;

        // CRC
        // compressed length
        // uncompressed length
        writeOut(ZipLong.getBytes(ze.getCrc()));
        writeOut(ZipLong.getBytes(ze.getCompressedSize()));
        writeOut(ZipLong.getBytes(ze.getSize()));
        written += 12;

        // file name length
        byte[] name = getBytes(ze.getName());
        writeOut(ZipShort.getBytes(name.length));
        written += 2;

        // extra field length
        byte[] extra = ze.getCentralDirectoryExtra();
        writeOut(ZipShort.getBytes(extra.length));
        written += 2;

        // file comment length
        String comm = ze.getComment();
        if (comm == null) {
            comm = "";
        }
        byte[] commentB = getBytes(comm);
        writeOut(ZipShort.getBytes(commentB.length));
        written += 2;

        // disk number start
        writeOut(ZERO);
        written += 2;

        // internal file attributes
        writeOut(ZipShort.getBytes(ze.getInternalAttributes()));
        written += 2;

        // external file attributes
        writeOut(ZipLong.getBytes(ze.getExternalAttributes()));
        written += 4;

        // relative offset of LFH
        writeOut((byte[]) offsets.get(ze));
        written += 4;

        // file name
        writeOut(name);
        written += name.length;

        // extra field
        writeOut(extra);
        written += extra.length;

        // file comment
        writeOut(commentB);
        written += commentB.length;
    
protected voidwriteDataDescriptor(ZipEntry ze)
Writes the data descriptor entry.

param
ze the entry to write
throws
IOException on error
since
1.1

        if (ze.getMethod() != DEFLATED || raf != null) {
            return;
        }
        writeOut(DD_SIG);
        writeOut(ZipLong.getBytes(entry.getCrc()));
        writeOut(ZipLong.getBytes(entry.getCompressedSize()));
        writeOut(ZipLong.getBytes(entry.getSize()));
        written += 16;
    
protected voidwriteLocalFileHeader(ZipEntry ze)
Writes the local file header entry

param
ze the entry to write
throws
IOException on error
since
1.1

        offsets.put(ze, ZipLong.getBytes(written));

        writeOut(LFH_SIG);
        written += 4;

        //store method in local variable to prevent multiple method calls
        final int zipMethod = ze.getMethod();

        // version needed to extract
        // general purpose bit flag
        if (zipMethod == DEFLATED && raf == null) {
            // requires version 2 as we are going to store length info
            // in the data descriptor
            writeOut(ZipShort.getBytes(20));

            // bit3 set to signal, we use a data descriptor
            writeOut(ZipShort.getBytes(8));
        } else {
            writeOut(ZipShort.getBytes(10));
            writeOut(ZERO);
        }
        written += 4;

        // compression method
        writeOut(ZipShort.getBytes(zipMethod));
        written += 2;

        // last mod. time and date
        writeOut(toDosTime(ze.getTime()));
        written += 4;

        // CRC
        // compressed length
        // uncompressed length
        localDataStart = written;
        if (zipMethod == DEFLATED || raf != null) {
            writeOut(LZERO);
            writeOut(LZERO);
            writeOut(LZERO);
        } else {
            writeOut(ZipLong.getBytes(ze.getCrc()));
            writeOut(ZipLong.getBytes(ze.getSize()));
            writeOut(ZipLong.getBytes(ze.getSize()));
        }
        written += 12;

        // file name length
        byte[] name = getBytes(ze.getName());
        writeOut(ZipShort.getBytes(name.length));
        written += 2;

        // extra field length
        byte[] extra = ze.getLocalFileDataExtra();
        writeOut(ZipShort.getBytes(extra.length));
        written += 2;

        // file name
        writeOut(name);
        written += name.length;

        // extra field
        writeOut(extra);
        written += extra.length;

        dataStart = written;
    
protected final voidwriteOut(byte[] data)
Write bytes to output or random access file.

param
data the byte array to write
throws
IOException on error
since
1.14

        writeOut(data, 0, data.length);
    
protected final voidwriteOut(byte[] data, int offset, int length)
Write bytes to output or random access file.

param
data the byte array to write
param
offset the start position to write from
param
length the number of bytes to write
throws
IOException on error
since
1.14

        if (raf != null) {
            raf.write(data, offset, length);
        } else {
            out.write(data, offset, length);
        }