/**
* 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>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>
- *
+ * 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>
+ *
*/
public class ZipOutputStream extends FilterOutputStream {
- /**
- * Compression method for deflated entries.
- *
- * @since 1.1
- */
- public static final int DEFLATED = java.util.zip.ZipEntry.DEFLATED;
-
- /**
- * Default compression level for deflated entries.
- *
- * @since Ant 1.7
- */
- public static final int DEFAULT_COMPRESSION = Deflater.DEFAULT_COMPRESSION;
-
- /**
- * Compression method for stored entries.
- *
- * @since 1.1
- */
- public static final int STORED = java.util.zip.ZipEntry.STORED;
-
- /**
- * Current entry.
- *
- * @since 1.1
- */
- private ZipEntry entry;
-
- /**
- * The file comment.
- *
- * @since 1.1
- */
- private String comment = "";
-
- /**
- * Compression level for next entry.
- *
- * @since 1.1
- */
- private int level = DEFAULT_COMPRESSION;
-
- /**
- * Has the compression level changed when compared to the last
- * entry?
- *
- * @since 1.5
- */
- private boolean hasCompressionLevelChanged = false;
-
- /**
- * Default compression method for next entry.
- *
- * @since 1.1
- */
- private int method = java.util.zip.ZipEntry.DEFLATED;
-
- /**
- * List of ZipEntries written so far.
- *
- * @since 1.1
- */
- private Vector entries = new Vector();
-
- /**
- * CRC instance to avoid parsing DEFLATED data twice.
- *
- * @since 1.1
- */
- private CRC32 crc = new CRC32();
-
- /**
- * Count the bytes written to out.
- *
- * @since 1.1
- */
- private long written = 0;
-
- /**
- * Data for local header data
- *
- * @since 1.1
- */
- private long dataStart = 0;
-
- /**
- * Offset for CRC entry in the local file header data for the
- * current entry starts here.
- *
- * @since 1.15
- */
- private long localDataStart = 0;
-
- /**
- * Start of central directory.
- *
- * @since 1.1
- */
- private long cdOffset = 0;
-
- /**
- * Length of central directory.
- *
- * @since 1.1
- */
- private long cdLength = 0;
-
- /**
- * Helper, a 0 as ZipShort.
- *
- * @since 1.1
- */
- private static final byte[] ZERO = {0, 0};
-
- /**
- * Helper, a 0 as ZipLong.
- *
- * @since 1.1
- */
- private static final byte[] LZERO = {0, 0, 0, 0};
-
- /**
- * Holds the offsets of the LFH starts for each entry.
- *
- * @since 1.1
- */
- private Hashtable offsets = new Hashtable();
-
- /**
- * The encoding to use for filenames and the file comment.
- *
- * <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 encoding = null;
-
- // CheckStyle:VisibilityModifier OFF - bc
-
- /**
- * This Deflater object is used for output.
- *
- * <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
- */
- protected Deflater def = new Deflater(level, true);
-
- /**
- * This buffer servers as a Deflater.
- *
- * <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
- */
- protected byte[] buf = new byte[512];
-
- // CheckStyle:VisibilityModifier ON
-
- /**
- * Optional random access output.
- *
- * @since 1.14
- */
- private RandomAccessFile raf = null;
-
- /**
- * Creates a new ZIP OutputStream filtering the underlying stream.
- * @param out the outputstream to zip
- * @since 1.1
- */
- public ZipOutputStream(OutputStream out) {
- super(out);
- }
-
- /**
- * 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);
-
+ /**
+ * Compression method for deflated entries.
+ *
+ * @since 1.1
+ */
+ public static final int DEFLATED = java.util.zip.ZipEntry.DEFLATED;
+
+ /**
+ * Default compression level for deflated entries.
+ *
+ * @since Ant 1.7
+ */
+ public static final int DEFAULT_COMPRESSION = Deflater.DEFAULT_COMPRESSION;
+
+ /**
+ * Compression method for stored entries.
+ *
+ * @since 1.1
+ */
+ public static final int STORED = java.util.zip.ZipEntry.STORED;
+
+ /**
+ * Current entry.
+ *
+ * @since 1.1
+ */
+ private ZipEntry entry;
+
+ /**
+ * The file comment.
+ *
+ * @since 1.1
+ */
+ private String comment = "";
+
+ /**
+ * Compression level for next entry.
+ *
+ * @since 1.1
+ */
+ private int level = DEFAULT_COMPRESSION;
+
+ /**
+ * Has the compression level changed when compared to the last entry?
+ *
+ * @since 1.5
+ */
+ private boolean hasCompressionLevelChanged = false;
+
+ /**
+ * Default compression method for next entry.
+ *
+ * @since 1.1
+ */
+ private int method = java.util.zip.ZipEntry.DEFLATED;
+
+ /**
+ * List of ZipEntries written so far.
+ *
+ * @since 1.1
+ */
+ private Vector entries = new Vector();
+
+ /**
+ * CRC instance to avoid parsing DEFLATED data twice.
+ *
+ * @since 1.1
+ */
+ private CRC32 crc = new CRC32();
+
+ /**
+ * Count the bytes written to out.
+ *
+ * @since 1.1
+ */
+ private long written = 0;
+
+ /**
+ * Data for local header data
+ *
+ * @since 1.1
+ */
+ private long dataStart = 0;
+
+ /**
+ * Offset for CRC entry in the local file header data for the current entry
+ * starts here.
+ *
+ * @since 1.15
+ */
+ private long localDataStart = 0;
+
+ /**
+ * Start of central directory.
+ *
+ * @since 1.1
+ */
+ private long cdOffset = 0;
+
+ /**
+ * Length of central directory.
+ *
+ * @since 1.1
+ */
+ private long cdLength = 0;
+
+ /**
+ * Helper, a 0 as ZipShort.
+ *
+ * @since 1.1
+ */
+ private static final byte[] ZERO = { 0, 0 };
+
+ /**
+ * Helper, a 0 as ZipLong.
+ *
+ * @since 1.1
+ */
+ private static final byte[] LZERO = { 0, 0, 0, 0 };
+
+ /**
+ * Holds the offsets of the LFH starts for each entry.
+ *
+ * @since 1.1
+ */
+ private Hashtable offsets = new Hashtable();
+
+ /**
+ * The encoding to use for filenames and the file comment.
+ *
+ * <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 encoding = null;
+
+ // CheckStyle:VisibilityModifier OFF - bc
+
+ /**
+ * This Deflater object is used for output.
+ *
+ * <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
+ */
+ protected Deflater def = new Deflater(level, true);
+
+ /**
+ * This buffer servers as a Deflater.
+ *
+ * <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
+ */
+ protected byte[] buf = new byte[512];
+
+ // CheckStyle:VisibilityModifier ON
+
+ /**
+ * Optional random access output.
+ *
+ * @since 1.14
+ */
+ private RandomAccessFile raf = null;
+
+ /**
+ * Creates a new ZIP OutputStream filtering the underlying stream.
+ *
+ * @param out
+ * the outputstream to zip
+ * @since 1.1
+ */
+ public ZipOutputStream(OutputStream out) {
+ super(out);
+ }
+
+ /**
+ * 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 = 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);
+ raf.close();
+ } catch (IOException inner) {
+ // ignore
}
+ raf = null;
+ }
+ out = new FileOutputStream(file);
}
-
- /**
- * This method indicates whether this archive is writing to a seekable stream (i.e., to a random
- * access file).
- *
- * <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 boolean isSeekable() {
- return raf != null;
- }
-
- /**
- * The encoding to use for filenames and the file comment.
- *
- * <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 setEncoding(String encoding) {
- this.encoding = encoding;
+ }
+
+ /**
+ * This method indicates whether this archive is writing to a seekable stream
+ * (i.e., to a random access file).
+ *
+ * <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 boolean isSeekable() {
+ return raf != null;
+ }
+
+ /**
+ * The encoding to use for filenames and the file comment.
+ *
+ * <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 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 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));
}
-
- /**
- * 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 encoding;
+ cdLength = written - cdOffset;
+ writeCentralDirectoryEnd();
+ offsets.clear();
+ entries.removeAllElements();
+ }
+
+ /**
+ * Writes all necessary data for this entry.
+ *
+ * @since 1.1
+ * @throws IOException
+ * on error
+ */
+ public void closeEntry() throws IOException {
+ if (entry == null) {
+ return;
}
- /**
- * 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();
+ 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);
}
- /**
- * Writes all necessary data for this entry.
- *
- * @since 1.1
- * @throws IOException on error
- */
- public void closeEntry() throws IOException {
- 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();
- // 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;
+ raf.seek(localDataStart);
+ writeOut(ZipLong.getBytes(entry.getCrc()));
+ writeOut(ZipLong.getBytes(entry.getCompressedSize()));
+ writeOut(ZipLong.getBytes(entry.getSize()));
+ raf.seek(save);
}
- /**
- * Begin writing next entry.
- * @param ze the entry to write
- * @since 1.1
- * @throws IOException on error
- */
- public void putNextEntry(ZipEntry ze) throws IOException {
- 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);
+ writeDataDescriptor(entry);
+ entry = null;
+ }
+
+ /**
+ * Begin writing next entry.
+ *
+ * @param ze
+ * the entry to write
+ * @since 1.1
+ * @throws IOException
+ * on error
+ */
+ public void putNextEntry(ZipEntry ze) throws IOException {
+ closeEntry();
+
+ entry = ze;
+ entries.addElement(entry);
+
+ if (entry.getMethod() == -1) { // not specified
+ entry.setMethod(method);
}
- /**
- * Set the file comment.
- * @param comment the comment
- * @since 1.1
- */
- public void setComment(String comment) {
- this.comment = comment;
+ if (entry.getTime() == -1) { // not specified
+ entry.setTime(System.currentTimeMillis());
}
- /**
- * Sets the compression level for subsequent entries.
- *
- * <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 setLevel(int level) {
- if (level < Deflater.DEFAULT_COMPRESSION
- || level > Deflater.BEST_COMPRESSION) {
- throw new IllegalArgumentException(
- "Invalid compression level: " + level);
- }
- hasCompressionLevelChanged = (this.level != level);
- this.level = level;
+ // 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());
}
- /**
- * Sets the default compression method for subsequent entries.
- *
- * <p>Default is DEFLATED.</p>
- * @param method an <code>int</code> from java.util.zip.ZipEntry
- * @since 1.1
- */
- public void setMethod(int method) {
- this.method = method;
+ if (entry.getMethod() == DEFLATED && hasCompressionLevelChanged) {
+ def.setLevel(level);
+ hasCompressionLevelChanged = false;
}
-
- /**
- * 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()) {
- def.setInput(b, offset, length);
- while (!def.needsInput()) {
- deflate();
- }
- }
- }
- } else {
- writeOut(b, offset, length);
- written += length;
- }
- crc.update(b, offset, length);
- }
-
- /**
- * Writes a single byte to ZIP entry.
- *
- * <p>Delegates to the three arg method.</p>
- * @param b the byte to write
- * @since 1.14
- * @throws IOException on error
- */
- public void write(int b) throws IOException {
- byte[] buff = new byte[1];
- buff[0] = (byte) (b & 0xff);
- write(buff, 0, 1);
+ writeLocalFileHeader(entry);
+ }
+
+ /**
+ * 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.
+ *
+ * <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 setLevel(int level) {
+ if (level < Deflater.DEFAULT_COMPRESSION
+ || level > Deflater.BEST_COMPRESSION) {
+ throw new IllegalArgumentException("Invalid compression level: " + level);
}
-
- /**
- * 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();
-
- if (raf != null) {
- raf.close();
- }
- if (out != null) {
- out.close();
+ hasCompressionLevelChanged = (this.level != level);
+ this.level = level;
+ }
+
+ /**
+ * Sets the default compression method for subsequent entries.
+ *
+ * <p>
+ * Default is DEFLATED.
+ * </p>
+ *
+ * @param method
+ * an <code>int</code> from java.util.zip.ZipEntry
+ * @since 1.1
+ */
+ 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()) {
+ def.setInput(b, offset, length);
+ while (!def.needsInput()) {
+ deflate();
+ }
}
+ }
+ } else {
+ writeOut(b, offset, length);
+ written += length;
}
-
- /**
- * 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();
- }
+ crc.update(b, offset, length);
+ }
+
+ /**
+ * Writes a single byte to ZIP entry.
+ *
+ * <p>
+ * Delegates to the three arg method.
+ * </p>
+ *
+ * @param b
+ * the byte to write
+ * @since 1.14
+ * @throws IOException
+ * on error
+ */
+ public void write(int b) throws IOException {
+ byte[] buff = new byte[1];
+ buff[0] = (byte) (b & 0xff);
+ write(buff, 0, 1);
+ }
+
+ /**
+ * 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();
+
+ if (raf != null) {
+ raf.close();
}
-
- /*
- * 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);
- }
+ if (out != null) {
+ out.close();
}
-
- /**
- * Writes the local file header entry
- * @param ze the entry to write
- * @throws IOException on error
- *
- * @since 1.1
- */
- protected void writeLocalFileHeader(ZipEntry ze) throws IOException {
- 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;
+ }
+
+ /**
+ * 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();
}
-
- /**
- * Writes the data descriptor entry.
- * @param ze the entry to write
- * @throws IOException on error
- *
- * @since 1.1
- */
- protected void writeDataDescriptor(ZipEntry ze) throws IOException {
- 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;
+ }
+
+ /*
+ * 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.
- * @param ze the entry to write
- * @throws IOException on error
- *
- * @since 1.1
- */
- protected void writeCentralFileHeader(ZipEntry ze) throws IOException {
- 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;
+ }
+
+ /**
+ * Writes the local file header entry
+ *
+ * @param ze
+ * the entry to write
+ * @throws IOException
+ * on error
+ *
+ * @since 1.1
+ */
+ protected void writeLocalFileHeader(ZipEntry ze) throws IOException {
+ 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);
}
-
- /**
- * 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);
+ 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()));
}
-
- /**
- * 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()));
+ 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;
+ }
+
+ /**
+ * Writes the data descriptor entry.
+ *
+ * @param ze
+ * the entry to write
+ * @throws IOException
+ * on error
+ *
+ * @since 1.1
+ */
+ protected void writeDataDescriptor(ZipEntry ze) throws IOException {
+ if (ze.getMethod() != DEFLATED || raf != null) {
+ return;
}
-
- /**
- * 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);
- 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);
+ writeOut(DD_SIG);
+ writeOut(ZipLong.getBytes(entry.getCrc()));
+ writeOut(ZipLong.getBytes(entry.getCompressedSize()));
+ writeOut(ZipLong.getBytes(entry.getSize()));
+ written += 16;
+ }
+
+ /**
+ * Writes the central file header entry.
+ *
+ * @param ze
+ * the entry to write
+ * @throws IOException
+ * on error
+ *
+ * @since 1.1
+ */
+ protected void writeCentralFileHeader(ZipEntry ze) throws IOException {
+ 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);
}
-
- /**
- * 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());
- }
- }
+ 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 = "";
}
-
- /**
- * 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);
+ 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;
+ }
+
+ /**
+ * 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);
+ int year = time.getYear() + 1900;
+ if (year < 1980) {
+ return DOS_TIME_MIN;
}
-
- /**
- * 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);
- }
+ 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);
+ }
+
+ /**
+ * 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());
+ }
}
-
- /**
- * 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;
- }
+ }
+
+ /**
+ * 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;
}
+ }
}