All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.carne.nio.compression.common.BitDecoder Maven / Gradle / Ivy

/*
 * Copyright (c) 2016 Holger de Carne and contributors, All Rights Reserved.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser Public License for more details.
 *
 * You should have received a copy of the GNU Lesser Public License
 * along with this program.  If not, see .
 */
package de.carne.nio.compression.common;

import java.io.EOFException;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;

import de.carne.nio.compression.util.Assert;

/**
 * Utility class providing bit-level access to a
 * {@linkplain ReadableByteChannel}'s data.
 * 

* The actual bit access is done via a {@linkplain BitRegister} instance. * Multiple bit registers can be used in parallel. *

*/ public final class BitDecoder { private final BitRegister[] registers; private final byte[] trailingBytes; private int trailingBytesIndex; private long totalInBits; /** * Construct {@code BitDecoder}. * * @param registers The bit registers to use. */ public BitDecoder(BitRegister[] registers) { this(registers, null); } /** * Construct {@code BitDecoder}. * * @param registers The bit registers to use. * @param trailingBytes The optional bytes to feed after the underlying * reader has reached EOF. */ public BitDecoder(BitRegister[] registers, byte... trailingBytes) { Assert.notNull(registers, "registers"); Assert.notEmpty(registers.length, "registers"); this.registers = new BitRegister[registers.length]; System.arraycopy(registers, 0, this.registers, 0, registers.length); if (trailingBytes != null) { this.trailingBytes = new byte[trailingBytes.length]; System.arraycopy(trailingBytes, 0, this.trailingBytes, 0, trailingBytes.length); } else { this.trailingBytes = new byte[0]; } init(); } private void init() { this.trailingBytesIndex = 0; this.totalInBits = 0L; } /** * Reset the decoder to it's initial state. */ public void reset() { clear(); init(); } /** * Clear all pending bits. */ public void clear() { this.totalInBits += this.registers[0].bitCount(); for (BitRegister register : this.registers) { register.clear(); } } /** * Get the total number of decoded bytes. * * @return The total number of decoded bytes. */ public long totalIn() { return (this.totalInBits + 7) >>> 3; } /** * Decode a number of bits from the source channel without discarding them. *

* This function uses the register {@code 0} for bit decoding. *

* * @param src The source channel to decode from. * @param count The number of bits to decode. * @return The decoded bits. * @throws IOException if an I/O error occurs. */ public int peekBits(ReadableByteChannel src, int count) throws IOException { return peekBits(src, count, 0); } /** * Decode a number of bits from the source channel without discarding them. * * @param src The source channel to decode from. * @param count The number of bits to decode. * @param registerIndex The register to use for bit decoding. * @return The decoded bits. * @throws IOException if an I/O error occurs. */ public int peekBits(ReadableByteChannel src, int count, int registerIndex) throws IOException { Assert.notNull(src, "src"); Assert.isValid(count >= 0, "count", count); Assert.isValid(0 <= registerIndex && registerIndex < this.registers.length, "registerIndex", registerIndex); feedBytes(src, count); return this.registers[registerIndex].peekBits(count); } /** * Decode a number of bits from the source channel and discard them. *

* This function uses the register {@code 0} for bit decoding. *

* * @param src The source channel to decode from. * @param count The number of bits to decode. * @return The decoded bits. * @throws IOException if an I/O error occurs. */ public int decodeBits(ReadableByteChannel src, int count) throws IOException { return decodeBits(src, count, 0); } /** * Decode a number of bits from the source channel and discard them. * * @param src The source channel to decode from. * @param count The number of bits to decode. * @param registerIndex The register to use for bit decoding. * @return The decoded bits. * @throws IOException if an I/O error occurs. */ public int decodeBits(ReadableByteChannel src, int count, int registerIndex) throws IOException { Assert.notNull(src, "src"); Assert.isValid(count >= 0, "count", count); Assert.isValid(0 <= registerIndex && registerIndex < this.registers.length, "registerIndex", registerIndex); int bits = peekBits(src, count, registerIndex); this.totalInBits += count; for (BitRegister register : this.registers) { register.discardBits(count); } return bits; } /** * Make sure the next decode or read action is byte-aligned. */ public void alignToByte() { int discardCount = this.registers[0].bitCount() % 8; if (discardCount != 0) { this.totalInBits += discardCount; for (BitRegister register : this.registers) { register.discardBits(discardCount); } } } /** * Perform a direct byte-aligned read and discard the corresponding bits. *

* This function allows optimized access bulk reading data. *

* * @param src The source channel to read from. * @param dst The {@linkplain ByteBuffer} to read into. * @return The number of read bytes or {@code -1} if the channel reached * end-of-stream. * @throws IOException if an I/O error occurs. */ public int readBytes(ReadableByteChannel src, ByteBuffer dst) throws IOException { Assert.notNull(src, "src"); Assert.notNull(dst, "dst"); alignToByte(); BitRegister register0 = this.registers[0]; int read = 0; while (register0.bitCount() > 0 && dst.hasRemaining()) { dst.put((byte) register0.peekBits(8)); for (BitRegister register : this.registers) { register.discardBits(8); } read++; } int directRead = (dst.hasRemaining() ? src.read(dst) : 0); if (directRead >= 0) { read += directRead; this.totalInBits += read * 8; } else if (read > 0) { this.totalInBits += read * 8; } else { read = -1; } return read; } /** * Perform a direct byte-aligned read of a single byte and discard the * corresponding bits. * * @param src The source channel to read from. * @return The read byte {@code -1} if the channel reached end-of-stream. * @throws IOException if an I/O error occurs. */ public int readByte(ReadableByteChannel src) throws IOException { Assert.notNull(src, "src"); alignToByte(); BitRegister register0 = this.registers[0]; int read; if (register0.bitCount() > 0) { read = register0.peekBits(8) & 0xff; this.totalInBits += 8; for (BitRegister register : this.registers) { register.discardBits(8); } } else { ByteBuffer readBuffer = ByteBuffer.allocate(1); read = src.read(readBuffer); readBuffer.flip(); if (read == 1) { read = readBuffer.get() & 0xff; this.totalInBits += 8; } } return read; } private void feedBytes(ReadableByteChannel src, int count) throws IOException { int currentBitcount = this.registers[0].bitCount(); if (count > currentBitcount) { int feedBytesRemainingCount = ((count - currentBitcount) + 7) / 8; if (this.trailingBytesIndex == 0) { ByteBuffer readBuffer = ByteBuffer.allocate(feedBytesRemainingCount); src.read(readBuffer); readBuffer.flip(); while (readBuffer.hasRemaining()) { byte b = readBuffer.get(); for (BitRegister register : this.registers) { register.feedBits(b); } feedBytesRemainingCount--; } } while (feedBytesRemainingCount > 0) { if (this.trailingBytesIndex >= this.trailingBytes.length) { throw new EOFException("Unable to read remaining bytes: " + feedBytesRemainingCount); } byte b = this.trailingBytes[this.trailingBytesIndex]; for (BitRegister register : this.registers) { register.feedBits(b); } this.trailingBytesIndex++; feedBytesRemainingCount--; } } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy