org.iq80.snappy.SnappyInputStream Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of SWBTripleStoreLevelDB Show documentation
Show all versions of SWBTripleStoreLevelDB Show documentation
TripleStore implementation for SemanticWebBuilder using LevelDB
The newest version!
/*
* Copyright (C) 2011 the original author or authors.
* See the notice.md file distributed with this work for additional
* information regarding copyright ownership.
*
* 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 org.iq80.snappy;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import static java.lang.Math.min;
import static java.lang.String.format;
import static org.iq80.snappy.SnappyInternalUtils.checkNotNull;
import static org.iq80.snappy.SnappyInternalUtils.checkPositionIndexes;
import static org.iq80.snappy.SnappyOutputStream.STREAM_HEADER;
import static org.iq80.snappy.SnappyOutputStream.MAX_BLOCK_SIZE;
/**
* This class implements an input stream for reading Snappy compressed data
* of the format produced by {@link SnappyOutputStream}.
*/
public class SnappyInputStream
extends InputStream
{
// The buffer size is the same as the block size.
// This works because the original data is not allowed to expand.
private final BufferRecycler recycler;
private final byte[] input;
private final byte[] uncompressed;
private final byte[] header = new byte[7];
private final InputStream in;
private final boolean verifyChecksums;
// Buffer is a reference to the real buffer for the current block:
// uncompressed if the block is compressed, or input if it is not.
// Valid is the total valid bytes in the referenced buffer.
private byte[] buffer;
private int valid;
private int position;
private boolean closed;
private boolean eof;
/**
* Creates a Snappy input stream to read data from the specified underlying input stream.
*
* @param in the underlying input stream
*/
public SnappyInputStream(InputStream in)
throws IOException
{
this(in, true);
}
/**
* Creates a Snappy input stream to read data from the specified underlying input stream.
*
* @param in the underlying input stream
* @param verifyChecksums if true, checksums in input stream will be verified
*/
public SnappyInputStream(InputStream in, boolean verifyChecksums)
throws IOException
{
this.in = in;
this.verifyChecksums = verifyChecksums;
recycler = BufferRecycler.instance();
input = recycler.allocInputBuffer(MAX_BLOCK_SIZE);
uncompressed = recycler.allocDecodeBuffer(MAX_BLOCK_SIZE);
// stream must begin with stream header
int offset = 0;
while (offset < header.length) {
int size = in.read(header, offset, header.length - offset);
if (size == -1) {
throw new EOFException("encountered EOF while reading stream header");
}
offset += size;
}
if (!Arrays.equals(header, STREAM_HEADER)) {
throw new IOException("invalid stream header");
}
}
@Override
public int read()
throws IOException
{
if (closed) {
return -1;
}
if (!ensureBuffer()) {
return -1;
}
return buffer[position++] & 0xFF;
}
@Override
public int read(byte[] output, int offset, int length)
throws IOException
{
checkNotNull(output, "output is null");
checkPositionIndexes(offset, offset + length, output.length);
if (closed) {
throw new IOException("Stream is closed");
}
if (length == 0) {
return 0;
}
if (!ensureBuffer()) {
return -1;
}
int size = min(length, available());
System.arraycopy(buffer, position, output, offset, size);
position += size;
return size;
}
@Override
public int available()
throws IOException
{
if (closed) {
return 0;
}
return valid - position;
}
@Override
public void close()
throws IOException
{
try {
in.close();
}
finally {
if (!closed) {
closed = true;
recycler.releaseInputBuffer(input);
recycler.releaseDecodeBuffer(uncompressed);
}
}
}
private boolean ensureBuffer()
throws IOException
{
if (available() > 0) {
return true;
}
if (eof) {
return false;
}
if (!readBlockHeader()) {
eof = true;
return false;
}
boolean compressed = getHeaderCompressedFlag();
int length = getHeaderLength();
readInput(length);
handleInput(length, compressed);
return true;
}
private void handleInput(int length, boolean compressed)
throws IOException
{
if (compressed) {
buffer = uncompressed;
try {
valid = Snappy.uncompress(input, 0, length, uncompressed, 0);
}
catch (CorruptionException e) {
throw new IOException("Corrupt input", e);
}
}
else {
buffer = input;
valid = length;
}
if (verifyChecksums) {
int expectedCrc32c = getCrc32c();
int actualCrc32c = Crc32C.maskedCrc32c(buffer, 0, valid);
if (expectedCrc32c != actualCrc32c) {
throw new IOException("Corrupt input: invalid checksum");
}
}
position = 0;
}
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;
}
}
private boolean readBlockHeader()
throws IOException
{
do {
int offset = 0;
while (offset < header.length) {
int size = in.read(header, offset, header.length - offset);
if (size == -1) {
// EOF on first byte means the stream ended cleanly
if (offset == 0) {
return false;
}
throw new EOFException("encountered EOF while reading block header");
}
offset += size;
}
} while (Arrays.equals(header, STREAM_HEADER));
return true;
}
private boolean getHeaderCompressedFlag()
throws IOException
{
int x = header[0] & 0xFF;
switch (x) {
case 0x00:
return false;
case 0x01:
return true;
default:
throw new IOException(format("invalid compressed flag in header: 0x%02x", x));
}
}
private int getHeaderLength()
throws IOException
{
int a = header[1] & 0xFF;
int b = header[2] & 0xFF;
int length = (a << 8) | b;
if ((length <= 0) || (length > MAX_BLOCK_SIZE)) {
throw new IOException("invalid block size in header: " + length);
}
return length;
}
private int getCrc32c()
throws IOException
{
return ((header[3] & 0xFF) << 24) |
((header[4] & 0xFF) << 16) |
((header[5] & 0xFF) << 8) |
(header[6] & 0xFF);
}
}