/**
*
* Copyright 2003 Sun Microsystems, Inc. All rights reserved.
* SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.*/packagenet.techjava.zip;importjava.io.IOException;importjava.io.OutputStream;importjava.io.UnsupportedEncodingException;importjava.util.Enumeration;importjava.util.Hashtable;importjava.util.Vector;importjava.util.zip.CRC32;importjava.util.zip.Deflater;importjava.util.zip.ZipException;publicclassZipOutputStreamextendsDeflaterOutputStreamimplementsZipConstants {privateZipEntry entry;privateVector entries=newVector();privateHashtable names=newHashtable();privateCRC32 crc=newCRC32();privatelongwritten;privatelonglocoff=0;privateString comment;privateintmethod=DEFLATED;privatebooleanfinished;privatebooleanclosed=false;/*** Check to make sure that this stream has not been closed*/privatevoidensureOpen()throwsIOException {if(closed) {thrownewIOException("Stream closed");
}
}/*** Compression method for uncompressed (STORED) entries.*/publicstaticfinalintSTORED=ZipEntry.STORED;/*** Compression method for compressed (DEFLATED) entries.*/publicstaticfinalintDEFLATED=ZipEntry.DEFLATED;/*** Creates a new ZIP output stream.
*@paramout the actual output stream*/publicZipOutputStream(OutputStream out) {super(out,newDeflater(Deflater.DEFAULT_COMPRESSION,true));
usesDefaultDeflater=true;
}/*** Sets the ZIP file comment.
*@paramcomment the comment string
*@exceptionIllegalArgumentException if the length of the specified
* ZIP file comment is greater than 0xFFFF bytes*/publicvoidsetComment(String comment) {if(comment!=null&&comment.length()>0xffff/3&&getUTF8Length(comment)>0xffff) {thrownewIllegalArgumentException("ZIP file comment too long.");
}this.comment=comment;
}/*** Sets the default compression method for subsequent entries. This
* default will be used whenever the compression method is not specified
* for an individual ZIP file entry, and is initially set to DEFLATED.
*@parammethod the default compression method
*@exceptionIllegalArgumentException if the specified compression method
* is invalid*/publicvoidsetMethod(intmethod) {if(method!=DEFLATED&&method!=STORED) {thrownewIllegalArgumentException("invalid compression method");
}this.method=method;
}/*** Sets the compression level for subsequent entries which are DEFLATED.
* The default setting is DEFAULT_COMPRESSION.
*@paramlevel the compression level (0-9)
*@exceptionIllegalArgumentException if the compression level is invalid*/publicvoidsetLevel(intlevel) {
def.setLevel(level);
}/*** Begins writing a new ZIP file entry and positions the stream to the
* start of the entry data. Closes the current entry if still active.
* The default compression method will be used if no compression method
* was specified for the entry, and the current time will be used if
* the entry has no set modification time.
*@parame the ZIP entry to be written
*@exceptionZipException if a ZIP format error has occurred
*@exceptionIOException if an I/O error has occurred*/publicvoidputNextEntry(ZipEntry e)throwsIOException {
ensureOpen();if(entry!=null) {
closeEntry();//close previous entry}if(e.time==-1) {
e.setTime(System.currentTimeMillis());
}if(e.method==-1) {
e.method=method;//use default method}switch(e.method) {caseDEFLATED:if(e.size==-1||e.csize==-1||e.crc==-1) {//store size, compressed size, and crc-32 in data descriptor//immediately following the compressed entry datae.flag=8;
}elseif(e.size!=-1&&e.csize!=-1&&e.crc!=-1) {//store size, compressed size, and crc-32 in LOC headere.flag=0;
}else{thrownewZipException("DEFLATED entry missing size, compressed size, or crc-32");
}
e.version=20;break;caseSTORED://compressed size, uncompressed size, and crc-32 must all be//set for entries using STORED compression methodif(e.size==-1) {
e.size=e.csize;
}elseif(e.csize==-1) {
e.csize=e.size;
}elseif(e.size!=e.csize) {thrownewZipException("STORED entry where compressed != uncompressed size");
}if(e.size==-1||e.crc==-1) {thrownewZipException("STORED entry missing size, compressed size, or crc-32");
}
e.version=10;
e.flag=0;break;default:thrownewZipException("unsupported compression method");
}
e.offset=written;if(names.put(e.name, e)!=null) {thrownewZipException("duplicate entry:"+e.name);
}
writeLOC(e);
entries.addElement(e);
entry=e;
}/*** Closes the current ZIP entry and positions the stream for writing
* the next entry.
*@exceptionZipException if a ZIP format error has occurred
*@exceptionIOException if an I/O error has occurred*/publicvoidcloseEntry()throwsIOException {
ensureOpen();
ZipEntry e=entry;if(e!=null) {switch(e.method) {caseDEFLATED:
def.finish();while(!def.finished()) {
deflate();
}if((e.flag&8)==0) {//verify size, compressed size, and crc-32 settingsif(e.size!=def.getTotalIn()) {thrownewZipException("invalid entry size (expected"+e.size+"but got"+def.getTotalIn()+"bytes)");
}if(e.csize!=def.getTotalOut()) {thrownewZipException("invalid entry compressed size (expected"+e.csize+"but got"+def.getTotalOut()+"bytes)");
}if(e.crc!=crc.getValue()) {thrownewZipException("invalid entry CRC-32 (expected 0x"+Long.toHexString(e.crc)+"but got 0x"+Long.toHexString(crc.getValue())+")");
}
}else{
e.size=def.getTotalIn();
e.csize=def.getTotalOut();
e.crc=crc.getValue();
writeEXT(e);
}
def.reset();
written+=e.csize;break;caseSTORED://we already know that both e.size and e.csize are the sameif(e.size!=written-locoff) {thrownewZipException("invalid entry size (expected"+e.size+"but got"+(written-locoff)+"bytes)");
}if(e.crc!=crc.getValue()) {thrownewZipException("invalid entry crc-32 (expected 0x"+Long.toHexString(e.crc)+"but got 0x"+Long.toHexString(crc.getValue())+")");
}break;default:thrownewInternalError("invalid compression method");
}
crc.reset();
entry=null;
}
}/*** Writes an array of bytes to the current ZIP entry data. This method
* will block until all the bytes are written.
*@paramb the data to be written
*@paramoff the start offset in the data
*@paramlen the number of bytes that are written
*@exceptionZipException if a ZIP file error has occurred
*@exceptionIOException if an I/O error has occurred*/publicsynchronizedvoidwrite(byte[] b,intoff,intlen)throwsIOException
{
ensureOpen();if(off<0||len<0||off>b.length-len) {thrownewIndexOutOfBoundsException();
}elseif(len==0) {return;
}if(entry==null) {thrownewZipException("no current ZIP entry");
}switch(entry.method) {caseDEFLATED:super.write(b, off, len);break;caseSTORED:
written+=len;if(written-locoff>entry.size) {thrownewZipException("attempt to write past end of STORED entry");
}
out.write(b, off, len);break;default:thrownewInternalError("invalid compression method");
}
crc.update(b, off, len);
}/*** Finishes writing the contents of the ZIP output stream without closing
* the underlying stream. Use this method when applying multiple filters
* in succession to the same output stream.
*@exceptionZipException if a ZIP file error has occurred
*@exceptionIOException if an I/O exception has occurred*/publicvoidfinish()throwsIOException {
ensureOpen();if(finished) {return;
}if(entry!=null) {
closeEntry();
}if(entries.size()<1) {thrownewZipException("ZIP file must have at least one entry");
}//write central directorylongoff=written;
Enumeration e=entries.elements();while(e.hasMoreElements()) {
writeCEN((ZipEntry)e.nextElement());
}
writeEND(off, written-off);
finished=true;
}/*** Closes the ZIP output stream as well as the stream being filtered.
*@exceptionZipException if a ZIP file error has occurred
*@exceptionIOException if an I/O error has occurred*/publicvoidclose()throwsIOException {if(!closed) {super.close();
closed=true;
}
}/** Writes local file (LOC) header for specified entry.*/privatevoidwriteLOC(ZipEntry e)throwsIOException {
writeInt(LOCSIG);//LOC header signaturewriteShort(e.version);//version needed to extractwriteShort(e.flag);//general purpose bit flagwriteShort(e.method);//compression methodwriteInt(e.time);//last modification timeif((e.flag&8)==8) {//store size, uncompressed size, and crc-32 in data descriptor//immediately following compressed entry datawriteInt(0);
writeInt(0);
writeInt(0);
}else{
writeInt(e.crc);//crc-32writeInt(e.csize);//compressed sizewriteInt(e.size);//uncompressed size}byte[] nameBytes=getUTF8Bytes(e.name);
writeShort(nameBytes.length);
writeShort(e.extra!=null?e.extra.length :0);
writeBytes(nameBytes,0, nameBytes.length);if(e.extra!=null) {
writeBytes(e.extra,0, e.extra.length);
}
locoff=written;
}/** Writes extra data descriptor (EXT) for specified entry.*/privatevoidwriteEXT(ZipEntry e)throwsIOException {
writeInt(EXTSIG);//EXT header signaturewriteInt(e.crc);//crc-32writeInt(e.csize);//compressed sizewriteInt(e.size);//uncompressed size}/** Write central directory (CEN) header for specified entry.
* REMIND: add support for file attributes*/privatevoidwriteCEN(ZipEntry e)throwsIOException {
writeInt(CENSIG);//CEN header signaturewriteShort(e.version);//version made bywriteShort(e.version);//version needed to extractwriteShort(e.flag);//general purpose bit flagwriteShort(e.method);//compression methodwriteInt(e.time);//last modification timewriteInt(e.crc);//crc-32writeInt(e.csize);//compressed sizewriteInt(e.size);//uncompressed sizebyte[] nameBytes=getUTF8Bytes(e.name);
writeShort(nameBytes.length);
writeShort(e.extra!=null?e.extra.length :0);byte[] commentBytes;if(e.comment!=null) {
commentBytes=getUTF8Bytes(e.comment);
writeShort(commentBytes.length);
}else{
commentBytes=null;
writeShort(0);
}
writeShort(0);//starting disk numberwriteShort(0);//internal file attributes (unused)writeInt(0);//external file attributes (unused)writeInt(e.offset);//relative offset of local headerwriteBytes(nameBytes,0, nameBytes.length);if(e.extra!=null) {
writeBytes(e.extra,0, e.extra.length);
}if(commentBytes!=null) {
writeBytes(commentBytes,0, commentBytes.length);
}
}/** Writes end of central directory (END) header.*/privatevoidwriteEND(longoff,longlen)throwsIOException {
writeInt(ENDSIG);//END record signaturewriteShort(0);//number of this diskwriteShort(0);//central directory start diskwriteShort(entries.size());//number of directory entries on diskwriteShort(entries.size());//total number of directory entrieswriteInt(len);//length of central directorywriteInt(off);//offset of central directoryif(comment!=null) {//zip file commentbyte[] b=getUTF8Bytes(comment);
writeShort(b.length);
writeBytes(b,0, b.length);
}else{
writeShort(0);
}
}/** Writes a 16-bit short to the output stream in little-endian byte order.*/privatevoidwriteShort(intv)throwsIOException {
OutputStream out=this.out;
out.write((v>>>0)&0xff);
out.write((v>>>8)&0xff);
written+=2;
}/** Writes a 32-bit int to the output stream in little-endian byte order.*/privatevoidwriteInt(longv)throwsIOException {
OutputStream out=this.out;
out.write((int)((v>>>0)&0xff));
out.write((int)((v>>>8)&0xff));
out.write((int)((v>>>16)&0xff));
out.write((int)((v>>>24)&0xff));
written+=4;
}/** Writes an array of bytes to the output stream.*/privatevoidwriteBytes(byte[] b,intoff,intlen)throwsIOException {super.out.write(b, off, len);
written+=len;
}/** Returns the length of String's UTF8 encoding.*/staticintgetUTF8Length(String s) {intcount=0;for(inti=0; i
count++;
}elseif(ch<=0x7ff) {
count+=2;
}else{
count+=3;
}
}returncount;
}/** Returns an array of bytes representing the UTF8 encoding
* of the specified String.*/privatestaticbyte[] getUTF8Bytes(String s) {//char[] c = s.toCharArray();//int len = c.length;Count the number of encoded bytes
//int count = 0;//for (int i = 0; i
//byte[] b = new byte[count];//int off = 0;//for (int i = 0; i > 6) | 0xc0);//b[off++] = (byte)((ch & 0x3f) | 0x80);//} else {//b[off++] = (byte)((ch >> 12) | 0xe0);//b[off++] = (byte)(((ch >> 6) & 0x3f) | 0x80);//b[off++] = (byte)((ch & 0x3f) | 0x80);//}//}//return b;try{returns.getBytes("gbk");
}catch(UnsupportedEncodingException e) {returngetUTF8Bytes(s);
}
}
}