001package com.hfg.util.io; 002 003import java.io.ByteArrayInputStream; 004import java.io.FilterInputStream; 005import java.io.IOException; 006import java.io.InputStream; 007import java.util.zip.CRC32; 008import java.util.zip.Deflater; 009 010//------------------------------------------------------------------------------ 011/** 012 Provides an InputStream that applies GZIP compression. 013 014 <div> 015 Based on discussion at http://stackoverflow.com/questions/11036280/compress-an-inputstream-with-gzip. 016 </div> 017 <div> 018 @author J. Alex Taylor, hairyfatguy.com 019 </div> 020 */ 021//------------------------------------------------------------------------------ 022// com.hfg XML/HTML Coding Library 023// 024// This library is free software; you can redistribute it and/or 025// modify it under the terms of the GNU Lesser General Public 026// License as published by the Free Software Foundation; either 027// version 2.1 of the License, or (at your option) any later version. 028// 029// This library is distributed in the hope that it will be useful, 030// but WITHOUT ANY WARRANTY; without even the implied warranty of 031// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 032// Lesser General Public License for more details. 033// 034// You should have received a copy of the GNU Lesser General Public 035// License along with this library; if not, write to the Free Software 036// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 037// 038// J. Alex Taylor, President, Founder, CEO, COO, CFO, OOPS hairyfatguy.com 039// jataylor@hairyfatguy.com 040//------------------------------------------------------------------------------ 041 042public class GZIPCompressInputStream extends FilterInputStream 043{ 044 private byte[] mBuffer = new byte[8196]; 045 private byte[] mInputBuffer = new byte[4096]; 046 private int mBufferLimit; 047 private int mBufferIndex; 048 private boolean mEndOfStreamReached; 049 private long mBytesRead = 0; 050 051 private enum State 052 { 053 HEADER, 054 DATA, 055 FINALIZATION, 056 TRAILER, 057 FINISH 058 } 059 060 // GZIP header magic number 061 private final static int GZIP_MAGIC = 0x8b1f; 062 063 private static final byte[] GZIP_HEADER = new byte[] { 064 (byte) GZIP_MAGIC, // Magic number (short) 065 (byte)(GZIP_MAGIC >> 8), // Magic number (short) 066 Deflater.DEFLATED, // Compression method (CM) 067 0, // Flags (FLG) 068 0, // Modification time MTIME (int) 069 0, // Modification time MTIME (int) 070 0, // Modification time MTIME (int) 071 0, // Modification time MTIME (int) 072 0, // Extra flags (XFLG) 073 0 // Operating system (OS) 074 }; 075 076 // Set the initial state 077 private State mCurrentState = State.HEADER; 078 079 private final Deflater mDeflater = new Deflater(Deflater.DEFLATED, true); 080 private final CRC32 mChecksum = new CRC32(); 081 082 private ByteArrayInputStream mTrailer; 083 084 //########################################################################## 085 // CONSTRUCTORS 086 //########################################################################## 087 088 //-------------------------------------------------------------------------- 089 public GZIPCompressInputStream(InputStream inValue) 090 { 091 super(inValue); 092 mChecksum.reset(); 093 } 094 095 096 //########################################################################## 097 // PUBLIC METHODS 098 //########################################################################## 099 100 //--------------------------------------------------------------------------- 101 @Override 102 public int read() 103 throws IOException 104 { 105 while (mBufferIndex >= mBufferLimit 106 && ! mEndOfStreamReached) 107 { 108 fillBuffer(); 109 } 110 111 return (mEndOfStreamReached ? -1 : mBuffer[mBufferIndex++]); 112 } 113 114 115 //--------------------------------------------------------------------------- 116 @Override 117 public int read(byte[] inBuffer, int inOffset, int inMaxReadLength) 118 throws IOException 119 { 120 int numCharsRead = 0; 121 do 122 { 123 byte theByte= (byte) read(); 124 125 if (! mEndOfStreamReached) 126 { 127 inBuffer[inOffset++] = theByte; 128 numCharsRead++; 129 } 130 } 131 while (! mEndOfStreamReached 132 && numCharsRead < inMaxReadLength); 133 134 return (mEndOfStreamReached && 0 == numCharsRead ? -1 : numCharsRead); 135 } 136 137 //-------------------------------------------------------------------------- 138 @Override 139 public void close() 140 throws IOException 141 { 142 super.close(); 143 mDeflater.end(); 144 145 if(mTrailer != null) 146 { 147 mTrailer.close(); 148 } 149 } 150 151 //--------------------------------------------------------------------------- 152 private void fillBuffer() 153 throws IOException 154 { 155 switch(mCurrentState) 156 { 157 case HEADER: 158 System.arraycopy(GZIP_HEADER, 0, mBuffer, 0, GZIP_HEADER.length); 159 mBufferLimit = GZIP_HEADER.length; 160 mCurrentState = State.DATA; 161 break; 162 163 case DATA: 164 165 mBufferLimit = 0; 166 int inputBufferLimit = 0; 167 while (mBufferLimit < mBuffer.length 168 && inputBufferLimit >= 0) 169 { 170 while (mDeflater.needsInput() 171 && inputBufferLimit >= 0) 172 { 173 inputBufferLimit = super.in.read(mInputBuffer, 0, mInputBuffer.length); 174 if (inputBufferLimit > 0) 175 { 176 mBytesRead += inputBufferLimit; 177 mDeflater.setInput(mInputBuffer, 0, inputBufferLimit); 178 mChecksum.update(mInputBuffer, 0, inputBufferLimit); 179 } 180 } 181 182 while (! mDeflater.needsInput() 183 && mBufferLimit < mBuffer.length) 184 { 185 mBufferLimit += mDeflater.deflate(mBuffer, mBufferLimit, mBuffer.length - mBufferLimit, Deflater.NO_FLUSH); 186 } 187 } 188 189 if (-1 == inputBufferLimit) 190 { 191 mCurrentState = State.FINALIZATION; 192 mDeflater.finish(); 193 } 194 break; 195 196 case FINALIZATION: 197 if (mDeflater.finished()) 198 { 199 long checksumValue = mChecksum.getValue(); 200 long compressionInputLength = mDeflater.getBytesRead(); 201 202 mTrailer = new ByteArrayInputStream( new byte[] { 203 (byte) (checksumValue >> 0), 204 (byte) (checksumValue >> 8), 205 (byte) (checksumValue >> 16), 206 (byte) (checksumValue >> 24), 207 208 (byte) (compressionInputLength >> 0), 209 (byte) (compressionInputLength >> 8), 210 (byte) (compressionInputLength >> 16), 211 (byte) (compressionInputLength >> 24), 212 }); 213 214 mCurrentState = State.TRAILER; 215 216 mBufferLimit = 0; 217 } 218 else 219 { 220 mBufferLimit = mDeflater.deflate(mBuffer, 0, mBuffer.length, Deflater.FULL_FLUSH); 221 } 222 break; 223 224 case TRAILER: 225 mBufferLimit = mTrailer.read(mBuffer, 0, mBuffer.length); 226 if (mTrailer.available() == 0) 227 { 228 mCurrentState = State.FINISH; 229 } 230 break; 231 232 case FINISH: 233 mEndOfStreamReached = true; 234 break; 235 } 236 237 // Reset the index 238 mBufferIndex = 0; 239 } 240}