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}