Package com.peterhi.runtime

Source Code of com.peterhi.runtime.Buffer

package com.peterhi.runtime;

import java.io.DataInput;
import java.io.DataOutput;
import java.io.EOFException;
import java.io.IOException;
import java.io.UTFDataFormatException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.charset.Charset;
import java.util.Arrays;

/**
* A buffer object that is backed by a {@code byte} array and can be read or written.
* Note that {@link Buffer} supports operations down to {@code bit} level (i.e. reading
* or writing of {@code bit} values).
* @author hytparadisee
*/
public class Buffer {
 
  /**
   * A constant representing end of file (EOF) or invalid values.
   * This value is {@value #EOF}.
   */
  public static final int EOF = -1;
 
  /**
   * When storing multi-byte data, big endian stores the most
   * significant eight {@code bit}s in the first {@code byte},
   * and the second most significant eight {@code bit}s in the
   * second {@code byte}, and so on. Java uses big endian.
   */
  public static final int BIG_ENDIAN = 0;
 
  /**
   * When storing multi-byte data, little endian stores the least
   * significant (i.e. the smallest) eight {@code bit}s in the
   * first {@code byte}, and the second least significant (the
   * second smallest) eight {@code bit}s in the second
   * {@code byte}, and so on.
   */
  public static final int LITTLE_ENDIAN = 1;
 
  private static final BigInteger TWO = BigInteger.valueOf(2);
 
  /**
   * The default capacity, in {@code byte}s, that the buffer holds data of.
   * The default value is {@value #CAPACITY_DEFAULT}.
   */
  public static final int CAPACITY_DEFAULT = 512;
 
  /**
   * The default capacity to grow by if the buffer is to be full.
   * The default value is {@value #CAPACITY_INCREMENT}.
   */
  public static final int CAPACITY_INCREMENT = 128;
 
  /**
   * The default behavior used to copy data from this {@link Buffer}
   * object to a {@code byte} array. This behavior will copy the data range
   * between the read cursor and the write cursor. Since both cursors
   * are specified in {@code bit}s, but the data return are in {@code bytes},
   * the default behavior will discard any preceding bits before the read
   * cursor at of the starting byte and the remaining bits after the write
   * cursor at of the ending byte, and do a copy from the {@code byte} index at
   * which the reading cursor is located, to the {@code byte} index at which
   * the writing cursor is located. The data returned will contain the bits
   * INCLUDING the offset/shift information. For example, the bits
   * {@code 11111111 11111111} whose read cursor is four and write cursor is
   * fourteen, once copied using this method, will result in the data
   * {@code 00111111 11110000 } returned. Compare this with
   * {@link #COPY_METHOD_ALIGNED}.
   *
   * @see #COPY_METHOD_ALIGNED
   */
  public static final int COPY_METHOD_DEFAULT = 0;
 
  /**
   * This behavior is similar to {@link #COPY_METHOD_DEFAULT}, but the
   * {@code bit}s offset/shift information will no longer be kept and
   * the bits will always be aligned into byte boundaries. For example,
   * the bits {@code 11111111 11111111} whose read cursor is four and
   * write cursor is fourteen, the data returned will first be extracted
   * to what looks like the returned value using the behavior
   * {@link #COPY_METHOD_DEFAULT} (of which the value will be
   * {@code 00111111 11110000}), and then the bits will be shifted to
   * {@code 00000011 11111111} so that the first bit returned is always
   * the starting bit of the data (i.e. the read cursor). Compare this
   * with {@link #COPY_METHOD_DEFAULT}
   *
   * @see #COPY_METHOD_DEFAULT
   */
  public static final int COPY_METHOD_ALIGNED = 1;
 
  /**
   * This behavior will copy all the data in the {@link Buffer} object,
   * ignoring the fact that some parts of the data have already been
   * read. Since the read cursor is ignored in this method. The data
   * being copied will always start at index zero (i.e. no more problem
   * with bit offsetting/shifting etc.).
   */
  public static final int COPY_METHOD_ALL_DATA = 2;
 
  /**
   * Looks up the available or supported endiannesses the
   * {@link Buffer} class has according to the value passed
   * in as {@code byteEndianness}, and returns whether this
   * value is valid.
   *
   * @param byteEndianness The endianness that is to be
   *        validated.
   *       
   * @return {@code true} if the endianness is found,
   *         available or supported. Otherwise {@code false}.
   *        
   * @see #BIG_ENDIAN
   * @see #LITTLE_ENDIAN
   */
  public static boolean isValidEndianness(int byteEndianness) {
    if (byteEndianness == BIG_ENDIAN) {
      return true;
    }
   
    if (byteEndianness == LITTLE_ENDIAN) {
      return true;
    }
   
    return false;
  }
 
  private byte[] backingData;
  private long readPos;
  private long writePos;
  private long markPos = EOF;
 
  /**
   * Creates a new instance of {@link Buffer} object with the specified capacity.
   *
   * @param capacityInBytes The capacity, in {@code byte}s, that this {@link Buffer}
   *       object initially can hold data of.
   *
   * @throws IllegalArgumentException one of the following happens:
   *        <ul>
   *         <li>{@code capacityInBytes} is negative.</li>
   *         <li>{@code capacityInBytes} is zero.</li>
   *        </ul>
   */
  public Buffer(int capacityInBytes)
    throws IllegalArgumentException {
    if (capacityInBytes < 0) {
      throw new IllegalArgumentException();
    }
   
    if (capacityInBytes == 0) {
      throw new IllegalArgumentException();
    }
   
    backingData = new byte[capacityInBytes];
  }
 
  /**
   * Creates a new instance of {@link Buffer} object with the capacity
   * defined by {@link #CAPACITY_DEFAULT}.
   *
   * @see #Buffer(int)
   */
  public Buffer() {
    this(CAPACITY_DEFAULT);
  }
 
  /**
   * Creates a new instance of {@link Buffer} object from existent data.
   * The data will be copied into the {@link Buffer}'s backing byte array.
   *
   * @param existingData A byte array containing the outside data to be copied into.
   *
   * @throws NullPointerException {@code existingData} is null.
   * @throws IllegalArgumentException length of {@code existingData} is zero.
   *
   * @see #Buffer(byte[], int, int)
   */
  public Buffer(byte[] existingData)
    throws NullPointerException,
         IllegalArgumentException {
    if (existingData == null) {
      throw new NullPointerException();
    }
   
    int length = existingData.length;
   
    if (length == 0) {
      throw new IllegalArgumentException();
    }
   
    backingData = Arrays.copyOf(existingData, length);
    writePos = Byte.SIZE * length;
  }
 
  /**
   * Creates a new instance of {@link Buffer} object from existent data with a range.
   *
   * @param existingData A {@code byte} array containing the outside data to be copied into.
   * @param byteOffset The index at which to start copying.
   * @param numOfBytes The number of {@code byte}s to copy.
   *
   * @throws NullPointerException {@code existingData} is null.
   * @throws IllegalArgumentException one of the following happens:
   *         <ul>
   *             <li>the length of {@code existingData} is zero.</li>
   *             <li>{@code byteOffset} is negative.</li>
   *             <li>{@code numOfBytes} is negative.</li>
   *             <li>{@code numOfBytes} is zero.</li>
   *             <li>the range defined by {@code byteOffset} and {@code numOfBytes} is invalid <br />
   *                  e.g. ({@code byteOffset} + {@code numOfBytes}) > the length of {@code existingData}.</li>
   *         </ul>
   */
  public Buffer(byte[] existingData, int byteOffset, int numOfBytes)
    throws NullPointerException,
         IllegalArgumentException {
    if (existingData == null) {
      throw new NullPointerException();
    }
   
    if (existingData.length == 0) {
      throw new IllegalArgumentException();
    }
   
    if (byteOffset < 0) {
      throw new IllegalArgumentException();
    }
   
    if (numOfBytes < 0) {
      throw new IllegalArgumentException();
    }
   
    if (numOfBytes == 0) {
      throw new IllegalArgumentException();
    }
   
    if (byteOffset + numOfBytes > existingData.length) {
      throw new IllegalArgumentException();
    }
   
    int from = byteOffset;
    int to = from + numOfBytes;
    backingData = Arrays.copyOfRange(existingData, from, to);
    writePos = Byte.SIZE * numOfBytes;
  }

  /**
   * <p>Creates a new instance of {@link Buffer} object from existent data with a {@code bit} precision range.
   * Note that because {@code bit} level operation on manipulating data in computing is not native operation
   * (as compared with {@code byte} level operations), {@code bit}wise alignment might occur.</p>
   *
   * <p>{@code Bit}wise alignment is necessary as modern computers usually represent data
   * as {@code bytes} but not {@code bits}. So data will be misaligned if the data is taken in the middle
   * of a single {@code byte}. The parameter {@code alignBits} tells {@link Buffer} whether to shift
   * the starting {@code bit}, specified by {@code bitOffset}, to be the first {@code bit} of this {@code Buffer}'s
   * first {@code bit}.</p>
   *
   * <p>The following example illustrate the use of {@code alignBits}:</p>
   *
   * <p>Let's say {@code existingData} is defined as {@code ((byte )63)}, which
   * in two's complement is represented as {@code 00111111}. If {@code bitOffset}
   * is defined in such a way that is not divisible by {@link Byte#SIZE}, then it
   * means an incomplete {@code byte} data will be copied into this {@code Buffer}.
   * Let's assume {@code bitOffset} is four. This means we will start copying data
   * from the fourth {@code bit} in the first {@code byte}. Therefore the preceding {@code 00001111}
   * will be discarded, leaving only {@code 00110000}. If {@code alignBits} is
   * {@code true}, {@code 00110000} will be shifted so that {@code bitOffset} will
   * become the first {@code bit} of the first {@code byte} of {@link Buffer}'s data. In this case,
   * {@code 00110000} will shift right} by four {@code bit}s to become
   * {@code xxxx0011}, with {@code xxxx} representing the data from subsequent {@code byte}s which
   * will follow in. If {@code alignBits} is {@code false}, then no shift will be done,
   * and {@code bitOffset} will keep its bit position. So it will stay to be {@code 00110000}.</p>
   *
   * <p>The graphical illustration is shown below:</p>
   *
   * <p>{@code existingData = 11111111 11111111}</p>
   *
   * <p>if {@code bitOffset} is {@code 4} and if {@code numOfBits} is {@code 10} then, we will see
   *    that the portion of data we need to take is shown in the following within square brackets
   *    {@code '[]'}</p>
   *   
   * <p>{@code existingData = 11[111111 1111]1111}</p>
   *
   * <p>Now we are going to take the data specified within {@code '[]'}. If {@code alignBits} is true,
   *    the created {@link Buffer}'s internal representation of bits will be: </p>
   *   
   * <p>{@code 11[111111 1111]1111} original<br/>
   *     {@code 00[111111 1111]0000} cleared out-of-range bits<br/>
   *    {@code 000000[11 11111111]} shift to the first bit to align</p>
   *   
   * <p>If {@code alignBits} is set to {@code false}, the last step of shift-aligning will be ignored,
   * and the bits will stay where they used to be, so the created {@link Buffer}'s internal
   * representation of bits will be: </p>
   *
   * <p>{@code 11[111111 1111]1111} original<br/>
   *    {@code 00[111111 1111]0000} cleared out-of-range bits<br/>
   *    {@code 00[111111 11110000]} no shift-align done, bits stay where they are</p>
   *
   * @param existingData A {@code byte} array containing the outside data to be copied into.
   * @param bitOffset The index at which to start copying.
   * @param numOfBits The number of {@code bit}s to copy.
   * @param alignBits {@code true} to shift {@code bit}s defined by {@code bitOffset} to be the first
   *        {@code bit} of the {@link Buffer}'s data. {@code false} to keep the {@code bit} position of
   *        {@code bitOffset}.
   *       
   * @throws NullPointerException {@code existingData} is null.
   * @throws IllegalArgumentException one of the following happens:
   *         <ul>
   *             <li>the length of {@code existingData} is zero.</li>
   *             <li>{@code bitOffset} is negative.</li>
   *             <li>{@code numOfBits} is negative.</li>
   *             <li>{@code numOfBits} is zero.</li>
   *             <li>the range defined by {@code bitOffset} and {@code numOfBytes} is invalid <br />
   *               e.g. ({@code bitOffset} + {@link Byte#SIZE} x {@code numOfBytes}) >
   *               {@link Byte#SIZE} x the length of {@code existingData}.</li>
   *         </ul>
   * @throws IOException the underlying I/O operation fails while trying to copy {@code existingData}
   *        into the {@link Buffer}'s backing data.
   */
  public Buffer(byte[] existingData, long bitOffset, long numOfBits, boolean alignBits)
    throws NullPointerException,
         IllegalArgumentException,
         IOException {
    if (existingData == null) {
      throw new NullPointerException();
    }
   
    if (existingData.length == 0) {
      throw new IllegalArgumentException();
    }
   
    if (bitOffset < 0) {
      throw new IllegalArgumentException();
    }
   
    if (numOfBits < 0) {
      throw new IllegalArgumentException();
    }
   
    if (numOfBits == 0) {
      throw new IllegalArgumentException();
    }
   
    if (bitOffset + numOfBits > Byte.SIZE * existingData.length) {
      throw new IllegalArgumentException();
    }
   
    if (alignBits == false) {
      int bytesToSkip = (int )(bitOffset % Byte.SIZE);
      skip(bytesToSkip);
    }

    if (alignBits) {
      backingData = alignAndCopyByteArray(existingData, bitOffset, numOfBits);
      writePos = numOfBits;
    } else {
      long endBitIndex = bitOffset + numOfBits;
      int startByteIndex = (int )(bitOffset / Byte.SIZE);
      int endByteIndex = (int )(endBitIndex / Byte.SIZE);
      int startBitOffsetAtFirstByte = (int )(bitOffset % Byte.SIZE);
      int endBitOffsetAtLastByte = (int )(endBitIndex % Byte.SIZE);
      boolean needOneMoreByteForRemainingBitsAfterLastByte =
        endBitOffsetAtLastByte != 0;
      int numOfBytesNeededForData = endByteIndex - startByteIndex;
     
      if (needOneMoreByteForRemainingBitsAfterLastByte) {
        numOfBytesNeededForData = numOfBytesNeededForData + 1;
      }
     
      int from = startByteIndex;
      int to = from + numOfBytesNeededForData;
      backingData = Arrays.copyOfRange(existingData, from, to);
     
      if (startBitOffsetAtFirstByte != 0) {
        for (int shift = 0; shift < startBitOffsetAtFirstByte; shift = shift + 1) {
          int mask = 0x01;
          int bitValueShifted = mask << shift;
          int bitValueShiftedAndFlipped = ~bitValueShifted;
          int bitsOfFirstByte = backingData[0];
          int bitsOfFirstByteWithBitsRemoved = bitsOfFirstByte & bitValueShiftedAndFlipped;
          backingData[0] = (byte )bitsOfFirstByteWithBitsRemoved;
        }
      }
     
      if (endBitOffsetAtLastByte != 0) {
        for (int shift = endBitOffsetAtLastByte; shift < Byte.SIZE; shift = shift + 1) {
          int mask = 0x01;
          int bitValueShifted = mask << shift;
          int bitValueShiftedAndFlipped = ~bitValueShifted;
          int bitsOfLastByte = backingData[backingData.length - 1];
          int bitsOfLastByteWithBitsRemoved = bitsOfLastByte & bitValueShiftedAndFlipped;
          backingData[backingData.length - 1] = (byte )bitsOfLastByteWithBitsRemoved;
        }
      }
     
      writePos = Byte.SIZE * (endByteIndex + startByteIndex) +
        endBitOffsetAtLastByte;
    }
  }
 
  /**
   * Gets the number of {@code bit}s that are currently available for reading.
   * Reading data beyond this number will, depending on the
   * methods you call, end up either returning an {@link #EOF},
   * or throwing a {@link InsufficientBufferException} or
   * a {@link EOFException}.
   *
   * @return The number of {@code bit}s that are currently available for reading.
   */
  public long readable() {
    long readableBits = writePos - readPos;
    return readableBits;
  }
 
  /**
   * Gets the number of {@code bit}s that are currently available for writing,
   * without the need to re-allocate an expanded data buffer to keep
   * the data. Please note that this method doesn't not indicate a
   * hard limit to the amount of data that can be written, since
   * {@link Buffer} does not actually have a limit to the number
   * of {@code bit}s to write.
   *
   * @return The number of {@code bit}s that can be written before this
   *         {@link Buffer} re-alloc-expands itself.
   */
  public long writable() {
    long writableBits = (long )Byte.SIZE * (long )backingData.length;
    writableBits = writableBits - writePos;
    return writableBits;
  }
 
  /**
   * Gets the number of {@code bit}s that are already written into this
   * {@link Buffer}. This number has nothing to do with how much
   * data has been read. In other words, no matter how much data
   * is read out from the {@link Buffer}, this number stays unchanged
   * unless new data arrive and are being written.
   * @return The number of bits written.
   */
  public long written() {
    long writtenBits = writePos;
    return writtenBits;
  }
 
  /**
   * Gets the number of {@code bytes} that this {@link Buffer}'s backing
   * data currently allocates. The actual data kept might be
   * smaller than this number.
   * @return The number of {@code byte}s it contains as the buffer.
   */
  public int size() {
    int bufferSize = backingData.length;
    return bufferSize;
  }
 
  /**
   * Performs a single {@code bit} read operation, and moves
   * the read cursor to the next {@code bit}. If no more data
   * is available, returns {@link #EOF}. Please note that this
   * method will not throw an or an {@link EOFException} to
   * indicate out-of-data situation.
   *
   * @return The {@code bit} being read, represented by {@code 1 (true)}
   *         or {@code 0 (false)}. If no more data is available, it
   *         may also return {@link #EOF}.
   * @throws IOException the underlying I/O operation fails.
   */
  public int read()
    throws IOException {
    if (readable() == 0) {
      return EOF;
    }

    long bitOffset = readPos;
    int atByte = (int )(bitOffset / Byte.SIZE);
    int atBitOfByte = (int )(bitOffset % Byte.SIZE);
    int shiftRight = atBitOfByte;
    int bitValue = backingData[atByte] >>> shiftRight;
    int bitMask = 0x01;
    bitValue = bitValue & bitMask;

    readPos = readPos + 1;
   
    return bitValue;
  }
 
  /**
   * <p>Performs a single {@code bit} read operation, and moves
   * the read cursor to the next {@code bit}. The return
   * value is a {@code boolean} value instead of an {@code int}
   * value. Since the return value cannot return an {@link #EOF}
   * value, an {@link EOFException} will be thrown if no more
   * data is available.</p>
   *
   * <p>This method will delegate to {@link #read()} and convert
   * the return value to the corresponding {@code boolean} value.</p>
   *
   * @return {@code true} if the data being read is non-{@code zero},
   *         otherwise, {@code false} if the data being read is
   *         {@code zero}.
   *
   * @throws EOFException There is no more data available for reading.
   * @throws IOException if the underlying {@code Buffer} operation
   *         throws an error.
   */
  public boolean readAsBoolean()
    throws EOFException,
         IOException {
    int bitValue = read();
   
    if (bitValue == EOF) {
      throw new EOFException();
    }
   
    return bitValue != 0;
  }
 
  /**
   * Performs a single {@code bit} write operation, and moves the
   * write cursor to the next {@code bit}. If there is currently
   * no more room to store the data to write (as defined in
   * {@link #writable()}), re-alloc-expands the {@link Buffer}'s
   * backing array to adapt to it.
   * @param data the {@code bit} being written, represented by {@code 1 (true)}
   *        or {@code 0 (false)}.
   * @throws IllegalArgumentException the parameter {@code data}'s value is
   *        neither {@code 1 (one)} nor {@code 0 (zero)}.
   * @throws IOException the underlying I/O operation fails.
   */
  public void write(int data)
    throws IllegalArgumentException,
         IOException {
    if (data != 0 &&
      data != 1) {
      throw new IllegalArgumentException();
    }
   
    if (writable() == 0) {
      int numOfBytesForNewBuffer = size() + CAPACITY_INCREMENT;
      backingData = Arrays.copyOf(backingData, numOfBytesForNewBuffer);
    }
   
    long bitOffset = writePos;
    int atByte = (int )(bitOffset / Byte.SIZE);
    int atBitOfByte = (int )(bitOffset % Byte.SIZE);
    int shiftLeft = atBitOfByte;
    int bitMask = 0x01;
    int bitMaskShifted = bitMask << shiftLeft;
   
    if (data == 0) {
      backingData[atByte] &= ~bitMaskShifted;
    } else {
      backingData[atByte] |=  bitMaskShifted;
    }
   
    writePos = writePos + 1;
  }
 
  /**
   * Performs a single {@code bit} write operation, and moves the
   * write cursor to the next {@code bit}. It takes a {@code boolean}
   * parameter instead of a {@code byte} to represent the data. If
   * there is currently no more room to store the data to write
   * (as defined in {@link #writable()}), re-alloc-expands the
   * {@link Buffer}'s backing array to adapt to it.
   *
   * @param data {@code true} to write {@code 1 (one)} as a {@code bit}
   *        into the {@link Buffer}. Otherwise, use {@code false} to write
   *        {@code 0 (zero)} as a {@code bit} into the {@link Buffer}.
   *       
   * @throws IOException the underlying I/O operation fails.
   */
  public void writeAsBoolean(boolean data)
    throws IOException {
    if (data) {
      write(1);
    } else {
      write(0);
    }
  }
 
  /**
   * Performs an arbitrary multi-{@code bit} read operation, and moves
   * the read cursor by {@code numOfBits} bits. This method returns
   * a {@link BigInteger} representing the {@code bit}s. The {@link BigInteger}
   * returned is always unsigned unless {@code emulateOverflow} is {@code true}.
   * If no more data is available, this method throws a {@code EOFException}.
   * If the amount of data available is less than the required data length
   * (that is, less than {@code numOfBits}), this method throws a
   * {@code InsufficientBufferException}. This method supports certain
   * byte endiannesses.
   * @param numOfBits The number of {@code bit}s to read.
   * @param emulateOverflow {@code true} to emulate field overflow, so that if
   *        the value is greater than the maximum allowed value, defined as
   *        {@code (2 ^ n - 1) / 2}, the value is then immediately subtracted
   *        by {@code 2 ^ n} to emulate an overflow situation. If emulateOverflow
   *        is {@code true}, the returned {@link BigInteger} is emulated as a
   *        signed value with an overflow. If {@code false} is specified,
   *        the {@link BigInteger} will not be processed by the field overflow
   *        mechanism, and will always be unsigned.
   * @param byteEndianness The {@code byte} level endianness used to represent
   *        the order in which different values are stored as bytes. Refer to
   *        constants starting with {@code ENDIANNESS_*}. By default, java uses
   *        {@link #BIG_ENDIAN}. Please note that this parameter does not
   *        affect the {@code bit} endianness, as {@code bit} endianness is
   *        assumed universal across platforms and languages.
   * @return A {@link BigInteger} representing the {@code bit}s.
   * @throws IllegalArgumentException one of the following occurs:
   *        <ul>
   *         <li>{@code numOfBits} is negative.</li>
   *         <li>{@code numOfBits} is zero.</li>
   *         <li>{@code byteEndianness} is not one of the values defined
   *           by {@code ENDIANNESS_*} constants.
   *           See {@link #isValidEndianness(int)}.</li>
   *        </ul>
   * @throws InsufficientBufferException the amount of data available is less
   *         than the required data length specified by {@code numOfBits}, so
   *         that a complete read operation of the {@code bits} is not possible.
   * @throws EOFException no more data is available (available data is zero).
   * @throws IOException other underlying I/O operations fail.
   */
  public BigInteger read(int numOfBits, boolean emulateOverflow, int byteEndianness)
    throws IllegalArgumentException,
         InsufficientBufferException,
         EOFException,
         IOException {
    if (numOfBits < 0) {
      throw new IllegalArgumentException();
    }
   
    if (numOfBits == 0) {
      throw new IllegalArgumentException();
    }
   
    if (!isValidEndianness(byteEndianness)) {
      throw new IllegalArgumentException();
    }
   
    long numOfBitsReadable = readable();
   
    if (numOfBitsReadable == 0) {
      throw new EOFException();
    }
   
    if (numOfBitsReadable < numOfBits) {
      throw new InsufficientBufferException();
    }
   
    BigInteger bitsValue = BigInteger.ZERO;
    int numOfBytesWithoutFractionalBits = (int )(numOfBits / Byte.SIZE);
   
    if (byteEndianness == LITTLE_ENDIAN) {
      for (int curByteIndex = 0;
           curByteIndex < numOfBytesWithoutFractionalBits;
           curByteIndex = curByteIndex + 1) {
        for (int curBitShift = 0;
             curBitShift < Byte.SIZE;
             curBitShift = curBitShift + 1) {
          int bitValue = read();
          int bitShift = Byte.SIZE * curByteIndex + curBitShift;
          bitsValue = setAndUpdateBitsValueBit(bitsValue, bitShift, bitValue);
        }
      }
     
      int numOfRemainingBitsAfterLastByte = (int )(numOfBits % Byte.SIZE);
     
      for (int curLastBitShift = 0;
           curLastBitShift < numOfRemainingBitsAfterLastByte;
           curLastBitShift = curLastBitShift + 1) {
        int bitValue = read();
        int bitShift = Byte.SIZE * numOfBytesWithoutFractionalBits + curLastBitShift;
        bitsValue = setAndUpdateBitsValueBit(bitsValue, bitShift, bitValue);
      }
    } else {
      int numOfPrecedingBitsBeforeFirstByte = (int )(numOfBits % Byte.SIZE);
     
      for (int curFirstBitShift = 0;
           curFirstBitShift < numOfPrecedingBitsBeforeFirstByte;
           curFirstBitShift = curFirstBitShift + 1) {
        int bitValue = read();
        int bitShift = Byte.SIZE * numOfBytesWithoutFractionalBits + curFirstBitShift;
        bitsValue = setAndUpdateBitsValueBit(bitsValue, bitShift, bitValue);
      }
     
      for (int curByteIndex = 0;
           curByteIndex < numOfBytesWithoutFractionalBits;
           curByteIndex = curByteIndex + 1) {
        for (int curBitShift = 0;
             curBitShift < Byte.SIZE;
             curBitShift = curBitShift + 1) {
          int bitValue = read();
          int bitShift =
            Byte.SIZE * (numOfBytesWithoutFractionalBits - curByteIndex - 1) + curBitShift;
          bitsValue = setAndUpdateBitsValueBit(bitsValue, bitShift, bitValue);
        }
      }
    }
   
    if (emulateOverflow) {
      BigInteger overflowBits = TWO.pow(numOfBits);
      bitsValue = bitsValue.subtract(overflowBits);
    }
   
    return bitsValue;
  }
 
  /**
   * Performs an arbitrary multi-{@code bit} write operation, and moves
   * the write cursor by {@code numOfBits} bits. This method requires
   * a {@link BigInteger} representing the {@code bit}s. The {@link BigInteger}
   * passed in is always unsigned. This method supports certain byte endiannesses.
   * @param numOfBits The number of {@code bit}s to write.
   * @param bitsValue A {@link BigInteger} that contains the {@code bit}s to write.
   * @param byteEndianness The {@code byte} level endianness used to represent
   *        the order in which different values are stored as bytes. Refer to
   *        constants starting with {@code ENDIANNESS_*}. By default, java uses
   *        {@link #BIG_ENDIAN}. Please note that this parameter does not
   *        affect the {@code bit} endianness, as {@code bit} endianness is
   *        assumed universal across platforms and languages.
   * @throws IllegalArgumentException one of the following occurs:
   *        <ul>
   *         <li>{@code numOfBits} is negative.</li>
   *         <li>{@code numOfBits} is zero.</li>
   *         <li>{@code byteEndianness} is not one of the values defined
   *           by {@code ENDIANNESS_*} constants.
   *           See {@link #isValidEndianness(int)}.</li>
   *        </ul>
   * @throws IOException the underlying I/O operation fails.
   */
  public void write(int numOfBits, BigInteger bitsValue, int byteEndianness)
    throws IllegalArgumentException,
         IOException {
    if (numOfBits < 0) {
      throw new IllegalArgumentException();
    }
   
    if (numOfBits == 0) {
      throw new IllegalArgumentException();
    }
   
    if (!isValidEndianness(byteEndianness)) {
      throw new IllegalArgumentException();
    }
   
    int numOfBytesWithoutFractionalBits = (int )(numOfBits / Byte.SIZE);
   
    if (byteEndianness == LITTLE_ENDIAN) {
      for (int curByteIndex = 0;
           curByteIndex < numOfBytesWithoutFractionalBits;
           curByteIndex = curByteIndex + 1) {
        for (int curBitShift = 0; curBitShift < Byte.SIZE; curBitShift = curBitShift + 1) {
          int bitShift = Byte.SIZE * curByteIndex + curBitShift;
          int bitValue = getBitsValueBit(bitsValue, bitShift);
          write(bitValue);
        }
      }
     
      int numOfRemainingBitsAfterLastByte = (int )(numOfBits % Byte.SIZE);
     
      for (int curLastBitShift = 0;
           curLastBitShift < numOfRemainingBitsAfterLastByte;
           curLastBitShift = curLastBitShift + 1) {
        int bitShift = Byte.SIZE * numOfBytesWithoutFractionalBits + curLastBitShift;
        int bitValue = getBitsValueBit(bitsValue, bitShift);
        write(bitValue);
      }
    } else {
      int numOfPrecedingBitsBeforeFirstByte = (int )(numOfBits % Byte.SIZE);
     
      for (int curFirstBitShift = 0;
           curFirstBitShift < numOfPrecedingBitsBeforeFirstByte;
           curFirstBitShift = curFirstBitShift + 1) {
        int bitShift = Byte.SIZE * numOfBytesWithoutFractionalBits + curFirstBitShift;
        int bitValue = getBitsValueBit(bitsValue, bitShift);
        write(bitValue);
      }
     
      for (int curByteIndex = 0;
           curByteIndex < numOfBytesWithoutFractionalBits;
           curByteIndex = curByteIndex + 1) {
        for (int curBitShift = 0; curBitShift < Byte.SIZE; curBitShift = curBitShift + 1) {
          int curByteIndexFromBehind = numOfBytesWithoutFractionalBits - curByteIndex - 1;
          int bitShift = Byte.SIZE * curByteIndexFromBehind + curBitShift;
          int bitValue = getBitsValueBit(bitsValue, bitShift);
          write(bitValue);
        }
      }
    }
  }

  /**
   * Skips a number of bits, specified by {@code numOfBitsToSkip}, so
   * that the read cursor will fast forward to the desired location.
   * The reference implementation simply reads and discards the data.
   * Note that according to the availability of the data in the
   * current {@code Buffer}, the actually skipped number of bits
   * might be less than the one passed as the parameter. So it is
   * advised to always use the return value to check for it.
   * @param numOfBitsToSkip The number of {@code bit}s to skip.
   * @return The number of {@code bit}s actually skipped. If
   *         the return value is smaller than {@code numOfBitsToSkip}
   *         it usually indicates that the cursor has been fast
   *         forwarded to the end of the available bits stream.
   * @throws IllegalArgumentException one of the following occurs:
   *        <ul>
   *         <li>{@code numOfBitsToSkip} is negative.</li>
   *         <li>{@code numOfBitsToSkip} is zero.</li>
   *        </ul>
   * @throws IOException the underlying I/O operation fails.
   */
  public long skip(long numOfBitsToSkip) throws IOException {
    if (numOfBitsToSkip < 0) {
      throw new IllegalArgumentException();
    }
   
    if (numOfBitsToSkip == 0) {
      throw new IllegalArgumentException();
    }
   
    long numOfBitsSkippable = readable();
    long numOfBitsActuallySkipped = Math.min(numOfBitsToSkip, numOfBitsSkippable);
   
    for (int curSkipBitIndex = 0;
         curSkipBitIndex < numOfBitsActuallySkipped;
         curSkipBitIndex = curSkipBitIndex + 1) {
      int discardedBit = read();
    }
   
    return numOfBitsActuallySkipped;
  }
 
  /**
   * Fills a number of {@code bit}s, specified by {@code numOfBitsToFill},
   * with a {@code bit} value specified by {@code bitValuePerBit}, so that
   * the write cursor will fast forward to the desired location. The reference
   * implementation will write the data specified by {@code bitValuePerBit}
   * repeatedly until {@code numOfBitsToFill bits} of data are written.
   * If the {@code bit}s being filled exceeds the current size of the
   * {@code Buffer}'s maximum allowed space, re-alloc-expanding will occur.
   * @param numOfBitsToFill The number of bits to fill to fast forward
   *       the write cursor. Note that re-alloc-expanding may occur.
   * @param bitValuePerBit The {@code bit} value to fill the buffer.
   * @throws IllegalArgumentException one of the following occurs:
   *        <ul>
   *         <li>{@code numOfBitsToFill} is negative.</li>
   *         <li>{@code numOfBitsToFill} is zero.</li>
   *         <li>{@code bitValuePerBit} is not a valid bit value
   *           (neither {@code 1 (true)} nor {@code 0 (false)}).</li>
   *        </ul>
   * @throws IOException the underlying I/O operation fails.
   */
  public void fill(long numOfBitsToFill, int bitValuePerBit)
    throws IllegalArgumentException,
         IOException {
    if (numOfBitsToFill < 0) {
      throw new IllegalArgumentException();
    }
   
    if (numOfBitsToFill == 0) {
      throw new IllegalArgumentException();
    }
   
    if (bitValuePerBit != 0 &&
      bitValuePerBit != 1) {
      throw new IllegalArgumentException();
    }
   
    for (long curBitFillIndex = 0;
          curBitFillIndex < numOfBitsToFill;
          curBitFillIndex = curBitFillIndex + 1) {
      write(bitValuePerBit);
    }
  }
 
  /**
   * Saves a mark at the current read cursor, subsequent read operations will
   * run normally, once {@link #reset()} is called, the recorded mark will be
   * used to substitute any read cursor position so the read cursor will be
   * rolled back to the previously marked position. This method is useful for
   * pre-reading and checking values. The caller may call {@link #mark()} first
   * and read as per normal, and if a desired value is found or an error is
   * encountered, call the reset to roll back to keep the cursor intact.
   *
   * @see #reset()
   */
  public void mark() {
    markPos = readPos;
  }
 
  /**
   * Replaces the current read cursor with the previously recorded position
   * assigned by a call to the {@link #mark()} method. Once this method is
   * called, the read cursor will be rolled back to the previously recorded
   * position, and the data that position will become available for read again.
   * Please note that a call to {@link #reset()} will also reset the marked
   * position to {@link #EOF}. So after calling {@link #reset()} to roll
   * back your read cursor, if you wish to be able to roll back to this
   * position again, call {@link #mark()} immediately after for the mark
   * position to be set to that position again. If there was no previously
   * marked position, {@link #reset()} does nothing.
   */
  public void reset() {
    if (markPos != EOF) {
      readPos = markPos;
      markPos = EOF;
    }
  }
 
  /**
   * Aligns the read cursor to the beginning of the next {@code byte}.
   * If the read cursor is in the middle of a byte, moves the cursor
   * to the start of next byte. If the cursor is already at the start
   * of a byte, {@link #alignRead()} does nothing and returns {@code zero}.
   * Note that {@link #alignRead()} will never make the read cursor
   * beyond the write cursor to make the data corrupted. If the write
   * cursor does not end in a full {@code byte} and if the read cursor
   * is near the write cursor, calling {@link #alignRead()} will always
   * return {@link #EOF} as the read cursor cannot go past the write
   * cursor to make a full {@code byte}.
   *
   * @return The number of {@code bits} skipped to traverse to the starting
   *         bit of the next {@code byte}. If the read cursor is already at
   *         the starting bit of a {@code byte}, returns {@code zero}; if
   *         the align operation cannot move the read cursor to the beginning
   *         {@code bit} of the next {@code byte} because the write cursor
   *         is not yet that far, returns {@link #EOF}.
   */
  public int alignRead() {
    int readByteIndex = (int )(readPos / Byte.SIZE);
    int readBitShiftAtIndex = (int )(readPos % Byte.SIZE);
    boolean alignNeeded = readBitShiftAtIndex != 0;
    long traverseResult = 0;
   
    if (alignNeeded) {
      int nextReadByteIndex = readByteIndex + 1;
      long alignedPos = (long )Byte.SIZE * (long )nextReadByteIndex;
     
      if (alignedPos > writePos) {
        traverseResult = EOF;
      } else {
        traverseResult = alignedPos - readPos;
        readPos = alignedPos;
      }
    }

    return (int )traverseResult;
  }
 
  /**
   * Aligns the write cursor to the beginning of the next {@code byte}.
   * If the write cursor is in the middle of a byte, moves the cursor
   * to the start of next byte. If the cursor is already at the start
   * of a byte, {@link #alignRead()} does nothing and returns {@code zero}.
   *
   * @return The number of {@code bits} skipped to traverse to the starting
   *         bit of the next {@code byte}. If the read cursor is already at
   *         the starting bit of a {@code byte}, returns {@code zero}.
   */
  public int alignWrite() {
    int writeByteIndex = (int )(writePos / Byte.SIZE);
    int writeBitShiftAtIndex = (int )(writePos % Byte.SIZE);
    boolean alignNeeded = writeBitShiftAtIndex != 0;
    long traverseResult = 0;
   
    if (alignNeeded) {
      int nextWriteByteIndex = writeByteIndex + 1;
      long alignedPos = (long )Byte.SIZE * (long )nextWriteByteIndex;
      traverseResult = alignedPos - writePos;
      writePos = alignedPos;
    }

    return (int )traverseResult;
  }
 
  /**
   * Exports the data in this {@link Buffer} to a {@code byte} array.
   * the parameter {@code copyMethod} specifies how to copy the data.
   * @param copyMethod How to copy the data from this {@link Buffer}
   *        to an array. Refer to {@code COPY_METHOD_*} for details.
   * @return The {@code byte} array containing the data of this
   *         {@link Buffer}.
   * @throws IllegalArgumentException the {@code copyMethod} is not
   *         a valid value (none of the specified {@code COPY_METHOD_*}
   *         values).
   * @see #COPY_METHOD_DEFAULT
   * @see #COPY_METHOD_ALIGNED
   * @see #COPY_METHOD_ALL_DATA
   */
  public byte[] toByteArray(int copyMethod) {
    if (copyMethod != COPY_METHOD_DEFAULT &&
      copyMethod != COPY_METHOD_ALIGNED &&
      copyMethod != COPY_METHOD_ALL_DATA) {
      throw new IllegalArgumentException();
    }
   
    byte[] byteArray;
   
    if (copyMethod == COPY_METHOD_ALL_DATA) {
      int numOfBytesWithoutFractionalBits = (int )(writePos / Byte.SIZE);
      int numOfRemainingBitsAfterLastByte = (int )(writePos % Byte.SIZE);
      boolean needOneMoreByteForRemainingBitsAfterLastByte =
        numOfRemainingBitsAfterLastByte != 0;
       
      int numOfBytesNeededForData = numOfBytesWithoutFractionalBits;
     
      if (needOneMoreByteForRemainingBitsAfterLastByte) {
        numOfBytesNeededForData = numOfBytesNeededForData + 1;
      }
     
      byteArray = Arrays.copyOf(backingData, numOfBytesNeededForData);
    } else if (copyMethod == COPY_METHOD_ALIGNED) {
      long numOfBitsToCopy = writePos - readPos;
      byteArray = alignAndCopyByteArray(backingData, readPos, numOfBitsToCopy);
    } else {
      int startByteIndex = (int )(readPos / Byte.SIZE);
      int endByteIndex = (int )(writePos / Byte.SIZE);
      int startBitShift = (int )(readPos % Byte.SIZE);
      int endBitShift = (int )(writePos % Byte.SIZE);
      boolean needOneMoreByteForRemainingBitsAfterLastByte =
        endBitShift != 0;
      int numOfBytesNeededForData = endByteIndex - startByteIndex;
     
      if (needOneMoreByteForRemainingBitsAfterLastByte) {
        numOfBytesNeededForData = numOfBytesNeededForData + 1;
      }
     
      byteArray = Arrays.copyOfRange(backingData, startByteIndex, startByteIndex + numOfBytesNeededForData);
     
      int mask = 0x01;
     
      if (startBitShift != 0) {
        for (int bitShift = 0; bitShift < startBitShift; bitShift = bitShift + 1) {
          int bitValueShifted = (mask << bitShift);
          int bitValueShiftedAndFlippedToClearBits = ~bitValueShifted;
          byteArray[startByteIndex] =
            (byte )(byteArray[startByteIndex] & bitValueShiftedAndFlippedToClearBits);
        }
      }
     
      if (endBitShift != 0) {
        for (int bitShift = endBitShift; bitShift < Byte.SIZE; bitShift = bitShift + 1) {
          int bitValueShifted = (mask << bitShift);
          int bitValueShiftedAndFlippedToClearBits = ~bitValueShifted;
          byteArray[endByteIndex] =
            (byte )(byteArray[endByteIndex] & bitValueShiftedAndFlippedToClearBits);
        }
      }
    }
   
    return byteArray;
  }
 
  private byte[] alignAndCopyByteArray(byte[] existingData, long bitOffset, long numOfBits) {
    int numOfBitsWithoutFractionalBits = (int )(numOfBits / Byte.SIZE);
    int numOfFractionalBits = (int )(numOfBits % Byte.SIZE);
    boolean needOneMoreByteForFractionalBits = numOfFractionalBits != 0;
   
    int numOfBytesNeededForData = numOfBitsWithoutFractionalBits;
   
    if (needOneMoreByteForFractionalBits) {
      numOfBytesNeededForData = numOfBytesNeededForData + 1;
    }
   
    byte[] byteArrayCopy = new byte[numOfBytesNeededForData];
   
    for (long curBitIndex = 0;
          curBitIndex < numOfBits;
          curBitIndex = curBitIndex + 1) {
      int srcByteIndex = (int )((bitOffset + curBitIndex) / Byte.SIZE);
      int srcBitShift = (int )((bitOffset + curBitIndex) % Byte.SIZE);
      int destByteIndex = (int )(curBitIndex / Byte.SIZE);
      int destBitShift = (int )(curBitIndex % Byte.SIZE);
      int mask = 0x01;
      int srcByteValue = existingData[srcByteIndex];
      int srcByteValueShifted = srcByteValue >>> srcBitShift;
      int srcBitValue = srcByteValueShifted & mask;
      int destBitValue = srcBitValue;
      int destBitValueShifted = destBitValue << destBitShift;
      byteArrayCopy[destByteIndex] = (byte )(byteArrayCopy[destByteIndex] | destBitValueShifted);
    }
   
    return byteArrayCopy;
  }
 
  private BigInteger setAndUpdateBitsValueBit(BigInteger bitsValue, int bitShift, int bitValue) {
    if (bitsValue == null) {
      throw new NullPointerException();
    }
   
    if (bitShift < 0) {
      throw new IllegalArgumentException();
    }
   
    BigInteger shiftedBitValue = BigInteger.ONE;
    shiftedBitValue = shiftedBitValue.shiftLeft(bitShift);
   
    if (bitValue != 0) {
      bitsValue = bitsValue.or(shiftedBitValue);
    } else {
      shiftedBitValue = shiftedBitValue.not();
      bitsValue = bitsValue.and(shiftedBitValue);
    }
   
    return bitsValue;
  }
 
  private int getBitsValueBit(BigInteger bitsValue, int bitShift) {
    if (bitsValue == null) {
      throw new NullPointerException();
    }
   
    if (bitShift < 0) {
      throw new IllegalArgumentException();
    }
   
    BigInteger shiftedBitValue = bitsValue.shiftRight(bitShift);
    int shiftedBitIntValue = shiftedBitValue.intValue();
    int bitMask = 0x01;
    int bitValue = shiftedBitIntValue & bitMask;
    return bitValue;
  }
}
TOP

Related Classes of com.peterhi.runtime.Buffer

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.