/** * Copyright 2014 JogAmp Community. All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of * conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this list * of conditions and the following disclaimer in the documentation and/or other materials * provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * The views and conclusions contained in the software and documentation are those of the * authors and should not be interpreted as representing official policies, either expressed * or implied, of JogAmp Community. */ package com.jogamp.common.util; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.ByteBuffer; import jogamp.common.Debug; /** * Versatile Bitstream implementation supporting: *
* Implementation will null
the stream references,
* hence {@link #setStream(Object)} must be called before re-using instance.
*
* markpos is kept, hence {@link #reset()} can be called multiple times. *
* @throws UnsupportedOperationException is not supported, i.e. if stream is not an {@link #canInput() input stream}. * @throws IllegalStateException if markpos has not been set via {@link #mark(int)} or reset operation failed. * @throws IOException if reset operation failed. */ void reset() throws UnsupportedOperationException, IllegalStateException, IOException; /** * Reads one byte from the stream. ** Returns {@link Bitstream#EOS} is end-of-stream is reached, * otherwise the resulting value. *
* @throws IOException * @throws UnsupportedOperationException is not supported, i.e. if stream is not an {@link #canInput() input stream}. */ int read() throws UnsupportedOperationException, IOException; /** * Writes one byte, to the stream. ** Returns {@link Bitstream#EOS} is end-of-stream is reached, * otherwise the written value. *
* @throws IOException * @throws UnsupportedOperationException is not supported, i.e. if stream is not an {@link #canOutput() output stream}. */ int write(final byte val) throws UnsupportedOperationException, IOException; } /** * Specific {@link ByteStream byte stream}. ** Can handle {@link #canInput() input} and {@link #canOutput() output} operations. *
*/ public static class ByteArrayStream implements ByteStream* Can handle {@link #canInput() input} and {@link #canOutput() output} operations. *
*/ public static class ByteBufferStream implements ByteStream* Can handle {@link #canInput() input} operations only. *
*/ public static class ByteInputStream implements ByteStream* Can handle {@link #canOutput() output} operations only. *
*/ public static class ByteOutputStream implements ByteStream* If the previous stream was in {@link #canOutput() output mode}, * {@link #flush()} is being called. *
* @throws IllegalArgumentException if requested outputMode doesn't match stream's {@link #canInput()} and {@link #canOutput()}. * @throws IOException could be caused by {@link #flush()}. */ public final void setStream(final T stream, final boolean outputMode) throws IllegalArgumentException, IOException { if( null != bytes && this.outputMode ) { flush(); } this.bytes.setStream(stream); this.outputMode = outputMode; resetLocal(); validateMode(); } /** Returns the currently used {@link ByteStream}. */ public final ByteStream
* Implementation will null
the stream references,
* hence {@link #setStream(Object)} must be called before re-using instance.
*
* If the closed stream was in {@link #canOutput() output mode}, * {@link #flush()} is being called. *
* * @throws IOException */ public final void close() throws IOException { if( null != bytes && this.outputMode ) { flush(); } bytes.close(); bytes = null; resetLocal(); } /** * Synchronizes all underlying {@link ByteStream#canOutput() output stream} operations, or do nothing. ** Method also flushes incomplete bytes to the underlying {@link ByteStream} * and hence skips to the next byte position. *
* @throws IllegalStateException if not in output mode or stream closed * @throws IOException */ public final void flush() throws IllegalStateException, IOException { if( !outputMode || null == bytes ) { throw new IllegalStateException("not in output-mode: "+this); } bytes.flush(); if( 0 != bitCount ) { bytes.write((byte)bitBuffer); bitBuffer = 0; bitCount = 0; } } /** Return true if stream can handle input, i.e. {@link #readBit(boolean)}. */ public final boolean canInput() { return null != bytes ? bytes.canInput() : false; } /** Return true if stream can handle output, i.e. {@link #writeBit(boolean, int)}. */ public final boolean canOutput() { return null != bytes ? bytes.canOutput() : false; } /** * Set markpos to current position, allowing the stream to be {@link #reset()}. * @param readLimit * @throws IllegalStateException if not in input mode or stream closed */ public final void mark(final int readLimit) throws IllegalStateException { if( outputMode || null == bytes ) { throw new IllegalStateException("not in input-mode: "+this); } bytes.mark(readLimit); bitsDataMark = bitBuffer; bitsCountMark = bitCount; } /** * Reset stream position to markpos as set via {@link #mark(int)}. ** markpos is kept, hence {@link #reset()} can be called multiple times. *
* @throws IllegalStateException if not in input mode or stream closed * @throws IllegalStateException if markpos has not been set via {@link #mark(int)} or reset operation failed. * @throws IOException if reset operation failed. */ public final void reset() throws IllegalStateException, IOException { if( outputMode || null == bytes ) { throw new IllegalStateException("not in input-mode: "+this); } if( 0 > bitsCountMark ) { throw new IllegalStateException("markpos not set: "+this); } bytes.reset(); bitBuffer = bitsDataMark; bitCount = bitsCountMark; } /** * Number of remaining bits in cache to read before next byte-read (input mode) * or number of remaining bits to be cached before next byte-write (output mode). ** Counting down from 7..0 7..0, starting with 0. *
** In input mode, zero indicates reading a new byte and cont. w/ 7. * In output mode, the cached byte is written when flipping over to 0. *
*/ public final int getBitCount() { return bitCount; } /** * Return the last bit number read or written counting from [0..7]. * If no bit access has been performed, 7 is returned. ** Returned value is normalized [0..7], i.e. independent from msb or lsb read order. *
*/ public final int getLastBitPos() { return 7 - bitCount; } /** * Return the next bit number to be read or write counting from [0..7]. * If no bit access has been performed, 0 is returned. ** Returned value is normalized [0..7], i.e. independent from msb or lsb read order. *
*/ public final int getBitPosition() { if( 0 == bitCount ) { return 0; } else { return 8 - bitCount; } } /** * Returns the current bit buffer. * @see #getBitCount() */ public final int getBitBuffer() { return bitBuffer; } /** * Returns the bit position in the stream. */ public final long position() { // final long bytePos = bytes.position() - ( !outputMode && 0 != bitCount ? 1 : 0 ); // return ( bytePos << 3 ) + getBitPosition(); if( null == bytes ) { return EOS; } else if( 0 == bitCount ) { return bytes.position() << 3; } else { final long bytePos = bytes.position() - ( outputMode ? 0 : 1 ); return ( bytePos << 3 ) + 8 - bitCount; } } /** * @param msbFirst if true incoming stream bit order is MSB to LSB, otherwise LSB to MSB. * @return the read bit or {@link #EOS} if end-of-stream is reached. * @throws IOException * @throws IllegalStateException if not in input mode or stream closed */ public final int readBit(final boolean msbFirst) throws IllegalStateException, IOException { if( outputMode || null == bytes ) { throw new IllegalStateException("not in input-mode: "+this); } if( msbFirst ) { // MSB if ( 0 < bitCount ) { bitCount--; return ( bitBuffer >>> bitCount ) & 0x01; } else { bitBuffer = bytes.read(); if( EOS == bitBuffer ) { return EOS; } else { bitCount=7; return bitBuffer >>> 7; } } } else { // LSB if ( 0 < bitCount ) { bitCount--; return ( bitBuffer >>> ( 7 - bitCount ) ) & 0x01; } else { bitBuffer = bytes.read(); if( EOS == bitBuffer ) { return EOS; } else { bitCount=7; return bitBuffer & 0x01; } } } } /** * @param msbFirst if true outgoing stream bit order is MSB to LSB, otherwise LSB to MSB. * @param bit * @return the currently written byte or {@link #EOS} if end-of-stream is reached. * @throws IOException * @throws IllegalStateException if not in output mode or stream closed */ public final int writeBit(final boolean msbFirst, final int bit) throws IllegalStateException, IOException { if( !outputMode || null == bytes ) { throw new IllegalStateException("not in output-mode: "+this); } if( msbFirst ) { // MSB if ( 0 < bitCount ) { bitCount--; bitBuffer |= ( 0x01 & bit ) << bitCount; if( 0 == bitCount ) { return bytes.write((byte)bitBuffer); } } else { bitCount = 7; bitBuffer = ( 0x01 & bit ) << 7; } } else { // LSB if ( 0 < bitCount ) { bitCount--; bitBuffer |= ( 0x01 & bit ) << ( 7 - bitCount ); if( 0 == bitCount ) { return bytes.write((byte)bitBuffer); } } else { bitCount = 7; bitBuffer = 0x01 & bit; } } return bitBuffer; } /** * It is implementation dependent, whether backward skip giving a negative number is supported or not. * * @param n number of bits to skip * @return actual skipped bits * @throws IOException * @throws IllegalStateException if closed */ public long skip(final long n) throws IllegalStateException, IOException { if( null == bytes ) { throw new IllegalStateException("closed: "+this); } if( DEBUG ) { System.err.println("Bitstream.skip.0: "+n+" - "+toStringImpl()); } if( n > 0 ) { if( n <= bitCount ) { bitCount -= (int)n; if( DEBUG ) { System.err.println("Bitstream.skip.F_N1: "+n+" - "+toStringImpl()); } return n; } else { // n > bitCount if( outputMode ) { if( 0 < bitCount ) { bytes.write((byte)bitBuffer); } bitBuffer = 0; } final long n2 = n - bitCount; // subtract cached bits, bitsCount is zero at this point final long n3 = n2 >>> 3; // bytes to skip final long n4 = bytes.skip(n3); // actual skipped bytes final int n5 = (int) ( n2 - ( n3 << 3 ) ); // remaining skip bits == nX % 8 final long nX = ( n4 << 3 ) + n5 + bitCount; // actual skipped bits /** if( DEBUG ) { System.err.println("Bitstream.skip.1: n2 "+n2+", n3 "+n3+", n4 "+n4+", n5 "+n5+", nX "+nX+" - "+toStringImpl()); } */ if( nX < n ) { // couldn't complete skipping .. EOS .. etc bitCount = 0; bitBuffer = 0; if( DEBUG ) { System.err.println("Bitstream.skip.F_EOS: "+n+" - "+toStringImpl()); } return nX; } bitCount = ( 8 - n5 ) & 7; // % 8 if( !outputMode && 0 < bitCount ) { bitBuffer = bytes.read(); } if( DEBUG ) { System.err.println("Bitstream.skip.F_N2: "+n+" - "+toStringImpl()); } return nX; } } else { // FIXME: Backward skip return 0; } } /** * Return incoming bits as read via {@link #readBit(boolean)}. ** The incoming bits are stored in MSB-first order, i.e. first on highest position and last bit on lowest position. * Hence reading w/ lsbFirst, the bit order will be reversed! *
* @param msbFirst if true incoming stream bit order is MSB to LSB, otherwise LSB to MSB. * @param n number of bits, maximum 31 bits * @return the read bits from 0-n in the given order or {@link #EOS}. * @throws IllegalStateException if not in input mode or stream closed * @throws IllegalArgumentException if n > 31 * @throws IOException */ public int readBits31(final boolean msbFirst, final int n) throws IllegalArgumentException, IOException { if( 31 < n ) { throw new IllegalArgumentException("n > 31: "+n); } if( outputMode || null == bytes ) { throw new IllegalStateException("not in input-mode: "+this); } if( !msbFirst || 0 == n ) { // Slow path int r = 0; int c = n; while(--c >= 0) { final int b = readBit(msbFirst); if( EOS == b ) { return EOS; } r |= b << c; } return r; } else { // fast path: MSB int c = n; final int n1 = Math.min(c, bitCount); // remaining portion int r; if( 0 < n1 ) { final int m1 = ( 1 << n1 ) - 1; bitCount -= n1; c -= n1; r = ( m1 & ( bitBuffer >>> bitCount ) ) << c; if( 0 == c ) { return r; } } else { r = 0; } assert( 0 == bitCount ); do { bitBuffer = bytes.read(); if( EOS == bitBuffer ) { return EOS; } final int n2 = Math.min(c, 8); // full portion final int m2 = ( 1 << n2 ) - 1; bitCount = 8 - n2; c -= n2; r |= ( m2 & ( bitBuffer >>> bitCount ) ) << c; } while ( 0 < c ); return r; } } /** * Write the given bits via {@link #writeBit(boolean, int)}. ** The given bits are scanned from LSB-first order. * Hence reading w/ msbFirst, the bit order will be reversed! *
* @param msbFirst if true incoming stream bit order is MSB to LSB, otherwise LSB to MSB. * @param n number of bits, maximum 31 bits * @param bits the bits to write * @return the written bits or {@link #EOS}. * @throws IllegalStateException if not in output mode or stream closed * @throws IllegalArgumentException if n > 31 * @throws IOException */ public int writeBits31(final boolean msbFirst, final int n, final int bits) throws IllegalStateException, IllegalArgumentException, IOException { if( 31 < n ) { throw new IllegalArgumentException("n > 31: "+n); } if( !outputMode || null == bytes ) { throw new IllegalStateException("not in output-mode: "+this); } if( !msbFirst || 0 == n ) { // Slow path int c = n; while(--c >= 0) { final int b = writeBit(msbFirst, ( bits >>> c ) & 0x1); if( EOS == b ) { return EOS; } } } else { // fast path: MSB int c = n; final int n1 = Math.min(c, bitCount); // remaining portion if( 0 < n1 ) { final int m1 = ( 1 << n1 ) - 1; bitCount -= n1; c -= n1; bitBuffer |= ( m1 & ( bits >> c ) ) << bitCount; if( 0 == bitCount ) { if( EOS == bytes.write((byte)bitBuffer) ) { return EOS; } } if( 0 == c ) { return bits; } } assert( 0 == bitCount ); do { final int n2 = Math.min(c, 8); // full portion final int m2 = ( 1 << n2 ) - 1; bitCount = 8 - n2; c -= n2; bitBuffer = ( m2 & ( bits >> c ) ) << bitCount; if( 0 == bitCount ) { if( EOS == bytes.write((byte)bitBuffer) ) { return EOS; } } } while ( 0 < c ); } return bits; } /** * Return incoming int8 as read via {@link #readBits31(boolean, int)}. * @param msbFirst if true incoming stream bit order is MSB to LSB, otherwise LSB to MSB. * @return {@link #EOS} or the 8bit value, which might be unsigned or 2-complement signed value. * In the signed case, user shall cast the result tobyte
.
* @throws IllegalStateException if not in input mode or stream closed
* @throws IOException
*/
public final int readInt8(final boolean msbFirst) throws IllegalStateException, IOException {
if( 0 == bitCount && msbFirst ) {
// fast path
if( outputMode || null == bytes ) {
throw new IllegalStateException("not in input-mode: "+this);
}
return bytes.read();
} else {
return readBits31(msbFirst, 8);
}
}
/**
* Write the given int8 via {@link #writeBits31(boolean, int, int)}.
* @param msbFirst if true incoming stream bit order is MSB to LSB, otherwise LSB to MSB.
* @return {@link #EOS} or the written 8bit value.
* @throws IllegalStateException if not in output mode or stream closed
* @throws IOException
*/
public final int writeInt8(final boolean msbFirst, final byte int8) throws IllegalStateException, IOException {
if( 0 == bitCount && msbFirst ) {
// fast path
if( !outputMode || null == bytes ) {
throw new IllegalStateException("not in output-mode: "+this);
}
return bytes.write(int8);
} else {
return this.writeBits31(msbFirst, 8, int8);
}
}
/**
* Return incoming int16 as read via {@link #readBits31(boolean, int)}
* and swap bytes if !bigEndian.
* @param msbFirst if true incoming stream bit order is MSB to LSB, otherwise LSB to MSB.
* @param bigEndian if false, swap incoming bytes to little-endian, otherwise leave them as little-endian.
* @return {@link #EOS} or the 16bit value, which might be unsigned or 2-complement signed value.
* In the signed case, user shall cast the result to short
.
* @throws IllegalStateException if not in input mode or stream closed
* @throws IOException
*/
public final int readInt16(final boolean msbFirst, final boolean bigEndian) throws IllegalStateException, IOException {
if( 0 == bitCount && msbFirst ) {
// fast path
if( outputMode || null == bytes ) {
throw new IllegalStateException("not in input-mode: "+this);
}
final int b1 = bytes.read();
final int b2 = EOS != b1 ? bytes.read() : EOS;
if( EOS == b2 ) {
return EOS;
} else if( bigEndian ) {
return b1 << 8 | b2;
} else {
return b2 << 8 | b1;
}
} else {
final int i16 = readBits31(msbFirst, 16);
if( EOS == i16 ) {
return EOS;
} else if( bigEndian ) {
return i16;
} else {
final int b1 = 0xff & ( i16 >>> 8 );
final int b2 = 0xff & i16;
return b2 << 8 | b1;
}
}
}
/**
* Return incoming int16 value and swap bytes if !bigEndian.
* @param bigEndian if false, swap incoming bytes to little-endian, otherwise leave them as little-endian.
* @return the 16bit value, which might be unsigned or 2-complement signed value.
* In the signed case, user shall cast the result to short
.
* @throws IndexOutOfBoundsException
*/
public static final int readInt16(final boolean bigEndian, final byte[] bytes, final int offset) throws IndexOutOfBoundsException {
checkBounds(bytes, offset, 2);
final int b1 = bytes[offset];
final int b2 = bytes[offset+1];
if( bigEndian ) {
return b1 << 8 | b2;
} else {
return b2 << 8 | b1;
}
}
/**
* Write the given int16 via {@link #writeBits31(boolean, int, int)},
* while swapping bytes if !bigEndian beforehand.
* @param msbFirst if true incoming stream bit order is MSB to LSB, otherwise LSB to MSB.
* @param bigEndian if false, swap given bytes to little-endian, otherwise leave them as little-endian.
* @return {@link #EOS} or the written 16bit value.
* @throws IllegalStateException if not in output mode or stream closed
* @throws IOException
*/
public final int writeInt16(final boolean msbFirst, final boolean bigEndian, final short int16) throws IllegalStateException, IOException {
if( 0 == bitCount && msbFirst ) {
// fast path
if( !outputMode || null == bytes ) {
throw new IllegalStateException("not in output-mode: "+this);
}
final byte hi = (byte) ( 0xff & ( int16 >>> 8 ) );
final byte lo = (byte) ( 0xff & int16 );
final byte b1, b2;
if( bigEndian ) {
b1 = hi;
b2 = lo;
} else {
b1 = lo;
b2 = hi;
}
if( EOS != bytes.write(b1) ) {
if( EOS != bytes.write(b2) ) {
return int16;
}
}
return EOS;
} else if( bigEndian ) {
return writeBits31(msbFirst, 16, int16);
} else {
final int b1 = 0xff & ( int16 >>> 8 );
final int b2 = 0xff & int16;
return writeBits31(msbFirst, 16, b2 << 8 | b1);
}
}
/**
* Return incoming int32 as read via {@link #readBits31(boolean, int)}
* and swap bytes if !bigEndian.
*
* In case the returned value shall be interpreted as uint32_t
* utilize {@link #toUInt32Long(int)} or {@link #toUInt32Int(int)} for
* an appropriate conversion.
*
* In case the returned value shall be interpreted as uint32_t
* utilize {@link #toUInt32Long(int)} or {@link #toUInt32Int(int)} for
* an appropriate conversion.
*
int32_t
value as uint32_t
,
* i.e. perform the following cast to long
:
* * final long l = 0xffffffffL & int32; **/ public static final long toUInt32Long(final int int32) { return 0xffffffffL & int32; } /** * Returns the reinterpreted given
int32_t
value
* as uint32_t
if ≤ {@link Integer#MAX_VALUE}
* as within an int
storage.
* Otherwise return -1.
*/
public static final int toUInt32Int(final int int32) {
return uint32LongtoInt(toUInt32Long(int32));
}
/**
* Returns the given uint32_t
value long
* value as int
if ≤ {@link Integer#MAX_VALUE}.
* Otherwise return -1.
*/
public static final int uint32LongtoInt(final long uint32) {
if( Integer.MAX_VALUE >= uint32 ) {
return (int)uint32;
} else {
return -1;
}
}
public String toString() {
return String.format("Bitstream[%s]", toStringImpl());
}
protected String toStringImpl() {
final String mode;
final long bpos;
if( null == bytes ) {
mode = "closed";
bpos = -1;
} else {
mode = outputMode ? "output" : "input";
bpos = bytes.position();
}
return String.format("%s, pos %d [byteP %d, bitCnt %d], bitbuf %s",
mode, position(), bpos, bitCount, toHexBinString(bitBuffer, 8));
}
private static final String strZeroPadding= "0000000000000000000000000000000000000000000000000000000000000000"; // 64
public static String toBinString(final int v, final int bitCount) {
if( 0 == bitCount ) {
return "";
}
final int mask = (int) ( ( 1L << bitCount ) - 1L );
final String s0 = Integer.toBinaryString( mask & v );
return strZeroPadding.substring(0, bitCount-s0.length())+s0;
}
public static String toHexBinString(final int v, final int bitCount) {
final int nibbles = 0 == bitCount ? 2 : ( bitCount + 3 ) / 4;
return String.format("[%0"+nibbles+"X, %s]", v, toBinString(v, bitCount));
}
public static void checkBounds(final byte[] sb, final int offset, final int remaining) throws IndexOutOfBoundsException {
if( offset + remaining > sb.length ) {
throw new IndexOutOfBoundsException("Buffer of size "+sb.length+" cannot hold offset "+offset+" + remaining "+remaining);
}
}
}