// package net.sf.zipme; class DeflaterEngine implements DeflaterConstants { private static final int TOO_FAR=4096; private int ins_h; /** * Hashtable, hashing three characters to an index for window, so * that window[index]..window[index+2] have this hash code. * Note that the array should really be unsigned short, so you need * to and the values with 0xffff. */ private short[] head; /** * prev[index & WMASK] points to the previous index that has the * same hash code as the string starting at index. This way * entries with the same hash code are in a linked list. * Note that the array should really be unsigned short, so you need * to and the values with 0xffff. */ private short[] prev; private int matchStart, matchLen; private boolean prevAvailable; private int blockStart; /** * strstart points to the current character in window. */ private int strstart; /** * lookahead is the number of characters starting at strstart in * window that are valid. * So window[strstart] until window[strstart+lookahead-1] are valid * characters. */ private int lookahead; /** * This array contains the part of the uncompressed stream that * is of relevance. The current character is indexed by strstart. */ private byte[] window; private int strategy, max_chain, max_lazy, niceLength, goodLength; /** * The current compression function. */ private int comprFunc; /** * The input data for compression. */ private byte[] inputBuf; /** * The total bytes of input read. */ private long totalIn; /** * The offset into inputBuf, where input data starts. */ private int inputOff; /** * The end offset of the input data. */ private int inputEnd; private DeflaterPending pending; private DeflaterHuffman huffman; DeflaterEngine( DeflaterPending pending){ this.pending=pending; huffman=new DeflaterHuffman(pending); this.hook26(); window=new byte[2 * WSIZE]; head=new short[HASH_SIZE]; prev=new short[WSIZE]; blockStart=strstart=1; } public void reset(){ huffman.reset(); this.hook27(); blockStart=strstart=1; lookahead=0; totalIn=0; prevAvailable=false; matchLen=MIN_MATCH - 1; for (int i=0; i < HASH_SIZE; i++) head[i]=0; for (int i=0; i < WSIZE; i++) prev[i]=0; } public final long getTotalIn(){ return totalIn; } public final void setStrategy( int strat){ strategy=strat; } public void setLevel( int lvl){ goodLength=DeflaterConstants.GOOD_LENGTH[lvl]; max_lazy=DeflaterConstants.MAX_LAZY[lvl]; niceLength=DeflaterConstants.NICE_LENGTH[lvl]; max_chain=DeflaterConstants.MAX_CHAIN[lvl]; if (DeflaterConstants.COMPR_FUNC[lvl] != comprFunc) { if (DeflaterConstants.DEBUGGING) System.err.println("Change from " + comprFunc + " to "+ DeflaterConstants.COMPR_FUNC[lvl]); switch (comprFunc) { case DEFLATE_STORED: if (strstart > blockStart) { huffman.flushStoredBlock(window,blockStart,strstart - blockStart,false); blockStart=strstart; } updateHash(); break; case DEFLATE_FAST: if (strstart > blockStart) { huffman.flushBlock(window,blockStart,strstart - blockStart,false); blockStart=strstart; } break; case DEFLATE_SLOW: if (prevAvailable) huffman.tallyLit(window[strstart - 1] & 0xff); if (strstart > blockStart) { huffman.flushBlock(window,blockStart,strstart - blockStart,false); blockStart=strstart; } prevAvailable=false; matchLen=MIN_MATCH - 1; break; } comprFunc=COMPR_FUNC[lvl]; } } private void updateHash(){ if (DEBUGGING) System.err.println("updateHash: " + strstart); ins_h=(window[strstart] << HASH_SHIFT) ^ window[strstart + 1]; } /** * Inserts the current string in the head hash and returns the previous * value for this hash. */ private int insertString(){ short match; int hash=((ins_h << HASH_SHIFT) ^ window[strstart + (MIN_MATCH - 1)]) & HASH_MASK; if (DEBUGGING) { if (hash != (((window[strstart] << (2 * HASH_SHIFT)) ^ (window[strstart + 1] << HASH_SHIFT) ^ (window[strstart + 2])) & HASH_MASK)) throw new Error("hash inconsistent: " + hash + "/"+ window[strstart]+ ","+ window[strstart + 1]+ ","+ window[strstart + 2]+ ","+ HASH_SHIFT); } prev[strstart & WMASK]=match=head[hash]; head[hash]=(short)strstart; ins_h=hash; return match & 0xffff; } private void slideWindow(){ System.arraycopy(window,WSIZE,window,0,WSIZE); matchStart-=WSIZE; strstart-=WSIZE; blockStart-=WSIZE; for (int i=0; i < HASH_SIZE; i++) { int m=head[i] & 0xffff; head[i]=m >= WSIZE ? (short)(m - WSIZE) : 0; } for (int i=0; i < WSIZE; i++) { int m=prev[i] & 0xffff; prev[i]=m >= WSIZE ? (short)(m - WSIZE) : 0; } } /** * Fill the window when the lookahead becomes insufficient. * Updates strstart and lookahead. * OUT assertions: strstart + lookahead <= 2*WSIZE * lookahead >= MIN_LOOKAHEAD or inputOff == inputEnd */ private void fillWindow(){ if (strstart >= WSIZE + MAX_DIST) slideWindow(); while (lookahead < DeflaterConstants.MIN_LOOKAHEAD && inputOff < inputEnd) { int more=2 * WSIZE - lookahead - strstart; if (more > inputEnd - inputOff) more=inputEnd - inputOff; System.arraycopy(inputBuf,inputOff,window,strstart + lookahead,more); this.hook28(more); inputOff+=more; totalIn+=more; lookahead+=more; } if (lookahead >= MIN_MATCH) updateHash(); } /** * Find the best (longest) string in the window matching the * string starting at strstart. * Preconditions: * strstart + MAX_MATCH <= window.length. * @param curMatch */ private boolean findLongestMatch(int curMatch){ int chainLength=this.max_chain; int niceLength=this.niceLength; short[] prev=this.prev; int scan=this.strstart; int match; int best_end=this.strstart + matchLen; int best_len=Math.max(matchLen,MIN_MATCH - 1); int limit=Math.max(strstart - MAX_DIST,0); int strend=scan + MAX_MATCH - 1; byte scan_end1=window[best_end - 1]; byte scan_end=window[best_end]; if (best_len >= this.goodLength) chainLength>>=2; if (niceLength > lookahead) niceLength=lookahead; if (DeflaterConstants.DEBUGGING && strstart > 2 * WSIZE - MIN_LOOKAHEAD) throw new Error("need lookahead"); do { if (DeflaterConstants.DEBUGGING && curMatch >= strstart) throw new Error("future match"); if (window[curMatch + best_len] != scan_end || window[curMatch + best_len - 1] != scan_end1 || window[curMatch] != window[scan] || window[curMatch + 1] != window[scan + 1]) continue; match=curMatch + 2; scan+=2; while (window[++scan] == window[++match] && window[++scan] == window[++match] && window[++scan] == window[++match] && window[++scan] == window[++match] && window[++scan] == window[++match] && window[++scan] == window[++match] && window[++scan] == window[++match] && window[++scan] == window[++match] && scan < strend) ; if (scan > best_end) { matchStart=curMatch; best_end=scan; best_len=scan - strstart; if (best_len >= niceLength) break; scan_end1=window[best_end - 1]; scan_end=window[best_end]; } scan=strstart; } while ((curMatch=(prev[curMatch & WMASK] & 0xffff)) > limit && --chainLength != 0); matchLen=Math.min(best_len,lookahead); return matchLen >= MIN_MATCH; } void setDictionary(byte[] buffer,int offset,int length){ if (DeflaterConstants.DEBUGGING && strstart != 1) throw new IllegalStateException("strstart not 1"); this.hook29(buffer,offset,length); if (length < MIN_MATCH) return; if (length > MAX_DIST) { offset+=length - MAX_DIST; length=MAX_DIST; } System.arraycopy(buffer,offset,window,strstart,length); updateHash(); length--; while (--length > 0) { insertString(); strstart++; } strstart+=2; blockStart=strstart; } private boolean deflateStored(boolean flush,boolean finish){ if (!flush && lookahead == 0) return false; strstart+=lookahead; lookahead=0; int storedLen=strstart - blockStart; if ((storedLen >= DeflaterConstants.MAX_BLOCK_SIZE) || (blockStart < WSIZE && storedLen >= MAX_DIST) || flush) { boolean lastBlock=finish; if (storedLen > DeflaterConstants.MAX_BLOCK_SIZE) { storedLen=DeflaterConstants.MAX_BLOCK_SIZE; lastBlock=false; } if (DeflaterConstants.DEBUGGING) System.err.println("storedBlock[" + storedLen + ","+ lastBlock+ "]"); huffman.flushStoredBlock(window,blockStart,storedLen,lastBlock); blockStart+=storedLen; return !lastBlock; } return true; } private boolean deflateFast(boolean flush,boolean finish){ if (lookahead < MIN_LOOKAHEAD && !flush) return false; while (lookahead >= MIN_LOOKAHEAD || flush) { if (lookahead == 0) { huffman.flushBlock(window,blockStart,strstart - blockStart,finish); blockStart=strstart; return false; } if (strstart > 2 * WSIZE - MIN_LOOKAHEAD) { slideWindow(); } int hashHead; if (lookahead >= MIN_MATCH && (hashHead=insertString()) != 0 && strategy != Deflater.HUFFMAN_ONLY && strstart - hashHead <= MAX_DIST && findLongestMatch(hashHead)) { if (DeflaterConstants.DEBUGGING) { for (int i=0; i < matchLen; i++) { if (window[strstart + i] != window[matchStart + i]) throw new Error(); } } boolean full=huffman.tallyDist(strstart - matchStart,matchLen); lookahead-=matchLen; if (matchLen <= max_lazy && lookahead >= MIN_MATCH) { while (--matchLen > 0) { strstart++; insertString(); } strstart++; } else { strstart+=matchLen; if (lookahead >= MIN_MATCH - 1) updateHash(); } matchLen=MIN_MATCH - 1; if (!full) continue; } else { huffman.tallyLit(window[strstart] & 0xff); strstart++; lookahead--; } if (huffman.isFull()) { boolean lastBlock=finish && lookahead == 0; huffman.flushBlock(window,blockStart,strstart - blockStart,lastBlock); blockStart=strstart; return !lastBlock; } } return true; } private boolean deflateSlow(boolean flush,boolean finish){ if (lookahead < MIN_LOOKAHEAD && !flush) return false; while (lookahead >= MIN_LOOKAHEAD || flush) { if (lookahead == 0) { if (prevAvailable) huffman.tallyLit(window[strstart - 1] & 0xff); prevAvailable=false; if (DeflaterConstants.DEBUGGING && !flush) throw new Error("Not flushing, but no lookahead"); huffman.flushBlock(window,blockStart,strstart - blockStart,finish); blockStart=strstart; return false; } if (strstart >= 2 * WSIZE - MIN_LOOKAHEAD) { slideWindow(); } int prevMatch=matchStart; int prevLen=matchLen; if (lookahead >= MIN_MATCH) { int hashHead=insertString(); if (strategy != Deflater.HUFFMAN_ONLY && hashHead != 0 && strstart - hashHead <= MAX_DIST && findLongestMatch(hashHead)) { if (matchLen <= 5 && (strategy == Deflater.FILTERED || (matchLen == MIN_MATCH && strstart - matchStart > TOO_FAR))) { matchLen=MIN_MATCH - 1; } } } if (prevLen >= MIN_MATCH && matchLen <= prevLen) { if (DeflaterConstants.DEBUGGING) { for (int i=0; i < matchLen; i++) { if (window[strstart - 1 + i] != window[prevMatch + i]) throw new Error(); } } huffman.tallyDist(strstart - 1 - prevMatch,prevLen); prevLen-=2; do { strstart++; lookahead--; if (lookahead >= MIN_MATCH) insertString(); } while (--prevLen > 0); strstart++; lookahead--; prevAvailable=false; matchLen=MIN_MATCH - 1; } else { if (prevAvailable) huffman.tallyLit(window[strstart - 1] & 0xff); prevAvailable=true; strstart++; lookahead--; } if (huffman.isFull()) { int len=strstart - blockStart; if (prevAvailable) len--; boolean lastBlock=(finish && lookahead == 0 && !prevAvailable); huffman.flushBlock(window,blockStart,len,lastBlock); blockStart+=len; return !lastBlock; } } return true; } public boolean deflate(boolean flush,boolean finish){ boolean progress; do { fillWindow(); boolean canFlush=flush && inputOff == inputEnd; if (DeflaterConstants.DEBUGGING) System.err.println("window: [" + blockStart + ","+ strstart+ ","+ lookahead+ "], "+ comprFunc+ ","+ canFlush); switch (comprFunc) { case DEFLATE_STORED: progress=deflateStored(canFlush,finish); break; case DEFLATE_FAST: progress=deflateFast(canFlush,finish); break; case DEFLATE_SLOW: progress=deflateSlow(canFlush,finish); break; default : throw new Error(); } } while (pending.isFlushed() && progress); return progress; } public void setInput(byte[] buf,int off,int len){ if (inputOff < inputEnd) throw new IllegalStateException("Old input was not completely processed"); int end=off + len; if (0 > off || off > end || end > buf.length) throw new ArrayIndexOutOfBoundsException(); inputBuf=buf; inputOff=off; inputEnd=end; } public final boolean needsInput(){ return inputEnd == inputOff; } protected void hook26(){ } protected void hook27(){ } protected void hook28(int more){ } protected void hook29(byte[] buffer,int offset,int length){ } }