/** * Copyright 2013 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.netflix.aegisthus.io.sstable.compression; import org.xerial.snappy.SnappyOutputStream; import javax.annotation.Nonnull; import java.io.EOFException; import java.io.IOException; import java.io.InputStream; import static java.lang.Math.min; /** * This class implements an input stream for reading Snappy compressed data of * the format produced by {@link SnappyOutputStream}. */ public class CompressionInputStream extends InputStream { private final byte[] buffer; private final CompressionMetadata cm; private final InputStream in; private final byte[] input; private boolean closed; private int position; private int valid; /** * Creates a Snappy input stream to read data from the specified underlying * input stream. * * @param in * the underlying input stream */ public CompressionInputStream(InputStream in, CompressionMetadata cm) { this.cm = cm; this.in = in; //chunkLength*2 because there are some cases where the data is larger than specified input = new byte[cm.chunkLength() * 2]; buffer = new byte[cm.chunkLength() * 2]; } @Override public int available() throws IOException { if (closed) { return 0; } if (valid > position) { return valid - position; } if (cm.currentLength() <= 0) { return 0; } readInput(cm.currentLength()); cm.incrementChunk(); return valid; } @Override public void close() throws IOException { try { in.close(); } finally { if (!closed) { closed = true; } } } private boolean finishedReading() throws IOException { return available() <= 0; } @Override public int read() throws IOException { if (closed) { return -1; } if (finishedReading()) { return -1; } return buffer[position++] & 0xFF; } @Override public int read(@Nonnull byte[] output, int offset, int length) throws IOException { if (closed) { throw new IOException("Stream is closed"); } if (length == 0) { return 0; } if (finishedReading()) { return -1; } int size = min(length, available()); System.arraycopy(buffer, position, output, offset, size); position += size; return size; } private void readInput(int length) throws IOException { int offset = 0; while (offset < length) { int size = in.read(input, offset, length - offset); if (size == -1) { throw new EOFException("encountered EOF while reading block data"); } offset += size; } // ignore checksum for now readChecksum(4); valid = cm.compressor().uncompress(input, 0, length, buffer, 0); position = 0; } private void readChecksum(int length) throws IOException { byte[] checksum = new byte[length]; int offset = 0; while (offset < length) { int size = in.read(checksum, offset, length - offset); if (size == -1) { throw new EOFException("encountered EOF while reading checksum."); } offset += size; } } }