io.airlift.compress.snappy.HadoopSnappyInputStream.java Source code

Java tutorial

Introduction

Here is the source code for io.airlift.compress.snappy.HadoopSnappyInputStream.java

Source

/*
 * 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 io.airlift.compress.snappy;

import org.apache.hadoop.io.compress.CompressionInputStream;

import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;

import static io.airlift.compress.snappy.SnappyConstants.SIZE_OF_LONG;

class HadoopSnappyInputStream extends CompressionInputStream {
    private final SnappyDecompressor decompressor = new SnappyDecompressor();
    private final InputStream in;

    private int uncompressedBlockLength;
    private byte[] uncompressedChunk = new byte[0];
    private int uncompressedChunkOffset;
    private int uncompressedChunkLength;

    private byte[] compressed = new byte[0];

    public HadoopSnappyInputStream(InputStream in) throws IOException {
        super(in);
        this.in = in;
    }

    @Override
    public int read() throws IOException {
        if (uncompressedChunkOffset >= uncompressedChunkLength) {
            readNextChunk(uncompressedChunk, 0, uncompressedChunk.length);
            if (uncompressedChunkLength == 0) {
                return -1;
            }
        }
        return uncompressedChunk[uncompressedChunkOffset++] & 0xFF;
    }

    @Override
    public int read(byte[] output, int offset, int length) throws IOException {
        if (uncompressedChunkOffset >= uncompressedChunkLength) {
            boolean directDecompress = readNextChunk(output, offset, length);
            if (uncompressedChunkLength == 0) {
                return -1;
            }
            if (directDecompress) {
                uncompressedChunkOffset += uncompressedChunkLength;
                return uncompressedChunkLength;
            }
        }
        int size = Math.min(length, uncompressedChunkLength - uncompressedChunkOffset);
        System.arraycopy(uncompressedChunk, uncompressedChunkOffset, output, offset, size);
        uncompressedChunkOffset += size;
        return size;
    }

    @Override
    public void resetState() throws IOException {
        uncompressedBlockLength = 0;
        uncompressedChunkOffset = 0;
        uncompressedChunkLength = 0;
    }

    private boolean readNextChunk(byte[] userBuffer, int userOffset, int userLength) throws IOException {
        uncompressedBlockLength -= uncompressedChunkOffset;
        uncompressedChunkOffset = 0;
        uncompressedChunkLength = 0;
        while (uncompressedBlockLength == 0) {
            uncompressedBlockLength = readBigEndianInt();
            if (uncompressedBlockLength == -1) {
                uncompressedBlockLength = 0;
                return false;
            }
        }

        int compressedChunkLength = readBigEndianInt();
        if (compressedChunkLength == -1) {
            return false;
        }

        if (compressed.length < compressedChunkLength) {
            // over allocate buffer which makes decompression easier
            compressed = new byte[compressedChunkLength + SIZE_OF_LONG];
        }
        readInput(compressedChunkLength, compressed);

        uncompressedChunkLength = SnappyDecompressor.getUncompressedLength(compressed, 0);
        if (uncompressedChunkLength > uncompressedBlockLength) {
            throw new IOException("Chunk uncompressed size is greater than block size");
        }

        boolean directUncompress = true;
        if (uncompressedChunkLength > userLength) {
            if (uncompressedChunk.length < uncompressedChunkLength) {
                // over allocate buffer which makes decompression easier
                uncompressedChunk = new byte[uncompressedChunkLength + SIZE_OF_LONG];
            }
            directUncompress = false;
            userBuffer = uncompressedChunk;
            userOffset = 0;
            userLength = uncompressedChunk.length;
        }

        int bytes = decompressor.decompress(compressed, 0, compressedChunkLength, userBuffer, userOffset,
                userLength);
        if (uncompressedChunkLength != bytes) {
            throw new IOException("Expected to read " + uncompressedChunkLength + " bytes, but data only contained "
                    + bytes + " bytes");
        }
        return directUncompress;
    }

    private void readInput(int length, byte[] buffer) throws IOException {
        int offset = 0;
        while (offset < length) {
            int size = in.read(buffer, offset, length - offset);
            if (size == -1) {
                throw new EOFException("encountered EOF while reading block data");
            }
            offset += size;
        }
    }

    private int readBigEndianInt() throws IOException {
        int b1 = in.read();
        if (b1 < 0) {
            return -1;
        }
        int b2 = in.read();
        int b3 = in.read();
        int b4 = in.read();

        // If any of the other bits are negative, the stream it truncated
        if ((b2 | b3 | b4) < 0) {
            throw new IOException("Stream is truncated");
        }
        return ((b1 << 24) + (b2 << 16) + (b3 << 8) + (b4));
    }
}