Modified: commons/sandbox/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/ZipOutputStream.java URL: http://svn.apache.org/viewvc/commons/sandbox/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/ZipOutputStream.java?rev=733688&r1=733687&r2=733688&view=diff ============================================================================== --- commons/sandbox/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/ZipOutputStream.java (original) +++ commons/sandbox/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/ZipOutputStream.java Mon Jan 12 03:15:34 2009 @@ -1,728 +1,943 @@ /* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. */ package org.apache.commons.compress.archivers.zip; +import java.io.File; +import java.io.FileOutputStream; +import java.io.FilterOutputStream; import java.io.IOException; import java.io.OutputStream; +import java.io.RandomAccessFile; import java.io.UnsupportedEncodingException; -import java.util.ArrayList; -import java.util.Calendar; import java.util.Date; import java.util.Hashtable; +import java.util.Vector; import java.util.zip.CRC32; import java.util.zip.Deflater; -import java.util.zip.DeflaterOutputStream; import java.util.zip.ZipException; /** * 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. <p> + * 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. + * + * <p>This class will try to use {...@link java.io.RandomAccessFile + * RandomAccessFile} when you know that the output is going to go to a + * file.</p> + * + * <p>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.</p> * - * This implementation will use a Data Descriptor to store size and CRC - * information for DEFLATED entries, this means, you don't need to calculate - * them yourself. Unfortunately this is not possible for the STORED method, here - * setting the CRC and uncompressed size information is required before {...@link - * #putNextEntry putNextEntry} will be called.</p> */ -public class ZipOutputStream - extends DeflaterOutputStream -{ - /** - * Helper, a 0 as ZipShort. +public class ZipOutputStream extends FilterOutputStream { + + private static final int BYTE_MASK = 0xFF; + private static final int SHORT = 2; + private static final int WORD = 4; + private static final int BUFFER_SIZE = 512; + /* + * Apparently Deflater.setInput gets slowed down a lot on Sun JVMs + * when it gets handed a really big buffer. See + * https://issues.apache.org/bugzilla/show_bug.cgi?id=45396 * - * @since 1.1 + * Using a buffer size of 8 kB proved to be a good compromise */ - private static final byte[] ZERO = {0, 0}; + private static final int DEFLATER_BLOCK_SIZE = 8192; /** - * Helper, a 0 as ZipLong. + * Compression method for deflated entries. * * @since 1.1 */ - private static final byte[] LZERO = {0, 0, 0, 0}; + public static final int DEFLATED = java.util.zip.ZipEntry.DEFLATED; /** - * Compression method for deflated entries. + * Default compression level for deflated entries. * - * @since 1.1 + * @since Ant 1.7 */ - public static final int DEFLATED = ZipEntry.DEFLATED; + public static final int DEFAULT_COMPRESSION = Deflater.DEFAULT_COMPRESSION; /** - * Compression method for deflated entries. + * Compression method for stored entries. * * @since 1.1 */ - public static final int STORED = ZipEntry.STORED; + public static final int STORED = java.util.zip.ZipEntry.STORED; - /* - * Various ZIP constants - */ /** - * local file header signature + * Current entry. * * @since 1.1 */ - protected static final ZipLong LFH_SIG = new ZipLong( 0X04034B50L ); + private ZipEntry entry; + /** - * data descriptor signature + * The file comment. * * @since 1.1 */ - protected static final ZipLong DD_SIG = new ZipLong( 0X08074B50L ); + private String comment = ""; + /** - * central file header signature + * Compression level for next entry. * * @since 1.1 */ - protected static final ZipLong CFH_SIG = new ZipLong( 0X02014B50L ); + private int level = DEFAULT_COMPRESSION; + /** - * end of central dir signature + * Has the compression level changed when compared to the last + * entry? * - * @since 1.1 + * @since 1.5 */ - protected static final ZipLong EOCD_SIG = new ZipLong( 0X06054B50L ); + private boolean hasCompressionLevelChanged = false; /** - * Smallest date/time ZIP can handle. + * Default compression method for next entry. * * @since 1.1 */ - private static final ZipLong DOS_TIME_MIN = new ZipLong( 0x00002100L ); + private int method = java.util.zip.ZipEntry.DEFLATED; /** - * The file comment. + * List of ZipEntries written so far. * * @since 1.1 */ - private String m_comment = ""; + private Vector entries = new Vector(); /** - * Compression level for next entry. + * CRC instance to avoid parsing DEFLATED data twice. * * @since 1.1 */ - private int m_level = Deflater.DEFAULT_COMPRESSION; + private CRC32 crc = new CRC32(); /** - * Default compression method for next entry. + * Count the bytes written to out. * * @since 1.1 */ - private int m_method = DEFLATED; + private long written = 0; /** - * List of ZipEntries written so far. + * Data for local header data * * @since 1.1 */ - private final ArrayList m_entries = new ArrayList(); + private long dataStart = 0; /** - * CRC instance to avoid parsing DEFLATED data twice. + * Offset for CRC entry in the local file header data for the + * current entry starts here. * - * @since 1.1 + * @since 1.15 */ - private final CRC32 m_crc = new CRC32(); + private long localDataStart = 0; /** - * Count the bytes written to out. + * Start of central directory. * * @since 1.1 */ - private long m_written; + private long cdOffset = 0; /** - * Data for current entry started here. + * Length of central directory. * * @since 1.1 */ - private long m_dataStart; + private long cdLength = 0; /** - * Start of central directory. + * Helper, a 0 as ZipShort. * * @since 1.1 */ - private ZipLong m_cdOffset = new ZipLong( 0 ); + private static final byte[] ZERO = {0, 0}; /** - * Length of central directory. + * Helper, a 0 as ZipLong. * * @since 1.1 */ - private ZipLong m_cdLength = new ZipLong( 0 ); + private static final byte[] LZERO = {0, 0, 0, 0}; /** - * Holds the offsets of the LFH starts for each entry + * Holds the offsets of the LFH starts for each entry. * * @since 1.1 */ - private final Hashtable m_offsets = new Hashtable(); + private Hashtable offsets = new Hashtable(); /** - * The encoding to use for filenames and the file comment. <p> + * The encoding to use for filenames and the file comment. * - * For a list of possible values see <a - * href="http://java.sun.com/products/jdk/1.2/docs/guide/internat/encoding.doc.html"> - * http://java.sun.com/products/jdk/1.2/docs/guide/internat/encoding.doc.html - * </a>. Defaults to the platform's default character encoding.</p> + * <p>For a list of possible values see <a + * href="http://java.sun.com/j2se/1.5.0/docs/guide/intl/encoding.doc.html">http://java.sun.com/j2se/1.5.0/docs/guide/intl/encoding.doc.html</a>. + * Defaults to the platform's default character encoding.</p> * * @since 1.3 */ - private String m_encoding; + private String encoding = null; + + // CheckStyle:VisibilityModifier OFF - bc /** - * Current entry. + * This Deflater object is used for output. * - * @since 1.1 + * <p>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.</p> + * + * @since 1.14 */ - private ZipArchiveEntry m_entry; + protected Deflater def = new Deflater(level, true); /** - * Creates a new ZIP OutputStream filtering the underlying stream. + * This buffer servers as a Deflater. * - * @param output the output stream to write to - * @since 1.1 + * <p>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.</p> + * + * @since 1.14 */ - public ZipOutputStream( final OutputStream output ) - { - super( output, new Deflater( Deflater.DEFAULT_COMPRESSION, true ) ); - } + protected byte[] buf = new byte[BUFFER_SIZE]; + + // CheckStyle:VisibilityModifier ON /** - * Convert a Date object to a DOS date/time field. <p> - * - * Stolen from InfoZip's <code>fileio.c</code></p> + * Optional random access output. * - * @param time Description of Parameter - * @return Description of the Returned Value - * @since 1.1 + * @since 1.14 */ - protected static ZipLong toDosTime( Date time ) - { - Calendar cal = Calendar.getInstance(); - cal.setTime( time ); - int year = cal.get(Calendar.YEAR); - int month = cal.get(Calendar.MONTH) + 1; - if( year < 1980 ) - { - return DOS_TIME_MIN; - } - long value = ( ( year - 1980 ) << 25 ) - | ( month << 21 ) - | ( cal.get(Calendar.DAY_OF_MONTH) << 16 ) - | ( cal.get(Calendar.HOUR_OF_DAY) << 11 ) - | ( cal.get(Calendar.MINUTE) << 5 ) - | ( cal.get(Calendar.SECOND) >> 1 ); - - byte[] result = new byte[ 4 ]; - result[ 0 ] = (byte)( ( value & 0xFF ) ); - result[ 1 ] = (byte)( ( value & 0xFF00 ) >> 8 ); - result[ 2 ] = (byte)( ( value & 0xFF0000 ) >> 16 ); - result[ 3 ] = (byte)( ( value & 0xFF000000l ) >> 24 ); - return new ZipLong( result ); - } + private RandomAccessFile raf = null; /** - * Set the file comment. - * - * @param comment The new Comment value + * Creates a new ZIP OutputStream filtering the underlying stream. + * @param out the outputstream to zip * @since 1.1 */ - public void setComment( String comment ) - { - m_comment = comment; + public ZipOutputStream(OutputStream out) { + super(out); } /** - * The encoding to use for filenames and the file comment. <p> - * - * For a list of possible values see <a - * href="http://java.sun.com/products/jdk/1.2/docs/guide/internat/encoding.doc.html"> - * http://java.sun.com/products/jdk/1.2/docs/guide/internat/encoding.doc.html - * </a>. Defaults to the platform's default character encoding.</p> - * - * @param encoding The new Encoding value - * @since 1.3 - */ - public void setEncoding( String encoding ) - { - m_encoding = encoding; + * 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 + */ + public ZipOutputStream(File file) throws IOException { + 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); + } } /** - * Sets the compression level for subsequent entries. <p> - * - * Default is Deflater.DEFAULT_COMPRESSION.</p> + * This method indicates whether this archive is writing to a seekable stream (i.e., to a random + * access file). * - * @param level The new Level value - * @since 1.1 + * <p>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 */ - public void setLevel( int level ) - { - m_level = level; + public boolean isSeekable() { + return raf != null; } /** - * Sets the default compression method for subsequent entries. <p> - * - * Default is DEFLATED.</p> + * The encoding to use for filenames and the file comment. * - * @param method The new Method value - * @since 1.1 + * <p>For a list of possible values see <a + * href="http://java.sun.com/j2se/1.5.0/docs/guide/intl/encoding.doc.html">http://java.sun.com/j2se/1.5.0/docs/guide/intl/encoding.doc.html</a>. + * Defaults to the platform's default character encoding.</p> + * @param encoding the encoding value + * @since 1.3 */ - public void setMethod( final int method ) - { - m_method = method; + public void setEncoding(String encoding) { + this.encoding = encoding; } /** * The encoding to use for filenames and the file comment. * * @return null if using the platform's default character encoding. + * * @since 1.3 */ - public String getEncoding() - { - return m_encoding; + public String getEncoding() { + return encoding; + } + + /** + * Finishs writing the contents and closes this as well as the + * underlying stream. + * + * @since 1.1 + * @throws IOException on error + */ + public void finish() throws IOException { + 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(); } /** * Writes all necessary data for this entry. * - * @throws IOException if an IO failure causes operation to fail * @since 1.1 + * @throws IOException on error */ - public void closeEntry() - throws IOException - { - if( m_entry == null ) - { + public void closeEntry() throws IOException { + if (entry == null) { return; } - long realCrc = m_crc.getValue(); - m_crc.reset(); + long realCrc = crc.getValue(); + crc.reset(); - if( m_entry.getMethod() == DEFLATED ) - { + if (entry.getMethod() == DEFLATED) { def.finish(); - while( !def.finished() ) - { + while (!def.finished()) { deflate(); } - m_entry.setSize( def.getTotalIn() ); - m_entry.setComprSize( def.getTotalOut() ); - m_entry.setCrc( realCrc ); + entry.setSize(adjustToLong(def.getTotalIn())); + entry.setCompressedSize(adjustToLong(def.getTotalOut())); + entry.setCrc(realCrc); def.reset(); - m_written += m_entry.getCompressedSize(); - } - else - { - if( m_entry.getCrc() != realCrc ) - { - throw new ZipException( "bad CRC checksum for entry " - + m_entry.getName() + ": " - + Long.toHexString( m_entry.getCrc() ) - + " instead of " - + Long.toHexString( realCrc ) ); + 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( m_entry.getSize() != m_written - m_dataStart ) - { - throw new ZipException( "bad size for entry " - + m_entry.getName() + ": " - + m_entry.getSize() - + " instead of " - + ( m_written - m_dataStart ) ); + 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); } - writeDataDescriptor( m_entry ); - m_entry = null; - } + // If random access output, write the local file header containing + // the correct CRC and compressed/uncompressed sizes + if (raf != null) { + long save = raf.getFilePointer(); - /* - * Found out by experiment, that DeflaterOutputStream.close() - * will call finish() - so we don't need to override close - * ourselves. - */ - /** - * Finishs writing the contents and closes this as well as the underlying - * stream. - * - * @throws IOException if an IO failure causes operation to fail - * @since 1.1 - */ - public void finish() - throws IOException - { - closeEntry(); - m_cdOffset = new ZipLong( m_written ); - final int size = m_entries.size(); - for( int i = 0; i < size; i++ ) - { - final ZipArchiveEntry entry = (ZipArchiveEntry)m_entries.get( i ); - writeCentralFileHeader( entry ); + raf.seek(localDataStart); + writeOut(ZipLong.getBytes(entry.getCrc())); + writeOut(ZipLong.getBytes(entry.getCompressedSize())); + writeOut(ZipLong.getBytes(entry.getSize())); + raf.seek(save); } - m_cdLength = new ZipLong( m_written - m_cdOffset.getValue() ); - writeCentralDirectoryEnd(); - m_offsets.clear(); - m_entries.clear(); + + writeDataDescriptor(entry); + entry = null; } /** * Begin writing next entry. - * - * @param entry the entry - * @throws IOException if an IO failure causes operation to fail + * @param ze the entry to write * @since 1.1 + * @throws IOException on error */ - public void putNextEntry( final ZipArchiveEntry entry ) - throws IOException - { + public void putNextEntry(ZipEntry ze) throws IOException { closeEntry(); - m_entry = entry; - m_entries.add( m_entry ); + entry = ze; + entries.addElement(entry); - if( m_entry.getMethod() == -1 ) - {// not specified - m_entry.setMethod( m_method ); + if (entry.getMethod() == -1) { // not specified + entry.setMethod(method); } - if( m_entry.getTime() == -1 ) - {// not specified - m_entry.setTime( System.currentTimeMillis() ); + if (entry.getTime() == -1) { // not specified + entry.setTime(System.currentTimeMillis()); } - if( m_entry.getMethod() == STORED ) - { - if( m_entry.getSize() == -1 ) - { - throw new ZipException( "uncompressed size is required for STORED method" ); + // 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( m_entry.getCrc() == -1 ) - { - throw new ZipException( "crc checksum is required for STORED method" ); + if (entry.getCrc() == -1) { + throw new ZipException("crc checksum is required for STORED" + + " method when not writing to a file"); } - m_entry.setComprSize( m_entry.getSize() ); + entry.setCompressedSize(entry.getSize()); } - else - { - def.setLevel( m_level ); + + if (entry.getMethod() == DEFLATED && hasCompressionLevelChanged) { + def.setLevel(level); + hasCompressionLevelChanged = false; } - writeLocalFileHeader( m_entry ); + writeLocalFileHeader(entry); } /** - * Writes bytes to ZIP entry. <p> - * - * Override is necessary to support STORED entries, as well as calculationg - * CRC automatically for DEFLATED entries.</p> + * Set the file comment. + * @param comment the comment + * @since 1.1 + */ + public void setComment(String comment) { + this.comment = comment; + } + + /** + * Sets the compression level for subsequent entries. * - * @param buffer the buffer to write to - * @param offset the offset to write to - * @param length the length of data to write - * @exception IOException if an IO error causes operation to fail + * <p>Default is Deflater.DEFAULT_COMPRESSION.</p> + * @param level the compression level. + * @throws IllegalArgumentException if an invalid compression level is specified. + * @since 1.1 */ - public void write( final byte[] buffer, - final int offset, - final int length ) - throws IOException - { - if( m_entry.getMethod() == DEFLATED ) - { - super.write( buffer, offset, length ); + public void setLevel(int level) { + if (level < Deflater.DEFAULT_COMPRESSION + || level > Deflater.BEST_COMPRESSION) { + throw new IllegalArgumentException( + "Invalid compression level: " + level); } - else - { - out.write( buffer, offset, length ); - m_written += length; - } - m_crc.update( buffer, offset, length ); + hasCompressionLevelChanged = (this.level != level); + this.level = level; } /** - * Retrieve the bytes for the given String in the encoding set for this - * Stream. + * Sets the default compression method for subsequent entries. * - * @param name the name to decode - * @return the bytes for string - * @exception ZipException if fail to retrieve bytes for specified string - * @since 1.3 + * <p>Default is DEFLATED.</p> + * @param method an <code>int</code> from java.util.zip.ZipEntry + * @since 1.1 */ - protected byte[] getBytes( String name ) - throws ZipException - { - if( m_encoding == null ) - { - return name.getBytes(); - } - else - { - try - { - return name.getBytes( m_encoding ); - } - catch( UnsupportedEncodingException uee ) - { - throw new ZipException( uee.getMessage() ); + public void setMethod(int method) { + this.method = method; + } + + /** + * 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 + */ + public void write(byte[] b, int offset, int length) throws IOException { + if (entry.getMethod() == DEFLATED) { + if (length > 0) { + if (!def.finished()) { + if (length <= DEFLATER_BLOCK_SIZE) { + def.setInput(b, offset, length); + deflateUntilInputIsNeeded(); + } else { + final int fullblocks = length / DEFLATER_BLOCK_SIZE; + for (int i = 0; i < fullblocks; i++) { + def.setInput(b, offset + i * DEFLATER_BLOCK_SIZE, + DEFLATER_BLOCK_SIZE); + deflateUntilInputIsNeeded(); + } + final int done = fullblocks * DEFLATER_BLOCK_SIZE; + if (done < length) { + def.setInput(b, offset + done, length - done); + deflateUntilInputIsNeeded(); + } + } + } } + } else { + writeOut(b, offset, length); + written += length; } + crc.update(b, offset, length); } /** - * Writes the "End of central dir record" + * Writes a single byte to ZIP entry. * - * @exception IOException when an IO erro causes operation to fail - * @since 1.1 + * <p>Delegates to the three arg method.</p> + * @param b the byte to write + * @since 1.14 + * @throws IOException on error */ - protected void writeCentralDirectoryEnd() - throws IOException - { - out.write( EOCD_SIG.getBytes() ); + public void write(int b) throws IOException { + byte[] buff = new byte[1]; + buff[0] = (byte) (b & BYTE_MASK); + write(buff, 0, 1); + } - // disk numbers - out.write( ZERO ); - out.write( ZERO ); + /** + * Closes this output stream and releases any system resources + * associated with the stream. + * + * @exception IOException if an I/O error occurs. + * @since 1.14 + */ + public void close() throws IOException { + finish(); - // number of entries - byte[] num = ( new ZipShort( m_entries.size() ) ).getBytes(); - out.write( num ); - out.write( num ); + if (raf != null) { + raf.close(); + } + if (out != null) { + out.close(); + } + } - // length and location of CD - out.write( m_cdLength.getBytes() ); - out.write( m_cdOffset.getBytes() ); + /** + * 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 + */ + public void flush() throws IOException { + if (out != null) { + out.flush(); + } + } - // ZIP file comment - byte[] data = getBytes( m_comment ); - out.write( ( new ZipShort( data.length ) ).getBytes() ); - out.write( data ); + /* + * Various ZIP constants + */ + /** + * local file header signature + * + * @since 1.1 + */ + protected static final byte[] LFH_SIG = ZipLong.getBytes(0X04034B50L); + /** + * data descriptor signature + * + * @since 1.1 + */ + protected static final byte[] DD_SIG = ZipLong.getBytes(0X08074B50L); + /** + * central file header signature + * + * @since 1.1 + */ + protected static final byte[] CFH_SIG = ZipLong.getBytes(0X02014B50L); + /** + * end of central dir signature + * + * @since 1.1 + */ + protected static final byte[] EOCD_SIG = ZipLong.getBytes(0X06054B50L); + + /** + * Writes next block of compressed data to the output stream. + * @throws IOException on error + * + * @since 1.14 + */ + protected final void deflate() throws IOException { + int len = def.deflate(buf, 0, buf.length); + if (len > 0) { + writeOut(buf, 0, len); + } } /** - * Writes the central file header entry + * Writes the local file header entry + * @param ze the entry to write + * @throws IOException on error * - * @param entry the zip entry - * @throws IOException when an IO error causes operation to fail * @since 1.1 */ - protected void writeCentralFileHeader( final ZipArchiveEntry entry ) - throws IOException - { - out.write( CFH_SIG.getBytes() ); - m_written += 4; + protected void writeLocalFileHeader(ZipEntry ze) throws IOException { + offsets.put(ze, ZipLong.getBytes(written)); - // version made by - out.write( ( new ZipShort( 20 ) ).getBytes() ); - m_written += 2; + writeOut(LFH_SIG); + written += WORD; + + //store method in local variable to prevent multiple method calls + final int zipMethod = ze.getMethod(); // version needed to extract // general purpose bit flag - if( entry.getMethod() == DEFLATED ) - { + // CheckStyle:MagicNumber OFF + if (zipMethod == DEFLATED && raf == null) { // requires version 2 as we are going to store length info // in the data descriptor - out.write( ( new ZipShort( 20 ) ).getBytes() ); + writeOut(ZipShort.getBytes(20)); // bit3 set to signal, we use a data descriptor - out.write( ( new ZipShort( 8 ) ).getBytes() ); - } - else - { - out.write( ( new ZipShort( 10 ) ).getBytes() ); - out.write( ZERO ); + writeOut(ZipShort.getBytes(8)); + } else { + writeOut(ZipShort.getBytes(10)); + writeOut(ZERO); } - m_written += 4; + // CheckStyle:MagicNumber ON + written += WORD; // compression method - out.write( ( new ZipShort( entry.getMethod() ) ).getBytes() ); - m_written += 2; + writeOut(ZipShort.getBytes(zipMethod)); + written += SHORT; // last mod. time and date - out.write( toDosTime( new Date( entry.getTime() ) ).getBytes() ); - m_written += 4; + writeOut(toDosTime(ze.getTime())); + written += WORD; // CRC // compressed length // uncompressed length - out.write( ( new ZipLong( entry.getCrc() ) ).getBytes() ); - out.write( ( new ZipLong( entry.getCompressedSize() ) ).getBytes() ); - out.write( ( new ZipLong( entry.getSize() ) ).getBytes() ); - m_written += 12; + 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())); + } + // CheckStyle:MagicNumber OFF + written += 12; + // CheckStyle:MagicNumber ON // file name length - byte[] name = getBytes( entry.getName() ); - out.write( ( new ZipShort( name.length ) ).getBytes() ); - m_written += 2; + byte[] name = getBytes(ze.getName()); + writeOut(ZipShort.getBytes(name.length)); + written += SHORT; // extra field length - byte[] extra = entry.getCentralDirectoryExtra(); - out.write( ( new ZipShort( extra.length ) ).getBytes() ); - m_written += 2; - - // file comment length - String comm = entry.getComment(); - if( comm == null ) - { - comm = ""; - } - byte[] comment = getBytes( comm ); - out.write( ( new ZipShort( comment.length ) ).getBytes() ); - m_written += 2; - - // disk number start - out.write( ZERO ); - m_written += 2; - - // internal file attributes - out.write( ( new ZipShort( entry.getInternalAttributes() ) ).getBytes() ); - m_written += 2; - - // external file attributes - out.write( ( new ZipLong( entry.getExternalAttributes() ) ).getBytes() ); - m_written += 4; - - // relative offset of LFH - out.write( ( (ZipLong)m_offsets.get( entry ) ).getBytes() ); - m_written += 4; + byte[] extra = ze.getLocalFileDataExtra(); + writeOut(ZipShort.getBytes(extra.length)); + written += SHORT; // file name - out.write( name ); - m_written += name.length; + writeOut(name); + written += name.length; // extra field - out.write( extra ); - m_written += extra.length; + writeOut(extra); + written += extra.length; - // file comment - out.write( comment ); - m_written += comment.length; + dataStart = written; } /** - * Writes the data descriptor entry + * Writes the data descriptor entry. + * @param ze the entry to write + * @throws IOException on error * - * @param ze Description of Parameter - * @throws IOException if an IO failure causes operation to fail * @since 1.1 */ - protected void writeDataDescriptor( ZipArchiveEntry ze ) - throws IOException - { - if( ze.getMethod() != DEFLATED ) - { + protected void writeDataDescriptor(ZipEntry ze) throws IOException { + if (ze.getMethod() != DEFLATED || raf != null) { return; } - out.write( DD_SIG.getBytes() ); - out.write( ( new ZipLong( m_entry.getCrc() ) ).getBytes() ); - out.write( ( new ZipLong( m_entry.getCompressedSize() ) ).getBytes() ); - out.write( ( new ZipLong( m_entry.getSize() ) ).getBytes() ); - m_written += 16; + writeOut(DD_SIG); + writeOut(ZipLong.getBytes(entry.getCrc())); + writeOut(ZipLong.getBytes(entry.getCompressedSize())); + writeOut(ZipLong.getBytes(entry.getSize())); + // CheckStyle:MagicNumber OFF + written += 16; + // CheckStyle:MagicNumber ON } /** - * Writes the local file header entry + * Writes the central file header entry. + * @param ze the entry to write + * @throws IOException on error * - * @param entry the zip entry - * @exception IOException when an IO error causes operation to fail * @since 1.1 */ - protected void writeLocalFileHeader( final ZipArchiveEntry entry ) - throws IOException - { - m_offsets.put( entry, new ZipLong( m_written ) ); + protected void writeCentralFileHeader(ZipEntry ze) throws IOException { + writeOut(CFH_SIG); + written += WORD; - out.write( LFH_SIG.getBytes() ); - m_written += 4; + // version made by + // CheckStyle:MagicNumber OFF + writeOut(ZipShort.getBytes((ze.getPlatform() << 8) | 20)); + written += SHORT; // version needed to extract // general purpose bit flag - if( entry.getMethod() == DEFLATED ) - { + if (ze.getMethod() == DEFLATED && raf == null) { // requires version 2 as we are going to store length info // in the data descriptor - out.write( ( new ZipShort( 20 ) ).getBytes() ); + writeOut(ZipShort.getBytes(20)); // bit3 set to signal, we use a data descriptor - out.write( ( new ZipShort( 8 ) ).getBytes() ); - } - else - { - out.write( ( new ZipShort( 10 ) ).getBytes() ); - out.write( ZERO ); + writeOut(ZipShort.getBytes(8)); + } else { + writeOut(ZipShort.getBytes(10)); + writeOut(ZERO); } - m_written += 4; + // CheckStyle:MagicNumber ON + written += WORD; // compression method - out.write( ( new ZipShort( entry.getMethod() ) ).getBytes() ); - m_written += 2; + writeOut(ZipShort.getBytes(ze.getMethod())); + written += SHORT; // last mod. time and date - out.write( toDosTime( new Date( entry.getTime() ) ).getBytes() ); - m_written += 4; + writeOut(toDosTime(ze.getTime())); + written += WORD; // CRC // compressed length // uncompressed length - if( entry.getMethod() == DEFLATED ) - { - out.write( LZERO ); - out.write( LZERO ); - out.write( LZERO ); - } - else - { - out.write( ( new ZipLong( entry.getCrc() ) ).getBytes() ); - out.write( ( new ZipLong( entry.getSize() ) ).getBytes() ); - out.write( ( new ZipLong( entry.getSize() ) ).getBytes() ); - } - m_written += 12; + writeOut(ZipLong.getBytes(ze.getCrc())); + writeOut(ZipLong.getBytes(ze.getCompressedSize())); + writeOut(ZipLong.getBytes(ze.getSize())); + // CheckStyle:MagicNumber OFF + written += 12; + // CheckStyle:MagicNumber ON // file name length - byte[] name = getBytes( entry.getName() ); - out.write( ( new ZipShort( name.length ) ).getBytes() ); - m_written += 2; + byte[] name = getBytes(ze.getName()); + writeOut(ZipShort.getBytes(name.length)); + written += SHORT; // extra field length - byte[] extra = entry.getLocalFileDataExtra(); - out.write( ( new ZipShort( extra.length ) ).getBytes() ); - m_written += 2; + byte[] extra = ze.getCentralDirectoryExtra(); + writeOut(ZipShort.getBytes(extra.length)); + written += SHORT; + + // file comment length + String comm = ze.getComment(); + if (comm == null) { + comm = ""; + } + byte[] commentB = getBytes(comm); + writeOut(ZipShort.getBytes(commentB.length)); + written += SHORT; + + // disk number start + writeOut(ZERO); + written += SHORT; + + // internal file attributes + writeOut(ZipShort.getBytes(ze.getInternalAttributes())); + written += SHORT; + + // external file attributes + writeOut(ZipLong.getBytes(ze.getExternalAttributes())); + written += WORD; + + // relative offset of LFH + writeOut((byte[]) offsets.get(ze)); + written += WORD; // file name - out.write( name ); - m_written += name.length; + writeOut(name); + written += name.length; // extra field - out.write( extra ); - m_written += extra.length; + writeOut(extra); + written += extra.length; + + // file comment + writeOut(commentB); + written += commentB.length; + } + + /** + * Writes the "End of central dir record". + * @throws IOException on error + * + * @since 1.1 + */ + protected void writeCentralDirectoryEnd() throws IOException { + 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); + } + + /** + * Smallest date/time ZIP can handle. + * + * @since 1.1 + */ + private static final byte[] DOS_TIME_MIN = ZipLong.getBytes(0x00002100L); + + /** + * Convert a Date object to a DOS date/time field. + * @param time the <code>Date</code> to convert + * @return the date as a <code>ZipLong</code> + * @since 1.1 + */ + protected static ZipLong toDosTime(Date time) { + return new ZipLong(toDosTime(time.getTime())); + } + + /** + * Convert a Date object to a DOS date/time field. + * + * <p>Stolen from InfoZip's <code>fileio.c</code></p> + * @param t number of milliseconds since the epoch + * @return the date as a byte array + * @since 1.26 + */ + protected static byte[] toDosTime(long t) { + Date time = new Date(t); + // CheckStyle:MagicNumberCheck OFF - I do not think that using constants + // here will improve the readablity + 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); + // CheckStyle:MagicNumberCheck ON + } + + /** + * 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 + */ + protected byte[] getBytes(String name) throws ZipException { + if (encoding == null) { + return name.getBytes(); + } else { + try { + return name.getBytes(encoding); + } catch (UnsupportedEncodingException uee) { + throw new ZipException(uee.getMessage()); + } + } + } + + /** + * Write bytes to output or random access file. + * @param data the byte array to write + * @throws IOException on error + * + * @since 1.14 + */ + protected final void writeOut(byte[] data) throws IOException { + writeOut(data, 0, data.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 + */ + protected final void writeOut(byte[] data, int offset, int length) + throws IOException { + if (raf != null) { + raf.write(data, offset, length); + } else { + out.write(data, offset, length); + } + } + + /** + * 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 + */ + protected static long adjustToLong(int i) { + if (i < 0) { + return 2 * ((long) Integer.MAX_VALUE) + 2 + i; + } else { + return i; + } + } - m_dataStart = m_written; + private void deflateUntilInputIsNeeded() throws IOException { + while (!def.needsInput()) { + deflate(); + } } }
Modified: commons/sandbox/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/ZipShort.java URL: http://svn.apache.org/viewvc/commons/sandbox/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/ZipShort.java?rev=733688&r1=733687&r2=733688&view=diff ============================================================================== --- commons/sandbox/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/ZipShort.java (original) +++ commons/sandbox/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/ZipShort.java Mon Jan 12 03:15:34 2009 @@ -1,115 +1,135 @@ /* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. */ package org.apache.commons.compress.archivers.zip; /** - * Utility class that represents a two byte integer with conversion rules for - * the big endian byte order of ZIP files. + * Utility class that represents a two byte integer with conversion + * rules for the big endian byte order of ZIP files. + * */ -public final class ZipShort implements Cloneable -{ - private int m_value; +public final class ZipShort implements Cloneable { + private static final int BYTE_MASK = 0xFF; + private static final int BYTE_1_MASK = 0xFF00; + private static final int BYTE_1_SHIFT = 8; + + private int value; /** * Create instance from a number. - * - * @param value Description of Parameter + * @param value the int to store as a ZipShort * @since 1.1 */ - public ZipShort( int value ) - { - this.m_value = value; + public ZipShort (int value) { + this.value = value; } /** * Create instance from bytes. - * - * @param bytes Description of Parameter + * @param bytes the bytes to store as a ZipShort * @since 1.1 */ - public ZipShort( byte[] bytes ) - { - this( bytes, 0 ); + public ZipShort (byte[] bytes) { + this(bytes, 0); } /** * Create instance from the two bytes starting at offset. - * - * @param bytes Description of Parameter - * @param offset Description of Parameter + * @param bytes the bytes to store as a ZipShort + * @param offset the offset to start * @since 1.1 */ - public ZipShort( byte[] bytes, int offset ) - { - m_value = ( bytes[ offset + 1 ] << 8 ) & 0xFF00; - m_value += ( bytes[ offset ] & 0xFF ); + public ZipShort (byte[] bytes, int offset) { + value = ZipShort.getValue(bytes, offset); } /** * Get value as two bytes in big endian byte order. - * - * @return The Bytes value + * @return the value as a a two byte array in big endian byte order * @since 1.1 */ - public byte[] getBytes() - { - byte[] result = new byte[ 2 ]; - result[ 0 ] = (byte)( m_value & 0xFF ); - result[ 1 ] = (byte)( ( m_value & 0xFF00 ) >> 8 ); + public byte[] getBytes() { + byte[] result = new byte[2]; + result[0] = (byte) (value & BYTE_MASK); + result[1] = (byte) ((value & BYTE_1_MASK) >> BYTE_1_SHIFT); return result; } /** * Get value as Java int. - * - * @return The Value value + * @return value as a Java int * @since 1.1 */ - public int getValue() - { - return m_value; + public int getValue() { + return value; + } + + /** + * Get value as two bytes in big endian byte order. + * @param value the Java int to convert to bytes + * @return the converted int as a byte array in big endian byte order + */ + public static byte[] getBytes(int value) { + byte[] result = new byte[2]; + result[0] = (byte) (value & BYTE_MASK); + result[1] = (byte) ((value & BYTE_1_MASK) >> BYTE_1_SHIFT); + return result; + } + + /** + * Helper method to get the value as a java int from two bytes starting at given array offset + * @param bytes the array of bytes + * @param offset the offset to start + * @return the correspondanding java int value + */ + public static int getValue(byte[] bytes, int offset) { + int value = (bytes[offset + 1] << BYTE_1_SHIFT) & BYTE_1_MASK; + value += (bytes[offset] & BYTE_MASK); + return value; + } + + /** + * Helper method to get the value as a java int from a two-byte array + * @param bytes the array of bytes + * @return the correspondanding java int value + */ + public static int getValue(byte[] bytes) { + return getValue(bytes, 0); } /** * Override to make two instances with same value equal. - * - * @param o Description of Parameter - * @return Description of the Returned Value + * @param o an object to compare + * @return true if the objects are equal * @since 1.1 */ - public boolean equals( Object o ) - { - if( o == null || !( o instanceof ZipShort ) ) - { + public boolean equals(Object o) { + if (o == null || !(o instanceof ZipShort)) { return false; } - return m_value == ( (ZipShort)o ).getValue(); + return value == ((ZipShort) o).getValue(); } /** * Override to make two instances with same value equal. - * - * @return Description of the Returned Value + * @return the value stored in the ZipShort * @since 1.1 */ - public int hashCode() - { - return m_value; + public int hashCode() { + return value; } }
