OSDN Git Service

リポジトリ内改行コードのLFへの修正
[charactermanaj/CharacterManaJ.git] / src / main / java / org / apache / tools / zip / ZipOutputStream.java
index db64a51..86c8055 100644 (file)
-/*\r
- *  Licensed to the Apache Software Foundation (ASF) under one or more\r
- *  contributor license agreements.  See the NOTICE file distributed with\r
- *  this work for additional information regarding copyright ownership.\r
- *  The ASF licenses this file to You under the Apache License, Version 2.0\r
- *  (the "License"); you may not use this file except in compliance with\r
- *  the License.  You may obtain a copy of the License at\r
- *\r
- *      http://www.apache.org/licenses/LICENSE-2.0\r
- *\r
- *  Unless required by applicable law or agreed to in writing, software\r
- *  distributed under the License is distributed on an "AS IS" BASIS,\r
- *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
- *  See the License for the specific language governing permissions and\r
- *  limitations under the License.\r
- *\r
- */\r
-\r
-package org.apache.tools.zip;\r
-\r
-import java.io.File;\r
-import java.io.FileOutputStream;\r
-import java.io.FilterOutputStream;\r
-import java.io.IOException;\r
-import java.io.OutputStream;\r
-import java.io.RandomAccessFile;\r
-import java.nio.ByteBuffer;\r
-import java.util.Date;\r
-import java.util.HashMap;\r
-import java.util.Iterator;\r
-import java.util.LinkedList;\r
-import java.util.List;\r
-import java.util.Map;\r
-import java.util.zip.CRC32;\r
-import java.util.zip.Deflater;\r
-import java.util.zip.ZipException;\r
-\r
-/**\r
- * Reimplementation of {@link java.util.zip.ZipOutputStream\r
- * java.util.zip.ZipOutputStream} that does handle the extended\r
- * functionality of this package, especially internal/external file\r
- * attributes and extra fields with different layouts for local file\r
- * data and central directory entries.\r
- *\r
- * <p>This class will try to use {@link java.io.RandomAccessFile\r
- * RandomAccessFile} when you know that the output is going to go to a\r
- * file.</p>\r
- *\r
- * <p>If RandomAccessFile cannot be used, this implementation will use\r
- * a Data Descriptor to store size and CRC information for {@link\r
- * #DEFLATED DEFLATED} entries, this means, you don't need to\r
- * calculate them yourself.  Unfortunately this is not possible for\r
- * the {@link #STORED STORED} method, here setting the CRC and\r
- * uncompressed size information is required before {@link\r
- * #putNextEntry putNextEntry} can be called.</p>\r
- *\r
- */\r
-@SuppressWarnings({"unchecked", "rawtypes"})\r
-public class ZipOutputStream extends FilterOutputStream {\r
-\r
-    private static final int BYTE_MASK = 0xFF;\r
-    private static final int SHORT = 2;\r
-    private static final int WORD = 4;\r
-    private static final int BUFFER_SIZE = 512;\r
-    /* \r
-     * Apparently Deflater.setInput gets slowed down a lot on Sun JVMs\r
-     * when it gets handed a really big buffer.  See\r
-     * https://issues.apache.org/bugzilla/show_bug.cgi?id=45396\r
-     *\r
-     * Using a buffer size of 8 kB proved to be a good compromise\r
-     */\r
-    private static final int DEFLATER_BLOCK_SIZE = 8192;\r
-\r
-    /**\r
-     * Compression method for deflated entries.\r
-     *\r
-     * @since 1.1\r
-     */\r
-    public static final int DEFLATED = java.util.zip.ZipEntry.DEFLATED;\r
-\r
-    /**\r
-     * Default compression level for deflated entries.\r
-     *\r
-     * @since Ant 1.7\r
-     */\r
-    public static final int DEFAULT_COMPRESSION = Deflater.DEFAULT_COMPRESSION;\r
-\r
-    /**\r
-     * Compression method for stored entries.\r
-     *\r
-     * @since 1.1\r
-     */\r
-    public static final int STORED = java.util.zip.ZipEntry.STORED;\r
-\r
-    /**\r
-     * default encoding for file names and comment.\r
-     */\r
-    static final String DEFAULT_ENCODING = null;\r
-\r
-    /**\r
-     * General purpose flag, which indicates that filenames are\r
-     * written in utf-8.\r
-     */\r
-    public static final int UFT8_NAMES_FLAG = 1 << 11;\r
-\r
-    /**\r
-     * General purpose flag, which indicates that filenames are\r
-     * written in utf-8.\r
-     * @deprecated use {@link #UFT8_NAMES_FLAG} instead\r
-     */\r
-    public static final int EFS_FLAG = UFT8_NAMES_FLAG;\r
-\r
-    /**\r
-     * Current entry.\r
-     *\r
-     * @since 1.1\r
-     */\r
-    private ZipEntry entry;\r
-\r
-    /**\r
-     * The file comment.\r
-     *\r
-     * @since 1.1\r
-     */\r
-    private String comment = "";\r
-\r
-    /**\r
-     * Compression level for next entry.\r
-     *\r
-     * @since 1.1\r
-     */\r
-    private int level = DEFAULT_COMPRESSION;\r
-\r
-    /**\r
-     * Has the compression level changed when compared to the last\r
-     * entry?\r
-     *\r
-     * @since 1.5\r
-     */\r
-    private boolean hasCompressionLevelChanged = false;\r
-\r
-    /**\r
-     * Default compression method for next entry.\r
-     *\r
-     * @since 1.1\r
-     */\r
-    private int method = java.util.zip.ZipEntry.DEFLATED;\r
-\r
-    /**\r
-     * List of ZipEntries written so far.\r
-     *\r
-     * @since 1.1\r
-     */\r
-    private final List entries = new LinkedList();\r
-\r
-    /**\r
-     * CRC instance to avoid parsing DEFLATED data twice.\r
-     *\r
-     * @since 1.1\r
-     */\r
-    private final CRC32 crc = new CRC32();\r
-\r
-    /**\r
-     * Count the bytes written to out.\r
-     *\r
-     * @since 1.1\r
-     */\r
-    private long written = 0;\r
-\r
-    /**\r
-     * Data for local header data\r
-     *\r
-     * @since 1.1\r
-     */\r
-    private long dataStart = 0;\r
-\r
-    /**\r
-     * Offset for CRC entry in the local file header data for the\r
-     * current entry starts here.\r
-     *\r
-     * @since 1.15\r
-     */\r
-    private long localDataStart = 0;\r
-\r
-    /**\r
-     * Start of central directory.\r
-     *\r
-     * @since 1.1\r
-     */\r
-    private long cdOffset = 0;\r
-\r
-    /**\r
-     * Length of central directory.\r
-     *\r
-     * @since 1.1\r
-     */\r
-    private long cdLength = 0;\r
-\r
-    /**\r
-     * Helper, a 0 as ZipShort.\r
-     *\r
-     * @since 1.1\r
-     */\r
-    private static final byte[] ZERO = {0, 0};\r
-\r
-    /**\r
-     * Helper, a 0 as ZipLong.\r
-     *\r
-     * @since 1.1\r
-     */\r
-    private static final byte[] LZERO = {0, 0, 0, 0};\r
-\r
-    /**\r
-     * Holds the offsets of the LFH starts for each entry.\r
-     *\r
-     * @since 1.1\r
-     */\r
-    private final Map offsets = new HashMap();\r
-\r
-    /**\r
-     * The encoding to use for filenames and the file comment.\r
-     *\r
-     * <p>For a list of possible values see <a\r
-     * 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>.\r
-     * Defaults to the platform's default character encoding.</p>\r
-     *\r
-     * @since 1.3\r
-     */\r
-    private String encoding = null;\r
-\r
-    /**\r
-     * The zip encoding to use for filenames and the file comment.\r
-     *\r
-     * This field is of internal use and will be set in {@link\r
-     * #setEncoding(String)}.\r
-     */\r
-    private ZipEncoding zipEncoding =\r
-        ZipEncodingHelper.getZipEncoding(DEFAULT_ENCODING);\r
-\r
-   // CheckStyle:VisibilityModifier OFF - bc\r
-\r
-    /**\r
-     * This Deflater object is used for output.\r
-     *\r
-     * <p>This attribute is only protected to provide a level of API\r
-     * backwards compatibility.  This class used to extend {@link\r
-     * java.util.zip.DeflaterOutputStream DeflaterOutputStream} up to\r
-     * Revision 1.13.</p>\r
-     *\r
-     * @since 1.14\r
-     */\r
-    protected Deflater def = new Deflater(level, true);\r
-\r
-    /**\r
-     * This buffer servers as a Deflater.\r
-     *\r
-     * <p>This attribute is only protected to provide a level of API\r
-     * backwards compatibility.  This class used to extend {@link\r
-     * java.util.zip.DeflaterOutputStream DeflaterOutputStream} up to\r
-     * Revision 1.13.</p>\r
-     *\r
-     * @since 1.14\r
-     */\r
-    protected byte[] buf = new byte[BUFFER_SIZE];\r
-\r
-    // CheckStyle:VisibilityModifier ON\r
-\r
-    /**\r
-     * Optional random access output.\r
-     *\r
-     * @since 1.14\r
-     */\r
-    private RandomAccessFile raf = null;\r
-\r
-    /**\r
-     * whether to use the general purpose bit flag when writing UTF-8\r
-     * filenames or not.\r
-     */\r
-    private boolean useUTF8Flag = true; \r
-\r
-    /**\r
-     * Whether to encode non-encodable file names as UTF-8.\r
-     */\r
-    private boolean fallbackToUTF8 = false;\r
-\r
-    /**\r
-     * whether to create UnicodePathExtraField-s for each entry.\r
-     */\r
-    private UnicodeExtraFieldPolicy createUnicodeExtraFields =\r
-        UnicodeExtraFieldPolicy.NEVER;\r
-\r
-    /**\r
-     * Creates a new ZIP OutputStream filtering the underlying stream.\r
-     * @param out the outputstream to zip\r
-     * @since 1.1\r
-     */\r
-    public ZipOutputStream(OutputStream out) {\r
-        super(out);\r
-    }\r
-\r
-    /**\r
-     * Creates a new ZIP OutputStream writing to a File.  Will use\r
-     * random access if possible.\r
-     * @param file the file to zip to\r
-     * @since 1.14\r
-     * @throws IOException on error\r
-     */\r
-    public ZipOutputStream(File file) throws IOException {\r
-        super(null);\r
-\r
-        try {\r
-            raf = new RandomAccessFile(file, "rw");\r
-            raf.setLength(0);\r
-        } catch (IOException e) {\r
-            if (raf != null) {\r
-                try {\r
-                    raf.close();\r
-                } catch (IOException inner) {\r
-                    // ignore\r
-                }\r
-                raf = null;\r
-            }\r
-            out = new FileOutputStream(file);\r
-        }\r
-    }\r
-\r
-    /**\r
-     * This method indicates whether this archive is writing to a\r
-     * seekable stream (i.e., to a random access file).\r
-     *\r
-     * <p>For seekable streams, you don't need to calculate the CRC or\r
-     * uncompressed size for {@link #STORED} entries before\r
-     * invoking {@link #putNextEntry}.\r
-     * @return true if seekable\r
-     * @since 1.17\r
-     */\r
-    public boolean isSeekable() {\r
-        return raf != null;\r
-    }\r
-\r
-    /**\r
-     * The encoding to use for filenames and the file comment.\r
-     *\r
-     * <p>For a list of possible values see <a\r
-     * 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>.\r
-     * Defaults to the platform's default character encoding.</p>\r
-     * @param encoding the encoding value\r
-     * @since 1.3\r
-     */\r
-    public void setEncoding(final String encoding) {\r
-        this.encoding = encoding;\r
-        this.zipEncoding = ZipEncodingHelper.getZipEncoding(encoding);\r
-        useUTF8Flag &= ZipEncodingHelper.isUTF8(encoding);\r
-    }\r
-\r
-    /**\r
-     * The encoding to use for filenames and the file comment.\r
-     *\r
-     * @return null if using the platform's default character encoding.\r
-     *\r
-     * @since 1.3\r
-     */\r
-    public String getEncoding() {\r
-        return encoding;\r
-    }\r
-\r
-    /**\r
-     * Whether to set the language encoding flag if the file name\r
-     * encoding is UTF-8.\r
-     *\r
-     * <p>Defaults to true.</p>\r
-     */\r
-    public void setUseLanguageEncodingFlag(boolean b) {\r
-        useUTF8Flag = b && ZipEncodingHelper.isUTF8(encoding);\r
-    }\r
-\r
-    /**\r
-     * Whether to create Unicode Extra Fields.\r
-     *\r
-     * <p>Defaults to NEVER.</p>\r
-     */\r
-    public void setCreateUnicodeExtraFields(UnicodeExtraFieldPolicy b) {\r
-        createUnicodeExtraFields = b;\r
-    }\r
-\r
-    /**\r
-     * Whether to fall back to UTF and the language encoding flag if\r
-     * the file name cannot be encoded using the specified encoding.\r
-     *\r
-     * <p>Defaults to false.</p>\r
-     */\r
-    public void setFallbackToUTF8(boolean b) {\r
-        fallbackToUTF8 = b;\r
-    }\r
-\r
-    /**\r
-     * Finishs writing the contents and closes this as well as the\r
-     * underlying stream.\r
-     *\r
-     * @since 1.1\r
-     * @throws IOException on error\r
-     */\r
-    public void finish() throws IOException {\r
-        closeEntry();\r
-        cdOffset = written;\r
-        for (Iterator i = entries.iterator(); i.hasNext(); ) {\r
-            writeCentralFileHeader((ZipEntry) i.next());\r
-        }\r
-        cdLength = written - cdOffset;\r
-        writeCentralDirectoryEnd();\r
-        offsets.clear();\r
-        entries.clear();\r
-    }\r
-\r
-    /**\r
-     * Writes all necessary data for this entry.\r
-     *\r
-     * @since 1.1\r
-     * @throws IOException on error\r
-     */\r
-    public void closeEntry() throws IOException {\r
-        if (entry == null) {\r
-            return;\r
-        }\r
-\r
-        long realCrc = crc.getValue();\r
-        crc.reset();\r
-\r
-        if (entry.getMethod() == DEFLATED) {\r
-            def.finish();\r
-            while (!def.finished()) {\r
-                deflate();\r
-            }\r
-\r
-            entry.setSize(adjustToLong(def.getTotalIn()));\r
-            entry.setCompressedSize(adjustToLong(def.getTotalOut()));\r
-            entry.setCrc(realCrc);\r
-\r
-            def.reset();\r
-\r
-            written += entry.getCompressedSize();\r
-        } else if (raf == null) {\r
-            if (entry.getCrc() != realCrc) {\r
-                throw new ZipException("bad CRC checksum for entry "\r
-                                       + entry.getName() + ": "\r
-                                       + Long.toHexString(entry.getCrc())\r
-                                       + " instead of "\r
-                                       + Long.toHexString(realCrc));\r
-            }\r
-\r
-            if (entry.getSize() != written - dataStart) {\r
-                throw new ZipException("bad size for entry "\r
-                                       + entry.getName() + ": "\r
-                                       + entry.getSize()\r
-                                       + " instead of "\r
-                                       + (written - dataStart));\r
-            }\r
-        } else { /* method is STORED and we used RandomAccessFile */\r
-            long size = written - dataStart;\r
-\r
-            entry.setSize(size);\r
-            entry.setCompressedSize(size);\r
-            entry.setCrc(realCrc);\r
-        }\r
-\r
-        // If random access output, write the local file header containing\r
-        // the correct CRC and compressed/uncompressed sizes\r
-        if (raf != null) {\r
-            long save = raf.getFilePointer();\r
-\r
-            raf.seek(localDataStart);\r
-            writeOut(ZipLong.getBytes(entry.getCrc()));\r
-            writeOut(ZipLong.getBytes(entry.getCompressedSize()));\r
-            writeOut(ZipLong.getBytes(entry.getSize()));\r
-            raf.seek(save);\r
-        }\r
-\r
-        writeDataDescriptor(entry);\r
-        entry = null;\r
-    }\r
-\r
-    /**\r
-     * Begin writing next entry.\r
-     * @param ze the entry to write\r
-     * @since 1.1\r
-     * @throws IOException on error\r
-     */\r
-    public void putNextEntry(ZipEntry ze) throws IOException {\r
-        closeEntry();\r
-\r
-        entry = ze;\r
-        entries.add(entry);\r
-\r
-        if (entry.getMethod() == -1) { // not specified\r
-            entry.setMethod(method);\r
-        }\r
-\r
-        if (entry.getTime() == -1) { // not specified\r
-            entry.setTime(System.currentTimeMillis());\r
-        }\r
-\r
-        // Size/CRC not required if RandomAccessFile is used\r
-        if (entry.getMethod() == STORED && raf == null) {\r
-            if (entry.getSize() == -1) {\r
-                throw new ZipException("uncompressed size is required for"\r
-                                       + " STORED method when not writing to a"\r
-                                       + " file");\r
-            }\r
-            if (entry.getCrc() == -1) {\r
-                throw new ZipException("crc checksum is required for STORED"\r
-                                       + " method when not writing to a file");\r
-            }\r
-            entry.setCompressedSize(entry.getSize());\r
-        }\r
-\r
-        if (entry.getMethod() == DEFLATED && hasCompressionLevelChanged) {\r
-            def.setLevel(level);\r
-            hasCompressionLevelChanged = false;\r
-        }\r
-        writeLocalFileHeader(entry);\r
-    }\r
-\r
-    /**\r
-     * Set the file comment.\r
-     * @param comment the comment\r
-     * @since 1.1\r
-     */\r
-    public void setComment(String comment) {\r
-        this.comment = comment;\r
-    }\r
-\r
-    /**\r
-     * Sets the compression level for subsequent entries.\r
-     *\r
-     * <p>Default is Deflater.DEFAULT_COMPRESSION.</p>\r
-     * @param level the compression level.\r
-     * @throws IllegalArgumentException if an invalid compression\r
-     * level is specified.\r
-     * @since 1.1\r
-     */\r
-    public void setLevel(int level) {\r
-        if (level < Deflater.DEFAULT_COMPRESSION\r
-            || level > Deflater.BEST_COMPRESSION) {\r
-            throw new IllegalArgumentException("Invalid compression level: "\r
-                                               + level);\r
-        }\r
-        hasCompressionLevelChanged = (this.level != level);\r
-        this.level = level;\r
-    }\r
-\r
-    /**\r
-     * Sets the default compression method for subsequent entries.\r
-     *\r
-     * <p>Default is DEFLATED.</p>\r
-     * @param method an <code>int</code> from java.util.zip.ZipEntry\r
-     * @since 1.1\r
-     */\r
-    public void setMethod(int method) {\r
-        this.method = method;\r
-    }\r
-\r
-    /**\r
-     * Writes bytes to ZIP entry.\r
-     * @param b the byte array to write\r
-     * @param offset the start position to write from\r
-     * @param length the number of bytes to write\r
-     * @throws IOException on error\r
-     */\r
-    public void write(byte[] b, int offset, int length) throws IOException {\r
-        if (entry.getMethod() == DEFLATED) {\r
-            if (length > 0) {\r
-                if (!def.finished()) {\r
-                    if (length <= DEFLATER_BLOCK_SIZE) {\r
-                        def.setInput(b, offset, length);\r
-                        deflateUntilInputIsNeeded();\r
-                    } else {\r
-                        final int fullblocks = length / DEFLATER_BLOCK_SIZE;\r
-                        for (int i = 0; i < fullblocks; i++) {\r
-                            def.setInput(b, offset + i * DEFLATER_BLOCK_SIZE,\r
-                                         DEFLATER_BLOCK_SIZE);\r
-                            deflateUntilInputIsNeeded();\r
-                        }\r
-                        final int done = fullblocks * DEFLATER_BLOCK_SIZE;\r
-                        if (done < length) {\r
-                            def.setInput(b, offset + done, length - done);\r
-                            deflateUntilInputIsNeeded();\r
-                        }\r
-                    }\r
-                }\r
-            }\r
-        } else {\r
-            writeOut(b, offset, length);\r
-            written += length;\r
-        }\r
-        crc.update(b, offset, length);\r
-    }\r
-\r
-    /**\r
-     * Writes a single byte to ZIP entry.\r
-     *\r
-     * <p>Delegates to the three arg method.</p>\r
-     * @param b the byte to write\r
-     * @since 1.14\r
-     * @throws IOException on error\r
-     */\r
-    public void write(int b) throws IOException {\r
-        byte[] buff = new byte[1];\r
-        buff[0] = (byte) (b & BYTE_MASK);\r
-        write(buff, 0, 1);\r
-    }\r
-\r
-    /**\r
-     * Closes this output stream and releases any system resources\r
-     * associated with the stream.\r
-     *\r
-     * @exception  IOException  if an I/O error occurs.\r
-     * @since 1.14\r
-     */\r
-    public void close() throws IOException {\r
-        finish();\r
-\r
-        if (raf != null) {\r
-            raf.close();\r
-        }\r
-        if (out != null) {\r
-            out.close();\r
-        }\r
-    }\r
-\r
-    /**\r
-     * Flushes this output stream and forces any buffered output bytes\r
-     * to be written out to the stream.\r
-     *\r
-     * @exception  IOException  if an I/O error occurs.\r
-     * @since 1.14\r
-     */\r
-    public void flush() throws IOException {\r
-        if (out != null) {\r
-            out.flush();\r
-        }\r
-    }\r
-\r
-    /*\r
-     * Various ZIP constants\r
-     */\r
-    /**\r
-     * local file header signature\r
-     *\r
-     * @since 1.1\r
-     */\r
-    protected static final byte[] LFH_SIG = ZipLong.getBytes(0X04034B50L);\r
-    /**\r
-     * data descriptor signature\r
-     *\r
-     * @since 1.1\r
-     */\r
-    protected static final byte[] DD_SIG = ZipLong.getBytes(0X08074B50L);\r
-    /**\r
-     * central file header signature\r
-     *\r
-     * @since 1.1\r
-     */\r
-    protected static final byte[] CFH_SIG = ZipLong.getBytes(0X02014B50L);\r
-    /**\r
-     * end of central dir signature\r
-     *\r
-     * @since 1.1\r
-     */\r
-    protected static final byte[] EOCD_SIG = ZipLong.getBytes(0X06054B50L);\r
-\r
-    /**\r
-     * Writes next block of compressed data to the output stream.\r
-     * @throws IOException on error\r
-     *\r
-     * @since 1.14\r
-     */\r
-    protected final void deflate() throws IOException {\r
-        int len = def.deflate(buf, 0, buf.length);\r
-        if (len > 0) {\r
-            writeOut(buf, 0, len);\r
-        }\r
-    }\r
-\r
-    /**\r
-     * Writes the local file header entry\r
-     * @param ze the entry to write\r
-     * @throws IOException on error\r
-     *\r
-     * @since 1.1\r
-     */\r
-    protected void writeLocalFileHeader(ZipEntry ze) throws IOException {\r
-\r
-        boolean encodable = zipEncoding.canEncode(ze.getName());\r
-        \r
-        final ZipEncoding entryEncoding;\r
-        \r
-        if (!encodable && fallbackToUTF8) {\r
-            entryEncoding = ZipEncodingHelper.UTF8_ZIP_ENCODING;\r
-        } else {\r
-            entryEncoding = zipEncoding;\r
-        }\r
-        \r
-        ByteBuffer name = entryEncoding.encode(ze.getName());        \r
-\r
-        if (createUnicodeExtraFields != UnicodeExtraFieldPolicy.NEVER) {\r
-\r
-            if (createUnicodeExtraFields == UnicodeExtraFieldPolicy.ALWAYS\r
-                || !encodable) {\r
-                ze.addExtraField(new UnicodePathExtraField(ze.getName(),\r
-                                                           name.array(),\r
-                                                           name.arrayOffset(),\r
-                                                           name.limit()));\r
-            }\r
-\r
-            String comm = ze.getComment();\r
-            if (comm != null && !"".equals(comm)) {\r
-\r
-                boolean commentEncodable = this.zipEncoding.canEncode(comm);\r
-\r
-                if (createUnicodeExtraFields == UnicodeExtraFieldPolicy.ALWAYS\r
-                    || !commentEncodable) {\r
-                    ByteBuffer commentB = entryEncoding.encode(comm);\r
-                    ze.addExtraField(new UnicodeCommentExtraField(comm,\r
-                                                                  commentB.array(),\r
-                                                                  commentB.arrayOffset(),\r
-                                                                  commentB.limit())\r
-                                     );\r
-                }\r
-            }\r
-        }\r
-\r
-        offsets.put(ze, ZipLong.getBytes(written));\r
-\r
-        writeOut(LFH_SIG);\r
-        written += WORD;\r
-\r
-        //store method in local variable to prevent multiple method calls\r
-        final int zipMethod = ze.getMethod();\r
-\r
-        writeVersionNeededToExtractAndGeneralPurposeBits(zipMethod,\r
-                                                         !encodable\r
-                                                         && fallbackToUTF8);\r
-        written += WORD;\r
-\r
-        // compression method\r
-        writeOut(ZipShort.getBytes(zipMethod));\r
-        written += SHORT;\r
-\r
-        // last mod. time and date\r
-        writeOut(toDosTime(ze.getTime()));\r
-        written += WORD;\r
-\r
-        // CRC\r
-        // compressed length\r
-        // uncompressed length\r
-        localDataStart = written;\r
-        if (zipMethod == DEFLATED || raf != null) {\r
-            writeOut(LZERO);\r
-            writeOut(LZERO);\r
-            writeOut(LZERO);\r
-        } else {\r
-            writeOut(ZipLong.getBytes(ze.getCrc()));\r
-            writeOut(ZipLong.getBytes(ze.getSize()));\r
-            writeOut(ZipLong.getBytes(ze.getSize()));\r
-        }\r
-        // CheckStyle:MagicNumber OFF\r
-        written += 12;\r
-        // CheckStyle:MagicNumber ON\r
-\r
-        // file name length\r
-        writeOut(ZipShort.getBytes(name.limit()));\r
-        written += SHORT;\r
-\r
-        // extra field length\r
-        byte[] extra = ze.getLocalFileDataExtra();\r
-        writeOut(ZipShort.getBytes(extra.length));\r
-        written += SHORT;\r
-\r
-        // file name\r
-        writeOut(name.array(), name.arrayOffset(), name.limit());\r
-        written += name.limit();\r
-\r
-        // extra field\r
-        writeOut(extra);\r
-        written += extra.length;\r
-\r
-        dataStart = written;\r
-    }\r
-\r
-    /**\r
-     * Writes the data descriptor entry.\r
-     * @param ze the entry to write\r
-     * @throws IOException on error\r
-     *\r
-     * @since 1.1\r
-     */\r
-    protected void writeDataDescriptor(ZipEntry ze) throws IOException {\r
-        if (ze.getMethod() != DEFLATED || raf != null) {\r
-            return;\r
-        }\r
-        writeOut(DD_SIG);\r
-        writeOut(ZipLong.getBytes(entry.getCrc()));\r
-        writeOut(ZipLong.getBytes(entry.getCompressedSize()));\r
-        writeOut(ZipLong.getBytes(entry.getSize()));\r
-        // CheckStyle:MagicNumber OFF\r
-        written += 16;\r
-        // CheckStyle:MagicNumber ON\r
-    }\r
-\r
-    /**\r
-     * Writes the central file header entry.\r
-     * @param ze the entry to write\r
-     * @throws IOException on error\r
-     *\r
-     * @since 1.1\r
-     */\r
-    protected void writeCentralFileHeader(ZipEntry ze) throws IOException {\r
-        writeOut(CFH_SIG);\r
-        written += WORD;\r
-\r
-        // version made by\r
-        // CheckStyle:MagicNumber OFF\r
-        writeOut(ZipShort.getBytes((ze.getPlatform() << 8) | 20));\r
-        written += SHORT;\r
-\r
-        final int zipMethod = ze.getMethod();\r
-        final boolean encodable = zipEncoding.canEncode(ze.getName());\r
-        writeVersionNeededToExtractAndGeneralPurposeBits(zipMethod,\r
-                                                         !encodable\r
-                                                         && fallbackToUTF8);\r
-        written += WORD;\r
-\r
-        // compression method\r
-        writeOut(ZipShort.getBytes(zipMethod));\r
-        written += SHORT;\r
-\r
-        // last mod. time and date\r
-        writeOut(toDosTime(ze.getTime()));\r
-        written += WORD;\r
-\r
-        // CRC\r
-        // compressed length\r
-        // uncompressed length\r
-        writeOut(ZipLong.getBytes(ze.getCrc()));\r
-        writeOut(ZipLong.getBytes(ze.getCompressedSize()));\r
-        writeOut(ZipLong.getBytes(ze.getSize()));\r
-        // CheckStyle:MagicNumber OFF\r
-        written += 12;\r
-        // CheckStyle:MagicNumber ON\r
-\r
-        // file name length\r
-        final ZipEncoding entryEncoding;\r
-        \r
-        if (!encodable && fallbackToUTF8) {\r
-            entryEncoding = ZipEncodingHelper.UTF8_ZIP_ENCODING;\r
-        } else {\r
-            entryEncoding = zipEncoding;\r
-        }\r
-        \r
-        ByteBuffer name = entryEncoding.encode(ze.getName());        \r
-\r
-        writeOut(ZipShort.getBytes(name.limit()));\r
-        written += SHORT;\r
-\r
-        // extra field length\r
-        byte[] extra = ze.getCentralDirectoryExtra();\r
-        writeOut(ZipShort.getBytes(extra.length));\r
-        written += SHORT;\r
-\r
-        // file comment length\r
-        String comm = ze.getComment();\r
-        if (comm == null) {\r
-            comm = "";\r
-        }\r
-        \r
-        ByteBuffer commentB = entryEncoding.encode(comm);\r
-        \r
-        writeOut(ZipShort.getBytes(commentB.limit()));\r
-        written += SHORT;\r
-\r
-        // disk number start\r
-        writeOut(ZERO);\r
-        written += SHORT;\r
-\r
-        // internal file attributes\r
-        writeOut(ZipShort.getBytes(ze.getInternalAttributes()));\r
-        written += SHORT;\r
-\r
-        // external file attributes\r
-        writeOut(ZipLong.getBytes(ze.getExternalAttributes()));\r
-        written += WORD;\r
-\r
-        // relative offset of LFH\r
-        writeOut((byte[]) offsets.get(ze));\r
-        written += WORD;\r
-\r
-        // file name\r
-        writeOut(name.array(), name.arrayOffset(), name.limit());\r
-        written += name.limit();\r
-\r
-        // extra field\r
-        writeOut(extra);\r
-        written += extra.length;\r
-\r
-        // file comment\r
-        writeOut(commentB.array(), commentB.arrayOffset(), commentB.limit());\r
-        written += commentB.limit();\r
-    }\r
-\r
-    /**\r
-     * Writes the &quot;End of central dir record&quot;.\r
-     * @throws IOException on error\r
-     *\r
-     * @since 1.1\r
-     */\r
-    protected void writeCentralDirectoryEnd() throws IOException {\r
-        writeOut(EOCD_SIG);\r
-\r
-        // disk numbers\r
-        writeOut(ZERO);\r
-        writeOut(ZERO);\r
-\r
-        // number of entries\r
-        byte[] num = ZipShort.getBytes(entries.size());\r
-        writeOut(num);\r
-        writeOut(num);\r
-\r
-        // length and location of CD\r
-        writeOut(ZipLong.getBytes(cdLength));\r
-        writeOut(ZipLong.getBytes(cdOffset));\r
-\r
-        // ZIP file comment\r
-        ByteBuffer data = this.zipEncoding.encode(comment);\r
-        writeOut(ZipShort.getBytes(data.limit()));\r
-        writeOut(data.array(), data.arrayOffset(), data.limit());\r
-    }\r
-\r
-    /**\r
-     * Smallest date/time ZIP can handle.\r
-     *\r
-     * @since 1.1\r
-     */\r
-    private static final byte[] DOS_TIME_MIN = ZipLong.getBytes(0x00002100L);\r
-\r
-    /**\r
-     * Convert a Date object to a DOS date/time field.\r
-     * @param time the <code>Date</code> to convert\r
-     * @return the date as a <code>ZipLong</code>\r
-     * @since 1.1\r
-     */\r
-    protected static ZipLong toDosTime(Date time) {\r
-        return new ZipLong(toDosTime(time.getTime()));\r
-    }\r
-\r
-    /**\r
-     * Convert a Date object to a DOS date/time field.\r
-     *\r
-     * <p>Stolen from InfoZip's <code>fileio.c</code></p>\r
-     * @param t number of milliseconds since the epoch\r
-     * @return the date as a byte array\r
-     * @since 1.26\r
-     */\r
-    @SuppressWarnings("deprecation")\r
-       protected static byte[] toDosTime(long t) {\r
-        Date time = new Date(t);\r
-        // CheckStyle:MagicNumberCheck OFF - I do not think that using constants\r
-        //                                   here will improve the readablity\r
-        int year = time.getYear() + 1900;\r
-        if (year < 1980) {\r
-            return DOS_TIME_MIN;\r
-        }\r
-        int month = time.getMonth() + 1;\r
-        long value =  ((year - 1980) << 25)\r
-            |         (month << 21)\r
-            |         (time.getDate() << 16)\r
-            |         (time.getHours() << 11)\r
-            |         (time.getMinutes() << 5)\r
-            |         (time.getSeconds() >> 1);\r
-        return ZipLong.getBytes(value);\r
-        // CheckStyle:MagicNumberCheck ON\r
-    }\r
-\r
-    /**\r
-     * Retrieve the bytes for the given String in the encoding set for\r
-     * this Stream.\r
-     * @param name the string to get bytes from\r
-     * @return the bytes as a byte array\r
-     * @throws ZipException on error\r
-     *\r
-     * @since 1.3\r
-     */\r
-    protected byte[] getBytes(String name) throws ZipException {\r
-        try {\r
-            ByteBuffer b =\r
-                ZipEncodingHelper.getZipEncoding(encoding).encode(name);\r
-            byte[] result = new byte[b.limit()];\r
-            System.arraycopy(b.array(), b.arrayOffset(), result, 0,\r
-                             result.length);\r
-            return result;\r
-        } catch (IOException ex) {\r
-            throw new ZipException("Failed to encode name: " + ex.getMessage());\r
-        }\r
-    }\r
-\r
-    /**\r
-     * Write bytes to output or random access file.\r
-     * @param data the byte array to write\r
-     * @throws IOException on error\r
-     *\r
-     * @since 1.14\r
-     */\r
-    protected final void writeOut(byte[] data) throws IOException {\r
-        writeOut(data, 0, data.length);\r
-    }\r
-\r
-    /**\r
-     * Write bytes to output or random access file.\r
-     * @param data the byte array to write\r
-     * @param offset the start position to write from\r
-     * @param length the number of bytes to write\r
-     * @throws IOException on error\r
-     *\r
-     * @since 1.14\r
-     */\r
-    protected final void writeOut(byte[] data, int offset, int length)\r
-        throws IOException {\r
-        if (raf != null) {\r
-            raf.write(data, offset, length);\r
-        } else {\r
-            out.write(data, offset, length);\r
-        }\r
-    }\r
-\r
-    /**\r
-     * Assumes a negative integer really is a positive integer that\r
-     * has wrapped around and re-creates the original value.\r
-     * @param i the value to treat as unsigned int.\r
-     * @return the unsigned int as a long.\r
-     * @since 1.34\r
-     */\r
-    protected static long adjustToLong(int i) {\r
-        if (i < 0) {\r
-            return 2 * ((long) Integer.MAX_VALUE) + 2 + i;\r
-        } else {\r
-            return i;\r
-        }\r
-    }\r
-\r
-    private void deflateUntilInputIsNeeded() throws IOException {\r
-        while (!def.needsInput()) {\r
-            deflate();\r
-        }\r
-    }\r
-\r
-    private void writeVersionNeededToExtractAndGeneralPurposeBits(final int\r
-                                                                  zipMethod,\r
-                                                                  final boolean\r
-                                                                  utfFallback)\r
-        throws IOException {\r
-\r
-        // CheckStyle:MagicNumber OFF\r
-        int versionNeededToExtract = 10;\r
-        int generalPurposeFlag = (useUTF8Flag || utfFallback) ? UFT8_NAMES_FLAG : 0;\r
-        if (zipMethod == DEFLATED && raf == null) {\r
-            // requires version 2 as we are going to store length info\r
-            // in the data descriptor\r
-            versionNeededToExtract =  20;\r
-            // bit3 set to signal, we use a data descriptor\r
-            generalPurposeFlag |= 8;\r
-        }\r
-        // CheckStyle:MagicNumber ON\r
-\r
-        // version needed to extract\r
-        writeOut(ZipShort.getBytes(versionNeededToExtract));\r
-        // general purpose bit flag\r
-        writeOut(ZipShort.getBytes(generalPurposeFlag));\r
-    }\r
-\r
-    /**\r
-     * enum that represents the possible policies for creating Unicode\r
-     * extra fields.\r
-     */\r
-    public static final class UnicodeExtraFieldPolicy {\r
-        /**\r
-         * Always create Unicode extra fields.\r
-         */\r
-        public static final UnicodeExtraFieldPolicy ALWAYS =\r
-            new UnicodeExtraFieldPolicy("always");\r
-        /**\r
-         * Never create Unicode extra fields.\r
-         */\r
-        public static final UnicodeExtraFieldPolicy NEVER =\r
-            new UnicodeExtraFieldPolicy("never");\r
-        /**\r
-         * Create Unicode extra fields for filenames that cannot be\r
-         * encoded using the specified encoding.\r
-         */\r
-        public static final UnicodeExtraFieldPolicy NOT_ENCODEABLE =\r
-            new UnicodeExtraFieldPolicy("not encodeable");\r
-\r
-        private final String name;\r
-        private UnicodeExtraFieldPolicy(String n) {\r
-            name = n;\r
-        }\r
-        public String toString() {\r
-            return name;\r
-        }\r
-    }\r
-}\r
+/*
+ *  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
+ *
+ *  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.tools.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.nio.ByteBuffer;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.zip.CRC32;
+import java.util.zip.Deflater;
+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>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>
+ *
+ */
+@SuppressWarnings({"unchecked", "rawtypes"})
+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
+     *
+     * Using a buffer size of 8 kB proved to be a good compromise
+     */
+    private static final int DEFLATER_BLOCK_SIZE = 8192;
+
+    /**
+     * 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;
+
+    /**
+     * default encoding for file names and comment.
+     */
+    static final String DEFAULT_ENCODING = null;
+
+    /**
+     * General purpose flag, which indicates that filenames are
+     * written in utf-8.
+     */
+    public static final int UFT8_NAMES_FLAG = 1 << 11;
+
+    /**
+     * General purpose flag, which indicates that filenames are
+     * written in utf-8.
+     * @deprecated use {@link #UFT8_NAMES_FLAG} instead
+     */
+    public static final int EFS_FLAG = UFT8_NAMES_FLAG;
+
+    /**
+     * 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 final List entries = new LinkedList();
+
+    /**
+     * CRC instance to avoid parsing DEFLATED data twice.
+     *
+     * @since 1.1
+     */
+    private final 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 final Map offsets = new HashMap();
+
+    /**
+     * 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;
+
+    /**
+     * The zip encoding to use for filenames and the file comment.
+     *
+     * This field is of internal use and will be set in {@link
+     * #setEncoding(String)}.
+     */
+    private ZipEncoding zipEncoding =
+        ZipEncodingHelper.getZipEncoding(DEFAULT_ENCODING);
+
+   // 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[BUFFER_SIZE];
+
+    // CheckStyle:VisibilityModifier ON
+
+    /**
+     * Optional random access output.
+     *
+     * @since 1.14
+     */
+    private RandomAccessFile raf = null;
+
+    /**
+     * whether to use the general purpose bit flag when writing UTF-8
+     * filenames or not.
+     */
+    private boolean useUTF8Flag = true; 
+
+    /**
+     * Whether to encode non-encodable file names as UTF-8.
+     */
+    private boolean fallbackToUTF8 = false;
+
+    /**
+     * whether to create UnicodePathExtraField-s for each entry.
+     */
+    private UnicodeExtraFieldPolicy createUnicodeExtraFields =
+        UnicodeExtraFieldPolicy.NEVER;
+
+    /**
+     * 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.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(final String encoding) {
+        this.encoding = encoding;
+        this.zipEncoding = ZipEncodingHelper.getZipEncoding(encoding);
+        useUTF8Flag &= ZipEncodingHelper.isUTF8(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;
+    }
+
+    /**
+     * Whether to set the language encoding flag if the file name
+     * encoding is UTF-8.
+     *
+     * <p>Defaults to true.</p>
+     */
+    public void setUseLanguageEncodingFlag(boolean b) {
+        useUTF8Flag = b && ZipEncodingHelper.isUTF8(encoding);
+    }
+
+    /**
+     * Whether to create Unicode Extra Fields.
+     *
+     * <p>Defaults to NEVER.</p>
+     */
+    public void setCreateUnicodeExtraFields(UnicodeExtraFieldPolicy b) {
+        createUnicodeExtraFields = b;
+    }
+
+    /**
+     * Whether to fall back to UTF and the language encoding flag if
+     * the file name cannot be encoded using the specified encoding.
+     *
+     * <p>Defaults to false.</p>
+     */
+    public void setFallbackToUTF8(boolean b) {
+        fallbackToUTF8 = b;
+    }
+
+    /**
+     * 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 (Iterator i = entries.iterator(); i.hasNext(); ) {
+            writeCentralFileHeader((ZipEntry) i.next());
+        }
+        cdLength = written - cdOffset;
+        writeCentralDirectoryEnd();
+        offsets.clear();
+        entries.clear();
+    }
+
+    /**
+     * 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();
+
+            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;
+    }
+
+    /**
+     * 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.add(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);
+    }
+
+    /**
+     * 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);
+        }
+        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()) {
+                    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 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 & BYTE_MASK);
+        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();
+        }
+        if (out != null) {
+            out.close();
+        }
+    }
+
+    /**
+     * 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();
+        }
+    }
+
+    /*
+     * 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 local file header entry
+     * @param ze the entry to write
+     * @throws IOException on error
+     *
+     * @since 1.1
+     */
+    protected void writeLocalFileHeader(ZipEntry ze) throws IOException {
+
+        boolean encodable = zipEncoding.canEncode(ze.getName());
+        
+        final ZipEncoding entryEncoding;
+        
+        if (!encodable && fallbackToUTF8) {
+            entryEncoding = ZipEncodingHelper.UTF8_ZIP_ENCODING;
+        } else {
+            entryEncoding = zipEncoding;
+        }
+        
+        ByteBuffer name = entryEncoding.encode(ze.getName());        
+
+        if (createUnicodeExtraFields != UnicodeExtraFieldPolicy.NEVER) {
+
+            if (createUnicodeExtraFields == UnicodeExtraFieldPolicy.ALWAYS
+                || !encodable) {
+                ze.addExtraField(new UnicodePathExtraField(ze.getName(),
+                                                           name.array(),
+                                                           name.arrayOffset(),
+                                                           name.limit()));
+            }
+
+            String comm = ze.getComment();
+            if (comm != null && !"".equals(comm)) {
+
+                boolean commentEncodable = this.zipEncoding.canEncode(comm);
+
+                if (createUnicodeExtraFields == UnicodeExtraFieldPolicy.ALWAYS
+                    || !commentEncodable) {
+                    ByteBuffer commentB = entryEncoding.encode(comm);
+                    ze.addExtraField(new UnicodeCommentExtraField(comm,
+                                                                  commentB.array(),
+                                                                  commentB.arrayOffset(),
+                                                                  commentB.limit())
+                                     );
+                }
+            }
+        }
+
+        offsets.put(ze, ZipLong.getBytes(written));
+
+        writeOut(LFH_SIG);
+        written += WORD;
+
+        //store method in local variable to prevent multiple method calls
+        final int zipMethod = ze.getMethod();
+
+        writeVersionNeededToExtractAndGeneralPurposeBits(zipMethod,
+                                                         !encodable
+                                                         && fallbackToUTF8);
+        written += WORD;
+
+        // compression method
+        writeOut(ZipShort.getBytes(zipMethod));
+        written += SHORT;
+
+        // last mod. time and date
+        writeOut(toDosTime(ze.getTime()));
+        written += WORD;
+
+        // 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()));
+        }
+        // CheckStyle:MagicNumber OFF
+        written += 12;
+        // CheckStyle:MagicNumber ON
+
+        // file name length
+        writeOut(ZipShort.getBytes(name.limit()));
+        written += SHORT;
+
+        // extra field length
+        byte[] extra = ze.getLocalFileDataExtra();
+        writeOut(ZipShort.getBytes(extra.length));
+        written += SHORT;
+
+        // file name
+        writeOut(name.array(), name.arrayOffset(), name.limit());
+        written += name.limit();
+
+        // 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;
+        }
+        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 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 += WORD;
+
+        // version made by
+        // CheckStyle:MagicNumber OFF
+        writeOut(ZipShort.getBytes((ze.getPlatform() << 8) | 20));
+        written += SHORT;
+
+        final int zipMethod = ze.getMethod();
+        final boolean encodable = zipEncoding.canEncode(ze.getName());
+        writeVersionNeededToExtractAndGeneralPurposeBits(zipMethod,
+                                                         !encodable
+                                                         && fallbackToUTF8);
+        written += WORD;
+
+        // compression method
+        writeOut(ZipShort.getBytes(zipMethod));
+        written += SHORT;
+
+        // last mod. time and date
+        writeOut(toDosTime(ze.getTime()));
+        written += WORD;
+
+        // CRC
+        // compressed length
+        // uncompressed length
+        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
+        final ZipEncoding entryEncoding;
+        
+        if (!encodable && fallbackToUTF8) {
+            entryEncoding = ZipEncodingHelper.UTF8_ZIP_ENCODING;
+        } else {
+            entryEncoding = zipEncoding;
+        }
+        
+        ByteBuffer name = entryEncoding.encode(ze.getName());        
+
+        writeOut(ZipShort.getBytes(name.limit()));
+        written += SHORT;
+
+        // extra field length
+        byte[] extra = ze.getCentralDirectoryExtra();
+        writeOut(ZipShort.getBytes(extra.length));
+        written += SHORT;
+
+        // file comment length
+        String comm = ze.getComment();
+        if (comm == null) {
+            comm = "";
+        }
+        
+        ByteBuffer commentB = entryEncoding.encode(comm);
+        
+        writeOut(ZipShort.getBytes(commentB.limit()));
+        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
+        writeOut(name.array(), name.arrayOffset(), name.limit());
+        written += name.limit();
+
+        // extra field
+        writeOut(extra);
+        written += extra.length;
+
+        // file comment
+        writeOut(commentB.array(), commentB.arrayOffset(), commentB.limit());
+        written += commentB.limit();
+    }
+
+    /**
+     * Writes the &quot;End of central dir record&quot;.
+     * @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
+        ByteBuffer data = this.zipEncoding.encode(comment);
+        writeOut(ZipShort.getBytes(data.limit()));
+        writeOut(data.array(), data.arrayOffset(), data.limit());
+    }
+
+    /**
+     * 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
+     */
+    @SuppressWarnings("deprecation")
+       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 {
+        try {
+            ByteBuffer b =
+                ZipEncodingHelper.getZipEncoding(encoding).encode(name);
+            byte[] result = new byte[b.limit()];
+            System.arraycopy(b.array(), b.arrayOffset(), result, 0,
+                             result.length);
+            return result;
+        } catch (IOException ex) {
+            throw new ZipException("Failed to encode name: " + ex.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;
+        }
+    }
+
+    private void deflateUntilInputIsNeeded() throws IOException {
+        while (!def.needsInput()) {
+            deflate();
+        }
+    }
+
+    private void writeVersionNeededToExtractAndGeneralPurposeBits(final int
+                                                                  zipMethod,
+                                                                  final boolean
+                                                                  utfFallback)
+        throws IOException {
+
+        // CheckStyle:MagicNumber OFF
+        int versionNeededToExtract = 10;
+        int generalPurposeFlag = (useUTF8Flag || utfFallback) ? UFT8_NAMES_FLAG : 0;
+        if (zipMethod == DEFLATED && raf == null) {
+            // requires version 2 as we are going to store length info
+            // in the data descriptor
+            versionNeededToExtract =  20;
+            // bit3 set to signal, we use a data descriptor
+            generalPurposeFlag |= 8;
+        }
+        // CheckStyle:MagicNumber ON
+
+        // version needed to extract
+        writeOut(ZipShort.getBytes(versionNeededToExtract));
+        // general purpose bit flag
+        writeOut(ZipShort.getBytes(generalPurposeFlag));
+    }
+
+    /**
+     * enum that represents the possible policies for creating Unicode
+     * extra fields.
+     */
+    public static final class UnicodeExtraFieldPolicy {
+        /**
+         * Always create Unicode extra fields.
+         */
+        public static final UnicodeExtraFieldPolicy ALWAYS =
+            new UnicodeExtraFieldPolicy("always");
+        /**
+         * Never create Unicode extra fields.
+         */
+        public static final UnicodeExtraFieldPolicy NEVER =
+            new UnicodeExtraFieldPolicy("never");
+        /**
+         * Create Unicode extra fields for filenames that cannot be
+         * encoded using the specified encoding.
+         */
+        public static final UnicodeExtraFieldPolicy NOT_ENCODEABLE =
+            new UnicodeExtraFieldPolicy("not encodeable");
+
+        private final String name;
+        private UnicodeExtraFieldPolicy(String n) {
+            name = n;
+        }
+        public String toString() {
+            return name;
+        }
+    }
+}