| /* |
| * LZMADecoder |
| * |
| * Authors: Lasse Collin <lasse.collin@tukaani.org> |
| * Igor Pavlov <http://7-zip.org/> |
| * |
| * This file has been put into the public domain. |
| * You can do whatever you want with this file. |
| */ |
| |
| package org.tukaani.xz.lzma; |
| |
| import java.io.IOException; |
| import org.tukaani.xz.lz.LZDecoder; |
| import org.tukaani.xz.rangecoder.RangeDecoder; |
| |
| public final class LZMADecoder extends LZMACoder { |
| private final LZDecoder lz; |
| private final RangeDecoder rc; |
| private final LiteralDecoder literalDecoder; |
| private final LengthDecoder matchLenDecoder = new LengthDecoder(); |
| private final LengthDecoder repLenDecoder = new LengthDecoder(); |
| |
| public LZMADecoder(LZDecoder lz, RangeDecoder rc, int lc, int lp, int pb) { |
| super(pb); |
| this.lz = lz; |
| this.rc = rc; |
| this.literalDecoder = new LiteralDecoder(lc, lp); |
| reset(); |
| } |
| |
| public void reset() { |
| super.reset(); |
| literalDecoder.reset(); |
| matchLenDecoder.reset(); |
| repLenDecoder.reset(); |
| } |
| |
| /** |
| * Returns true if LZMA end marker was detected. It is encoded as |
| * the maximum match distance which with signed ints becomes -1. This |
| * function is needed only for LZMA1. LZMA2 doesn't use the end marker |
| * in the LZMA layer. |
| */ |
| public boolean endMarkerDetected() { |
| return reps[0] == -1; |
| } |
| |
| public void decode() throws IOException { |
| lz.repeatPending(); |
| |
| while (lz.hasSpace()) { |
| int posState = lz.getPos() & posMask; |
| |
| if (rc.decodeBit(isMatch[state.get()], posState) == 0) { |
| literalDecoder.decode(); |
| } else { |
| int len = rc.decodeBit(isRep, state.get()) == 0 |
| ? decodeMatch(posState) |
| : decodeRepMatch(posState); |
| |
| // NOTE: With LZMA1 streams that have the end marker, |
| // this will throw CorruptedInputException. LZMAInputStream |
| // handles it specially. |
| lz.repeat(reps[0], len); |
| } |
| } |
| |
| rc.normalize(); |
| } |
| |
| private int decodeMatch(int posState) throws IOException { |
| state.updateMatch(); |
| |
| reps[3] = reps[2]; |
| reps[2] = reps[1]; |
| reps[1] = reps[0]; |
| |
| int len = matchLenDecoder.decode(posState); |
| int distSlot = rc.decodeBitTree(distSlots[getDistState(len)]); |
| |
| if (distSlot < DIST_MODEL_START) { |
| reps[0] = distSlot; |
| } else { |
| int limit = (distSlot >> 1) - 1; |
| reps[0] = (2 | (distSlot & 1)) << limit; |
| |
| if (distSlot < DIST_MODEL_END) { |
| reps[0] |= rc.decodeReverseBitTree( |
| distSpecial[distSlot - DIST_MODEL_START]); |
| } else { |
| reps[0] |= rc.decodeDirectBits(limit - ALIGN_BITS) |
| << ALIGN_BITS; |
| reps[0] |= rc.decodeReverseBitTree(distAlign); |
| } |
| } |
| |
| return len; |
| } |
| |
| private int decodeRepMatch(int posState) throws IOException { |
| if (rc.decodeBit(isRep0, state.get()) == 0) { |
| if (rc.decodeBit(isRep0Long[state.get()], posState) == 0) { |
| state.updateShortRep(); |
| return 1; |
| } |
| } else { |
| int tmp; |
| |
| if (rc.decodeBit(isRep1, state.get()) == 0) { |
| tmp = reps[1]; |
| } else { |
| if (rc.decodeBit(isRep2, state.get()) == 0) { |
| tmp = reps[2]; |
| } else { |
| tmp = reps[3]; |
| reps[3] = reps[2]; |
| } |
| |
| reps[2] = reps[1]; |
| } |
| |
| reps[1] = reps[0]; |
| reps[0] = tmp; |
| } |
| |
| state.updateLongRep(); |
| |
| return repLenDecoder.decode(posState); |
| } |
| |
| |
| private class LiteralDecoder extends LiteralCoder { |
| private final LiteralSubdecoder[] subdecoders; |
| |
| LiteralDecoder(int lc, int lp) { |
| super(lc, lp); |
| |
| subdecoders = new LiteralSubdecoder[1 << (lc + lp)]; |
| for (int i = 0; i < subdecoders.length; ++i) |
| subdecoders[i] = new LiteralSubdecoder(); |
| } |
| |
| void reset() { |
| for (int i = 0; i < subdecoders.length; ++i) |
| subdecoders[i].reset(); |
| } |
| |
| void decode() throws IOException { |
| int i = getSubcoderIndex(lz.getByte(0), lz.getPos()); |
| subdecoders[i].decode(); |
| } |
| |
| |
| private class LiteralSubdecoder extends LiteralSubcoder { |
| void decode() throws IOException { |
| int symbol = 1; |
| |
| if (state.isLiteral()) { |
| do { |
| symbol = (symbol << 1) | rc.decodeBit(probs, symbol); |
| } while (symbol < 0x100); |
| |
| } else { |
| int matchByte = lz.getByte(reps[0]); |
| int offset = 0x100; |
| int matchBit; |
| int bit; |
| |
| do { |
| matchByte <<= 1; |
| matchBit = matchByte & offset; |
| bit = rc.decodeBit(probs, offset + matchBit + symbol); |
| symbol = (symbol << 1) | bit; |
| offset &= (0 - bit) ^ ~matchBit; |
| } while (symbol < 0x100); |
| } |
| |
| lz.putByte((byte)symbol); |
| state.updateLiteral(); |
| } |
| } |
| } |
| |
| |
| private class LengthDecoder extends LengthCoder { |
| int decode(int posState) throws IOException { |
| if (rc.decodeBit(choice, 0) == 0) |
| return rc.decodeBitTree(low[posState]) + MATCH_LEN_MIN; |
| |
| if (rc.decodeBit(choice, 1) == 0) |
| return rc.decodeBitTree(mid[posState]) |
| + MATCH_LEN_MIN + LOW_SYMBOLS; |
| |
| return rc.decodeBitTree(high) |
| + MATCH_LEN_MIN + LOW_SYMBOLS + MID_SYMBOLS; |
| } |
| } |
| } |