| /* DeflaterEngine.java -- | |
| Copyright (C) 2001, 2004 Free Software Foundation, Inc. | |
| This file is part of GNU Classpath. | |
| GNU Classpath is free software; you can redistribute it and/or modify | |
| it under the terms of the GNU General Public License as published by | |
| the Free Software Foundation; either version 2, or (at your option) | |
| any later version. | |
| GNU Classpath is distributed in the hope that it will be useful, but | |
| WITHOUT ANY WARRANTY; without even the implied warranty of | |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
| General Public License for more details. | |
| You should have received a copy of the GNU General Public License | |
| along with GNU Classpath; see the file COPYING. If not, write to the | |
| Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | |
| 02110-1301 USA. | |
| Linking this library statically or dynamically with other modules is | |
| making a combined work based on this library. Thus, the terms and | |
| conditions of the GNU General Public License cover the whole | |
| combination. | |
| As a special exception, the copyright holders of this library give you | |
| permission to link this library with independent modules to produce an | |
| executable, regardless of the license terms of these independent | |
| modules, and to copy and distribute the resulting executable under | |
| terms of your choice, provided that you also meet, for each linked | |
| independent module, the terms and conditions of the license of that | |
| module. An independent module is a module which is not derived from | |
| or based on this library. If you modify this library, you may extend | |
| this exception to your version of the library, but you are not | |
| obligated to do so. If you do not wish to do so, delete this | |
| exception statement from your version. */ | |
| package java.util.zip; | |
| 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 int 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; | |
| /** The adler checksum */ | |
| private Adler32 adler; | |
| /* DEFLATE ALGORITHM: | |
| * | |
| * The uncompressed stream is inserted into the window array. When | |
| * the window array is full the first half is thrown away and the | |
| * second half is copied to the beginning. | |
| * | |
| * The head array is a hash table. Three characters build a hash value | |
| * and they the value points to the corresponding index in window of | |
| * the last string with this hash. The prev array implements a | |
| * linked list of matches with the same hash: prev[index & WMASK] points | |
| * to the previous index with the same hash. | |
| * | |
| * | |
| */ | |
| DeflaterEngine(DeflaterPending pending) { | |
| this.pending = pending; | |
| huffman = new DeflaterHuffman(pending); | |
| adler = new Adler32(); | |
| window = new byte[2*WSIZE]; | |
| head = new short[HASH_SIZE]; | |
| prev = new short[WSIZE]; | |
| /* We start at index 1, to avoid a implementation deficiency, that | |
| * we cannot build a repeat pattern at index 0. | |
| */ | |
| blockStart = strstart = 1; | |
| } | |
| public void reset() | |
| { | |
| huffman.reset(); | |
| adler.reset(); | |
| 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 void resetAdler() | |
| { | |
| adler.reset(); | |
| } | |
| public final int getAdler() | |
| { | |
| int chksum = (int) adler.getValue(); | |
| return chksum; | |
| } | |
| public final int 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 InternalError("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; | |
| /* Slide the hash table (could be avoided with 32 bit values | |
| * at the expense of memory usage). | |
| */ | |
| for (int i = 0; i < HASH_SIZE; i++) | |
| { | |
| int m = head[i] & 0xffff; | |
| head[i] = m >= WSIZE ? (short) (m - WSIZE) : 0; | |
| } | |
| /* Slide the prev table. | |
| */ | |
| 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 the window is almost full and there is insufficient lookahead, | |
| * move the upper half to the lower one to make room in the upper half. | |
| */ | |
| if (strstart >= WSIZE + MAX_DIST) | |
| slideWindow(); | |
| /* If there is not enough lookahead, but still some input left, | |
| * read in the input | |
| */ | |
| 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); | |
| adler.update(inputBuf, inputOff, 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]; | |
| /* Do not waste too much time if we already have a good match: */ | |
| if (best_len >= this.goodLength) | |
| chainLength >>= 2; | |
| /* Do not look for matches beyond the end of the input. This is necessary | |
| * to make deflate deterministic. | |
| */ | |
| if (niceLength > lookahead) | |
| niceLength = lookahead; | |
| if (DeflaterConstants.DEBUGGING | |
| && strstart > 2*WSIZE - MIN_LOOKAHEAD) | |
| throw new InternalError("need lookahead"); | |
| do { | |
| if (DeflaterConstants.DEBUGGING && curMatch >= strstart) | |
| throw new InternalError("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; | |
| /* We check for insufficient lookahead only every 8th comparison; | |
| * the 256th check will be made at strstart+258. | |
| */ | |
| 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) { | |
| // if (DeflaterConstants.DEBUGGING && ins_h == 0) | |
| // System.err.println("Found match: "+curMatch+"-"+(scan-strstart)); | |
| 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"); | |
| adler.update(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) | |
| /* Block is full */ | |
| || (blockStart < WSIZE && storedLen >= MAX_DIST) | |
| /* Block may move out of window */ | |
| || 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) | |
| { | |
| /* We are flushing everything */ | |
| huffman.flushBlock(window, blockStart, strstart - blockStart, | |
| finish); | |
| blockStart = strstart; | |
| return false; | |
| } | |
| if (strstart > 2 * WSIZE - MIN_LOOKAHEAD) | |
| { | |
| /* slide window, as findLongestMatch need this. | |
| * This should only happen when flushing and the window | |
| * is almost full. | |
| */ | |
| slideWindow(); | |
| } | |
| int hashHead; | |
| if (lookahead >= MIN_MATCH | |
| && (hashHead = insertString()) != 0 | |
| && strategy != Deflater.HUFFMAN_ONLY | |
| && strstart - hashHead <= MAX_DIST | |
| && findLongestMatch(hashHead)) | |
| { | |
| /* longestMatch sets matchStart and matchLen */ | |
| if (DeflaterConstants.DEBUGGING) | |
| { | |
| for (int i = 0 ; i < matchLen; i++) | |
| { | |
| if (window[strstart+i] != window[matchStart + i]) | |
| throw new InternalError(); | |
| } | |
| } | |
| 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 | |
| { | |
| /* No match found */ | |
| 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; | |
| /* We are flushing everything */ | |
| if (DeflaterConstants.DEBUGGING && !flush) | |
| throw new InternalError("Not flushing, but no lookahead"); | |
| huffman.flushBlock(window, blockStart, strstart - blockStart, | |
| finish); | |
| blockStart = strstart; | |
| return false; | |
| } | |
| if (strstart >= 2 * WSIZE - MIN_LOOKAHEAD) | |
| { | |
| /* slide window, as findLongestMatch need this. | |
| * This should only happen when flushing and the window | |
| * is almost full. | |
| */ | |
| 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)) | |
| { | |
| /* longestMatch sets matchStart and matchLen */ | |
| /* Discard match if too small and too far away */ | |
| if (matchLen <= 5 | |
| && (strategy == Deflater.FILTERED | |
| || (matchLen == MIN_MATCH | |
| && strstart - matchStart > TOO_FAR))) { | |
| matchLen = MIN_MATCH - 1; | |
| } | |
| } | |
| } | |
| /* previous match was better */ | |
| 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 InternalError(); | |
| } | |
| } | |
| 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 InternalError(); | |
| } | |
| } | |
| while (pending.isFlushed() /* repeat while we have no pending output */ | |
| && progress); /* and progress was made */ | |
| 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; | |
| /* We want to throw an ArrayIndexOutOfBoundsException early. The | |
| * check is very tricky: it also handles integer wrap around. | |
| */ | |
| if (0 > off || off > end || end > buf.length) | |
| throw new ArrayIndexOutOfBoundsException(); | |
| inputBuf = buf; | |
| inputOff = off; | |
| inputEnd = end; | |
| } | |
| public final boolean needsInput() | |
| { | |
| return inputEnd == inputOff; | |
| } | |
| } |