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

com.googlecode.jinahya.io.BitOutput 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.ByteArrayOutputStream;
import java.io.DataOutput;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UTFDataFormatException;

import java.util.BitSet;


/**
 * Bit-level wrapper for OutputStream.
 *
 * @author Jin Kwon
 */
public class BitOutput {


    /**
     * Creates a new instance.
     *
     * @param out target octet output
     */
    public BitOutput(final OutputStream out) {
        super();

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

        this.out = out;
    }


    /**
     * Writes an unsigned byte value. This method doesn't check the validity of
     * the value and writes the lower length bits in
     * value.
     *
     * @param length bit length between 0 (exclusive) and 8 (inclusive).
     * @param value value
     * @throws IOException if an I/O error occurs.
     */
    protected final void writeUnsignedByte(final int length, final int value)
        throws IOException {

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

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

        final int required = length - (8 - index);
        if (required > 0) {
            writeUnsignedByte(length - required, value >> required);
            writeUnsignedByte(required, value);
            return;
        }

        index += length;
        for (int i = 0; i < length; i++) {
            index--;
            set.set(index, ((value >>> i) & 0x01) == 0x01);
        }

        index += length;
        if (index == 8) {
            int octet = 0x00;
            for (int i = 0; i < 8; i++) {
                octet <<= 1;
                octet |= (set.get(i) ? 0x01 : 0x00);
            }
            out.write(octet);
            count++;
            index = 0;
        }
    }


    /**
     * Writes an unsigned short value. This method doesn't check the validity of
     * the value and writes the lower length bits in
     * value.
     *
     * @param length bit length between 0 (exclusive) and 16 (inclusive)
     * @param value value to write
     * @throws IOException if an I/O error occurs
     */
    protected final void writeUnsignedShort(final int length, final int value)
        throws IOException {

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

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

        final int quotient = length / 8;
        final int remainder = length % 8;

        if (remainder > 0) {
            writeUnsignedByte(remainder, value >> (quotient * 8));
        }

        for (int i = quotient - 1; i >= 0; i--) {
            writeUnsignedByte(8, value >> (8 * i));
        }
    }


    /**
     * Writes a boolean value. Only one bit is used; 1 for true 0 for false.
     *
     * @param value value
     * @throws IOException if an I/O error occurs
     */
    public final void writeBoolean(final boolean value) throws IOException {
        writeUnsignedByte(0x01, value ? 0x01 : 0x00);
    }


    /**
     * Writes an unsigned int value. An IllegalArgumentException
     * will be thrown if length or value is out of
     * valid range.
     *
     * @param length bit length between 1 (inclusive) and 32 (exclusive)
     * @param value unsigned int value
     * @throws IOException if an I/O error occurs.
     */
    public final void writeUnsignedInt(final int length, final int value)
        throws IOException {

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

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

        if ((value >> length) != 0x00) {
            throw new IllegalArgumentException(
                "value(" + value + ") >> length(" + length + ") != 0x00");
        }

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

        if (remainder > 0) {
            writeUnsignedShort(remainder, value >> (quotient * 0x10));
        }

        for (int i = quotient - 1; i >= 0; i--) {
            writeUnsignedShort(0x10, value >> (0x10 * i));
        }
    }


    /**
     * Writes a signed int.
     *
     * @param length bit length between 1 (exclusive) and 32 (inclusive).
     * @param value signed int value
     * @throws IOException if an I/O error occurs.
     */
    public final void writeInt(final int length, final int value)
        throws IOException {

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

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

        if (length < 0x20) {
            if (value < 0x00) {
                if (value >> length != -1) {
                    throw new IllegalArgumentException(
                        "value(" + value + ") >> length(" + length + ") != -1");
                }
            } else { // value >= 0x00
                if ((value >> length) != 0x00) {
                    throw new IllegalArgumentException(
                        "value(" + value + ") >> length(" + length + ") != 0");
                }
            }
        }

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

        if (remainder > 0) {
            writeUnsignedShort(remainder, value >> (quotient * 0x10));
        }

        for (int i = quotient - 1; i >= 0; i--) {
            writeUnsignedShort(0x10, value >> (0x10 * i));
        }
    }


    /**
     * Writes a 32-bit signed integer.
     *
     * @param value a 32-bit signed integer
     * @throws IOException if an I/O error occurs.
     */
    public final void writeInt(final int value) throws IOException {
        writeInt(0x20, value);
    }


    /**
     * Writes a float value.
     *
     * @param value the float value
     * @throws IOException if an I/O error occurs.
     */
    public final void writeFloat(final float value) throws IOException {
        writeInt(Float.floatToRawIntBits(value));
    }


    /**
     * Writes an unsigned long.
     *
     * @param length bit length between 1 (inclusive) and 64 (exclusive)
     * @param value value to write
     * @throws IOException if an I/O error occurs.
     */
    public final void writeUnsignedLong(final int length, final long value)
        throws IOException {

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

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

        if ((value >> length) != 0) {
            throw new IllegalArgumentException(
                "value(" + value + ") >> length(" + length + ") != 0");
        }

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

        if (remainder > 0) {
            writeUnsignedShort(
                remainder, (int) ((value >> (quotient * 0x10)) & 0xFFFF));
        }

        for (int i = quotient - 1; i >= 0; i--) {
            writeUnsignedShort(0x10, (int) ((value >> (0x10 * i)) & 0xFFFF));
        }
    }


    /**
     * Writes a signed long value in length bits.
     *
     * @param length bit length between 1 (exclusive) and 64 (inclusive).
     * @param value value to write
     * @throws IOException if an I/O error occurs.
     */
    public final void writeLong(final int length, final long value)
        throws IOException {

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

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

        if (length < 64) {
            if (value < 0L) {
                if (value >> length != -1L) {
                    throw new IllegalArgumentException(
                        "value(" + value + ") >> length(" + length
                        + ") != -1L");
                }
            } else {
                if ((value >> length) != 0x00L) {
                    throw new IllegalArgumentException(
                        "value(" + value + ") >> length(" + length
                        + ") != 0x00L");
                }
            }
        }

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

        if (remainder > 0x00) {
            writeUnsignedShort(
                remainder, (int) ((value >> (quotient * 0x10)) & 0xFFFF));
        }

        for (int i = quotient - 1; i >= 0; i--) {
            writeUnsignedShort(16, (int) ((value >> (16 * i)) & 0xFFFF));
        }
    }


    /**
     * Writes a 64-bit signed long value.
     *
     * @param value the value
     * @throws IOException if an I/O error occurs.
     */
    public final void writeLong(final long value) throws IOException {
        writeLong(0x40, value);
    }


    /**
     * Writes a double value.
     *
     * @param value the value
     * @throws IOException if an I/O error occurs.
     */
    public final void writeDouble(final double value) throws IOException {
        writeLong(Double.doubleToRawLongBits(value));
    }


    /**
     * Writes given bytes.
     *
     * @param bytes bytes to write
     * @throws IOException if an I/O error occurs.
     */
    public final void writeBytes(final byte[] bytes) throws IOException {

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

        writeBytes(bytes, 0, bytes.length);
    }


    /**
     * Writes length bytes in bytes from
     * offset.
     *
     * @param bytes bytes
     * @param offset the start offset in bytes
     * @param length number of bytes to write
     * @throws IOException if an I/O error occurs.
     */
    public final void writeBytes(final byte[] bytes, final int offset,
        final int length)
        throws IOException {

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

        if (offset < 0) {
            throw new IndexOutOfBoundsException("offset(" + offset + ") < 0");
        }

        if (length < 0) {
            throw new IndexOutOfBoundsException("length(" + length + ") < 0");
        }
        if (length > 65535) {
            throw new IllegalArgumentException(
                "length(" + length + ") > 65535");
        }

        if (offset + length > bytes.length) {
            throw new IllegalArgumentException(
                "offset(" + offset + ") + length(" + length
                + ") > bytes.length(" + bytes.length + ")");
        }

        writeUnsignedShort(0x0F, length); // 15
        for (int i = 0; i < length; i++) {
            writeUnsignedByte(0x08, bytes[offset + i] & 0xFF);
        }
    }


    /**
     * Writes a 7-bit ASCII string. Writes a 31-bit unsigned integer for the
     * character count following all characters which each is written as 7-bit
     * unsigned byte.
     *
     * @param value ASCII string to write
     * @throws IOException if an I/O error occurs
     */
    public final void writeASCII(final String value) throws IOException {

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

        final byte[] bytes = value.getBytes("US-ASCII");
        writeUnsignedInt(0x1F, bytes.length);
        for (int i = 0; i < bytes.length; i++) {
            writeUnsignedByte(0x07, bytes[i] & 0xFF);
        }
    }


    /**
     * Writes a UTF string. Identical to {@link DataOutput#writeUTF(String)}.
     *
     * @param value string to write
     * @throws IOException if an I/O error occurs
     * @see DataOutput#writeUTF(String) 
     */
    public final void writeUTF(final String value) throws IOException {

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

        final ByteArrayOutputStream baos = new ByteArrayOutputStream();

        for (int i = 0; i < value.length(); i++) {

            final char c = value.charAt(i);

            if (baos.size() > 65535) {
                throw new UTFDataFormatException("too long");
            }

            if (c >= '\u0001' && c <= '\u007F') {
                baos.write(c);
                continue;
            }

            if (c == '\u0000' || (c >= '\u0080' && c <= '\u07FF')) {
                baos.write((0xC0 | (0x1F & (c >> 6))));
                baos.write((0x80 | (0x3F & c)));
                continue;
            }

            if (c >= '\u0800' && c <= '\uFFFF') {
                baos.write((0xE0 | (0x0F & (c >> 12))));
                baos.write((0x80 | (0x3F & (c >> 6))));
                baos.write((0x80 | (0x3F & c)));
                continue;
            }
        }

        final byte[] bytes = baos.toByteArray();
        writeUnsignedInt(0x10, bytes.length);
        for (int i = 0; i < bytes.length; i++) {
            writeUnsignedByte(0x08, bytes[i]);
        }
    }


    /**
     * Aligns to given length as bytes.
     *
     * @param length the number of bytes to align
     * @return the bits padded 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 > 0) { // bit index to write
            bits = (8 - index);
            writeUnsignedByte(8 - index, 0);
        }

        int octets = count % length;

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

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

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

        return bits;
    }


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


    /** output target .*/
    private final OutputStream out;


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


    /** bit index to write. */
    private int index = 0;


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


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


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


}