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 }