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}