/*
* Copyright © 2010-2011 Rebecca G. Bettencourt / Kreative Software
* <p>
* The contents of this file are subject to the Mozilla Public License
* Version 1.1 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* <a href="http://www.mozilla.org/MPL/">http://www.mozilla.org/MPL/</a>
* <p>
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
* License for the specific language governing rights and limitations
* under the License.
* <p>
* Alternatively, the contents of this file may be used under the terms
* of the GNU Lesser General Public License (the "LGPL License"), in which
* case the provisions of LGPL License are applicable instead of those
* above. If you wish to allow use of your version of this file only
* under the terms of the LGPL License and not to allow others to use
* your version of this file under the MPL, indicate your decision by
* deleting the provisions above and replace them with the notice and
* other provisions required by the LGPL License. If you do not delete
* the provisions above, a recipient may use your version of this file
* under either the MPL or the LGPL License.
* @since KSFL 1.2
* @author Rebecca G. Bettencourt, Kreative Software
*/
package com.kreative.binpack;
import java.io.*;
import java.math.BigInteger;
import java.math.MathContext;
import java.text.CharacterIterator;
import java.text.StringCharacterIterator;
import java.util.BitSet;
public class BitOutputStream extends OutputStream implements Closeable, DataOutput {
private DataOutputStream out;
private int bitpos;
private int bittmp;
private long bitswritten;
public BitOutputStream(OutputStream out) {
this.out = new DataOutputStream(out);
this.bitpos = 0;
this.bittmp = 0;
this.bitswritten = 0L;
}
public boolean atBitBoundary(int multiple) {
if (multiple < 0) throw new IllegalArgumentException();
else if (multiple == 0 || multiple == 1) return true;
else return ((bitswritten % (long)multiple) == 0L);
}
public boolean atByteBoundary(int multiple) {
if (multiple < 0) throw new IllegalArgumentException();
else if (multiple == 0) return true;
else if (multiple == 1) return (bitpos == 0);
else return ((bitpos == 0) && (((bitswritten >> 3L) % (long)multiple) == 0L));
}
public long bitsWritten() {
return bitswritten;
}
public long bytesWritten() {
return (bitswritten >> 3L);
}
public void writeBit(boolean bit) throws IOException {
if (bitpos == 0) {
bitpos = 128;
bittmp = 0;
}
if (bit) bittmp |= bitpos;
bitpos >>= 1;
bitswritten++;
if (bitpos == 0) {
out.writeByte(bittmp);
}
}
public void writeBits(int n, BitSet bits) throws IOException {
if (n < 0) throw new IllegalArgumentException();
else while (n > 0) {
n--;
writeBit(bits.get(n));
}
}
public void writeBitsLE(int n, BitSet bits) throws IOException {
if (n < 0) throw new IllegalArgumentException();
else if (n == 0); // nothing
else if (bitpos != 0 || ((n & 7) != 0))
throw new IOException("Can't write little-endian values unless on a byte boundry with a byte-multiple width");
else {
int nb = (n >> 3) - 1;
while (n > 0) {
n--;
writeBit(bits.get((n & 7) | ((nb - (n >> 3)) << 3)));
}
}
}
public void writeInteger(int n, BigInteger i) throws IOException {
if (n < 0) throw new IllegalArgumentException();
else while (n > 0) {
n--;
writeBit(i.testBit(n));
}
}
public void writeIntegerLE(int n, BigInteger i) throws IOException {
if (n < 0) throw new IllegalArgumentException();
else if (n == 0); // nothing
else if (bitpos != 0 || ((n & 7) != 0))
throw new IOException("Can't write little-endian values unless on a byte boundry with a byte-multiple width");
else {
int nb = (n >> 3) - 1;
while (n > 0) {
n--;
writeBit(i.testBit((n & 7) | ((nb - (n >> 3)) << 3)));
}
}
}
public void writeFloat(int n, MathContext mc, Number v) throws IOException {
if (n < 0) throw new IllegalArgumentException();
else {
int s = FPUtilities.optimalSignWidth(n);
int e = FPUtilities.optimalExponentWidth(n);
int m = FPUtilities.optimalMantissaWidth(n);
writeFloat(s, e, m, mc, v);
}
}
public void writeFloatLE(int n, MathContext mc, Number v) throws IOException {
if (n < 0) throw new IllegalArgumentException();
else {
int s = FPUtilities.optimalSignWidth(n);
int e = FPUtilities.optimalExponentWidth(n);
int m = FPUtilities.optimalMantissaWidth(n);
writeFloatLE(s, e, m, mc, v);
}
}
public void writeFloat(int s, int e, int m, MathContext mc, Number v) throws IOException {
if (s < 0 || s > 1 || e < 0 || m < 0) throw new IllegalArgumentException();
else {
int b = FPUtilities.optimalBias(e);
writeFloat(s, e, m, b, mc, v);
}
}
public void writeFloatLE(int s, int e, int m, MathContext mc, Number v) throws IOException {
if (s < 0 || s > 1 || e < 0 || m < 0) throw new IllegalArgumentException();
else {
int b = FPUtilities.optimalBias(e);
writeFloatLE(s, e, m, b, mc, v);
}
}
public void writeFloat(int s, int e, int m, int b, MathContext mc, Number v) throws IOException {
if (s < 0 || s > 1 || e < 0 || m < 0) throw new IllegalArgumentException();
else {
BigInteger[] r = FPUtilities.encodeFloat(v, s, e, m, b, mc);
writeInteger(s, r[0]);
writeInteger(e, r[1]);
writeInteger(m, r[2]);
}
}
public void writeFloatLE(int s, int e, int m, int b, MathContext mc, Number v) throws IOException {
if (s < 0 || s > 1 || e < 0 || m < 0) throw new IllegalArgumentException();
else if (bitpos != 0 || (((s+e+m) & 7) != 0))
throw new IOException("Can't write little-endian values unless on a byte boundry with a byte-multiple width");
else {
BigInteger[] r = FPUtilities.encodeFloat(v, s, e, m, b, mc);
BigInteger i = FPUtilities.joinFloat(r[0], r[1], r[2], s, e, m);
writeIntegerLE(s+e+m, i);
}
}
public void close() throws IOException {
if (bitpos != 0) {
out.writeByte(bittmp);
}
out.flush();
out.close();
}
public void flush() throws IOException {
out.flush();
}
public void write(int b) throws IOException {
if (bitpos == 0) {
out.write(b);
bitswritten += 8L;
} else {
int pos = 128;
while (pos != 0) {
writeBit((b & pos) != 0);
pos >>>= 1;
}
}
}
public void write(byte[] b) throws IOException {
if (bitpos == 0) {
out.write(b);
bitswritten += ((long)b.length << 3L);
} else {
for (byte bb : b) {
int pos = 128;
while (pos != 0) {
writeBit((bb & pos) != 0);
pos >>>= 1;
}
}
}
}
public void write(byte[] b, int off, int len) throws IOException {
if (bitpos == 0) {
out.write(b, off, len);
bitswritten += ((long)len << 3L);
} else {
for (int i = 0; i < len; off++, i++) {
int pos = 128;
while (pos != 0) {
writeBit((b[off] & pos) != 0);
pos >>>= 1;
}
}
}
}
public void writeBoolean(boolean v) throws IOException {
if (bitpos == 0) {
out.writeBoolean(v);
bitswritten += 8L;
} else {
writeByte(v ? 1 : 0);
}
}
public void writeByte(int v) throws IOException {
if (bitpos == 0) {
out.writeByte(v);
bitswritten += 8L;
} else {
int pos = 128;
while (pos != 0) {
writeBit((v & pos) != 0);
pos >>>= 1;
}
}
}
public void writeShort(int v) throws IOException {
if (bitpos == 0) {
out.writeShort(v);
bitswritten += 16L;
} else {
int pos = 32768;
while (pos != 0) {
writeBit((v & pos) != 0);
pos >>>= 1;
}
}
}
public void writeShortLE(int v) throws IOException {
if (bitpos == 0) {
out.writeShort(Short.reverseBytes((short)v));
bitswritten += 16L;
} else {
throw new IOException("Can't write little-endian values unless on a byte boundry with a byte-multiple width");
}
}
public void writeChar(int v) throws IOException {
if (bitpos == 0) {
out.writeChar(v);
bitswritten += 16L;
} else {
int pos = 32768;
while (pos != 0) {
writeBit((v & pos) != 0);
pos >>>= 1;
}
}
}
public void writeCharLE(int v) throws IOException {
if (bitpos == 0) {
out.writeChar(Character.reverseBytes((char)v));
bitswritten += 16L;
} else {
throw new IOException("Can't write little-endian values unless on a byte boundry with a byte-multiple width");
}
}
public void writeInt(int v) throws IOException {
if (bitpos == 0) {
out.writeInt(v);
bitswritten += 32L;
} else {
int pos = Integer.MIN_VALUE;
while (pos != 0) {
writeBit((v & pos) != 0);
pos >>>= 1;
}
}
}
public void writeIntLE(int v) throws IOException {
if (bitpos == 0) {
out.writeInt(Integer.reverseBytes(v));
bitswritten += 32L;
} else {
throw new IOException("Can't write little-endian values unless on a byte boundry with a byte-multiple width");
}
}
public void writeLong(long v) throws IOException {
if (bitpos == 0) {
out.writeLong(v);
bitswritten += 64L;
} else {
long pos = Long.MIN_VALUE;
while (pos != 0L) {
writeBit((v & pos) != 0L);
pos >>>= 1L;
}
}
}
public void writeLongLE(long v) throws IOException {
if (bitpos == 0) {
out.writeLong(Long.reverseBytes(v));
bitswritten += 64L;
} else {
throw new IOException("Can't write little-endian values unless on a byte boundry with a byte-multiple width");
}
}
public void writeFloat(float v) throws IOException {
if (bitpos == 0) {
out.writeFloat(v);
bitswritten += 32L;
} else {
writeInt(Float.floatToRawIntBits(v));
}
}
public void writeFloatLE(float v) throws IOException {
if (bitpos == 0) {
out.writeInt(Integer.reverseBytes(Float.floatToRawIntBits(v)));
bitswritten += 32L;
} else {
throw new IOException("Can't write little-endian values unless on a byte boundry with a byte-multiple width");
}
}
public void writeDouble(double v) throws IOException {
if (bitpos == 0) {
out.writeDouble(v);
bitswritten += 64L;
} else {
writeLong(Double.doubleToRawLongBits(v));
}
}
public void writeDoubleLE(double v) throws IOException {
if (bitpos == 0) {
out.writeLong(Long.reverseBytes(Double.doubleToRawLongBits(v)));
bitswritten += 64L;
} else {
throw new IOException("Can't write little-endian values unless on a byte boundry with a byte-multiple width");
}
}
public void writeBytes(String s) throws IOException {
if (bitpos == 0) {
out.writeBytes(s);
bitswritten += ((long)s.length() << 3L);
} else {
CharacterIterator i = new StringCharacterIterator(s);
for (char ch = i.first(); ch != CharacterIterator.DONE; ch = i.next()) {
writeByte(ch);
}
}
}
public void writeChars(String s) throws IOException {
if (bitpos == 0) {
out.writeChars(s);
bitswritten += ((long)s.length() << 4L);
} else {
CharacterIterator i = new StringCharacterIterator(s);
for (char ch = i.first(); ch != CharacterIterator.DONE; ch = i.next()) {
writeChar(ch);
}
}
}
public void writeCharsLE(String s) throws IOException {
if (bitpos == 0) {
CharacterIterator i = new StringCharacterIterator(s);
for (char ch = i.first(); ch != CharacterIterator.DONE; ch = i.next()) {
out.writeChar(Character.reverseBytes(ch));
bitswritten += 16L;
}
} else {
throw new IOException("Can't write little-endian values unless on a byte boundry with a byte-multiple width");
}
}
public void writeUTF(String str) throws IOException {
throw new UnsupportedOperationException();
}
}