/* Protocol Definition Language Copyright (C) 2003-2006 Marcus Andersson This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* * Created on Dec 17, 2006 */ package net.sf.nmedit.jpdl2.stream; import java.util.Arrays; /** * A stream for bitwise writing and reading. * * <h1>History</h1> * <h3>2008-02-20</h3> * <ul> * <li>implements PDLDataSource</li> * </ul> * <h3>2007-01-02</h3> * <ul> * <li>getInt(0) and append(data, 0) cases are now working</li> * </ul> * <h3>2006-12-18</h3> * <ul> * <li>simpler and more readable getInt()/append() implementation</li> * <li>added wrap() methods to wrap existing arrays</li> * <li>added toIntArray()/toByteArray() methods to extract the internal data quickly</li> * <li>added JUnit tests see test/net.sf.nmedit.jpdl.BitStreamTest</li> * </ul> * <h3>2006-06-14</h3> * <ul> * <li>getInt(0) now returns 0 as expected</li> * <li>getInt(X) now fails when X is larger than number of available bits or negative</li> * </ul> * * @author Christian Schneider */ public class BitStream implements Cloneable, PDLDataSource { // empty byte[] array static final byte[] EMPTY_BYTES = new byte[0]; // empty int[] array static final int[] EMPTY_INTS = new int[0]; // write position/number of written bits private int size; // read position private int position; // array containing the bits private int[] data; // ****** constructors ****** /** * Creates a new bitstream for bitwise writing and reading. * The bitstream has an initial capacity of 32*initialCapacity bits. * @param initialCapacity initial capacity (32*initialCapacity bits) * @throws NegativeArraySizeException if the initialCapacity is negative */ public BitStream(int initialCapacity) { this(new int[initialCapacity], 0); } /** * Creates a new bitstream for bitwise writing and reading. */ public BitStream() { this(10); } /** * Creates a new bitstream with the specfied initial data. * * @param data initial data of the bitstream * @param bitcount number of valid bits - remaining bits are undefined * @throws IllegalArgumentException if bitcount>data.length*32 */ private BitStream(int[] data, int bitcount) { if (bitcount > data.length*32) throw new IllegalArgumentException("invalid number of bits: "+bitcount); this.data = data; this.size = bitcount; this.position = 0; } // ****** wrapping functions ****** /** * Creates a bitstream with the specified initial data. * * @param data initial data of the bitstream * @param bitcount number of valid bits - remaining bits are undefined * @throws IllegalArgumentException if bitcount>data.length*32 */ public final static BitStream wrap(int[] data, int bitcount) { return new BitStream(data, bitcount); } /** * Creates a bitstream with the specified initial data. * @param data initial data of the bitstream */ public final static BitStream wrap(int[] data) { return new BitStream(data, data.length * 32); } /** * Creates a bitstream with the specified initial data. * @param data initial data of the bitstream */ public final static BitStream wrap(byte[] data) { return BitStream.wrap(data, 0, data.length); } /** * Creates a bitstream with the specified initial data. * As initial the complete bytes data[start..end-1] are used. * * @param data initial data of the bitstream * @param start start index * @param end end index (exklusive) * @throws IndexOutOfBoundsException if start<0 or end<0 or start>end or end>data.length */ public final static BitStream wrap(byte[] data, int start, int end) { if ((start < 0) || (end < 0) || (start > end) || (end > data.length)) throw new IndexOutOfBoundsException ( "start " + start + ", end " + end + ", data.length " + data.length); final int bytes = end-start; final int[] idata = new int[(bytes+3)/4]; int i=0; final int iend = idata.length-1; for (;i<iend;i++) { // byte index int b = start+(i<<2); idata[i] = b2i(data[b], data[b+1], data[b+2], data[b+3]); } if (i<idata.length) { int b = start+(i<<2); idata[i] = b2i(data[b], b+1<end?data[b+1]:0, b+2<end?data[b+2]:0, b+3<end?data[b+3]:0); } return new BitStream(idata, bytes*8); } public static BitStream copyOf(BitStream src) { BitStream copy = new BitStream(src.data.length); System.arraycopy(src.data, 0, copy.data, 0, src.data.length); copy.position = src.position; copy.size = src.size; return copy; } // ****** read / write functions ****** /** * Reads the specified number of bits and returns them. * * @param bitcount number of bits to read [0..32] * @throws IllegalArgumentException if bitcount not in [0..32] or the specified number of bits is not available */ public final int getInt(int bitcount) { if (bitcount == 0) return 0; // ensure bits in range [0..32] ensureBitRange0to32(bitcount); // ensure bits available if (!isAvailable(bitcount)) throw new IllegalArgumentException("specified number of bits not available: "+bitcount); // get array index int idx = indexof(position); // calculate available bits in current field int available = 32-bitsof(position); // number of bits of right shift operation int rshift = available-bitcount; // get raw value int value = data[idx]; if (rshift>=0) { value >>>= rshift; } else { value = (value<<-rshift)|(data[idx+1]>>>(32+rshift)); } // move pointer position+=bitcount; // unset bits that do not belong to the value return unsetbits(value, bitcount); } /** * Reads the next 32-bit integer from this bitstream. * Using this method is equal to using <code>getInt(32)</code>. * @return the next integer */ public final int getInt() { return getInt(32); } /** * Reads the next 8-bit byte from this bitstream. * Using this method is equal to using <code>(byte)getInt(8)</code>. * @return the next byte */ public final byte getByte() { return (byte)getInt(8); } /** * Appends the specified number of (least significant) bits of the specified value. * * @param value integer containing the bits to write aligned at the least significant bit * @param bitcount number of bits to write [0..32] * @throws IllegalArgumentException if bitcount not in [0..32] */ public final void append( int value, int bitcount ) { if (bitcount == 0) return ; // ensure bits in range [0..32] ensureBitRange0to32(bitcount); // ensure only specified number of bits are set value = unsetbits(value, bitcount); // calculate remaining bits in current array field int remaining = 32-bitsof(size); // get array index int idx = indexof(size); // ensure array is big enough if (idx+2>data.length) { int[] expanded = new int[(idx+2)<<1]; System.arraycopy(data, 0, expanded, 0, data.length); data = expanded; } // number of bits of left shift operation int lshift = remaining-bitcount; // store bits if (lshift>=0) { data[idx] |= (value << lshift); } else { data[idx] |= (value>>>-lshift); data[idx+1] = value << (32+lshift); } // add bits size += bitcount; } /** * Appends all 32 bits of the specified integer * @param data data to append */ public final void append(int data) { append(data, 32); } /** * Appends the specified number of bits of the byte value. * The byte is interpreted as lowest significant byte of an integer * (and the other three bytes are set to zero). * @param data data to append * @param bitcount number of bits to append [0..32] */ public final void append(byte data, int bitcount) { append(unsignedByte(data), bitcount); } /** * Appends all 8 bits of the specified byte * @param data data to append */ public final void append(byte data) { append(unsignedByte(data), 8); } // ****** control functions ****** /** * Clears all data and resets the read position to zero. */ public void clear() { setSize(0); } /** * Sets the size of the bitstream. This method only * makes the bitstream smaller. * @param size number of valid bits * * TODO there seems to be a bug where the remaining bits are not cleared (set to 0) */ public void setSize(int size) { // array can only be made smaller if (this.size > size) { int idx = indexof(size); // clear full fields if (idx+1<data.length) Arrays.fill(data, idx+1, data.length, 0); // clear bits in last field data[idx] &= 0xFFFFFFFF << (32-bitsof(size)); // update size and pointer this.size = size; position = Math.min(position, size); } } /** * Returns true if the specified number of bits is available for reading. * @param bitcount number of bits to check if they are available * @return true if the specified number of bits is available for reading */ public final boolean isAvailable( int bitcount ) { return position + bitcount <= size; } /** * Returns the number of valid bits in this bitstream. * @return the number of valid bits in this bitstream */ public int getSize() { return size; } /** * Returns the current reading position of this bitstream. * @return the current reading position of this bitstream */ public int getPosition() { return position; } /** * Sets the current reading position of this bitstream. * The position can be invalid but has to be >=0. * @param position new position * @throws IllegalArgumentException if the new position is negative */ public void setPosition( int position ) { if (position<0) throw new IllegalArgumentException("invalid position: "+position); this.position = position; } // ****** misc functions ****** /** * Returns a copy of this bitstream. */ public BitStream clone() { try { return (BitStream) super.clone(); } catch (CloneNotSupportedException e) { throw new InternalError(e.getMessage()); } } // ****** toArray functions ****** /** * Returns an array of byte containing all completed 8-bit blocks * stored in this bitstream. */ public byte[] toByteArray() { // return empty array if (size <= 0) return EMPTY_BYTES; // calculate number of complete byte blocks int bytes = size/8; // calculate number of complete integer blocks int blocks = size/32; // create array byte[] bdata = new byte[bytes]; // for each byte b: int b = 0; for (;b<blocks;b+=4) { // write complete integer block int d = data[b>>>2]; bdata[b] = (byte) ((d>>24)&0xFF); bdata[b+1] = (byte) ((d>>16)&0xFF); bdata[b+2] = (byte) ((d>> 8)&0xFF); bdata[b+3] = (byte) ((d )&0xFF); } // write remaining 0-3 bytes while (b<bytes) { bdata[b] = (byte) ((data[b>>>2]>>>(24-(8*(b%4))))&0xFF); b++; } // return data return bdata; } /** * Returns an array of byte containing all completed 32-bit blocks * stored in this bitstream. */ public int[] toIntArray() { // return empty array if (size <= 0) return EMPTY_INTS; // calculate number of complete integer blocks int ints = size/32; // create array int[] idata = new int[ints]; // copy data System.arraycopy(data, 0, idata, 0, ints); // return data return idata; } // ****** helpers ****** /** * Converts a signed byte to an unsigned integer. * * @param b the byte value to convert * @return unsigned integer */ static final int unsignedByte(byte b) { return (int) (b & 0xFF); } /** * Creates an integer from the four specified bytes. * * The sign of the return value is the sign of the specified byte b3. * Thus <code>signum(b2i(b3, 0, 0, 0)) == signum(b3)</code> is always <code>true</code>. * * @param b3 sets the bits 31..24 * @param b2 sets the bits 23..16 * @param b1 sets the bits 15.. 8 * @param b0 sets the bits 7.. 0 * @return an integer created from four bytes */ static final int b2i(byte b3, byte b2, byte b1, byte b0) { return (unsignedByte(b3)<<24) | (unsignedByte(b2)<<16) | (unsignedByte(b1)<<8) | unsignedByte(b0); } /** * Ensures that the specified argument is in the range <code>[0..32]</code>. * @param bitcount checked if in range <code>[0..32]</code> * @throws IllegalArgumentException if the condition is violated */ static final void ensureBitRange0to32(int bitcount) throws IllegalArgumentException { if ((bitcount>32) || (bitcount<0)) throw new IllegalArgumentException("invalid number of bits:"+bitcount); } /** * Extracts the index encoded in the specified integer. * The index is stored at the bit position 31..5. * The return value is equal to a division of the position by 32, * thus for positive or zero arguments following is <code>true</code>: * <code>p/32==indexof(p)</code>. * * The following condition is <code>true</code> for positive or zero * arguments: <code>p==indexof(p)*32 +bitsof(p)</code>. * * @param position contains the index * @return index encoded in the specified integer */ static final int indexof(int position) { return position >>> 5; // == position / 32 } /** * Extracts the bit-position encoded in the specified integer. * The value is stored at the bit position 4..0. * * The return value is equal to a modulo division by 32, * thus for positive or zero arguments following is <code>true</code>: * <code>p mod 32==bitsof(p)</code>. * * The following condition is <code>true</code> for positive or zero * arguments: <code>p=indexof(p)*32 +bitsof(p)</code>. * * @param position contains the index * @return index encoded in the specified integer */ static final int bitsof(int position) { return position & 0x1F; // == position % 32 } /** * Clears the 32-bitcount most significant bits. * * @param value value of which the msb's should be cleared * @param bitcount number of least significant bits that are left unchanged * @return value with 32-bitcount most significant bits cleared */ static final int unsetbits(int value, int bitcount) { return (bitcount<32) ? (value & ~(0xFFFFFFFF << bitcount)) : value; } public int remaining() { return Math.max(0, size-position); } }