public class ByteBuffer {
    int _capacity;
    byte[] _items;

    public ByteBuffer() {
        _capacity = 0;
        _items = null;
    }

    public byte[] data() {
        return _items;
    }

    public int GetCapacity() {
        return _capacity;
    }

    public void SetCapacity(int newCapacity) {
        if (newCapacity == _capacity) {
            return;
        }

        byte[] newBuffer;
        if (newCapacity > 0) {
            newBuffer = new byte[newCapacity];
            if (_capacity > 0) {
                int len = _capacity;
                if (newCapacity < len) len = newCapacity;

                System.arraycopy(_items, 0, newBuffer, 0, len);
            }
        } else {
            newBuffer = null;
        }

        _items = newBuffer;
        _capacity = newCapacity;
    }
} public class LimitedSequentialInStream extends InputStream {

    private static final int EOF = -1;

    InputStream _stream;
    long _size;
    long _pos;
    boolean _wasFinished;

    public LimitedSequentialInStream() {
    }

    public void setStream(java.io.InputStream stream) {
        _stream = stream;
    }

    public void init(long streamSize) {
        _size = streamSize;
        _pos = 0;
        _wasFinished = false;
    }

    public int read() throws java.io.IOException {
        int ret = _stream.read();
        if (ret == EOF) _wasFinished = true;
        return ret;
    }

    public int read(byte[] data, int off, int size) throws java.io.IOException {
        long sizeToRead2 = (_size - _pos);
        if (size < sizeToRead2) sizeToRead2 = size;

        int sizeToRead = (int) sizeToRead2;

        if (sizeToRead > 0) {
            int realProcessedSize = _stream.read(data, off, sizeToRead);
            if (realProcessedSize == EOF) {
                _wasFinished = true;
                return EOF;
            }
            _pos += realProcessedSize;
            return realProcessedSize;
        }

        return EOF;
    }
} public class LockedInStream {
    IInStream _stream;

    public LockedInStream() {
    }

    public void Init(IInStream stream) {
        _stream = stream;
    }

    public synchronized int read(long startPos, byte[] data, int size) throws java.io.IOException {
        _stream.seekFromBegin(startPos);
        return _stream.read(data, 0, size);
    }

    public synchronized int read(long startPos, byte[] data, int off, int size) throws java.io.IOException {
        _stream.seekFromBegin(startPos);
        return _stream.read(data, off, size);
    }
} public class LockedSequentialInStreamImp extends java.io.InputStream {
    LockedInStream _lockedInStream;
    long _pos;

    public LockedSequentialInStreamImp() {
    }

    public void Init(LockedInStream lockedInStream, long startPos) {
        _lockedInStream = lockedInStream;
        _pos = startPos;
    }

    public int read() throws java.io.IOException {
        throw new java.io.IOException("LockedSequentialInStreamImp : read() not implemented");
    }

    public int read(byte[] data, int off, int size) throws java.io.IOException {
        int realProcessedSize = _lockedInStream.read(_pos, data, off, size);
        if (realProcessedSize == -1) return -1; // EOF

        _pos += realProcessedSize;

        return realProcessedSize;
    }

} public class LongVector {
    private static final int CAPACITY_INCR = 10;

    private long[] data = new long[CAPACITY_INCR];
    int elt = 0;

    public LongVector() {
    }

    public int size() {
        return elt;
    }

    private void ensureCapacity(int minCapacity) {
        int oldCapacity = data.length;
        if (minCapacity > oldCapacity) {
            long[] oldData = data;
            int newCapacity = oldCapacity + CAPACITY_INCR;
            if (newCapacity < minCapacity) {
                newCapacity = minCapacity;
            }
            data = new long[newCapacity];
            System.arraycopy(oldData, 0, data, 0, elt);
        }
    }

    public long get(int index) {
        if (index >= elt) {
            throw new ArrayIndexOutOfBoundsException(index);
        }

        return data[index];
    }

    public void Reserve(int s) {
        ensureCapacity(s);
    }

    public void add(long b) {
        ensureCapacity(elt + 1);
        data[elt++] = b;
    }

    public void clear() {
        elt = 0;
    }

    public boolean isEmpty() {
        return elt == 0;
    }

    public long Back() {
        if (elt < 1) {
            throw new ArrayIndexOutOfBoundsException(0);
        }

        return data[elt - 1];
    }

    public void DeleteBack() {
        remove(elt - 1);
    }

    public long remove(int index) {
        if (index >= elt) {
            throw new ArrayIndexOutOfBoundsException(index);
        }
        long oldValue = data[index];

        int numMoved = elt - index - 1;
        if (numMoved > 0) {
            System.arraycopy(data, index + 1, data, index, numMoved);
        }

        return oldValue;
    }

} public class ObjectVector<E> extends ArrayList<E> {

    public ObjectVector() {
        super();
    }

    public void reserve(int s) {
        ensureCapacity(s);
    }

    public E last() {
        return get(size() - 1);
    }

    public E first() {
        return get(0);
    }

    public E popLast() {
        return remove(size() - 1);
    }
} public class BindPair {

    public final int inIndex;
    public final int outIndex;


    public BindPair(int inIndex, int outIndex) {
        this.inIndex = inIndex;
        this.outIndex = outIndex;
    }

    public int getInIndex() {
        return inIndex;
    }

    public int getOutIndex() {
        return outIndex;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        BindPair bindPair = (BindPair) o;
        return inIndex == bindPair.inIndex && outIndex == bindPair.outIndex;
    }

    @Override
    public int hashCode() {
        int result = inIndex;
        result = 31 * result + outIndex;
        return result;
    }
} public interface CoderMixer2 {

    void setBindInfo(BindInfo bindInfo);

    void setCoderInfo(int coderIndex, LongVector inSizes, LongVector outSizes);
} public class CoderMixer2ST implements ICompressCoder2, CoderMixer2 {

    private BindInfo bindInfo = new BindInfo();
    private ObjectVector<STCoderInfo> coders = new ObjectVector<>();


    public void setBindInfo(BindInfo bindInfo) {
        this.bindInfo = bindInfo;
    }

    public void addCoderCommon(boolean isMain) {
        CoderStreamsInfo csi = bindInfo.coders.get(coders.size());
        coders.add(new STCoderInfo(csi.getInStreamsCount(), csi.getOutStreamsCount(), isMain));
    }

    public void addCoder2(ICompressCoder2 coder, boolean isMain) {
        addCoderCommon(isMain);
        coders.last().Coder2 = coder;
    }

    public void addCoder(ICompressCoder coder, boolean isMain) {
        addCoderCommon(isMain);
        coders.last().Coder = coder;
    }

    public void setCoderInfo(int coderIndex, LongVector inSizes, LongVector outSizes) {
        coders.get(coderIndex).setCoderInfo(inSizes, outSizes);
    }

    public InputStream getInStream(ObjectVector<InputStream> inStreams, int streamIndex) throws IOException {
        for (int i = 0; i < bindInfo.inStreams.size(); i++) {
            if (bindInfo.inStreams.get(i) == streamIndex) {
                return inStreams.get(i);
            }
        }

        int binderIndex = bindInfo.findBinderForInStream(streamIndex);
        if (binderIndex < 0) {
            throw new IOException("Can't find binder for input stream");
        }

        int coderIndex = bindInfo.findOutStream(bindInfo.bindPairs.get(binderIndex).outIndex);
        CoderInfo coder = coders.get(coderIndex);
        if (!(coder.Coder instanceof InputStream) || !(coder.Coder instanceof ICompressSetInStream)
                || coder.NumInStreams > 1)
        {
            throw new IOException("Coder is not implemented");
        }

        ICompressSetInStream setInStream = (ICompressSetInStream) coder.Coder;
        int startIndex = bindInfo.getCoderInStreamIndex(coderIndex);

        for (int i = 0; i < coder.NumInStreams; i++) {
            setInStream.setInStream(getInStream(inStreams, startIndex + i));
        }

        return (InputStream) coder.Coder;
    }

    public OutputStream getOutStream(ObjectVector<OutputStream> outStreams, int streamIndex) throws IOException {
        for (int i = 0; i < bindInfo.outStreams.size(); i++) {
            if (bindInfo.outStreams.get(i) == streamIndex) {
                return outStreams.get(i);
            }
        }
        int binderIndex = bindInfo.findBinderForOutStream(streamIndex);
        if (binderIndex < 0) {
            throw new IOException("Can't find binder for out stream");
        }

        int coderIndex = bindInfo.findInStream(bindInfo.bindPairs.get(binderIndex).inIndex);

        CoderInfo coder = coders.get(coderIndex);
        if (!(coder.Coder instanceof OutputStream) || !(coder.Coder instanceof ICompressSetOutStream)
                || coder.NumOutStreams > 1)
        {
            throw new IOException("Coder is not implemented");
        }

        ICompressSetOutStream setOutStream = (ICompressSetOutStream) coder.Coder;
        int startIndex = bindInfo.GetCoderOutStreamIndex(coderIndex);

        for (int i = 0; i < coder.NumOutStreams; i++) {
            setOutStream.setOutStream(getOutStream(outStreams, startIndex + i));
        }

        return (OutputStream) coder.Coder;
    }

    public Result code(
            ObjectVector<InputStream> inStreams,
            int numInStreams,
            ObjectVector<OutputStream> outStreams,
            int numOutStreams) throws IOException
    {
        if (numInStreams != bindInfo.inStreams.size() || numOutStreams != bindInfo.outStreams.size()) {
            return Result.ERROR_INVALID_ARGS;
        }

        // Find main coder
        int _mainCoderIndex = -1;
        for (int i = 0; i < coders.size(); i++) {
            if (coders.get(i).IsMain) {
                _mainCoderIndex = i;
                break;
            }
        }
        if (_mainCoderIndex < 0) {
            for (int i = 0; i < coders.size(); i++) {
                if (coders.get(i).NumInStreams > 1) {
                    if (_mainCoderIndex >= 0) {
                        return Result.ERROR_NOT_IMPLEMENTED;
                    }
                    _mainCoderIndex = i;
                }
            }
        }
        if (_mainCoderIndex < 0) {
            _mainCoderIndex = 0;
        }

        CoderInfo mainCoder = coders.get(_mainCoderIndex);

        ObjectVector<InputStream> seqInStreams = new ObjectVector<>();
        ObjectVector<OutputStream> seqOutStreams = new ObjectVector<>();
        int startInIndex = bindInfo.getCoderInStreamIndex(_mainCoderIndex);
        int startOutIndex = bindInfo.GetCoderOutStreamIndex(_mainCoderIndex);
        for (int i = 0; i < mainCoder.NumInStreams; i++) {
            seqInStreams.add(getInStream(inStreams, startInIndex + i));
        }
        for (int i = 0; i < mainCoder.NumOutStreams; i++) {
            seqOutStreams.add(getOutStream(outStreams, startOutIndex + i));
        }
        ObjectVector<InputStream> seqInStreamsSpec = new ObjectVector<>();
        ObjectVector<OutputStream> seqOutStreamsSpec = new ObjectVector<>();
        for (int i = 0; i < mainCoder.NumInStreams; i++) {
            seqInStreamsSpec.add(seqInStreams.get(i));
        }
        for (int i = 0; i < mainCoder.NumOutStreams; i++) {
            seqOutStreamsSpec.add(seqOutStreams.get(i));
        }

        for (int i = 0; i < coders.size(); i++) {
            if (i == _mainCoderIndex) {
                continue;
            }

            CoderInfo coder = coders.get(i);
            if (coder instanceof ICompressSetOutStreamSize) {
                ICompressSetOutStreamSize setOutStreamSize = (ICompressSetOutStreamSize) coder.Coder;
                setOutStreamSize.setOutStreamSize(coder.OutSizePointers.get(0));
            }
        }
        if (mainCoder.Coder != null) {
            mainCoder.Coder.code(
                    seqInStreamsSpec.get(0),
                    seqOutStreamsSpec.get(0),
                    mainCoder.OutSizePointers.get(0));
        } else {
            Result res = mainCoder.Coder2.code(
                    seqInStreamsSpec,
                    mainCoder.NumInStreams,
                    seqOutStreamsSpec,
                    mainCoder.NumOutStreams);
            if (res != Result.OK) return res;
        }

        OutputStream stream = seqOutStreams.first();
        stream.flush();

        return Result.OK;
    }

    public void close() {
    }
} public class CoderStreamsInfo {

    private final int inStreamsCount;
    private final int outStreamsCount;


    public CoderStreamsInfo(int inStreamsCount, int outStreamsCount) {
        this.inStreamsCount = inStreamsCount;
        this.outStreamsCount = outStreamsCount;
    }

    public int getInStreamsCount() {
        return inStreamsCount;
    }

    public int getOutStreamsCount() {
        return outStreamsCount;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        CoderStreamsInfo that = (CoderStreamsInfo) o;
        return inStreamsCount == that.inStreamsCount && outStreamsCount == that.outStreamsCount;
    }

    @Override
    public int hashCode() {
        int result = inStreamsCount;
        result = 31 * result + outStreamsCount;
        return result;
    }
} public class FilterCoder extends OutputStream implements ICompressCoder, ICompressSetOutStream {

    public ICompressFilter filter = null;

    OutputStream _outStream = null;
    int _bufferPos;

    boolean _outSizeIsDefined;
    long _outSize;
    long _nowPos64;

    public void code(InputStream inStream, OutputStream outStream, long outSize) throws IOException {
        throw new IOException("Not implemented");
    }

    public void write(int b) {
        throw new UnknownError("FilterCoder write");
    }

    public void write(byte b[], int off, int size) throws IOException {
        if (b == null) {
            throw new NullPointerException();
        } else if ((off < 0) || (off > b.length) || (size < 0) ||
                ((off + size) > b.length) || ((off + size) < 0))
        {
            throw new IndexOutOfBoundsException();
        } else if (size == 0) {
            return;
        }

        if (off != 0) throw new IOException("FilterCoder - off <> 0");

        int cur_off = 0;
        while (size > 0) {
            int sizeMax = kBufferSize - _bufferPos;
            int sizeTemp = size;
            if (sizeTemp > sizeMax) {
                sizeTemp = sizeMax;
            }
            System.arraycopy(b, cur_off, _buffer, _bufferPos, sizeTemp);
            size -= sizeTemp;
            cur_off = cur_off + sizeTemp;
            int endPos = _bufferPos + sizeTemp;
            _bufferPos = filter.filter(_buffer, endPos);
            if (_bufferPos == 0) {
                _bufferPos = endPos;
                break;
            }
            if (_bufferPos > endPos) {
                if (size != 0) {
                    throw new IOException("FilterCoder - write() : size  <> 0"); // return Result.ERROR_FAIL;
                }
                break;
            }

            WriteWithLimit(_outStream, _bufferPos);

            int i = 0;
            while (_bufferPos < endPos) {
                _buffer[i++] = _buffer[_bufferPos++];
            }
            _bufferPos = i;
        }

        // return Result.OK;
    }

    void WriteWithLimit(OutputStream outStream, int size) throws IOException {
        if (_outSizeIsDefined) {
            long remSize = _outSize - _nowPos64;
            if (size > remSize) {
                size = (int) remSize;
            }
        }

        outStream.write(_buffer, 0, size);

        _nowPos64 += size;
    }

    byte[] _buffer;

    static final int kBufferSize = 1 << 17;

    public FilterCoder() {
        _buffer = new byte[kBufferSize];
    }


    public void setOutStream(OutputStream outStream) {
        _bufferPos = 0;
        _outStream = outStream;
        _nowPos64 = 0;
        _outSizeIsDefined = false;
        filter.init();
    }

    public void flush() throws IOException {
        if (_bufferPos != 0) {
            int endPos = filter.filter(_buffer, _bufferPos);
            if (endPos > _bufferPos) {
                for (; _bufferPos < endPos; _bufferPos++) {
                    _buffer[_bufferPos] = 0;
                }
                if (filter.filter(_buffer, endPos) != endPos) {
                    throw new IOException("FilterCoder - flush() : ERROR_FAIL");
                }
            }
            _outStream.write(_buffer, 0, _bufferPos);
            _bufferPos = 0;
        }
        _outStream.flush();
    }

    public void close() throws IOException {
        if (_outStream != null) _outStream.close();
        _outStream = null;
    }

} public class BCJ2_x86_Decoder implements ICompressCoder2 {

    public static final int kNumMoveBits = 5;

    InBuffer _mainInStream = new InBuffer();
    InBuffer _callStream = new InBuffer();
    InBuffer _jumpStream = new InBuffer();

    BitDecoder _statusE8Decoder[] = new BitDecoder[256];
    BitDecoder _statusE9Decoder = new BitDecoder(kNumMoveBits);
    BitDecoder _statusJccDecoder = new BitDecoder(kNumMoveBits);

    OutWindow _outStream = new OutWindow();
    Decoder _rangeDecoder = new Decoder();


    Result codeReal(
            ObjectVector<java.io.InputStream> inStreams,
            int numInStreams,
            ObjectVector<java.io.OutputStream> outStreams,
            int numOutStreams) throws java.io.IOException
    {

        if (numInStreams != 4 || numOutStreams != 1) {
            return Result.ERROR_INVALID_ARGS;
        }

        _mainInStream.Create(1 << 16);
        _callStream.Create(1 << 20);
        _jumpStream.Create(1 << 16);
        _rangeDecoder.Create(1 << 20);
        _outStream.Create(1 << 16);

        _mainInStream.SetStream(inStreams.get(0));
        _callStream.SetStream(inStreams.get(1));
        _jumpStream.SetStream(inStreams.get(2));
        _rangeDecoder.setStream(inStreams.get(3));
        _outStream.SetStream(outStreams.get(0));

        _mainInStream.Init();
        _callStream.Init();
        _jumpStream.Init();
        _rangeDecoder.Init();
        _outStream.Init();

        for (int i = 0; i < 256; i++) {
            _statusE8Decoder[i] = new BitDecoder(kNumMoveBits);
            _statusE8Decoder[i].Init();
        }
        _statusE9Decoder.Init();
        _statusJccDecoder.Init();

        int prevByte = 0;
        int processedBytes = 0;
        for (; ; ) {
            if (processedBytes > (1 << 20)) {
                processedBytes = 0;
            }

            processedBytes++;
            int b = _mainInStream.read();
            if (b == -1) {
                flush();
                return Result.OK;
            }
            _outStream.WriteByte(b);
            if ((b != 0xE8) && (b != 0xE9) && (!((prevByte == 0x0F) && ((b & 0xF0) == 0x80)))) {
                prevByte = b;
                continue;
            }

            boolean status;
            if (b == 0xE8) {
                status = (_statusE8Decoder[prevByte].decode(_rangeDecoder) == 1);
            } else if (b == 0xE9) {
                status = (_statusE9Decoder.decode(_rangeDecoder) == 1);
            } else {
                status = (_statusJccDecoder.decode(_rangeDecoder) == 1);
            }

            if (status) {
                int src;
                if (b == 0xE8) {
                    int b0 = _callStream.read();
                    src = b0 << 24;

                    b0 = _callStream.read();
                    src |= b0 << 16;

                    b0 = _callStream.read();
                    src |= b0 << 8;

                    b0 = _callStream.read();
                    if (b0 == -1) return Result.FALSE;
                    src |= b0;

                } else {
                    int b0 = _jumpStream.read();
                    src = b0 << 24;

                    b0 = _jumpStream.read();
                    src |= b0 << 16;

                    b0 = _jumpStream.read();
                    src |= b0 << 8;

                    b0 = _jumpStream.read();
                    if (b0 == -1) return Result.FALSE;
                    src |= b0;

                }
                int dest = src - ((int) _outStream.GetProcessedSize() + 4);
                _outStream.WriteByte(dest);
                _outStream.WriteByte((dest >> 8));
                _outStream.WriteByte((dest >> 16));
                _outStream.WriteByte((dest >> 24));
                prevByte = dest >> 24 & 0xFF;
                processedBytes += 4;
            } else {
                prevByte = b;
            }
        }
    }

    public void flush() throws java.io.IOException {
        _outStream.flush();
    }

    public Result code(
            ObjectVector<InputStream> inStreams,
            int numInStreams,
            ObjectVector<OutputStream> outStreams,
            int numOutStreams) throws IOException
    {
        try {
            return codeReal(inStreams, numInStreams, outStreams, numOutStreams);
        } finally {
            releaseStreams();
        }
    }

    void releaseStreams() throws java.io.IOException {
        _mainInStream.ReleaseStream();
        _callStream.ReleaseStream();
        _jumpStream.ReleaseStream();
        _rangeDecoder.releaseStream();
        _outStream.ReleaseStream();
    }

    public void close() throws java.io.IOException {
        releaseStreams();
    }

} public class BCJ_x86_Decoder implements ICompressFilter {

    int _bufferPos;
    int[] _prevMask = new int[1];
    int[] _prevPos = new int[1];

    static final boolean[] kMaskToAllowedStatus = {true, true, true, false, true, false, false, false};

    static final int[] kMaskToBitNumber = {0, 1, 2, 2, 3, 3, 3, 3};

    static boolean Test86MSByte(int b) {
        return ((b) == 0 || (b) == 0xFF);
    }

    static int x86_Convert(byte[] buffer, int endPos, int nowPos,
            int[] prevMask, int[] prevPos, boolean encoding)
    {
        int bufferPos = 0;
        int limit;

        if (endPos < 5) {
            return 0;
        }

        if (nowPos - prevPos[0] > 5) {
            prevPos[0] = nowPos - 5;
        }

        limit = endPos - 5;
        while (bufferPos <= limit) {
            int b = (buffer[bufferPos] & 0xFF);
            int offset;
            if (b != 0xE8 && b != 0xE9) {
                bufferPos++;
                continue;
            }
            offset = (nowPos + bufferPos - prevPos[0]);
            prevPos[0] = (nowPos + bufferPos);
            if (offset > 5) {
                prevMask[0] = 0;
            } else {
                for (int i = 0; i < offset; i++) {
                    prevMask[0] &= 0x77;
                    prevMask[0] <<= 1;
                }
            }
            b = (buffer[bufferPos + 4] & 0xFF);
            if (Test86MSByte(b) && kMaskToAllowedStatus[(prevMask[0] >> 1) & 0x7] &&
                    (prevMask[0] >>> 1) < 0x10)
            {
                int src =
                        (b << 24) |
                                ((buffer[bufferPos + 3] & 0xFF) << 16) |
                                ((buffer[bufferPos + 2] & 0xFF) << 8) |
                                (buffer[bufferPos + 1] & 0xFF);

                int dest;
                for (; ; ) {
                    int index;
                    if (encoding) {
                        dest = (nowPos + bufferPos + 5) + src;
                    } else {
                        dest = src - (nowPos + bufferPos + 5);
                    }
                    if (prevMask[0] == 0) {
                        break;
                    }
                    index = kMaskToBitNumber[prevMask[0] >>> 1];
                    b = (dest >> (24 - index * 8)) & 0xFF;
                    if (!Test86MSByte(b)) {
                        break;
                    }
                    src = dest ^ ((1 << (32 - index * 8)) - 1);
                }
                buffer[bufferPos + 4] = (byte) (~(((dest >> 24) & 1) - 1));
                buffer[bufferPos + 3] = (byte) (dest >> 16);
                buffer[bufferPos + 2] = (byte) (dest >> 8);
                buffer[bufferPos + 1] = (byte) dest;
                bufferPos += 5;
                prevMask[0] = 0;
            } else {
                bufferPos++;
                prevMask[0] |= 1;
                if (Test86MSByte(b)) {
                    prevMask[0] |= 0x10;
                }
            }
        }
        return bufferPos;
    }

    public int SubFilter(byte[] data, int size) {
        return x86_Convert(data, size, _bufferPos, _prevMask, _prevPos, false);
    }

    public void init() {
        _bufferPos = 0;
        _prevMask[0] = 0;
        _prevPos[0] = -5;
    }

    public int filter(byte[] data, int size) {
        int processedSize = SubFilter(data, size);
        _bufferPos += processedSize;
        return processedSize;
    }
} public interface ICompressCoder {

    void code(InputStream inStream, OutputStream outStream, long outSize) throws IOException;
} public interface ICompressCoder2 {

    public Result code(
            ObjectVector<InputStream> inStreams,
            int numInStreams,
            ObjectVector<OutputStream> outStreams,
            int numOutStreams) throws java.io.IOException;

    public void close() throws java.io.IOException;
} public interface ICompressFilter {

    void init();

    /**
     * if (outSize <= size): filter have converted outSize bytes
     * if (outSize > size): filter have not converted anything.
     * and it needs at least outSize bytes to convert one block
     * (it's for crypto block algorithms).
     *
     * @return outSize (UInt32)
     */
    int filter(byte[] data, int size);
} public abstract class IInStream extends InputStream {

    public abstract long seekFromBegin(long offset) throws IOException;

    public abstract long seekFromCurrent(long offset) throws IOException;
} public enum Result {
    OK,
    FALSE,

    ERROR_NOT_IMPLEMENTED,
    ERROR_FAIL,
    ERROR_INVALID_ARGS,
    ;
}
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import org.jamel.j7zip.common.ByteBuffer;
import org.jamel.j7zip.common.LimitedSequentialInStream;
import org.jamel.j7zip.common.LockedInStream;
import org.jamel.j7zip.common.LockedSequentialInStreamImp;
import org.jamel.j7zip.common.LongVector;
import org.jamel.j7zip.common.ObjectVector;
import org.jamel.j7zip.archive.common.BindPair;
import org.jamel.j7zip.archive.common.CoderMixer2;
import org.jamel.j7zip.archive.common.CoderMixer2ST;
import org.jamel.j7zip.archive.common.CoderStreamsInfo;
import org.jamel.j7zip.archive.common.FilterCoder;
import org.jamel.j7zip.compression.Branch.BCJ2_x86_Decoder;
import org.jamel.j7zip.compression.Branch.BCJ_x86_Decoder;
import org.jamel.j7zip.ICompressCoder;
import org.jamel.j7zip.ICompressCoder2;
import org.jamel.j7zip.ICompressFilter;
import org.jamel.j7zip.IInStream;
import org.jamel.j7zip.Result;
package org.jamel.j7zip.archive.sevenZip;



class Decoder {

    boolean _bindInfoExPrevIsDefined;
    BindInfoEx _bindInfoExPrev;

    CoderMixer2ST _mixerCoderSTSpec;
    CoderMixer2 _mixerCoderCommon;

    ICompressCoder2 _mixerCoder;
    ObjectVector<Object> _decoders;

    public Decoder() {
        _bindInfoExPrevIsDefined = false;
        _bindInfoExPrev = new BindInfoEx();

        _mixerCoder = null;
        _decoders = new ObjectVector<>();
    }

    static void ConvertFolderItemInfoToBindInfo(Folder folder, BindInfoEx bindInfo) {
        bindInfo.clear();

        for (int i = 0; i < folder.bindPairs.size(); i++) {
            BindPair bindPair = new BindPair(
                    folder.bindPairs.get(i).inIndex,
                    folder.bindPairs.get(i).outIndex);
            bindInfo.bindPairs.add(bindPair);
        }
        int outStreamIndex = 0;
        for (int i = 0; i < folder.coders.size(); i++) {
            CoderInfo coderInfo = folder.coders.get(i);
            bindInfo.coders.add(new CoderStreamsInfo(
                    coderInfo.getInStreamsCount(),
                    coderInfo.getOutStreamsCount()));
            AltCoderInfo altCoderInfo = coderInfo.getAltCoders().first();
            bindInfo.CoderMethodIDs.add(altCoderInfo.MethodID);
            for (int j = 0; j < coderInfo.getOutStreamsCount(); j++, outStreamIndex++) {
                if (folder.findBindPairForOutStream(outStreamIndex) < 0) {
                    bindInfo.outStreams.add(outStreamIndex);
                }
            }
        }
        for (int i = 0; i < folder.packStreams.size(); i++) {
            bindInfo.inStreams.add(folder.packStreams.get(i));
        }
    }

    Result decode(IInStream inStream, long startPos, LongVector packSizes, int packSizesOffset,
            Folder folderInfo, OutputStream outStream) throws IOException
    {
        ObjectVector<InputStream> inStreams = new ObjectVector<>();

        LockedInStream lockedInStream = new LockedInStream();
        lockedInStream.Init(inStream);

        for (int j = 0; j < folderInfo.packStreams.size(); j++) {
            LockedSequentialInStreamImp lockedStreamImpSpec = new LockedSequentialInStreamImp();
            lockedStreamImpSpec.Init(lockedInStream, startPos);
            startPos += packSizes.get(j + packSizesOffset);

            LimitedSequentialInStream streamSpec = new LimitedSequentialInStream();
            streamSpec.setStream(lockedStreamImpSpec);
            streamSpec.init(packSizes.get(j + packSizesOffset));
            inStreams.add(streamSpec);
        }

        int numCoders = folderInfo.coders.size();

        BindInfoEx bindInfo = new BindInfoEx();
        ConvertFolderItemInfoToBindInfo(folderInfo, bindInfo);
        boolean createNewCoders;
        createNewCoders = (!_bindInfoExPrevIsDefined || !bindInfo.equals(_bindInfoExPrev));

        if (createNewCoders) {
            int i;
            _decoders.clear();

            if (_mixerCoder != null) _mixerCoder.close();

            _mixerCoderSTSpec = new CoderMixer2ST();
            _mixerCoder = _mixerCoderSTSpec;
            _mixerCoderCommon = _mixerCoderSTSpec;
            _mixerCoderCommon.setBindInfo(bindInfo);

            for (i = 0; i < numCoders; i++) {
                CoderInfo coderInfo = folderInfo.coders.get(i);
                AltCoderInfo altCoderInfo = coderInfo.getAltCoders().first();

                if (coderInfo.isSimpleCoder()) {
                    ICompressCoder decoder = null;
                    ICompressFilter filter = null;

                    if (altCoderInfo.MethodID.equals(MethodID.k_LZMA)) {
                        decoder = new org.jamel.j7zip.compression.LZMA.Decoder();
                    }

                    if (altCoderInfo.MethodID.equals(MethodID.k_BCJ_X86)) {
                        filter = new BCJ_x86_Decoder();
                    }

                    if (altCoderInfo.MethodID.equals(MethodID.k_Copy)) {
                        decoder = new org.jamel.j7zip.compression.Copy.Decoder();
                    }

                    if (filter != null) {
                        FilterCoder coderSpec = new FilterCoder();
                        decoder = coderSpec;
                        coderSpec.filter = filter;
                    }

                    if (decoder == null) {
                        return Result.ERROR_NOT_IMPLEMENTED;
                    }

                    _decoders.add(decoder);
                    _mixerCoderSTSpec.addCoder(decoder, false);
                } else {
                    ICompressCoder2 decoder = null;
                    if (altCoderInfo.MethodID.equals(MethodID.k_BCJ2)) {
                        decoder = new BCJ2_x86_Decoder();