diff options
author | Sven Gothel <[email protected]> | 2014-02-20 17:42:36 +0100 |
---|---|---|
committer | Sven Gothel <[email protected]> | 2014-02-20 17:42:36 +0100 |
commit | d4d337be925a28b8701ac335c2b5cc6e390cabc9 (patch) | |
tree | 97b3f7bea859838635f91875fba99413c4d2a516 /src | |
parent | 86bdae8ce26d291c0096ed500581239dd2a87125 (diff) |
Bug 890: Adding versatile Bitstream implementation
We already have several locations where bitstream operations are required and
partially implemented (JPEG decoder, media parsing, ..)
as well as endian related conversion (elf parser, ..).
Create a versatile Bitstream class allowing:
- Utilize I/O operations on I/O streams, buffers and arrays
- Consider MSBfirst / LSBfirst mode
- Linear bit R/W operations
- Bulk R/W operations w/ endian related type conversion
- Allow mark/reset and switching streams and input/output mode
- Optimized operations
Complete set of unit tests included, covering hopefully all cases.
Diffstat (limited to 'src')
-rw-r--r-- | src/java/com/jogamp/common/util/Bitstream.java | 1341 | ||||
-rw-r--r-- | src/junit/com/jogamp/common/util/BitstreamData.java | 119 | ||||
-rw-r--r-- | src/junit/com/jogamp/common/util/TestBitstream00.java | 115 | ||||
-rw-r--r-- | src/junit/com/jogamp/common/util/TestBitstream01.java | 342 | ||||
-rw-r--r-- | src/junit/com/jogamp/common/util/TestBitstream02.java | 133 | ||||
-rw-r--r-- | src/junit/com/jogamp/common/util/TestBitstream03.java | 154 | ||||
-rw-r--r-- | src/junit/com/jogamp/common/util/TestBitstream04.java | 158 |
7 files changed, 2362 insertions, 0 deletions
diff --git a/src/java/com/jogamp/common/util/Bitstream.java b/src/java/com/jogamp/common/util/Bitstream.java new file mode 100644 index 0000000..7bf0c16 --- /dev/null +++ b/src/java/com/jogamp/common/util/Bitstream.java @@ -0,0 +1,1341 @@ +/** + * 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: + * <ul> + * <li>Utilize I/O operations on I/O streams, buffers and arrays</li> + * <li>Consider MSBfirst / LSBfirst mode</li> + * <li>Linear bit R/W operations</li> + * <li>Bulk R/W operations w/ endian related type conversion</li> + * <li>Allow mark/reset and switching streams and input/output mode</li> + * <li>Optimized operations</li> + * </ul> + */ +public class Bitstream<T> { + private static final boolean DEBUG = Debug.debug("Bitstream"); + + /** End of stream marker, {@value} or 0xFFFFFFFF */ + public static final int EOS = -1; + + /** + * General byte stream. + */ + public static interface ByteStream<T> { + /** Sets the underlying stream, without {@link #close()}ing the previous one. */ + void setStream(final T stream); + + /** Returns the underlying stream */ + T getStream(); + + /** + * Closing the underlying stream, implies {@link #flush()}. + * <p> + * Implementation will <code>null</code> the stream references, + * hence {@link #setStream(Object)} must be called before re-using instance. + * </p> + * @throws IOException + */ + void close() throws IOException; + + /** + * Synchronizes all underlying {@link #canOutput() output stream} operations, or do nothing. + * @throws IOException + */ + void flush() throws IOException; + + /** Return true if stream can handle input, i.e. {@link #read()}. */ + boolean canInput(); + + /** Return true if stream can handle output, i.e. {@link #write(byte)} */ + boolean canOutput(); + + /** + * Returns the byte position in the stream. + */ + long position(); + + /** + * It is implementation dependent, whether backward skip giving a negative number is supported or not. + * @param n number of bytes to skip + * @return actual skipped bytes + * @throws IOException + */ + long skip(final long n) throws IOException; + + /** + * Set <i>markpos</i> to current position, allowing the stream to be {@link #reset()}. + * @param readLimit + * @throws UnsupportedOperationException is not supported, i.e. if stream is not an {@link #canInput() input stream}. + */ + void mark(final int readLimit) throws UnsupportedOperationException; + + /** + * Reset stream position to <i>markpos</i> as set via {@link #mark(int)}. + * <p> + * <i>markpos</i> is kept, hence {@link #reset()} can be called multiple times. + * </p> + * @throws UnsupportedOperationException is not supported, i.e. if stream is not an {@link #canInput() input stream}. + * @throws IllegalStateException if <i>markpos</i> 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. + * <p> + * Returns {@link Bitstream#EOS} is end-of-stream is reached, + * otherwise the resulting value. + * </p> + * @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. + * <p> + * Returns {@link Bitstream#EOS} is end-of-stream is reached, + * otherwise the written value. + * </p> + * @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}. + * <p> + * Can handle {@link #canInput() input} and {@link #canOutput() output} operations. + * </p> + */ + public static class ByteArrayStream implements ByteStream<byte[]> { + private byte[] media; + private int pos; + private int posMark; + + public ByteArrayStream(final byte[] stream) { + setStream(stream); + } + + @Override + public void setStream(final byte[] stream) { + media = stream; + pos = 0; + posMark = -1; + } + + @Override + public byte[] getStream() { return media; } + + @Override + public void close() { + media = null; + } + @Override + public void flush() { + // NOP + } + + @Override + public boolean canInput() { return true; } + + @Override + public boolean canOutput() { return true; } + + @Override + public long position() { return pos; } + + @Override + public long skip(final long n) { + final long skip; + if( n >= 0 ) { + final int remaining = media.length - pos; + skip = Math.min(remaining, (int)n); + } else { + final int n2 = (int)n * -1; + skip = -1 * Math.min(pos, n2); + } + pos += skip; + return skip; + } + + @Override + public void mark(final int readLimit) { + posMark = pos; + } + + @Override + public void reset() throws IllegalStateException { + if( 0 > posMark ) { + throw new IllegalStateException("markpos not set"); + } + if(DEBUG) { System.err.println("rewind: "+pos+" -> "+posMark); } + pos = posMark; + } + + @Override + public int read() { + final int r; + if( media.length > pos ) { + r = 0xff & media[pos++]; + } else { + r = -1; // EOS + } + if( DEBUG ) { + if( EOS != r ) { + System.err.println("u8["+(pos-1)+"] -> "+toHexBinString(r, 8)); + } else { + System.err.println("u8["+(pos-0)+"] -> EOS"); + } + } + return r; + } + + @Override + public int write(final byte val) { + final int r; + if( media.length > pos ) { + media[pos++] = val; + r = 0xff & val; + } else { + r = -1; // EOS + } + if( DEBUG ) { + if( EOS != r ) { + System.err.println("u8["+(pos-1)+"] <- "+toHexBinString(r, 8)); + } else { + System.err.println("u8["+(pos-0)+"] <- EOS"); + } + } + return r; + } + } + + /** + * Specific {@link ByteStream byte stream}. + * <p> + * Can handle {@link #canInput() input} and {@link #canOutput() output} operations. + * </p> + */ + public static class ByteBufferStream implements ByteStream<ByteBuffer> { + private ByteBuffer media; + private int pos; + private int posMark; + + public ByteBufferStream(final ByteBuffer stream) { + setStream(stream); + } + + @Override + public void setStream(final ByteBuffer stream) { + media = stream; + pos = 0; + posMark = -1; + } + + @Override + public ByteBuffer getStream() { return media; } + + @Override + public void close() { + media = null; + } + @Override + public void flush() { + // NOP + } + + @Override + public boolean canInput() { return true; } + + @Override + public boolean canOutput() { return true; } + + @Override + public long position() { return pos; } + + @Override + public long skip(final long n) { + final long skip; + if( n >= 0 ) { + final int remaining = media.limit() - pos; + skip = Math.min(remaining, (int)n); + } else { + final int n2 = (int)n * -1; + skip = -1 * Math.min(pos, n2); + } + pos += skip; + return skip; + } + + @Override + public void mark(final int readLimit) { + posMark = pos; + } + + @Override + public void reset() throws IllegalStateException { + if( 0 > posMark ) { + throw new IllegalStateException("markpos not set"); + } + if(DEBUG) { System.err.println("rewind: "+pos+" -> "+posMark); } + media.position(posMark); + pos = posMark; + } + + @Override + public int read() { + final int r; + if( media.limit() > pos ) { + r = 0xff & media.get(pos++); + } else { + r = -1; // EOS + } + if( DEBUG ) { + if( EOS != r ) { + System.err.println("u8["+(pos-1)+"] -> "+toHexBinString(r, 8)); + } else { + System.err.println("u8["+(pos-0)+"] -> EOS"); + } + } + return r; + } + + @Override + public int write(final byte val) { + final int r; + if( media.limit() > pos ) { + media.put(pos++, val); + r = 0xff & val; + } else { + r = -1; // EOS + } + if( DEBUG ) { + if( EOS != r ) { + System.err.println("u8["+(pos-1)+"] <- "+toHexBinString(r, 8)); + } else { + System.err.println("u8["+(pos-0)+"] <- EOS"); + } + } + return r; + } + } + + /** + * Specific {@link ByteStream byte stream}. + * <p> + * Can handle {@link #canInput() input} operations only. + * </p> + */ + public static class ByteInputStream implements ByteStream<InputStream> { + private BufferedInputStream media; + private long pos; + private long posMark; + + public ByteInputStream(final InputStream stream) { + setStream(stream); + } + + @Override + public void setStream(final InputStream stream) { + if( stream instanceof BufferedInputStream ) { + media = (BufferedInputStream) stream; + } else if( null != stream ) { + media = new BufferedInputStream(stream); + } else { + media = null; + } + pos = 0; + posMark = -1; + } + + @Override + public InputStream getStream() { return media; } + + @Override + public void close() throws IOException { + if( null != media ) { + media.close(); + media = null; + } + } + @Override + public void flush() { + // NOP + } + + @Override + public boolean canInput() { return true; } + + @Override + public boolean canOutput() { return false; } + + @Override + public long position() { return pos; } + + @Override + public long skip(final long n) throws IOException { + final long skip = media.skip(n); + pos += skip; + return skip; + } + + @Override + public void mark(final int readLimit) { + media.mark(readLimit); + posMark = pos; + } + + @Override + public void reset() throws IllegalStateException, IOException { + if( 0 > posMark ) { + throw new IllegalStateException("markpos not set"); + } + if(DEBUG) { System.err.println("rewind: "+pos+" -> "+posMark); } + media.reset(); + pos = posMark; + } + + @Override + public int read() throws IOException { + final int r = media.read(); + if(DEBUG) { + if( EOS != r ) { + System.err.println("u8["+pos+"] -> "+toHexBinString(r, 8)); + } else { + System.err.println("u8["+pos+"] -> EOS"); + } + } + if( EOS != r ) { + pos++; + } + return r; + } + + @Override + public int write(final byte val) throws UnsupportedOperationException { + throw new UnsupportedOperationException("not allowed with input stream"); + } + } + + /** + * Specific {@link ByteStream byte stream}. + * <p> + * Can handle {@link #canOutput() output} operations only. + * </p> + */ + public static class ByteOutputStream implements ByteStream<OutputStream> { + private BufferedOutputStream media; + private long pos = 0; + + public ByteOutputStream(final OutputStream stream) { + setStream(stream); + } + + @Override + public void setStream(final OutputStream stream) { + if( stream instanceof BufferedOutputStream ) { + media = (BufferedOutputStream) stream; + } else if( null != stream ) { + media = new BufferedOutputStream(stream); + } else { + media = null; + } + pos = 0; + } + + @Override + public void close() throws IOException { + if( null != media ) { + media.close(); + media = null; + } + } + @Override + public void flush() throws IOException { + if( null != media ) { + media.flush(); + } + } + + @Override + public boolean canInput() { return false; } + + @Override + public boolean canOutput() { return true; } + + @Override + public long position() { return pos; } + + @Override + public long skip(final long n) throws IOException { + long i = n; + while(i > 0) { + media.write(0); + i--; + } + final long skip = n-i; // should be n + pos += skip; + return skip; + } + + @Override + public OutputStream getStream() { return media; } + + @Override + public void mark(final int readLimit) throws UnsupportedOperationException { + throw new UnsupportedOperationException("not allowed with output stream"); + } + + @Override + public void reset() throws UnsupportedOperationException { + throw new UnsupportedOperationException("not allowed with output stream"); + } + + @Override + public int read() throws UnsupportedOperationException { + throw new UnsupportedOperationException("not allowed with output stream"); + } + + @Override + public int write(final byte val) throws IOException { + final int r = 0xff & val; + media.write(r); + if(DEBUG) { + System.err.println("u8["+pos+"] <- "+toHexBinString(r, 8)); + } + pos++; + return r; + } + } + + private ByteStream<T> bytes; + /** 8-bit cache of byte stream */ + private int bitBuffer; + private int bitsDataMark; + + /** See {@link #getBitCount()}. */ + private int bitCount; + private int bitsCountMark; + + private boolean outputMode; + + /** + * @param stream + * @param outputMode + * @throws IllegalArgumentException if requested <i>outputMode</i> doesn't match stream's {@link #canInput()} and {@link #canOutput()}. + */ + public Bitstream(final ByteStream<T> stream, final boolean outputMode) throws IllegalArgumentException { + this.bytes = stream; + this.outputMode = outputMode; + resetLocal(); + validateMode(); + } + + private final void resetLocal() { + bitBuffer = 0; + bitCount = 0; + bitsDataMark = 0; + bitsCountMark = -1; + } + private final void validateMode() throws IllegalArgumentException { + if( !canInput() && !canOutput() ) { + throw new IllegalArgumentException("stream can neither input nor output: "+this); + } + if( outputMode && !canOutput() ) { + throw new IllegalArgumentException("stream cannot output as requested: "+this); + } + if( !outputMode && !canInput() ) { + throw new IllegalArgumentException("stream cannot input as requested: "+this); + } + } + + /** + * Sets the underlying stream, without {@link #close()}ing the previous one. + * <p> + * If the previous stream was in {@link #canOutput() output mode}, + * {@link #flush()} is being called. + * </p> + * @throws IllegalArgumentException if requested <i>outputMode</i> 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<T> getStream() { return bytes; } + + /** Returns the currently used {@link ByteStream}'s {@link ByteStream#getStream()}. */ + public final T getSubStream() { return bytes.getStream(); } + + /** + * Closing the underlying stream, implies {@link #flush()}. + * <p> + * Implementation will <code>null</code> the stream references, + * hence {@link #setStream(Object)} must be called before re-using instance. + * </p> + * <p> + * If the closed stream was in {@link #canOutput() output mode}, + * {@link #flush()} is being called. + * </p> + * + * @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. + * <p> + * Method also flushes incomplete bytes to the underlying {@link ByteStream} + * and hence skips to the next byte position. + * </p> + * @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 <i>markpos</i> 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 <i>markpos</i> as set via {@link #mark(int)}. + * <p> + * <i>markpos</i> is kept, hence {@link #reset()} can be called multiple times. + * </p> + * @throws IllegalStateException if not in input mode or stream closed + * @throws IllegalStateException if <i>markpos</i> 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). + * <p> + * Counting down from 7..0 7..0, starting with 0. + * </p> + * <p> + * 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. + * </p> + */ + 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. + * <p> + * Returned value is normalized [0..7], i.e. independent from <i>msb</i> or <i>lsb</i> read order. + * </p> + */ + 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. + * <p> + * Returned value is normalized [0..7], i.e. independent from <i>msb</i> or <i>lsb</i> read order. + * </p> + */ + 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)}. + * <p> + * The incoming bits are stored in MSB-first order, i.e. first on highest position and last bit on lowest position. + * Hence reading w/ <i>lsbFirst</i>, the bit order will be reversed! + * </p> + * @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)}. + * <p> + * The given bits are scanned from LSB-first order. + * Hence reading w/ <i>msbFirst</i>, the bit order will be reversed! + * </p> + * @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 to <code>byte</code>. + * @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 <code>short</code>. + * @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 <code>short</code>. + * @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. + * <p> + * In case the returned value shall be interpreted as unsigned, + * it shall be cast to <code>long</code> as follows: + * <pre> + * final long l = 0xffffffffL & int32; + * </pre> + * </p> + * @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 32bit value, which might be unsigned or 2-complement signed value. + * @throws IllegalStateException if not in input mode or stream closed + * @throws IOException + */ + public final int readInt32(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; + final int b3 = EOS != b2 ? bytes.read() : EOS; + final int b4 = EOS != b3 ? bytes.read() : EOS; + if( EOS == b4 ) { + return EOS; + } else if( bigEndian ) { + return b1 << 24 | b2 << 16 | b3 << 8 | b4; + } else { + return b4 << 24 | b3 << 16 | b2 << 8 | b1; + } + } else { + final int i16a = readBits31(msbFirst, 16); + final int i16b = EOS != i16a ? readBits31(msbFirst, 16) : EOS; + if( EOS == i16b ) { + return EOS; + } else if( bigEndian ) { + return i16a << 16 | i16b; + } else { + final int b1 = 0xff & ( i16a >>> 8 ); + final int b2 = 0xff & i16a; + final int b3 = 0xff & ( i16b >>> 8 ); + final int b4 = 0xff & i16b; + return b4 << 24 | b3 << 16 | b2 << 8 | b1; + } + } + } + + /** + * Return incoming int32 as read via {@link #readBits31(boolean, int)} + * and swap bytes if !bigEndian. + * <p> + * In case the returned value shall be interpreted as unsigned, + * it shall be cast to <code>long</code> as follows: + * <pre> + * final long l = 0xffffffffL & int32; + * </pre> + * </p> + * @param bigEndian if false, swap incoming bytes to little-endian, otherwise leave them as little-endian. + * @return the 32bit value, which might be unsigned or 2-complement signed value. + * @throws IndexOutOfBoundsException + */ + public static final int readInt32(final boolean bigEndian, final byte[] bytes, final int offset) throws IndexOutOfBoundsException { + checkBounds(bytes, offset, 4); + final int b1 = bytes[offset]; + final int b2 = bytes[offset+1]; + final int b3 = bytes[offset+2]; + final int b4 = bytes[offset+3]; + if( bigEndian ) { + return b1 << 24 | b2 << 16 | b3 << 8 | b4; + } else { + return b4 << 24 | b3 << 16 | b2 << 8 | b1; + } + } + + /** + * Write the given int32 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 32bit value. + * @throws IllegalStateException if not in output mode or stream closed + * @throws IOException + */ + public final int writeInt32(final boolean msbFirst, final boolean bigEndian, final int int32) throws IllegalStateException, IOException { + if( 0 == bitCount && msbFirst ) { + // fast path + if( !outputMode || null == bytes ) { + throw new IllegalStateException("not in output-mode: "+this); + } + final byte p1 = (byte) ( 0xff & ( int32 >>> 24 ) ); + final byte p2 = (byte) ( 0xff & ( int32 >>> 16 ) ); + final byte p3 = (byte) ( 0xff & ( int32 >>> 8 ) ); + final byte p4 = (byte) ( 0xff & int32 ); + final byte b1, b2, b3, b4; + if( bigEndian ) { + b1 = p1; + b2 = p2; + b3 = p3; + b4 = p4; + } else { + b1 = p4; + b2 = p3; + b3 = p2; + b4 = p1; + } + if( EOS != bytes.write(b1) ) { + if( EOS != bytes.write(b2) ) { + if( EOS != bytes.write(b3) ) { + if( EOS != bytes.write(b4) ) { + return int32; + } + } + } + } + return EOS; + } else if( bigEndian ) { + final int hi = 0x0000ffff & ( int32 >>> 16 ); + final int lo = 0x0000ffff & int32 ; + if( EOS != writeBits31(msbFirst, 16, hi) ) { + if( EOS != writeBits31(msbFirst, 16, lo) ) { + return int32; + } + } + return EOS; + } else { + final int p1 = 0xff & ( int32 >>> 24 ); + final int p2 = 0xff & ( int32 >>> 16 ); + final int p3 = 0xff & ( int32 >>> 8 ); + final int p4 = 0xff & int32 ; + if( EOS != writeBits31(msbFirst, 16, p4 << 8 | p3) ) { + if( EOS != writeBits31(msbFirst, 16, p2 << 8 | p1) ) { + return int32; + } + } + return EOS; + } + } + + 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); + } + } +} diff --git a/src/junit/com/jogamp/common/util/BitstreamData.java b/src/junit/com/jogamp/common/util/BitstreamData.java new file mode 100644 index 0000000..93e8ad0 --- /dev/null +++ b/src/junit/com/jogamp/common/util/BitstreamData.java @@ -0,0 +1,119 @@ +/** + * 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.nio.ByteBuffer; + +public class BitstreamData { + // + // MSB -> LSB over whole data + // + public static final byte[] testBytesMSB = new byte[] { (byte)0xde, (byte)0xaf, (byte)0xca, (byte)0xfe }; + public static final String[] testStringsMSB = new String[] { "11011110", "10101111", "11001010", "11111110" }; + public static final String testStringMSB = testStringsMSB[0]+testStringsMSB[1]+testStringsMSB[2]+testStringsMSB[3]; + + // + // MSB -> LSB, reverse bit-order over each byte of testBytesLSB + // + public static final byte[] testBytesMSB_rev = new byte[] { (byte)0xfe, (byte)0xca, (byte)0xaf, (byte)0xde }; + public static final String[] testStringsMSB_rev = new String[] { "11111110", "11001010", "10101111", "11011110" }; + public static final String testStringMSB_rev = testStringsMSB_rev[0]+testStringsMSB_rev[1]+testStringsMSB_rev[2]+testStringsMSB_rev[3]; + + // + // LSB -> MSB over whole data + // + public static final byte[] testBytesLSB = new byte[] { (byte)0x7f, (byte)0x53, (byte)0xf5, (byte)0x7b }; + public static final String[] testStringsLSB = new String[] { "01111111", "01010011", "11110101", "01111011" }; + public static final String testStringLSB = testStringsLSB[0]+testStringsLSB[1]+testStringsLSB[2]+testStringsLSB[3]; + + // + // LSB -> MSB, reverse bit-order over each byte of testBytesMSB + // + public static final byte[] testBytesLSB_revByte = new byte[] { (byte)0x7b, (byte)0xf5, (byte)0x53, (byte)0x7f }; + public static final String[] testStringsLSB_revByte = new String[] { "01111011", "11110101", "01010011", "01111111" }; + public static final String testStringLSB_revByte = testStringsLSB_revByte[0]+testStringsLSB_revByte[1]+testStringsLSB_revByte[2]+testStringsLSB_revByte[3]; + + public static final void dumpData(String prefix, byte[] data, int offset, int len) { + for(int i=0; i<len; ) { + System.err.printf("%s: %03d: ", prefix, i); + for(int j=0; j<8 && i<len; j++, i++) { + final int v = 0xFF & data[offset+i]; + System.err.printf(toHexBinaryString(v, 8)+", "); + } + System.err.println(""); + } + } + public static final void dumpData(String prefix, ByteBuffer data, int offset, int len) { + for(int i=0; i<len; ) { + System.err.printf("%s: %03d: ", prefix, i); + for(int j=0; j<8 && i<len; j++, i++) { + final int v = 0xFF & data.get(offset+i); + System.err.printf(toHexBinaryString(v, 8)+", "); + } + System.err.println(""); + } + } + + public static String toHexString(int v) { + return "0x"+Integer.toHexString(v); + } + public static final String strZeroPadding= "0000000000000000000000000000000000000000000000000000000000000000"; // 64 + public static String toBinaryString(int v, 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 toBinaryString(long v, int bitCount) { + if( 0 == bitCount ) { + return ""; + } + final long mask = ( 1L << bitCount ) - 1L; + final String s0 = Long.toBinaryString( mask & v ); + return strZeroPadding.substring(0, bitCount-s0.length())+s0; + } + public static String toHexBinaryString(long v, int bitCount) { + final int nibbles = 0 == bitCount ? 2 : ( bitCount + 3 ) / 4; + return String.format("[%0"+nibbles+"X, %s]", v, toBinaryString(v, bitCount)); + } + public static String toHexBinaryString(int v, int bitCount) { + final int nibbles = 0 == bitCount ? 2 : ( bitCount + 3 ) / 4; + return String.format("[%0"+nibbles+"X, %s]", v, toBinaryString(v, bitCount)); + } + public static String toHexBinaryString(short v, int bitCount) { + final int nibbles = 0 == bitCount ? 2 : ( bitCount + 3 ) / 4; + return String.format("[%0"+nibbles+"X, %s]", v, toBinaryString(v, bitCount)); + } + public static String toUnsignedBinaryString(final int int32) { + final long l = 0xffffffffL & int32; + return l+", "+toHexBinaryString(l, 32); + } +} diff --git a/src/junit/com/jogamp/common/util/TestBitstream00.java b/src/junit/com/jogamp/common/util/TestBitstream00.java new file mode 100644 index 0000000..ef1fd45 --- /dev/null +++ b/src/junit/com/jogamp/common/util/TestBitstream00.java @@ -0,0 +1,115 @@ +/** + * 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.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.IntBuffer; +import java.nio.LongBuffer; + +import org.junit.Test; + +import com.jogamp.common.nio.Buffers; +import com.jogamp.common.os.Platform; +import static com.jogamp.common.util.BitstreamData.*; +import com.jogamp.junit.util.JunitTracer; + +import org.junit.FixMethodOrder; +import org.junit.runners.MethodSorters; + +/** + * Test basic bit operations for {@link Bitstream} + */ +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +public class TestBitstream00 extends JunitTracer { + + @Test + public void test00ShowByteOrder() { + int i_ff = 0xff; + byte b_ff = (byte)i_ff; + System.err.println("i_ff "+i_ff+", "+toHexBinaryString(i_ff, 8)); + System.err.println("b_ff "+b_ff+", "+toHexBinaryString(0xff & b_ff, 8)); + + System.err.println("Platform.LITTLE_ENDIAN: "+Platform.isLittleEndian()); + showOrderImpl(null); + showOrderImpl(ByteOrder.BIG_ENDIAN); + showOrderImpl(ByteOrder.LITTLE_ENDIAN); + + dumpData("tstMSB.whole", testBytesMSB, 0, testBytesMSB.length); + dumpData("tstLSB.pbyte", testBytesLSB_revByte, 0, testBytesLSB_revByte.length); + dumpData("tstLSB.whole", testBytesLSB, 0, testBytesLSB.length); + } + void showOrderImpl(ByteOrder byteOrder) { + final ByteBuffer bb_long = ByteBuffer.allocate(Buffers.SIZEOF_LONG); + if( null != byteOrder ) { + bb_long.order(byteOrder); + } + System.err.println("Order: "+byteOrder+" -> "+bb_long.order()); + final LongBuffer lb = bb_long.asLongBuffer(); + lb.put(0, 0x0807060504030201L); + dumpData("long."+byteOrder, bb_long, 0, bb_long.capacity()); + + final ByteBuffer bb_int = ByteBuffer.allocate(Buffers.SIZEOF_INT); + if( null != byteOrder ) { + bb_int.order(byteOrder); + } + final IntBuffer ib = bb_int.asIntBuffer(); + ib.put(0, 0x04030201); + dumpData("long."+byteOrder, bb_int, 0, bb_int.capacity()); + + dumpData("tstMSB.whole", testBytesMSB, 0, testBytesMSB.length); + dumpData("tstLSB.pbyte", testBytesLSB_revByte, 0, testBytesLSB_revByte.length); + dumpData("tstLSB.whole", testBytesLSB, 0, testBytesLSB.length); + } + + @Test + public void test01ShiftSigned() { + shiftSigned(0xA0000000); // negative w/ '1010' top-nibble + shiftSigned(-1); + } + void shiftSigned(final int i0) { + System.err.printf("i0 %012d, %s%n", i0, toHexBinaryString(i0, 32)); + { + int im = i0; + for(int i=0; i<32; i++) { + final int bitA = ( 0 != ( i0 & ( 1 << i ) ) ) ? 1 : 0; + final int bitB = im & 0x01; + System.err.printf("[%02d]: bit[%d, %d], im %012d, %s%n", i, bitA, bitB, im, toHexBinaryString(im, 32)); + im = im >>> 1; + } + } + } + + public static void main(String args[]) throws IOException { + String tstname = TestBitstream00.class.getName(); + org.junit.runner.JUnitCore.main(tstname); + } + +} diff --git a/src/junit/com/jogamp/common/util/TestBitstream01.java b/src/junit/com/jogamp/common/util/TestBitstream01.java new file mode 100644 index 0000000..a8ae4a5 --- /dev/null +++ b/src/junit/com/jogamp/common/util/TestBitstream01.java @@ -0,0 +1,342 @@ +/** + * 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.IOException; +import java.nio.ByteBuffer; + +import org.junit.Assert; +import org.junit.Test; + +import com.jogamp.junit.util.JunitTracer; +import static com.jogamp.common.util.BitstreamData.*; + +import org.junit.FixMethodOrder; +import org.junit.runners.MethodSorters; + +/** + * Test {@link Bitstream} w/ raw linear and bulk read/write access w/o semantics: + * <ul> + * <li>{@link Bitstream#readBit(boolean)}</li> + * <li>{@link Bitstream#writeBit(boolean, int)}</li> + * <li>{@link Bitstream#mark(int)}</li> + * <li>{@link Bitstream#reset()}</li> + * <li>{@link Bitstream#flush()}</li> + * <li>{@link Bitstream#readBits31(boolean, int)}</li> + * <li>{@link Bitstream#writeBits31(boolean, int, int)}</li> + * </ul> + */ +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +public class TestBitstream01 extends JunitTracer { + + Bitstream<ByteBuffer> getTestStream(final boolean msbFirst, int preBits, final int skipBits, final int postBits) throws IOException { + final int byteCount = ( preBits + skipBits + postBits + 7 ) / 8; + final ByteBuffer bbTest = ByteBuffer.allocate(byteCount); + final Bitstream.ByteBufferStream bbsTest = new Bitstream.ByteBufferStream(bbTest); + final Bitstream<ByteBuffer> bsTest = new Bitstream<ByteBuffer>(bbsTest, true /* outputMode */); + final String sTest0; + if( msbFirst ) { + sTest0 = testStringMSB.substring(0, preBits+skipBits+postBits); + } else { + sTest0 = testStringLSB.substring(0, preBits+skipBits+postBits); + } + for(int i=0; i<preBits+skipBits+postBits; i++) { + final int bit = Integer.valueOf(sTest0.substring(i, i+1)); + bsTest.writeBit(msbFirst, bit); + } + Assert.assertEquals(preBits+skipBits+postBits, bsTest.position()); + bsTest.setStream(bsTest.getSubStream(), false /* outputMode */); // switch to input-mode, implies flush() + return bsTest; + } + + String getTestStreamResultAsString(final boolean msbFirst, int preBits, final int skipBits, final int postBits) { + final String pre, post; + if( msbFirst ) { + pre = testStringMSB.substring(0, preBits); + post = testStringMSB.substring(preBits+skipBits, preBits+skipBits+postBits); + } else { + pre = testStringLSB.substring(0, preBits); + post = testStringLSB.substring(preBits+skipBits, preBits+skipBits+postBits); + } + final String r = pre + post; + System.err.println("Test: <"+pre+"> + <"+post+"> = <"+r+">"); + return r; + } + + @Test + public void test01LinearBitsMSBFirst() throws IOException { + testLinearBitsImpl(true /* msbFirst */); + } + @Test + public void test02LinearBitsLSBFirst() throws IOException { + testLinearBitsImpl(false /* msbFirst */); + } + void testLinearBitsImpl(final boolean msbFirst) throws IOException { + testLinearBitsImpl(msbFirst, 0, 0, 1); + testLinearBitsImpl(msbFirst, 0, 0, 3); + testLinearBitsImpl(msbFirst, 0, 0, 8); + testLinearBitsImpl(msbFirst, 0, 0, 10); + testLinearBitsImpl(msbFirst, 0, 0, 30); + testLinearBitsImpl(msbFirst, 0, 0, 32); + + testLinearBitsImpl(msbFirst, 3, 0, 3); + testLinearBitsImpl(msbFirst, 8, 0, 3); + testLinearBitsImpl(msbFirst, 9, 0, 3); + + testLinearBitsImpl(msbFirst, 0, 1, 1); + testLinearBitsImpl(msbFirst, 0, 1, 3); + testLinearBitsImpl(msbFirst, 0, 2, 8); + testLinearBitsImpl(msbFirst, 0, 8, 10); + testLinearBitsImpl(msbFirst, 0, 12, 20); + testLinearBitsImpl(msbFirst, 0, 23, 9); + + testLinearBitsImpl(msbFirst, 1, 1, 1); + testLinearBitsImpl(msbFirst, 2, 1, 3); + testLinearBitsImpl(msbFirst, 7, 2, 8); + testLinearBitsImpl(msbFirst, 8, 8, 8); + testLinearBitsImpl(msbFirst, 15, 12, 5); + testLinearBitsImpl(msbFirst, 16, 11, 5); + } + + String readBits(final boolean msbFirst, final Bitstream<?> copy, final Bitstream<?> input, final int preCount, final int count) throws IOException { + final StringBuilder sbRead = new StringBuilder(); + int i = 0; + while( i < count ) { + final int bit = input.readBit(msbFirst); + if( Bitstream.EOS == bit ) { + System.err.printf(" EOS"); + break; + } else { + sbRead.append( ( 0 != bit ) ? '1' : '0' ); + i++; + Assert.assertEquals(i+preCount, input.position()); + if( null != copy ) { + copy.writeBit(msbFirst, bit); + Assert.assertEquals(i+preCount, copy.position()); + } + } + } + Assert.assertEquals(i+preCount, input.position()); + if( null != copy ) { + Assert.assertEquals(i+preCount, copy.position()); + } + return sbRead.toString(); + } + + void testLinearBitsImpl(final boolean msbFirst, int preBits, int skipBits, final int postBits) throws IOException { + final int totalBits = preBits+skipBits+postBits; + System.err.println("XXX TestLinearBits: msbFirst "+msbFirst+", preBits "+preBits+", skipBits "+skipBits+", postBits "+postBits+", totalBits "+totalBits); + + // prepare bitstream + System.err.println("Prepare bitstream"); + final Bitstream<ByteBuffer> bsTest = getTestStream(msbFirst, preBits, skipBits, postBits); + dumpData("Test", bsTest.getSubStream(), 0, bsTest.getSubStream().limit()); + final String sTest = getTestStreamResultAsString(msbFirst, preBits, skipBits, postBits); + + // init copy-bitstream + final int byteCount = ( totalBits + 7 ) / 8; + final ByteBuffer bbCopy = ByteBuffer.allocate(byteCount); + final Bitstream.ByteBufferStream bbsCopy = new Bitstream.ByteBufferStream(bbCopy); + final Bitstream<ByteBuffer> bsCopy = new Bitstream<ByteBuffer>(bbsCopy, true /* outputMode */); + + // read-bitstream .. and copy bits while reading + System.err.println("Reading bitstream: "+bsTest); + { + final String sReadPre = readBits(msbFirst, bsCopy, bsTest, 0, preBits); + { + final int skippedBits = (int) bsTest.skip(skipBits); + Assert.assertEquals(skipBits, skippedBits); + } + { + final int skippedBits = (int) bsCopy.skip(skipBits); + Assert.assertEquals(skipBits, skippedBits); + } + final String sReadPost = readBits(msbFirst, bsCopy, bsTest, preBits+skipBits, postBits); + final String sRead = sReadPre + sReadPost; + System.err.println("Read.Test: <"+sReadPre+"> + <"+sReadPost+"> = <"+sRead+">"); + Assert.assertEquals(sTest, sRead); + Assert.assertEquals(totalBits, bsTest.position()); + Assert.assertEquals(totalBits, bsCopy.position()); + } + + // read copy .. + bsCopy.setStream(bsCopy.getSubStream(), false /* outputMode */); // switch to input-mode, implies flush() + dumpData("Copy", bbCopy, 0, bbCopy.limit()); + System.err.println("Reading copy-bitstream: "+bsCopy); + bsCopy.mark(0); // mark at beginning + Assert.assertEquals(0, bsCopy.position()); + { + final String sReadPre1 = readBits(msbFirst, null, bsCopy, 0, preBits); + { + final int skippedBits = (int) bsCopy.skip(skipBits); + Assert.assertEquals(skipBits, skippedBits); + } + final String sReadPost1 = readBits(msbFirst, null, bsCopy, preBits+skipBits, postBits); + final String sRead1 = sReadPre1 + sReadPost1; + System.err.println("Read.Copy.1: <"+sReadPre1+"> + <"+sReadPost1+"> = <"+sRead1+">"); + Assert.assertEquals(sTest, sRead1); + + bsCopy.reset(); + final String sReadPre2 = readBits(msbFirst, null, bsCopy, 0, preBits); + Assert.assertEquals(sReadPre1, sReadPre2); + { + final int skippedBits = (int) bsCopy.skip(skipBits); + Assert.assertEquals(skipBits, skippedBits); + } + final String sReadPost2 = readBits(msbFirst, null, bsCopy, preBits+skipBits, postBits); + Assert.assertEquals(sReadPost1, sReadPost2); + final String sRead2 = sReadPre2 + sReadPost2; + System.err.println("Read.Copy.2: <"+sReadPre2+"> + <"+sReadPost2+"> = <"+sRead2+">"); + Assert.assertEquals(sTest, sRead2); + Assert.assertEquals(totalBits, bsCopy.position()); + } + } + + @Test + public void test03BulkBitsMSBFirst() throws IOException { + testBulkBitsImpl(true); + } + @Test + public void test04BulkBitsLSBFirst() throws IOException { + testBulkBitsImpl(false); + } + void testBulkBitsImpl(final boolean msbFirst) throws IOException { + testBulkBitsImpl(msbFirst, 0, 0, 1); + testBulkBitsImpl(msbFirst, 0, 0, 3); + testBulkBitsImpl(msbFirst, 0, 0, 8); + testBulkBitsImpl(msbFirst, 0, 0, 10); + testBulkBitsImpl(msbFirst, 0, 0, 30); + testBulkBitsImpl(msbFirst, 0, 0, 31); + + testBulkBitsImpl(msbFirst, 3, 0, 3); + testBulkBitsImpl(msbFirst, 8, 0, 3); + testBulkBitsImpl(msbFirst, 9, 0, 3); + + testBulkBitsImpl(msbFirst, 0, 1, 1); + testBulkBitsImpl(msbFirst, 0, 1, 3); + testBulkBitsImpl(msbFirst, 0, 2, 8); + testBulkBitsImpl(msbFirst, 0, 8, 10); + testBulkBitsImpl(msbFirst, 0, 12, 20); + testBulkBitsImpl(msbFirst, 0, 23, 9); + testBulkBitsImpl(msbFirst, 0, 1, 31); + + testBulkBitsImpl(msbFirst, 1, 1, 1); + testBulkBitsImpl(msbFirst, 2, 1, 3); + testBulkBitsImpl(msbFirst, 7, 2, 8); + testBulkBitsImpl(msbFirst, 8, 8, 8); + testBulkBitsImpl(msbFirst, 15, 12, 5); + testBulkBitsImpl(msbFirst, 16, 11, 5); + } + + void testBulkBitsImpl(final boolean msbFirst, int preBits, final int skipBits, final int postBits) throws IOException { + final int totalBits = preBits+skipBits+postBits; + System.err.println("XXX TestBulkBits: msbFirst "+msbFirst+", preBits "+preBits+", skipBits "+skipBits+", postBits "+postBits+", totalBits "+totalBits); + + // prepare bitstream + System.err.println("Prepare bitstream"); + final Bitstream<ByteBuffer> bsTest = getTestStream(msbFirst, preBits, skipBits, postBits); + dumpData("Test", bsTest.getSubStream(), 0, bsTest.getSubStream().limit()); + final String sTest = getTestStreamResultAsString(msbFirst, preBits, skipBits, postBits); + + // init copy-bitstream + final int byteCount = ( totalBits + 7 ) / 8; + final ByteBuffer bbCopy = ByteBuffer.allocate(byteCount); + final Bitstream.ByteBufferStream bbsCopy = new Bitstream.ByteBufferStream(bbCopy); + final Bitstream<ByteBuffer> bsCopy = new Bitstream<ByteBuffer>(bbsCopy, true /* outputMode */); + + // read-bitstream .. and copy bits while reading + System.err.println("Reading bitstream: "+bsTest); + { + final int readBitsPre = bsTest.readBits31(msbFirst, preBits); + Assert.assertEquals(readBitsPre, bsCopy.writeBits31(msbFirst, preBits, readBitsPre)); + + final int skippedReadBits = (int) bsTest.skip(skipBits); + final int skippedBitsCopy = (int) bsCopy.skip(skipBits); + + final int readBitsPost = bsTest.readBits31(msbFirst, postBits); + Assert.assertEquals(readBitsPost, bsCopy.writeBits31(msbFirst, postBits, readBitsPost)); + final String sReadPre = toBinaryString(readBitsPre, preBits); + final String sReadPost = toBinaryString(readBitsPost, postBits); + final String sRead = sReadPre + sReadPost; + System.err.println("Read.Test: <"+sReadPre+"> + <"+sReadPost+"> = <"+sRead+">"); + + Assert.assertEquals(skipBits, skippedReadBits); + Assert.assertEquals(sTest, sRead); + Assert.assertEquals(totalBits, bsTest.position()); + Assert.assertEquals(skipBits, skippedBitsCopy); + } + + // read copy .. + bsCopy.setStream(bsCopy.getSubStream(), false /* outputMode */); // switch to input-mode, implies flush() + dumpData("Copy", bbCopy, 0, bbCopy.limit()); + System.err.println("Reading copy-bitstream: "+bsCopy); + Assert.assertEquals(0, bsCopy.position()); + { + final int copyBitsPre = bsCopy.readBits31(msbFirst, preBits); + + final int skippedCopyBits = (int) bsCopy.skip(skipBits); + + final int copyBitsPost = bsCopy.readBits31(msbFirst, postBits); + final String sCopyPre = toBinaryString(copyBitsPre, preBits); + final String sCopyPost = toBinaryString(copyBitsPost, postBits); + final String sCopy = sCopyPre + sCopyPost; + System.err.println("Copy.Test: <"+sCopyPre+"> + <"+sCopyPost+"> = <"+sCopy+">"); + + Assert.assertEquals(skipBits, skippedCopyBits); + Assert.assertEquals(sTest, sCopy); + Assert.assertEquals(totalBits, bsCopy.position()); + } + } + + @Test + public void test05ErrorHandling() throws IOException { + // prepare bitstream + final Bitstream<ByteBuffer> bsTest = getTestStream(false, 0, 0, 0); + System.err.println("01a: "+bsTest); + bsTest.close(); + System.err.println("01b: "+bsTest); + + try { + bsTest.readBit(false); + } catch (Exception e) { + Assert.assertNotNull(e); + } + try { + bsTest.writeBit(false, 1); + } catch (Exception e) { + Assert.assertNotNull(e); + } + } + + public static void main(String args[]) throws IOException { + String tstname = TestBitstream01.class.getName(); + org.junit.runner.JUnitCore.main(tstname); + } + +} diff --git a/src/junit/com/jogamp/common/util/TestBitstream02.java b/src/junit/com/jogamp/common/util/TestBitstream02.java new file mode 100644 index 0000000..dc7333c --- /dev/null +++ b/src/junit/com/jogamp/common/util/TestBitstream02.java @@ -0,0 +1,133 @@ +/** + * 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.IOException; +import java.nio.ByteBuffer; + +import org.junit.Assert; +import org.junit.Test; + +import com.jogamp.common.nio.Buffers; +import com.jogamp.junit.util.JunitTracer; + +import static com.jogamp.common.util.BitstreamData.*; + +import org.junit.FixMethodOrder; +import org.junit.runners.MethodSorters; + +/** + * Test {@link Bitstream} w/ int8 read/write access w/ semantics + * as well as with aligned and unaligned access. + * <ul> + * <li>{@link Bitstream#readInt8(boolean, boolean)}</li> + * <li>{@link Bitstream#writeInt8(boolean, boolean, byte)}</li> + * </ul> + */ +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +public class TestBitstream02 extends JunitTracer { + + @Test + public void test01Int8BitsAligned() throws IOException { + test01Int8BitsAlignedImpl((byte)0); + test01Int8BitsAlignedImpl((byte)1); + test01Int8BitsAlignedImpl((byte)7); + test01Int8BitsAlignedImpl(Byte.MIN_VALUE); + test01Int8BitsAlignedImpl(Byte.MAX_VALUE); + test01Int8BitsAlignedImpl((byte)0xff); + } + void test01Int8BitsAlignedImpl(byte val8) throws IOException { + // Test with buffer defined value + final ByteBuffer bb = ByteBuffer.allocate(Buffers.SIZEOF_BYTE); + System.err.println("XXX Test01Int8BitsAligned: value "+val8+", "+toHexBinaryString(val8, 8)); + bb.put(0, val8); + + final Bitstream.ByteBufferStream bbs = new Bitstream.ByteBufferStream(bb); + final Bitstream<ByteBuffer> bs = new Bitstream<ByteBuffer>(bbs, false /* outputMode */); + { + final byte r8 = (byte) bs.readInt8(true /* msbFirst */); + System.err.println("Read8.1 "+r8+", "+toHexBinaryString(r8, 8)); + Assert.assertEquals(val8, r8); + } + + // Test with written bitstream value + bs.setStream(bs.getSubStream(), true /* outputMode */); + bs.writeInt8(true /* msbFirst */, val8); + bs.setStream(bs.getSubStream(), false /* outputMode */); // switch to input-mode, implies flush() + { + final byte r8 = (byte) bs.readInt8(true /* msbFirst */); + System.err.println("Read8.2 "+r8+", "+toHexBinaryString(r8, 8)); + Assert.assertEquals(val8, r8); + } + } + + @Test + public void test02Int8BitsUnaligned() throws IOException { + test02Int8BitsUnalignedImpl(0); + test02Int8BitsUnalignedImpl(1); + test02Int8BitsUnalignedImpl(7); + test02Int8BitsUnalignedImpl(8); + test02Int8BitsUnalignedImpl(15); + test02Int8BitsUnalignedImpl(24); + test02Int8BitsUnalignedImpl(25); + } + void test02Int8BitsUnalignedImpl(final int preBits) throws IOException { + test02Int8BitsUnalignedImpl(preBits, (byte)0); + test02Int8BitsUnalignedImpl(preBits, (byte)1); + test02Int8BitsUnalignedImpl(preBits, (byte)7); + test02Int8BitsUnalignedImpl(preBits, Byte.MIN_VALUE); + test02Int8BitsUnalignedImpl(preBits, Byte.MAX_VALUE); + test02Int8BitsUnalignedImpl(preBits, (byte)0xff); + } + void test02Int8BitsUnalignedImpl(int preBits, byte val8) throws IOException { + final int preBytes = ( preBits + 7 ) >>> 3; + final int byteCount = preBytes + Buffers.SIZEOF_BYTE; + final ByteBuffer bb = ByteBuffer.allocate(byteCount); + System.err.println("XXX Test02Int8BitsUnaligned: preBits "+preBits+", value "+val8+", "+toHexBinaryString(val8, 8)); + + // Test with written bitstream value + final Bitstream.ByteBufferStream bbs = new Bitstream.ByteBufferStream(bb); + final Bitstream<ByteBuffer> bs = new Bitstream<ByteBuffer>(bbs, true /* outputMode */); + bs.writeBits31(true /* msbFirst */, preBits, 0); + bs.writeInt8(true /* msbFirst */, val8); + bs.setStream(bs.getSubStream(), false /* outputMode */); // switch to input-mode, implies flush() + + final int rPre = (short) bs.readBits31(true /* msbFirst */, preBits); + final byte r8 = (byte) bs.readInt8(true /* msbFirst */); + System.err.println("ReadPre "+rPre+", "+toBinaryString(rPre, preBits)); + System.err.println("Read8 "+r8+", "+toHexBinaryString(r8, 8)); + Assert.assertEquals(val8, r8); + } + + public static void main(String args[]) throws IOException { + String tstname = TestBitstream02.class.getName(); + org.junit.runner.JUnitCore.main(tstname); + } + +} diff --git a/src/junit/com/jogamp/common/util/TestBitstream03.java b/src/junit/com/jogamp/common/util/TestBitstream03.java new file mode 100644 index 0000000..7eb89e7 --- /dev/null +++ b/src/junit/com/jogamp/common/util/TestBitstream03.java @@ -0,0 +1,154 @@ +/** + * 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.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +import org.junit.Assert; +import org.junit.Test; + +import com.jogamp.common.nio.Buffers; +import com.jogamp.junit.util.JunitTracer; + +import static com.jogamp.common.util.BitstreamData.*; + +import org.junit.FixMethodOrder; +import org.junit.runners.MethodSorters; + +/** + * Test {@link Bitstream} w/ int16 read/write access w/ semantics + * as well as with aligned and unaligned access. + * <ul> + * <li>{@link Bitstream#readInt16(boolean, boolean)}</li> + * <li>{@link Bitstream#writeInt16(boolean, boolean, short)}</li> + * </ul> + */ +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +public class TestBitstream03 extends JunitTracer { + + @Test + public void test01Int16BitsAligned() throws IOException { + test01Int16BitsImpl(null); + test01Int16BitsImpl(ByteOrder.BIG_ENDIAN); + test01Int16BitsImpl(ByteOrder.LITTLE_ENDIAN); + } + void test01Int16BitsImpl(ByteOrder byteOrder) throws IOException { + test01Int16BitsAlignedImpl(byteOrder, (short)0); + test01Int16BitsAlignedImpl(byteOrder, (short)1); + test01Int16BitsAlignedImpl(byteOrder, (short)7); + test01Int16BitsAlignedImpl(byteOrder, (short)0x0fff); + test01Int16BitsAlignedImpl(byteOrder, Short.MIN_VALUE); + test01Int16BitsAlignedImpl(byteOrder, Short.MAX_VALUE); + test01Int16BitsAlignedImpl(byteOrder, (short)0xffff); + } + void test01Int16BitsAlignedImpl(ByteOrder byteOrder, short val16) throws IOException { + // Test with buffer defined value + final ByteBuffer bb = ByteBuffer.allocate(Buffers.SIZEOF_SHORT); + if( null != byteOrder ) { + bb.order(byteOrder); + } + final boolean bigEndian = ByteOrder.BIG_ENDIAN == bb.order(); + System.err.println("XXX Test01Int16BitsAligned: byteOrder "+byteOrder+" (bigEndian "+bigEndian+"), value "+val16+", "+toHexBinaryString(val16, 16)); + bb.putShort(0, val16); + + final Bitstream.ByteBufferStream bbs = new Bitstream.ByteBufferStream(bb); + final Bitstream<ByteBuffer> bs = new Bitstream<ByteBuffer>(bbs, false /* outputMode */); + { + final short r16 = (short) bs.readInt16(true /* msbFirst */, bigEndian); + System.err.println("Read16.1 "+r16+", "+toHexBinaryString(r16, 16)); + Assert.assertEquals(val16, r16); + } + + // Test with written bitstream value + bs.setStream(bs.getSubStream(), true /* outputMode */); + bs.writeInt16(true /* msbFirst */, bigEndian, val16); + bs.setStream(bs.getSubStream(), false /* outputMode */); // switch to input-mode, implies flush() + { + final short r16 = (short) bs.readInt16(true /* msbFirst */, bigEndian); + System.err.println("Read16.2 "+r16+", "+toHexBinaryString(r16, 16)); + Assert.assertEquals(val16, r16); + } + } + + @Test + public void test02Int16BitsUnaligned() throws IOException { + test02Int16BitsUnalignedImpl(null); + test02Int16BitsUnalignedImpl(ByteOrder.BIG_ENDIAN); + test02Int16BitsUnalignedImpl(ByteOrder.LITTLE_ENDIAN); + } + void test02Int16BitsUnalignedImpl(ByteOrder byteOrder) throws IOException { + test02Int16BitsUnalignedImpl(byteOrder, 0); + test02Int16BitsUnalignedImpl(byteOrder, 1); + test02Int16BitsUnalignedImpl(byteOrder, 7); + test02Int16BitsUnalignedImpl(byteOrder, 8); + test02Int16BitsUnalignedImpl(byteOrder, 15); + test02Int16BitsUnalignedImpl(byteOrder, 24); + test02Int16BitsUnalignedImpl(byteOrder, 25); + } + void test02Int16BitsUnalignedImpl(ByteOrder byteOrder, final int preBits) throws IOException { + test02Int16BitsUnalignedImpl(byteOrder, preBits, (short)0); + test02Int16BitsUnalignedImpl(byteOrder, preBits, (short)1); + test02Int16BitsUnalignedImpl(byteOrder, preBits, (short)7); + test02Int16BitsUnalignedImpl(byteOrder, preBits, (short)0x0fff); + test02Int16BitsUnalignedImpl(byteOrder, preBits, Short.MIN_VALUE); + test02Int16BitsUnalignedImpl(byteOrder, preBits, Short.MAX_VALUE); + test02Int16BitsUnalignedImpl(byteOrder, preBits, (short)0xffff); + } + void test02Int16BitsUnalignedImpl(ByteOrder byteOrder, int preBits, short val16) throws IOException { + final int preBytes = ( preBits + 7 ) >>> 3; + final int byteCount = preBytes + Buffers.SIZEOF_SHORT; + final ByteBuffer bb = ByteBuffer.allocate(byteCount); + if( null != byteOrder ) { + bb.order(byteOrder); + } + final boolean bigEndian = ByteOrder.BIG_ENDIAN == bb.order(); + System.err.println("XXX Test02Int16BitsUnaligned: byteOrder "+byteOrder+" (bigEndian "+bigEndian+"), preBits "+preBits+", value "+val16+", "+toHexBinaryString(val16, 16)); + + // Test with written bitstream value + final Bitstream.ByteBufferStream bbs = new Bitstream.ByteBufferStream(bb); + final Bitstream<ByteBuffer> bs = new Bitstream<ByteBuffer>(bbs, true /* outputMode */); + bs.writeBits31(true /* msbFirst */, preBits, 0); + bs.writeInt16(true /* msbFirst */, bigEndian, val16); + bs.setStream(bs.getSubStream(), false /* outputMode */); // switch to input-mode, implies flush() + + final int rPre = (short) bs.readBits31(true /* msbFirst */, preBits); + final short r16 = (short) bs.readInt16(true /* msbFirst */, bigEndian); + System.err.println("ReadPre "+rPre+", "+toBinaryString(rPre, preBits)); + System.err.println("Read16 "+r16+", "+toHexBinaryString(r16, 16)); + Assert.assertEquals(val16, r16); + } + + public static void main(String args[]) throws IOException { + String tstname = TestBitstream03.class.getName(); + org.junit.runner.JUnitCore.main(tstname); + } + +} diff --git a/src/junit/com/jogamp/common/util/TestBitstream04.java b/src/junit/com/jogamp/common/util/TestBitstream04.java new file mode 100644 index 0000000..dfc9c90 --- /dev/null +++ b/src/junit/com/jogamp/common/util/TestBitstream04.java @@ -0,0 +1,158 @@ +/** + * 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.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +import org.junit.Assert; +import org.junit.Test; + +import com.jogamp.common.nio.Buffers; +import com.jogamp.junit.util.JunitTracer; + +import static com.jogamp.common.util.BitstreamData.*; + +import org.junit.FixMethodOrder; +import org.junit.runners.MethodSorters; + +/** + * Test {@link Bitstream} w/ int32 read/write access w/ semantics + * as well as with aligned and unaligned access. + * <ul> + * <li>{@link Bitstream#readInt32(boolean, boolean)}</li> + * <li>{@link Bitstream#writeInt32(boolean, boolean, int)}</li> + * </ul> + */ +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +public class TestBitstream04 extends JunitTracer { + + @Test + public void test01Int32BitsAligned() throws IOException { + test01Int32BitsImpl(null); + test01Int32BitsImpl(ByteOrder.BIG_ENDIAN); + test01Int32BitsImpl(ByteOrder.LITTLE_ENDIAN); + } + void test01Int32BitsImpl(ByteOrder byteOrder) throws IOException { + test01Int32BitsAlignedImpl(byteOrder, 0); + test01Int32BitsAlignedImpl(byteOrder, 1); + test01Int32BitsAlignedImpl(byteOrder, -1); + test01Int32BitsAlignedImpl(byteOrder, 7); + test01Int32BitsAlignedImpl(byteOrder, 0x0fffffff); + test01Int32BitsAlignedImpl(byteOrder, Integer.MIN_VALUE); + test01Int32BitsAlignedImpl(byteOrder, Integer.MAX_VALUE); + test01Int32BitsAlignedImpl(byteOrder, 0xffffffff); + } + void test01Int32BitsAlignedImpl(ByteOrder byteOrder, int val32) throws IOException { + // Test with buffer defined value + final ByteBuffer bb = ByteBuffer.allocate(Buffers.SIZEOF_INT); + if( null != byteOrder ) { + bb.order(byteOrder); + } + final boolean bigEndian = ByteOrder.BIG_ENDIAN == bb.order(); + System.err.println("XXX Test01Int32BitsAligned: byteOrder "+byteOrder+" (bigEndian "+bigEndian+"), value "+val32+", "+toHexBinaryString(val32, 32)); + System.err.println("XXX Test01Int32BitsAligned: "+toUnsignedBinaryString(val32)); + bb.putInt(0, val32); + + final Bitstream.ByteBufferStream bbs = new Bitstream.ByteBufferStream(bb); + final Bitstream<ByteBuffer> bs = new Bitstream<ByteBuffer>(bbs, false /* outputMode */); + { + final int r32 = bs.readInt32(true /* msbFirst */, bigEndian); + System.err.println("Read32.1 "+r32+", "+toHexBinaryString(r32, 32)+", "+toUnsignedBinaryString(r32)); + Assert.assertEquals(val32, r32); + } + + // Test with written bitstream value + bs.setStream(bs.getSubStream(), true /* outputMode */); + bs.writeInt32(true /* msbFirst */, bigEndian, val32); + bs.setStream(bs.getSubStream(), false /* outputMode */); // switch to input-mode, implies flush() + { + final int r32 = bs.readInt32(true /* msbFirst */, bigEndian); + System.err.println("Read32.2 "+r32+", "+toHexBinaryString(r32, 32)+", "+toUnsignedBinaryString(r32)); + Assert.assertEquals(val32, r32); + } + } + + @Test + public void test02Int32BitsUnaligned() throws IOException { + test02Int32BitsUnalignedImpl(null); + test02Int32BitsUnalignedImpl(ByteOrder.BIG_ENDIAN); + test02Int32BitsUnalignedImpl(ByteOrder.LITTLE_ENDIAN); + } + void test02Int32BitsUnalignedImpl(ByteOrder byteOrder) throws IOException { + test02Int32BitsUnalignedImpl(byteOrder, 0); + test02Int32BitsUnalignedImpl(byteOrder, 1); + test02Int32BitsUnalignedImpl(byteOrder, 7); + test02Int32BitsUnalignedImpl(byteOrder, 8); + test02Int32BitsUnalignedImpl(byteOrder, 15); + test02Int32BitsUnalignedImpl(byteOrder, 24); + test02Int32BitsUnalignedImpl(byteOrder, 25); + } + void test02Int32BitsUnalignedImpl(ByteOrder byteOrder, final int preBits) throws IOException { + test02Int32BitsUnalignedImpl(byteOrder, preBits, 0); + test02Int32BitsUnalignedImpl(byteOrder, preBits, 1); + test02Int32BitsUnalignedImpl(byteOrder, preBits, -1); + test02Int32BitsUnalignedImpl(byteOrder, preBits, 7); + test02Int32BitsUnalignedImpl(byteOrder, preBits, 0x0fffffff); + test02Int32BitsUnalignedImpl(byteOrder, preBits, Integer.MIN_VALUE); + test02Int32BitsUnalignedImpl(byteOrder, preBits, Integer.MAX_VALUE); + test02Int32BitsUnalignedImpl(byteOrder, preBits, 0xffffffff); + } + void test02Int32BitsUnalignedImpl(ByteOrder byteOrder, int preBits, int val32) throws IOException { + final int preBytes = ( preBits + 7 ) >>> 3; + final int byteCount = preBytes + Buffers.SIZEOF_INT; + final ByteBuffer bb = ByteBuffer.allocate(byteCount); + if( null != byteOrder ) { + bb.order(byteOrder); + } + final boolean bigEndian = ByteOrder.BIG_ENDIAN == bb.order(); + System.err.println("XXX Test02Int32BitsUnaligned: byteOrder "+byteOrder+" (bigEndian "+bigEndian+"), preBits "+preBits+", value "+val32+", "+toHexBinaryString(val32, 32)); + System.err.println("XXX Test02Int32BitsUnaligned: "+toUnsignedBinaryString(val32)); + + // Test with written bitstream value + final Bitstream.ByteBufferStream bbs = new Bitstream.ByteBufferStream(bb); + final Bitstream<ByteBuffer> bs = new Bitstream<ByteBuffer>(bbs, true /* outputMode */); + bs.writeBits31(true /* msbFirst */, preBits, 0); + bs.writeInt32(true /* msbFirst */, bigEndian, val32); + bs.setStream(bs.getSubStream(), false /* outputMode */); // switch to input-mode, implies flush() + + final int rPre = bs.readBits31(true /* msbFirst */, preBits); + final int r32 = bs.readInt32(true /* msbFirst */, bigEndian); + System.err.println("ReadPre "+rPre+", "+toBinaryString(rPre, preBits)); + System.err.println("Read32 "+r32+", "+toHexBinaryString(r32, 32)+", "+toUnsignedBinaryString(r32)); + Assert.assertEquals(val32, r32); + } + + public static void main(String args[]) throws IOException { + String tstname = TestBitstream04.class.getName(); + org.junit.runner.JUnitCore.main(tstname); + } + +} |