001    /*
002    Copyright (c) 1996-2012, Damon Hart-Davis
003    All rights reserved.
004    
005    Redistribution and use in source and binary forms, with or without
006    modification, are permitted provided that the following conditions are
007    met:
008    
009      * Redistributions of source code must retain the above copyright
010        notice, this list of conditions and the following disclaimer.
011    
012      * Redistributions in binary form must reproduce the above copyright
013        notice, this list of conditions and the following disclaimer in the
014        documentation and/or other materials provided with the
015        distribution.
016    
017    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
018    IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
019    TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
020    PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
021    OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
022    SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
023    LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
024    DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
025    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
026    (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
027    OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
028    */
029    package org.hd.d.pg2k.webSvr.util;
030    
031    import java.io.BufferedOutputStream;
032    import java.io.FilterOutputStream;
033    import java.io.IOException;
034    import java.io.OutputStream;
035    import java.util.zip.CRC32;
036    import java.util.zip.Deflater;
037    
038    /**Substitute for GZIPOutputStream that maximises compression and has a usable flush().
039     * This is also more careful about its output writes for efficiency,
040     * and indeed buffers them to minimise the number of write()s downstream.
041     * This is especially useful where each write() potentially has a significant cost
042     * such as an OS call, a disc write, or a network packet.
043     * <p>
044     * This will automatically throttle back from maximum to default compression
045     * (which usually yields nearly as good results on significantly less effort)
046     * when the system is conserving power/energy or is busy.
047     */
048    public class FlushableGZIPOutputStream extends net.sf.jazzlib.DeflaterOutputStream
049        {
050        private final CRC32 crc = new CRC32();
051        private final static int GZIP_MAGIC = 0x8b1f;
052        private final OutputStream os;
053    
054        /**Note when input has arrived and not yet been compressed and flushed downstream. */
055        private boolean somethingWritten;
056    
057        /**Creates flush()able equivalent of GZIPOutputStream, with maximum/best compression and default output buffer size.
058         * Useful for, for example, HTTP compression.
059         * @param os  underlying output stream; must not be null
060         */
061        public FlushableGZIPOutputStream(final OutputStream os)
062            throws IOException
063            { this(os, 8192); }
064    
065        /**Compression level to minimise output size, at high CPU cost. */
066        public static final int BEST_COMPRESSION = net.sf.jazzlib.Deflater.BEST_COMPRESSION;
067    
068        /**Compression level to minimise CPU cost. */
069        public static final int BEST_SPEED = net.sf.jazzlib.Deflater.BEST_SPEED;
070    
071        /**Compression level to give reasonable balance of output compression (usually most of that available) and CPU cost. */
072        public static final int DEFAULT_COMPRESSION = net.sf.jazzlib.Deflater.DEFAULT_COMPRESSION;
073    
074        /**Creates flush()able equivalent of GZIPOutputStream, with best compression.
075         * Useful for, for example, HTTP compression.
076         * @param os  underlying output stream; must not be null
077         * @param bufsize  output buffer size; must be positive
078         */
079        public FlushableGZIPOutputStream(final OutputStream os, final int bufsize)
080            throws IOException
081            { this(os, bufsize, BEST_COMPRESSION); }
082    
083        /**Creates flush()able equivalent of GZIPOutputStream, with specified compression level.
084         * Useful for, for example, HTTP compression.
085         * @param os  underlying output stream; must not be null
086         * @param bufsize  output buffer size; must be positive
087         * @param compressionLevel -1 for default, else in inclusive range 1 for fastest to 9 for best compression
088         */
089        public FlushableGZIPOutputStream(final OutputStream os, final int bufsize, final int compressionLevel)
090            throws IOException
091            {
092            super(new FilterOutputStream(new BufferedOutputStream(os, bufsize)){
093                /**Suppress inappropriate/inefficient flush()es by DeflaterOutputStream. */
094                @Override public void flush() { }
095                },
096                new net.sf.jazzlib.Deflater(compressionLevel, true));
097            this.os = os;
098            writeHeader();
099            crc.reset();
100            }
101    
102    
103        /**All output is written via this routine. */
104        @Override
105        public synchronized void write(final byte[] buf, final int off, final int len)
106            throws IOException
107            {
108    //if(WATCH_FLUSHES) { System.out.println("compressedStream.out.write(b, off, "+len+")"); }
109            somethingWritten = true;
110            super.write(buf, off, len);
111            crc.update(buf, off, len);
112            }
113    
114        /**Flush any accumulated input downstream in compressed form.
115         * We overcome some bugs/misfeatures here so that:
116         * <ul>
117         * <li>We won't allow the GZIP header to be flushed on its own
118         *     without real compressed data in the same write downstream.
119         * <li>We ensure that any accumulated uncompressed data really is forced
120         *     through the compressor.
121         * <li>We prevent spurious empty compressed blocks being produced
122         *     from successive flush()es with no intervening new data.
123         * </ul>
124         */
125        @Override
126        public synchronized void flush() throws IOException
127            {
128            // If no uncompressed input data has been written
129            // (since our last flush or since creation)
130            // then avoid trying to flush anything out now,
131            // which also avoids forcing out the GZIP header on its own.
132            if(!somethingWritten) { return; }
133    
134            // We call this to get def.flush() called,
135            // but suppress the (usually premature) out.flush() called internally.
136            super.flush();
137    
138            // Since super.flush() seems to fail to reliably force output,
139            // possibly due to over-cautious def.needsInput() guard following def.flush(),
140            // we try to force the issue here by bypassing the guard.
141            int len;
142            while((len = def.deflate(buf, 0, buf.length)) > 0)
143                { out.write(buf, 0, len); }
144    
145            // Really flush the stream below us...
146    //if(WATCH_FLUSHES) { System.out.println("downstream.out.flush()"); }
147            os.flush();
148    
149            // Further flush()es ignored until more input data data written.
150            somethingWritten = false;
151            }
152    
153        /**Flush out any remaining compressed data, add the trailer, and close the stream. */
154        @Override
155        public synchronized void close()
156            throws IOException
157            {
158            boolean writtenTrailer = false;
159    
160            if(!def.finished())
161                {
162                def.finish();
163                do
164                    {
165                    int len = def.deflate(buf, 0, buf.length);
166                    if(len <= 0) { break; }
167    
168                    // If there is room for the trailer at the end of the buffer
169                    // and we've just finished deflating everything
170                    // then sneak in the trailer here to save one write().
171                    if((len + TRAILER_BYTES <= buf.length) && def.finished())
172                        {
173                        System.arraycopy(generateTrailer(), 0, buf, len, TRAILER_BYTES);
174                        writtenTrailer = true;
175                        len += TRAILER_BYTES;
176                        };
177    
178                    out.write(buf, 0, len);
179                    } while(!def.finished());
180                }
181    
182            // Write trailer iff only not already done.
183            if(!writtenTrailer)
184                { out.write(generateTrailer()); }
185    
186            out.close();
187            }
188    
189        /**Constant (10-byte) header array.
190         * All zeros except magic number and algorithm.
191         */
192        private static final byte[] header = {
193            (byte) GZIP_MAGIC, // Intel order, LSB first.
194            (byte) (GZIP_MAGIC >> 8),
195            (byte) Deflater.DEFLATED,
196            0, 0, 0, 0, 0, 0, 0
197            };
198    
199        /**Write header directly downstream. */
200        private void writeHeader()
201            throws IOException
202            {
203            // Single write for efficiency.
204            out.write(header);
205            }
206    
207        /**Length of the trailer in bytes. */
208        private static final int TRAILER_BYTES = 8;
209    
210        /**Return header to write downstream. */
211        private byte[] generateTrailer()
212            {
213            final byte b[] = new byte[TRAILER_BYTES];
214            final int crcValue = (int) crc.getValue();
215            b[0] = (byte) (crcValue); // Intel order, LSB first.
216            b[1] = (byte) (crcValue >> 8);
217            b[2] = (byte) (crcValue >> 16);
218            b[3] = (byte) (crcValue >> 24);
219            final int totalIn = def.getTotalIn();
220            b[4] = (byte) (totalIn); // Intel order, LSB first.
221            b[5] = (byte) (totalIn >> 8);
222            b[6] = (byte) (totalIn >> 16);
223            b[7] = (byte) (totalIn >> 24);
224            return(b);
225            }
226        }