// package net.sf.zipme; /** * Inflater is used to decompress data that has been compressed according * to the "deflate" standard described in rfc1950. * The usage is as following. First you have to set some input with * <code>setInput()</code>, then inflate() it. If inflate doesn't * inflate any bytes there may be three reasons: * <ul> * <li>needsInput() returns true because the input buffer is empty. * You have to provide more input with <code>setInput()</code>. * NOTE: needsInput() also returns true when, the stream is finished. * </li> * <li>needsDictionary() returns true, you have to provide a preset * dictionary with <code>setDictionary()</code>.</li> * <li>finished() returns true, the inflater has finished.</li> * </ul> * Once the first output byte is produced, a dictionary will not be * needed at a later stage. * @author John Leuner, Jochen Hoenicke * @author Tom Tromey * @date May 17, 1999 * @since JDK 1.1 */ public class Inflater { private static final int CPLENS[]={3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258}; private static final int CPLEXT[]={0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0}; private static final int CPDIST[]={1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577}; private static final int CPDEXT[]={0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13}; private static final int DECODE_HEADER=0; private static final int DECODE_DICT=1; private static final int DECODE_BLOCKS=2; private static final int DECODE_STORED_LEN1=3; private static final int DECODE_STORED_LEN2=4; private static final int DECODE_STORED=5; private static final int DECODE_DYN_HEADER=6; private static final int DECODE_HUFFMAN=7; private static final int DECODE_HUFFMAN_LENBITS=8; private static final int DECODE_HUFFMAN_DIST=9; private static final int DECODE_HUFFMAN_DISTBITS=10; private static final int DECODE_CHKSUM=11; private static final int FINISHED=12; /** * This variable contains the current state. */ private int mode; /** * The adler checksum of the dictionary or of the decompressed * stream, as it is written in the header resp. footer of the * compressed stream. <br> * Only valid if mode is DECODE_DICT or DECODE_CHKSUM. */ private int readAdler; /** * The number of bits needed to complete the current state. This * is valid, if mode is DECODE_DICT, DECODE_CHKSUM, * DECODE_HUFFMAN_LENBITS or DECODE_HUFFMAN_DISTBITS. */ private int neededBits; private int repLength, repDist; private int uncomprLen; /** * True, if the last block flag was set in the last block of the * inflated stream. This means that the stream ends after the * current block. */ private boolean isLastBlock; /** * The total number of inflated bytes. */ private long totalOut; /** * The total number of bytes set with setInput(). This is not the * value returned by getTotalIn(), since this also includes the * unprocessed input. */ private long totalIn; /** * This variable stores the nowrap flag that was given to the constructor. * True means, that the inflated stream doesn't contain a header nor the * checksum in the footer. */ private boolean nowrap; private StreamManipulator input; private OutputWindow outputWindow; private InflaterDynHeader dynHeader; private InflaterHuffmanTree litlenTree, distTree; /** * Creates a new inflater. */ public Inflater(){ this(false); } /** * Creates a new inflater. * @param nowrap true if no header and checksum field appears in the * stream. This is used for GZIPed input. For compatibility with * Sun JDK you should provide one byte of input more than needed in * this case. */ public Inflater( boolean nowrap){ this.nowrap=nowrap; this.hook32(); input=new StreamManipulator(); outputWindow=new OutputWindow(); mode=nowrap ? DECODE_BLOCKS : DECODE_HEADER; } /** * Frees all objects allocated by the inflater. There's no reason * to call this, since you can just rely on garbage collection (even * for the Sun implementation). Exists only for compatibility * with Sun's JDK, where the compressor allocates native memory. * If you call any method (even reset) afterwards the behaviour is * <i>undefined</i>. */ public void end(){ outputWindow=null; input=null; dynHeader=null; litlenTree=null; distTree=null; } /** * Returns true, if the inflater has finished. This means, that no * input is needed and no output can be produced. */ public boolean finished(){ return mode == FINISHED && outputWindow.getAvailable() == 0; } /** * Gets the number of unprocessed input. Useful, if the end of the * stream is reached and you want to further process the bytes after * the deflate stream. * @return the number of bytes of the input which were not processed. */ public int getRemaining(){ return input.getAvailableBytes(); } /** * Gets the total number of processed compressed input bytes. * @return the total number of bytes of processed input bytes. */ public int getTotalIn(){ return (int)(totalIn - getRemaining()); } /** * Gets the total number of processed compressed input bytes. * @return the total number of bytes of processed input bytes. * @since 1.5 */ public long getBytesRead(){ return totalIn - getRemaining(); } /** * Gets the total number of output bytes returned by inflate(). * @return the total number of output bytes. */ public int getTotalOut(){ return (int)totalOut; } /** * Gets the total number of output bytes returned by inflate(). * @return the total number of output bytes. * @since 1.5 */ public long getBytesWritten(){ return totalOut; } /** * Inflates the compressed stream to the output buffer. If this * returns 0, you should check, whether needsDictionary(), * needsInput() or finished() returns true, to determine why no * further output is produced. * @param buf the output buffer. * @return the number of bytes written to the buffer, 0 if no further * output can be produced. * @exception DataFormatException if deflated stream is invalid. * @exception IllegalArgumentException if buf has length 0. */ public int inflate( byte[] buf) throws DataFormatException { return inflate(buf,0,buf.length); } /** * Inflates the compressed stream to the output buffer. If this * returns 0, you should check, whether needsDictionary(), * needsInput() or finished() returns true, to determine why no * further output is produced. * @param buf the output buffer. * @param off the offset into buffer where the output should start. * @param len the maximum length of the output. * @return the number of bytes written to the buffer, 0 if no further * output can be produced. * @exception DataFormatException if deflated stream is invalid. * @exception IndexOutOfBoundsException if the off and/or len are wrong. */ public int inflate( byte[] buf, int off, int len) throws DataFormatException { if (len == 0) return 0; if (0 > off || off > off + len || off + len > buf.length) throw new ArrayIndexOutOfBoundsException(); int count=0; int more; do { if (mode != DECODE_CHKSUM) { more=outputWindow.copyOutput(buf,off,len); this.hook33(buf,off,more); off+=more; count+=more; totalOut+=more; len-=more; if (len == 0) return count; } } while (decode() || (outputWindow.getAvailable() > 0 && mode != DECODE_CHKSUM)); return count; } /** * Returns true, if a preset dictionary is needed to inflate the input. */ public boolean needsDictionary(){ return mode == DECODE_DICT && neededBits == 0; } /** * Returns true, if the input buffer is empty. * You should then call setInput(). <br> * <em>NOTE</em>: This method also returns true when the stream is finished. */ public boolean needsInput(){ return input.needsInput(); } /** * Resets the inflater so that a new stream can be decompressed. All * pending input and output will be discarded. */ public void reset(){ mode=nowrap ? DECODE_BLOCKS : DECODE_HEADER; totalIn=totalOut=0; input.reset(); outputWindow.reset(); dynHeader=null; litlenTree=null; distTree=null; isLastBlock=false; } /** * Sets the preset dictionary. This should only be called, if * needsDictionary() returns true and it should set the same * dictionary, that was used for deflating. The getAdler() * function returns the checksum of the dictionary needed. * @param buffer the dictionary. * @exception IllegalStateException if no dictionary is needed. * @exception IllegalArgumentException if the dictionary checksum is * wrong. */ public void setDictionary( byte[] buffer){ setDictionary(buffer,0,buffer.length); } /** * Sets the preset dictionary. This should only be called, if * needsDictionary() returns true and it should set the same * dictionary, that was used for deflating. The getAdler() * function returns the checksum of the dictionary needed. * @param buffer the dictionary. * @param off the offset into buffer where the dictionary starts. * @param len the length of the dictionary. * @exception IllegalStateException if no dictionary is needed. * @exception IllegalArgumentException if the dictionary checksum is * wrong. * @exception IndexOutOfBoundsException if the off and/or len are wrong. */ public void setDictionary( byte[] buffer, int off, int len){ if (!needsDictionary()) throw new IllegalStateException(); this.hook34(buffer,off,len); outputWindow.copyDict(buffer,off,len); mode=DECODE_BLOCKS; } /** * Sets the input. This should only be called, if needsInput() * returns true. * @param buf the input. * @exception IllegalStateException if no input is needed. */ public void setInput( byte[] buf){ setInput(buf,0,buf.length); } /** * Sets the input. This should only be called, if needsInput() * returns true. * @param buf the input. * @param off the offset into buffer where the input starts. * @param len the length of the input. * @exception IllegalStateException if no input is needed. * @exception IndexOutOfBoundsException if the off and/or len are wrong. */ public void setInput( byte[] buf, int off, int len){ input.setInput(buf,off,len); totalIn+=len; } /** * Decodes the deflate header. * @return false if more input is needed. * @exception DataFormatException if header is invalid. */ private boolean decodeHeader() throws DataFormatException { int header=input.peekBits(16); if (header < 0) return false; input.dropBits(16); header=((header << 8) | (header >> 8)) & 0xffff; if (header % 31 != 0) throw new DataFormatException("Header checksum illegal"); if ((header & 0x0f00) != (Deflater.DEFLATED << 8)) throw new DataFormatException("Compression Method unknown"); if ((header & 0x0020) == 0) { mode=DECODE_BLOCKS; } else { mode=DECODE_DICT; neededBits=32; } return true; } /** * Decodes the dictionary checksum after the deflate header. * @return false if more input is needed. */ private boolean decodeDict(){ while (neededBits > 0) { int dictByte=input.peekBits(8); if (dictByte < 0) return false; input.dropBits(8); readAdler=(readAdler << 8) | dictByte; neededBits-=8; } return false; } /** * Decodes the huffman encoded symbols in the input stream. * @return false if more input is needed, true if output window is * full or the current block ends. * @exception DataFormatException if deflated stream is invalid. */ private boolean decodeHuffman() throws DataFormatException { int free=outputWindow.getFreeSpace(); while (free >= 258) { int symbol; switch (mode) { case DECODE_HUFFMAN: while (((symbol=litlenTree.getSymbol(input)) & ~0xff) == 0) { outputWindow.write(symbol); if (--free < 258) return true; } if (symbol < 257) { if (symbol < 0) return false; else { distTree=null; litlenTree=null; mode=DECODE_BLOCKS; return true; } } try { repLength=CPLENS[symbol - 257]; neededBits=CPLEXT[symbol - 257]; } catch ( ArrayIndexOutOfBoundsException ex) { throw new DataFormatException("Illegal rep length code"); } case DECODE_HUFFMAN_LENBITS: if (neededBits > 0) { mode=DECODE_HUFFMAN_LENBITS; int i=input.peekBits(neededBits); if (i < 0) return false; input.dropBits(neededBits); repLength+=i; } mode=DECODE_HUFFMAN_DIST; case DECODE_HUFFMAN_DIST: symbol=distTree.getSymbol(input); if (symbol < 0) return false; try { repDist=CPDIST[symbol]; neededBits=CPDEXT[symbol]; } catch (ArrayIndexOutOfBoundsException ex) { throw new DataFormatException("Illegal rep dist code"); } case DECODE_HUFFMAN_DISTBITS: if (neededBits > 0) { mode=DECODE_HUFFMAN_DISTBITS; int i=input.peekBits(neededBits); if (i < 0) return false; input.dropBits(neededBits); repDist+=i; } outputWindow.repeat(repLength,repDist); free-=repLength; mode=DECODE_HUFFMAN; break; default : throw new IllegalStateException(); } } return true; } /** * Decodes the adler checksum after the deflate stream. * @return false if more input is needed. * @exception DataFormatException if checksum doesn't match. */ private boolean decodeChksum() throws DataFormatException { while (neededBits > 0) { int chkByte=input.peekBits(8); if (chkByte < 0) return false; input.dropBits(8); readAdler=(readAdler << 8) | chkByte; neededBits-=8; } this.hook35(); mode=FINISHED; return false; } /** * Decodes the deflated stream. * @return false if more input is needed, or if finished. * @exception DataFormatException if deflated stream is invalid. */ private boolean decode() throws DataFormatException { switch (mode) { case DECODE_HEADER: return decodeHeader(); case DECODE_DICT: return decodeDict(); case DECODE_CHKSUM: return decodeChksum(); case DECODE_BLOCKS: if (isLastBlock) { if (nowrap) { mode=FINISHED; return false; } else { input.skipToByteBoundary(); neededBits=32; mode=DECODE_CHKSUM; return true; } } int type=input.peekBits(3); if (type < 0) return false; input.dropBits(3); if ((type & 1) != 0) isLastBlock=true; switch (type >> 1) { case DeflaterConstants.STORED_BLOCK: input.skipToByteBoundary(); mode=DECODE_STORED_LEN1; break; case DeflaterConstants.STATIC_TREES: litlenTree=InflaterHuffmanTree.defLitLenTree; distTree=InflaterHuffmanTree.defDistTree; mode=DECODE_HUFFMAN; break; case DeflaterConstants.DYN_TREES: dynHeader=new InflaterDynHeader(); mode=DECODE_DYN_HEADER; break; default : throw new DataFormatException("Unknown block type " + type); } return true; case DECODE_STORED_LEN1: { if ((uncomprLen=input.peekBits(16)) < 0) return false; input.dropBits(16); mode=DECODE_STORED_LEN2; } case DECODE_STORED_LEN2: { int nlen=input.peekBits(16); if (nlen < 0) return false; input.dropBits(16); if (nlen != (uncomprLen ^ 0xffff)) throw new DataFormatException("broken uncompressed block"); mode=DECODE_STORED; } case DECODE_STORED: { int more=outputWindow.copyStored(input,uncomprLen); uncomprLen-=more; if (uncomprLen == 0) { mode=DECODE_BLOCKS; return true; } return !input.needsInput(); } case DECODE_DYN_HEADER: if (!dynHeader.decode(input)) return false; litlenTree=dynHeader.buildLitLenTree(); distTree=dynHeader.buildDistTree(); mode=DECODE_HUFFMAN; case DECODE_HUFFMAN: case DECODE_HUFFMAN_LENBITS: case DECODE_HUFFMAN_DIST: case DECODE_HUFFMAN_DISTBITS: return decodeHuffman(); case FINISHED: return false; default : throw new IllegalStateException(); } } protected void hook32(){ } protected void hook33(byte[] buf,int off,int more) throws DataFormatException { } protected void hook34(byte[] buffer,int off,int len){ } protected void hook35() throws DataFormatException { } }