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

com.googlecode.jinahya.io.BitInput Maven / Gradle / Ivy

/*
 *  Copyright 2010 Jin Kwon.
 *
 *  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.googlecode.jinahya.io;


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

import java.util.BitSet;


/**
 * Bit-level wrapper for InputStream.
 *
 * @author Jin Kwon
 */
public class BitInput {


    /**
     * Creates a new instance.
     *
     * @param in source octet input
     */
    public BitInput(final InputStream in) {
        super();

        if (in == null) {
            throw new NullPointerException("null in");
        }

        this.in = in;
    }


    /**
     * Reads an unsigned byte value.
     *
     * @param length bit length between 0 (exclusive) and 8 (inclusive)
     * @return an unsigned byte value.
     * @throws IOException if an I/O error occurs.
     */
    protected final int readUnsignedByte(final int length) throws IOException {

        if (length <= 0) {
            throw new IllegalArgumentException("length(" + length + ") <= 0");
        }

        if (length > 8) {
            throw new IllegalArgumentException("length(" + length + ") > 8");
        }

        if (index == 8) {
            int octet = in.read();
            if (octet == -1) {
                throw new EOFException("eof");
            }
            for (int i = 7; i >= 0; i--) {
                set.set(i, (octet & 0x01) == 0x01);
                octet >>= 1;
            }
            index = 0;
            count++;
        }

        final int required = length - (8 - index);

        if (required > 0) {
            return (readUnsignedByte(length - required) << required)
                | readUnsignedByte(required);
        }

        int value = 0x00;
        for (int i = 0; i < length; i++) {
            value <<= 1;
            value |= (set.get(index + i) ? 0x01 : 0x00);
        }

        index += length;

        return value;
    }


    /**
     * Reads 1-bit boolean value.
     *
     * @return boolean value
     * @throws IOException if an I/O error occurs.
     */
    public final boolean readBoolean() throws IOException {

        return readUnsignedByte(1) == 0x01;
    }


    /**
     * Reads an unsigned short value.
     *
     * @param length bit length between 0 (exclusive) and 16 (inclusive).
     * @return an unsigned short value.
     * @throws IOException if an I/O error occurs.
     */
    protected final int readUnsignedShort(final int length) throws IOException {

        if (length <= 0) {
            throw new IllegalArgumentException("length(" + length + ") <= 0");
        }

        if (length > 16) {
            throw new IllegalArgumentException("length(" + length + ") > 16");
        }

        final int quotient = length / 0x08;
        final int remainder = length % 0x08;

        int value = 0x00;
        for (int i = 0; i < quotient; i++) {
            value <<= 0x08;
            value |= readUnsignedByte(0x08);
        }

        if (remainder > 0) {
            value <<= remainder;
            value |= readUnsignedByte(remainder);
        }

        return value;
    }


    /**
     * Reads an unsigned int.
     *
     * @param length bit length between 1 (inclusive) and 32 (exclusive).
     * @return an unsigned int value
     * @throws IOException if an I/O error occurs
     */
    public final int readUnsignedInt(final int length) throws IOException {

        if (length < 1) {
            throw new IllegalArgumentException("length(" + length + ") < 1");
        }

        if (length >= 32) {
            throw new IllegalArgumentException("length(" + length + ") >= 32");
        }

        final int quotient = length / 0x10;
        final int remainder = length % 0x10;

        int value = 0x00;
        for (int i = 0; i < quotient; i++) {
            value <<= 0x10;
            value |= readUnsignedShort(0x10);
        }

        if (remainder > 0) {
            value <<= remainder;
            value |= readUnsignedShort(remainder);
        }

        return value;
    }


    /**
     * Reads a signed int.
     *
     * @param length bit length between 1 (exclusive) and 32 (inclusive).
     * @return a lengthbit-long signed integer
     * @throws IOException if an I/O error occurs.
     */
    public final int readInt(final int length) throws IOException {

        if (length <= 1) {
            throw new IllegalArgumentException("length(" + length + ") <= 1");
        }

        if (length > 32) {
            throw new IllegalArgumentException("length(" + length + ") > 32");
        }

        int value = 0x00;

        if (readBoolean()) {
            value--;
        }

        value <<= (length - 1);

        value |= readUnsignedInt(length - 1);

        return value;
    }


    /**
     * Reads an 32-bit signed integer.
     *
     * @return a 32-bit signed integer
     * @throws IOException if an I/O error occurs.
     */
    public final int readInt() throws IOException {
        return readInt(0x20);
    }


    /**
     * Reads a float value.
     *
     * @return float value
     * @throws IOException if an I/O error occurs
     */
    public final float readFloat() throws IOException {
        return Float.intBitsToFloat(readInt(32));
    }


    /**
     * Reads an unsigned long.
     *
     * @param length bit length between 1 (inclusive) and 64 (exclusive)
     * @return an unsigned long value
     * @throws IOException if an I/O error occurs
     */
    public final long readUnsignedLong(final int length) throws IOException {

        if (length < 1) {
            throw new IllegalArgumentException("length(" + length + ") < 1");
        }

        if (length >= 64) {
            throw new IllegalArgumentException("length(" + length + ") >= 64");
        }

        final int quotient = length / 0x10;
        final int remainder = length % 0x10;

        long value = 0x00L;
        for (int i = 0x00; i < quotient; i++) {
            value <<= 0x10;
            value |= readUnsignedShort(0x10);
        }

        if (remainder > 0x00) {
            value <<= remainder;
            value |= readUnsignedShort(remainder);
        }

        return value;
    }


    /**
     * Reads a signed long value.
     *
     * @param length bit length between 1 (exclusive) and 64 (inclusive).
     * @return a signed long value.
     * @throws IOException if an I/O error occurs.
     */
    public final long readLong(final int length) throws IOException {

        if (length <= 1) {
            throw new IllegalArgumentException("length(" + length + ") <= 1");
        }

        if (length > 64) {
            throw new IllegalArgumentException("length(" + length + ") > 64");
        }

        long value = 0x00L;
        if (readBoolean()) {
            value--;
        }
        value <<= (length - 1);

        value |= readUnsignedLong(length - 1);

        return value;
    }


    /**
     * Reads a 64-bit signed long.
     *
     * @return a 64-bit signed long
     * @throws IOException if an I/O error occurs.
     */
    public final long readLong() throws IOException {
        return readLong(0x40);
    }


    /**
     * Reads a 64-bit double value.
     *
     * @return double value
     * @throws IOException if an I/O error occurs.
     */
    public final double readDouble() throws IOException {
        return Double.longBitsToDouble(readLong());
    }


    /**
     * Reads a byte array. First, a 31-bit unsigned integer is read for the byte
     * array length. And then each byte is read as 8-bit unsigned byte.
     *
     * @return a byte array
     * @throws IOException if an I/O error occurs.
     */
    public final byte[] readBytes() throws IOException {

        final byte[] bytes = new byte[readUnsignedInt(0x0F)]; // 15

        for (int i = 0; i < bytes.length; i++) {
            bytes[i] = (byte) readUnsignedByte(8);
        }

        return bytes;
    }


    /**
     * Reads a 7-bit ASCII String. First, a 31-bit unsigned integer is read for
     * the character count following the characters which each is read as 7-bit
     * unsigned byte.
     *
     * @return an ASCII String
     * @throws IOException if an I/O error occurs.
     */
    public final String readASCII() throws IOException {

        final byte[] bytes = new byte[readUnsignedInt(0x1F)]; // 31
        for (int i = 0; i < bytes.length; i++) {
            bytes[i] = (byte) readUnsignedByte(0x07);
        }

        return new String(bytes, "US-ASCII");
    }


    /**
     * Reads a modified-UTF8 String. Identical to {@link DataInput#readUTF()}.
     *
     * @return a String
     * @throws IOException if an I/O error occurs.
     * @see DataInput#readUTF() 
     */
    public final String readUTF() throws IOException {

        final CharArrayWriter caw = new CharArrayWriter();

        final int length = readUnsignedInt(0x10); // 16
        int i = 0;
        for (; i < length; i++) {

            final int first = readUnsignedByte(0x08);
            if (first <= 0x7F) { // 0xxxxxxx
                caw.write(first);
                continue;
            }

            if (first <= 0xBF) {
                throw new UTFDataFormatException(
                    "illegal first byte: " + first);
            }

            if (first <= 0xDF) { // 110xxxxx
                final int second = readUnsignedByte(0x08); // EOFException
                i++;
                if (second > 0xBF) { // !10xxxxxxxx
                    throw new UTFDataFormatException(
                        "illegal second byte: " + second);
                }
                caw.write(((first & 0x1F) << 6) | (second & 0x3F));
                continue;
            }

            if (first <= 0xEF) { // 1110xxxx
                final int second = readUnsignedByte(0x08); // EOFException
                i++;
                if (second > 0xBF) { // !10xxxxxxxx
                    throw new UTFDataFormatException(
                        "illegal second byte: " + second);
                }
                final int third = readUnsignedByte(0x08); // EOFException
                i++;
                if (third > 0xBF) { // !10xxxxxxxx
                    throw new UTFDataFormatException(
                        "illegal third byte: " + second);
                }
                caw.write(((first & 0x0F) << 12)
                    | ((second & 0x3F) << 6)
                    | (third & 0x3F));
                continue;
            }

            throw new UTFDataFormatException("illegal first byte: " + first);
        }

        return caw.toString();
    }


    /**
     * Align to given length bytes.
     *
     * @param length number of bytes to align
     * @return bits discarded to align
     * @throws IOException if an I/O error occurs.
     */
    public final int align(final int length) throws IOException {

        if (length <= 0) {
            throw new IllegalArgumentException("length(" + length + ") <= 0");
        }

        int bits = 0;

        if (index < 8) { // bit index to read
            bits += (8 - index);
            readUnsignedByte(8 - index);
        }

        int octets = count % length;

        if (octets == 0) {
            return bits;
        }

        if (octets > 0) {
            octets = length - octets;
        } else { // octets < 0
            octets = 0 - octets;
        }

        for (; octets > 0; octets--) {
            readUnsignedByte(8);
            bits += 8;
        }

        return bits;
    }


    /**
     * Align to 1 byte.
     *
     * @return bits discarded to align
     * @throws IOException if an I/O error occurs.
     */
    public final int align() throws IOException {
        return align(1);
    }


    /** input source. */
    private final InputStream in;


    /** bit set. */
    private final BitSet set = new BitSet(8);


    /** bit index to read. */
    private int index = 8;


    /** so far read octet count. */
    private int count = 0;


    /** test. */
    int getCount() {
        return count;
    }


    /** test. */
    void setCount(int count) {
        this.count = count;
    }


}





© 2015 - 2025 Weber Informatics LLC | Privacy Policy