/*
* 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 jinahya.io;
import java.io.IOException;
import java.io.OutputStream;
import java.util.BitSet;
/**
*
* @author <a href="mailto:jinahya@gmail.com">Jin Kwon</a>
*/
public class BitOutput {
/**
* Creates a new instance.
*
* @param out target octet output
*/
public BitOutput(final OutputStream out) {
super();
if ((this.out = out) == null) {
throw new IllegalArgumentException("null out");
}
this.set = new BitSet(8);
}
/**
* Writes an unsigned byte value.
*
* @param length bit length between 0 (exclusive) and 8 (inclusive).
* @param value value
* @throws IOException if an I/O error occurs.
*/
private void writeUnsignedByte(final int length, int value)
throws IOException {
if (length <= 0) {
throw new IllegalArgumentException(
"illegal length(" + length + ") <= 0");
}
if (length > 8) {
throw new IllegalArgumentException(
"illegal length(" + length + ") > 8");
}
final int required = length - (8 - index);
if (required > 0) {
writeUnsignedByte(length - required, value >> required);
writeUnsignedByte(required, value);
return;
}
for (int i = length - 1; i >= 0; i--) {
set.set(index + i, (value & 0x01) == 0x01);
value >>= 1;
}
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 a boolean value.
*
* @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 unsgined short value.
*
* @param length bit length between 0 (exclusive) and 16 (inclusive)
* @param value
* @throws IOException
*/
private void writeUnsignedShort(final int length, final int value)
throws IOException {
if (length <= 0) {
throw new IllegalArgumentException(
"illegal length(" + length + ") <= 0");
}
if (length > 16) {
throw new IllegalArgumentException(
"illegal 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 an unsigned int value.
*
* @param length bit length between 1 (inclusive) and 32 (exclusive)
* @param value unsinged 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(
"illegal length(" + length + ") < 1");
}
if (length >= 0x20) {
throw new IllegalArgumentException(
"illegal length(" + length + ") >= 32");
}
if (value < 0x00) {
throw new IllegalArgumentException("illegal value: " + value);
} else {
if ((value >> length) != 0x00) {
throw new IllegalArgumentException("illegal value: " + value);
}
}
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));
}
}
/**
*
* @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(
"illegal length(" + length + ") <= 1");
}
if (length > 0x20) {
throw new IllegalArgumentException(
"illegal length(" + length + ") > 32");
}
if (length < 0x20) {
if (value < 0x00) {
if (value >> length != -1) {
throw new IllegalArgumentException(
"illegal value: " + value);
}
} else {
if ((value >> length) != 0x00) {
throw new IllegalArgumentException(
"illegal value: " + value);
}
}
}
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));
}
}
/**
*
* @param length
* @param value
* @throws IOException
*/
public final void writeUnsignedLong(final int length, final long value)
throws IOException {
if (length < 1) {
throw new IllegalArgumentException(
"illegal length(" + length + ") < 1");
}
if (length >= 64) {
throw new IllegalArgumentException(
"illegal length(" + length + ") >= 64");
}
if (value < 0) {
throw new IllegalArgumentException("negative value: " + value);
} else {
if ((value >> length) != 0) {
throw new IllegalArgumentException(
"value out of range: " + value);
}
}
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));
}
}
/**
*
* @param length
* @param value
* @throws IOException
*/
public final void writeLong(final int length, final long value)
throws IOException {
if (length <= 1) {
throw new IllegalArgumentException(
"illegal length(" + length + ") <= 1");
}
if (length > 64) {
throw new IllegalArgumentException(
"illegal length(" + length + ") > 64");
}
if (length < 64) {
if (value < 0L) {
if (value >> length != -1L) {
throw new IllegalArgumentException(
"illegal value: " + value);
}
} else {
if ((value >> length) != 0L) {
throw new IllegalArgumentException(
"illegal value: " + value);
}
}
}
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));
}
}
/**
*
* @param bytes
* @throws IOException
*/
public final void writeBytes(final byte[] bytes) throws IOException {
writeBytes(bytes, 0, bytes.length);
}
/**
*
* @param bytes
* @param offset
* @param length
* @throws IOException
*/
public final void writeBytes(final byte[] bytes, final int offset,
final int length)
throws IOException {
if (bytes == null) {
throw new IllegalArgumentException("null bytes");
}
if (offset < 0) {
throw new IllegalArgumentException("negative offset");
}
if (offset >= bytes.length) {
throw new IllegalArgumentException(
"offset(" + offset + ") >= bytes.length(" + bytes.length + ")");
}
if (length < 0) {
throw new IllegalArgumentException("length(" + length + ") < 0");
}
if ((offset + length) > bytes.length) {
throw new IllegalArgumentException(
"offset(" + offset + ") + length(" + length
+ ") > bytes.length(" + bytes.length + ")");
}
for (int i = 0; i < length; i++) {
writeUnsignedByte(8, bytes[offset + i] & 0xFF);
}
}
/**
* Aling to given <code>length</code> as octets.
*
* @param length the number of <b>octets</b> to align
*/
public void aling(final int length) throws IOException {
if (length <= 0) {
throw new IllegalArgumentException(
"illegal length: " + length + " <= 0");
}
int octets = count % length;
if (octets > 0) {
octets = length - octets;
} else if (octets == 0) {
octets = length;
} else { // mod < 0
octets = 0 - octets;
}
if (index > 0) {
writeUnsignedByte(8 - index, 0);
octets--;
}
if (octets == length) {
return;
}
for (int i = 0; i < octets; i++) {
writeUnsignedByte(8, 0);
}
}
private final OutputStream out;
private final BitSet set;
private int index = 0;
private int count = 0;
int count() {
return count;
}
void count(final int count) {
this.count = count;
}
}