001/*
002 * This file is part of lzo-java, an implementation of LZO in Java.
003 * https://github.com/shevek/lzo-java
004 *
005 * The Java portion of this library is:
006 * Copyright (C) 2011 Shevek <shevek@anarres.org>
007 * All Rights Reserved.
008 *
009 * The preprocessed C portion of this library is:
010 * Copyright (C) 2006-2011 Markus Franz Xaver Johannes Oberhumer
011 * All Rights Reserved.
012 *
013 * This library is free software; you can redistribute it and/or
014 * modify it under the terms of the GNU General Public License 
015 * as published by the Free Software Foundation; either version 
016 * 2 of the License, or (at your option) any later version.
017 *
018 * This library is distributed in the hope that it will be useful, 
019 * but WITHOUT ANY WARRANTY; without even the implied warranty
020 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
021 * See the GNU General Public License for more details.
022 *
023 * You should have received a copy of the GNU General Public
024 * License along with the LZO library; see the file COPYING.
025 * If not, see <http://www.gnu.org/licenses/> or write to the
026 * Free Software Foundation, Inc., 51 Franklin Street, Fifth
027 * Floor, Boston, MA 02110-1301, USA.
028
029 * As a special exception, the copyright holders of this file
030 * give you permission to link this file with independent
031 * modules to produce an executable, regardless of the license 
032 * terms of these independent modules, and to copy and distribute
033 * the resulting executable under terms of your choice, provided
034 * that you also meet, for each linked independent module, 
035 * the terms and conditions of the license of that module. An
036 * independent module is a module which is not derived from or 
037 * based on this library or file. If you modify this file, you may 
038 * extend this exception to your version of the file, but
039 * you are not obligated to do so. If you do not wish to do so,
040 * delete this exception statement from your version.
041 */
042package org.anarres.lzo;
043
044import java.io.EOFException;
045import java.io.IOException;
046import java.io.InputStream;
047import javax.annotation.CheckForSigned;
048import javax.annotation.Nonnegative;
049import javax.annotation.Nonnull;
050import org.apache.commons.logging.Log;
051import org.apache.commons.logging.LogFactory;
052
053/**
054 *
055 * @author shevek
056 */
057public class LzoInputStream extends InputStream {
058
059    private static final Log LOG = LogFactory.getLog(LzoInputStream.class.getName());
060    private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
061    protected final InputStream in;
062    private final LzoDecompressor decompressor;
063    protected byte[] inputBuffer = EMPTY_BYTE_ARRAY;
064    protected byte[] outputBuffer = EMPTY_BYTE_ARRAY;
065    protected int outputBufferPos;
066    protected final lzo_uintp outputBufferLen = new lzo_uintp();        // Also, end, since we base outputBuffer at 0.
067
068    public LzoInputStream(@Nonnull InputStream in, @Nonnull LzoDecompressor decompressor) {
069        this.in = in;
070        this.decompressor = decompressor;
071    }
072
073    public void setInputBufferSize(@Nonnegative int inputBufferSize) {
074        if (inputBufferSize > inputBuffer.length)
075            inputBuffer = new byte[inputBufferSize];
076    }
077
078    public void setOutputBufferSize(@Nonnegative int outputBufferSize) {
079        if (outputBufferSize > outputBuffer.length)
080            outputBuffer = new byte[outputBufferSize];
081    }
082
083    @Override
084    public int available() throws IOException {
085        return outputBufferLen.value - outputBufferPos;
086    }
087
088    @Override
089    public int read() throws IOException {
090        if (!fill())
091            return -1;
092        return outputBuffer[outputBufferPos++] & 0xFF;
093    }
094
095    @Override
096    public int read(byte[] b) throws IOException {
097        return read(b, 0, b.length);
098    }
099
100    @Override
101    public int read(byte[] b, int off, int len) throws IOException {
102        if (!fill())
103            return -1;
104        len = Math.min(len, available());
105        System.arraycopy(outputBuffer, outputBufferPos, b, off, len);
106        outputBufferPos += len;
107        return len;
108    }
109
110    protected void logState(@Nonnull String when) {
111        LOG.info("\n");
112        LOG.info(when + " Input buffer size=" + inputBuffer.length);
113        LOG.info(when + " Output buffer pos=" + outputBufferPos + "; length=" + outputBufferLen + "; size=" + outputBuffer.length);
114        // testInvariants();
115    }
116
117    private boolean fill() throws IOException {
118        while (available() == 0)
119            if (!readBlock())  // Always consumes 8 bytes, so guaranteed to terminate.
120                return false;
121        return true;
122    }
123
124    protected boolean readBlock() throws IOException {
125        // logState("Before readBlock");
126        int outputBufferLength = readInt(true);
127        if (outputBufferLength == -1)
128            return false;
129        setOutputBufferSize(outputBufferLength);
130        int inputBufferLength = readInt(false);
131        setInputBufferSize(inputBufferLength);
132        readBytes(inputBuffer, 0, inputBufferLength);
133        decompress(outputBufferLength, inputBufferLength);
134        return true;
135    }
136
137    protected void decompress(@Nonnegative int outputBufferLength, @Nonnegative int inputBufferLength) throws IOException {
138        // logState("Before decompress");
139        try {
140            outputBufferPos = 0;
141            outputBufferLen.value = outputBuffer.length;
142            int code = decompressor.decompress(inputBuffer, 0, inputBufferLength, outputBuffer, 0, outputBufferLen);
143            if (code != LzoTransformer.LZO_E_OK) {
144                logState("LZO error: " + code);
145                // FileUtils.writeByteArrayToFile(new File("bytes.out"), Arrays.copyOfRange(inputBuffer, 0, inputBufferLength));
146                throw new IllegalArgumentException(decompressor.toErrorString(code));
147            }
148            if (outputBufferLen.value != outputBufferLength) {
149                logState("Output underrun: ");
150                // FileUtils.writeByteArrayToFile(new File("bytes.out"), Arrays.copyOfRange(inputBuffer, 0, inputBufferLength));
151                throw new IllegalStateException("Expected " + outputBufferLength + " bytes, but got only " + outputBufferLen);
152            }
153        } catch (IndexOutOfBoundsException e) {
154            logState("IndexOutOfBoundsException: " + e);
155            // FileUtils.writeByteArrayToFile(new File("bytes.out"), Arrays.copyOfRange(inputBuffer, 0, inputBufferLength));
156            throw new IOException(e);
157        }
158        // LOG.info(inputBufferLength + " -> " + outputBufferLen);
159        // logState("After decompress");
160    }
161
162    @CheckForSigned
163    protected int readInt(boolean start_of_frame) throws IOException {
164        int b1 = in.read();
165        if (b1 == -1) {
166            if (start_of_frame)
167                return -1;
168            else
169                throw new EOFException("EOF before reading 4-byte integer.");
170        }
171        int b2 = in.read();
172        int b3 = in.read();
173        int b4 = in.read();
174        if ((b1 | b2 | b3 | b4) < 0)
175            throw new EOFException("EOF while reading 4-byte integer.");
176        return ((b1 << 24) + (b2 << 16) + (b3 << 8) + b4);
177    }
178
179    protected void readBytes(@Nonnull byte[] buf, @Nonnegative int off, @Nonnegative int length) throws IOException {
180        while (length > 0) {
181            int count = in.read(buf, off, length);
182            if (count < 0)
183                throw new EOFException();
184            off += count;
185            length -= count;
186        }
187    }
188
189    @Override
190    public void close() throws IOException {
191        in.close();
192    }
193
194}