package yuku.alkitab.yes2.compress; import yuku.alkitab.yes2.io.RandomAccessFileRandomInputStream; import yuku.alkitab.yes2.io.RandomInputStream; import yuku.bintex.ValueMap; import yuku.snappy.codec.Snappy; import java.io.IOException; public class SnappyInputStream extends RandomInputStream { public final String TAG = SnappyInputStream.class.getSimpleName(); private final RandomAccessFileRandomInputStream input; private final Snappy snappy; private final long baseOffset; private final int block_size; private final int[] compressed_block_sizes; private final int[] compressed_block_offsets; private int current_block_index = 0; private int current_block_skip = 0; private byte[] compressed_buf; private int uncompressed_block_index = -1; private byte[] uncompressed_buf; private int uncompressed_len = -1; // -1 means not initialized public SnappyInputStream(RandomAccessFileRandomInputStream input, long baseOffset, int block_size, int[] compressed_block_sizes, int[] compressed_block_offsets) throws IOException { this.input = input; this.block_size = block_size; this.snappy = new Snappy.Factory().newInstance(); this.baseOffset = baseOffset; this.compressed_block_sizes = compressed_block_sizes; this.compressed_block_offsets = compressed_block_offsets; this.compressed_buf = new byte[snappy.maxCompressedLength(block_size)]; this.uncompressed_buf = new byte[block_size]; } @Override public void seek(long n) throws IOException { int offset = (int) n; int block_index = offset / block_size; int block_skip = offset - (block_index * block_size); this.current_block_index = block_index; this.current_block_skip = block_skip; prepareBuffer(); } private void prepareBuffer() throws IOException { int block_index = current_block_index; // if uncompressed_block_index is already equal to the requested block_index // then we do not need to re-decompress again if (uncompressed_block_index != block_index) { input.seek(baseOffset + compressed_block_offsets[block_index]); input.read(compressed_buf, 0, compressed_block_sizes[block_index]); uncompressed_len = snappy.decompress(compressed_buf, 0, uncompressed_buf, 0, compressed_block_sizes[block_index]); if (uncompressed_len < 0) { throw new IOException("Error in decompressing: " + uncompressed_len); } uncompressed_block_index = block_index; } } @Override public int read() throws IOException { if (uncompressed_len == -1) { prepareBuffer(); } int can_read = uncompressed_len - current_block_skip; if (can_read == 0) { if (current_block_index >= compressed_block_sizes.length) { return -1; // EOF } else { // need to move to the next block current_block_index++; current_block_skip = 0; prepareBuffer(); } } int res = /* need to convert to uint8: */ 0xff & uncompressed_buf[current_block_skip]; current_block_skip++; return res; } @Override public int read(byte[] buffer, int offset, int length) throws IOException { if (uncompressed_len == -1) { prepareBuffer(); } int res = 0; int want_read = length; while (want_read > 0) { int can_read = uncompressed_len - current_block_skip; if (can_read == 0) { if (current_block_index >= compressed_block_sizes.length) { // EOF if (res == 0) return -1; // we didn't manage to read any return res; } else { // need to move to the next block current_block_index++; current_block_skip = 0; prepareBuffer(); can_read = uncompressed_len; } } int will_read = want_read > can_read? can_read: want_read; System.arraycopy(uncompressed_buf, current_block_skip, buffer, offset, will_read); current_block_skip += will_read; offset += will_read; want_read -= will_read; res += will_read; } return res; } @Override public int read(byte[] buffer) throws IOException { return read(buffer, 0, buffer.length); } @Override public long getFilePointer() throws IOException { return current_block_index * block_size + current_block_skip; } @Override public long skip(long n) throws IOException { seek(getFilePointer() + n); return n; } public static SnappyInputStream getInstanceFromAttributes(RandomAccessFileRandomInputStream input, ValueMap sectionAttributes, long sectionContentOffset) throws IOException { int compressionVersion = sectionAttributes.getInt("compression.version", 0); if (compressionVersion > 1) { throw new IOException("Compression version " + compressionVersion + " is not supported"); } ValueMap compressionInfo = sectionAttributes.getSimpleMap("compression.info"); int block_size = compressionInfo.getInt("block_size"); int[] compressed_block_sizes = compressionInfo.getIntArray("compressed_block_sizes"); int[] compressed_block_offsets = new int[compressed_block_sizes.length + 1]; { // convert compressed_block_sizes into offsets int c = 0; for (int i = 0, len = compressed_block_sizes.length; i < len; i++) { compressed_block_offsets[i] = c; c += compressed_block_sizes[i]; } compressed_block_offsets[compressed_block_sizes.length] = c; } return new SnappyInputStream(input, sectionContentOffset, block_size, compressed_block_sizes, compressed_block_offsets); } }