Package com.peterhi.runtime

Source Code of com.peterhi.runtime.BufferHelper

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 com.peterhi.obsolete.RT;

/**
* {@link BufferHelper} is a wrapper class designed around
* {@link Buffer} objects to simplify the reading and writing
* of common data types. It internally keeps an instance of
* {@link Buffer} object and reads / writes data using this
* object. Please note that since {@link Buffer} object itself
* is not synchronized, and {@link BufferHelper} can accept
* an external {@link Buffer} object to be passed into this
* helper class, synchronization is never reinforced in this
* helper class. You need to sync them manually.
*
* @see Buffer
*
* @author hytparadisee
*
*/
public class BufferHelper implements DataInput, DataOutput {

  public static final BigInteger UNSIGNED_LONG_MASK;
 
  static {
    UNSIGNED_LONG_MASK = new BigInteger("0xffffffffffffffff");
  }
 
  private final Buffer buffer;
 
  /**
   * Creates an instance of {@link BufferHelper} using an
   * existing {@link Buffer} object. You can later use this
   * {@link BufferHelper} object to write to or read from
   * the underlying {@link Buffer}. You can get the reference
   * to the underlying {@link Buffer} later at any time using
   * {@link #getBuffer()} method.
   *
   * @param existingBuffer The existing {@link Buffer} to use as the
   *        underlying data object.
   *       
   * @throws NullPointerException if {@code existingBuffer} is {@code null}.
   */
  public BufferHelper(Buffer existingBuffer)
    throws NullPointerException {
    if (existingBuffer == null) {
      throw new NullPointerException();
    }
   
    this.buffer = existingBuffer;
  }
 
  /**
   * Creates an instance of {@link BufferHelper} with a freshly
   * created underlying {@link Buffer} object. This is a shorthand method.
   * The parameter {@code capacityInBytes} is used for the creation of
   * the underlying {@link Buffer}. Please refer to {@link Buffer#Buffer(int)}
   * for more information.
   *
   * @param capacityInBytes This parameter is passed in to
   *        {@link Buffer#Buffer(int)}.
   *
   * @throws IllegalArgumentException thrown when the creation of the underlying {@link Buffer}
   *         fails from the {@link Buffer#Buffer(int)} constructor.
   *        
   * @see Buffer#Buffer(int)
   */
  public BufferHelper(int capacityInBytes)
    throws IllegalArgumentException {
    this(new Buffer(capacityInBytes));
  }
 
  /**
   * Creates an instance of {@link BufferHelper} with a freshly
   * created underlying {@link Buffer} object using its default
   * constructor. This is a shorthand method. Please refer to
   * {@link Buffer#Buffer()} for more information.
   *
   * @see Buffer#Buffer()
   */
  public BufferHelper() {
    this(new Buffer());
  }
 
  /**
   * Creates an instance of {@link BufferHelper} with a freshly
   * created underlying {@link Buffer} object. This is a shorthand method.
   * The parameters {@code existingData} is used for the creation of the
   * underlying {@link Buffer}. Please refer to {@link Buffer#Buffer(byte[])}
   * for more information.
   *
   * @param existingData This parameter is passed in to {@link Buffer#Buffer(byte[])}.
   *
   * @throws NullPointerException thrown when the creation of the underlying {@link Buffer}
   *         fails from the {@link Buffer#Buffer(byte[])} constructor.
   * @throws IllegalArgumentException thrown when the creation of the underlying {@link Buffer}
   *         fails from the {@link Buffer#Buffer(byte[])} constructor.
   *        
   * @see Buffer#Buffer(byte[])
   * @see #BufferHelper(byte[], int, int)
   */
  public BufferHelper(byte[] existingData)
    throws NullPointerException,
         IllegalArgumentException {
    this(new Buffer(existingData));
  }
 
  /**
   * Creates an instance of {@link BufferHelper} with a freshly
   * created underlying {@link Buffer} object. This is a shorthand method.
   * The parameters {@code existingData}, {@code byteOffset} and
   * {@code numOfBytes} are used for the creation of the underlying
   * {@link Buffer}. Please refer to {@link Buffer#Buffer(byte[], int, int)}
   * for more information.
   *
   * @param existingData This parameter is passed in to {@link Buffer#Buffer(byte[], int, int)}.
   * @param byteOffset This parameter is passed in to {@link Buffer#Buffer(byte[], int, int)}.
   * @param numOfBytes This parameter is passed in to {@link Buffer#Buffer(byte[], int, int)}.
   *
   * @throws NullPointerException thrown when the creation of the underlying {@link Buffer}
   *         fails from the {@link Buffer#Buffer(byte[], int, int)} constructor.
   * @throws IllegalArgumentException thrown when the creation of the underlying {@link Buffer}
   *         fails from the {@link Buffer#Buffer(byte[], int, int)} constructor.
   *        
   * @see Buffer#Buffer(byte[], int, int)
   */
  public BufferHelper(byte[] existingData, int byteOffset, int numOfBytes)
    throws NullPointerException,
         IllegalArgumentException {
    this(new Buffer(existingData, byteOffset, numOfBytes));
  }
 
  /**
   * Creates an instance of {@link BufferHelper} with a freshly
   * created underlying {@link Buffer} object. This is a shorthand method.
   * The parameters {@code existingData}, {@code bitOffset}, {@code numOfBits}
   * and {@code alignBits} are used for the creation of the underlying
   * {@link Buffer}. Please refer to {@link Buffer#Buffer(byte[], int, int)}
   * for more information.
   *
   * @param existingData This parameter is passed in to {@link Buffer#Buffer(byte[], long, long, boolean)}.
   * @param bitOffset This parameter is passed in to {@link Buffer#Buffer(byte[], long, long, boolean)}.
   * @param numOfBits This parameter is passed in to {@link Buffer#Buffer(byte[], long, long, boolean)}.
   * @param alignBits This parameter is passed in to {@link Buffer#Buffer(byte[], long, long, boolean)}.
   *
   * @throws NullPointerException thrown when the creation of the underlying {@link Buffer}
   *         fails from the {@link Buffer#Buffer(byte[], long, long, boolean)} constructor.
   * @throws IllegalArgumentException thrown when the creation of the underlying {@link Buffer}
   *         fails from the {@link Buffer#Buffer(byte[], long, long, boolean)} constructor.
   * @throws IOException thrown when the creation of the underlying {@link Buffer}
   *         fails from the {@link Buffer#Buffer(byte[], long, long, boolean)} constructor.
   *        
   * @see Buffer#Buffer(byte[], long, long, boolean)
   */
  public BufferHelper(byte[] existingData, long bitOffset, long numOfBits, boolean alignBits)
    throws NullPointerException,
         IllegalArgumentException,
         IOException {
    this(new Buffer(existingData, bitOffset, numOfBits, alignBits));
  }
 
  /**
   * Gets the reference to the underlying {@link Buffer} object.
   * The underlying {@link Buffer} may have been an existing object
   * passed in via the constructor, or a newly created object at
   * the constructor.
   *
   * @return The underlying {@link Buffer} instance.
   */
  public Buffer getBuffer() {
    return buffer;
  }
 
  /**
   * Gets the data of the underlying {@link Buffer} as a byte array.
   * The parameter {@code copyMethod} defines how data is to be
   * copied. Refer to {@code Buffer.COPY_METHOD_*} in the class
   * {@link Buffer} and the method {@link Buffer#toByteArray(int)}
   * for more details.
   *
   * @param copyMethod This parameter is passed in to
   *        {@link Buffer#toByteArray(int)}.
   *       
   * @return The {@code byte} array containing the data.
   *
   * @see Buffer#toByteArray(int)
   * @see Buffer#COPY_METHOD_DEFAULT
   * @see Buffer#COPY_METHOD_ALIGNED
   * @see Buffer#COPY_METHOD_ALL_DATA
   */
  public byte[] toByteArray(int copyMethod) {
    return buffer.toByteArray(copyMethod);
  }

  /**
   * Reads the next {@code byte} of data from the underlying
   * {@link Buffer} object. Returning an {@code int} value
   * which is actually an {@code unsigned byte} representation
   * of the data (value range between {@code zero} and {@code 255}).
   * This method will return {@link Buffer#EOF} if the
   * underlying {@link Buffer} has insufficient data to return
   * a {@code byte}. To be compliant, this method will not
   * throw any exception regarding data availability. But if
   * the underlying {@link Buffer} has an erroneous I/O operation
   * that caused the throw, this method will relay exactly that
   * exception.
   * 
   * @return An {@code unsigned byte} representing data between
   *         {@code zero} and {@code 255}. Or {@link Buffer#EOF} if the
   *         underlying {@link Buffer} doesn't have enough data
   *         available for a {@code byte} to be read.
   *
   * @throws IOException thrown when the underlying {@link Buffer}
   *         encounters an error reading the required amount of data.
   *        
   * @see #read(byte[], int, int)
   */
  public int read() throws IOException {
    int numOfBytesReadable = (int )(buffer.readable() / Byte.SIZE);
   
    if (numOfBytesReadable > 0) {
      BigInteger bitsValue = buffer.read(Byte.SIZE, false, Buffer.BIG_ENDIAN);
      byte byteValue = bitsValue.byteValue();
      int intValue = byteValue & 0xff;
      return intValue;
    }

    return Buffer.EOF;
  }

  /**
   * Attempts to read as many {@code bytes} as the {@code byte}
   * array, {@code copyDataTo}, can hold. This operation may
   * not always read the full {@code byte}s as requested, and
   * the number of {@code byte}s actually read is indicated by
   * the return value. If there's currently no (or insufficient)
   * data available, the return value is {@link Buffer#EOF}. To be compliant,
   * this method will not throw any exception regarding data availability.
   * But if the underlying {@link Buffer} has an erroneous I/O operation
   * that caused the throw, this method will relay exactly that exception.
   *
   * @param copyDataTo A {@code byte} array used to hold data.
   *
   * @return The number of {@code byte}s actually read after the method completes.
   *         Or {@link Buffer#EOF} if there is no (or insufficient) data available.
   *
   * @throws NullPointerException thrown if {@code copyDataTo} is {@code null}.
   * @throws IllegalArgumentException thrown if {@code copyDataTo} is an empty
   *         ({@code zero}-length) {@code byte} array.
   * @throws IOException thrown when the underlying {@link Buffer}
   *         encounters an error reading the required amount of data.
   *
   * @see {@link #read(byte[], int, int)}
   */
  public int read(byte[] copyDataTo)
    throws NullPointerException,
         IOException {
    if (copyDataTo == null) {
      throw new NullPointerException();
    }
   
    return read(copyDataTo, 0, copyDataTo.length);
  }

  /**
   * Reads the subsequent {@code n byte}s of data, specified by the
   * parameter {@code numOfBytes}, into a {@code byte} array named
   * as the parameter {@code copyDataTo}, starting at index known
   * as the parameter {@code byteOffset}. This operation may not always read
   * the full {@code byte}s as requested, and the number of
   * {@code byte}s actually read is indicated by the return value.
   * If there's currently no (or insufficient) data available, the
   * return value is {@link Buffer#EOF}. To be compliant, this method will not
   * throw any exception regarding data availability. But if
   * the underlying {@link Buffer} has an erroneous I/O operation
   * that caused the throw, this method will relay exactly that
   * exception.
   *
   *
   * @param copyDataTo A {@code byte} array used to hold data.
   * @param byteOffset The starting index at which the {@code byte}s are copied to.
   * @param numOfBytes The number of {@code byte}s to read, actual number may be smaller.
   *
   * @return The number of {@code byte}s actually read after the method completes. Or
   *        {@link Buffer#EOF} if there is no (or insufficient) data available.
   *
   * @throws NullPointerException if {@code copyDataTo} is {@code null}.
   * @throws IllegalArgumentException thrown when one of the following occurs:
   *        <ul>
   *         <li>if {@code copyDataTo} is an empty ({@code zero}-length) {@code byte} array.</li>
   *         <li>if {@code byteOffset} is negative.</li>
   *         <li>if {@code numOfBytes} is negative.</li>
   *         <li>if {@code numOfBytes} is zero.</li>
   *         <li>if {@code byteOffset} and {@code numOfBytes} went out of bounds
   *                  so that {@code byteOffset + numOfBytes} is greater than the
   *                  number of {@code byte}s the array {@code copyDataTo} can hold.</li>
   *        </ul>
   * @throws IOException thrown when the underlying {@link Buffer}
   *         encounters an error reading the required amount of data.
   *
   * @see {@link #read()}
   *
   */
  public int read(byte[] copyDataTo, int byteOffset, int numOfBytes)
    throws NullPointerException,
         IllegalArgumentException,
         IOException {
    if (copyDataTo == null) {
      throw new NullPointerException();
    }
   
    if (copyDataTo.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 > copyDataTo.length) {
      throw new IllegalArgumentException();
    }
   
    int numOfBytesReadable = (int )(buffer.readable() / Byte.SIZE);
    int numOfBytesToRead = Math.min(numOfBytes, numOfBytesReadable);
    int numOfBytesActuallyRead = 0;
   
    for (int nthByteToRead = 0; nthByteToRead < numOfBytesToRead; nthByteToRead = nthByteToRead + 1) {
      int byteIndex = byteOffset + nthByteToRead;
      int dataValue = read();
     
      if (dataValue == Buffer.EOF) {
        break;
      }
     
      byte byteValue = (byte )dataValue;
      copyDataTo[byteIndex] = byteValue;
      numOfBytesActuallyRead = numOfBytesActuallyRead + 1;
    }
   
    return numOfBytesActuallyRead;
  }

  /**
   * Skips a number of bytes, specified by the parameter {@code numOfBytes}
   * to fast-forward to the desired position. It is possible that the actual
   * number of {@code byte}s skipped may be smaller than the designated
   * {@code numOfBytes} parameter. Therefore, the actual number of {@code byte}s
   * skipped is shown in the return value.
   *
   * @param numOfBytes The number of {@code byte}s planned to skip. If
   *       {@code numOfBytes} is {@code zero}. This method does nothing and
   *        returns {@code zero}.
   *
   * @return The actual number of bytes skipped. The position of the
   *        {@link Buffer} is modified as a result of the skip operation,
   *         if the return value is {@code zero}, the backing {@link Buffer}
   *         is not modified.
   *
   * @throws IllegalArgumentException thrown when one of the following occurs:
   *        <ul>
   *         <li>{@code numOfBytes} is negative.</li>
   *         <li>{@code numOfBytes} is zero.</li>
   *        </ul>
   * @throws IOException if the underlying {@link Buffer} operation throws
   *        an error.
   */
  public long skip(long numOfBytes)
    throws IllegalArgumentException,
         IOException {
    if (numOfBytes < 0) {
      throw new IllegalArgumentException();
    }
   
    if (numOfBytes == 0) {
      throw new IllegalArgumentException();
    }
   
    long numOfBytesReadable = buffer.readable() / (long )Byte.SIZE;
    long numOfBytesToSkip = Math.min(numOfBytes, numOfBytesReadable);
    long numOfBitsToSkip = Byte.SIZE * numOfBytesToSkip;
    long numOfBitsActuallySkipped = buffer.skip(numOfBitsToSkip);
    long numOfBytesActuallySkipped = numOfBitsActuallySkipped / Byte.SIZE;
    return numOfBytesActuallySkipped;
  }

  /**
   * Returns the number of {@code byte}s that the underlying {@link Buffer}
   * can be read without blocking or waiting. Due to the fact that a
   * {@link Buffer} may have data that is a fraction of a {@code byte},
   * this method doesn't mean that the underlying {@link Buffer} will have
   * no data if this method returns {@code zero}. Say if the underlying
   * {@link Buffer} has 11 {@code bit}s. The available {@code bit}s will
   * be 11, but the available {@code byte}s will only be 1, because the
   * remaining 3 {@code bit}s are not considered a full {@code byte}.
   *
   * @return The number of {@code byte}s that can be read without blocking
   *         or waiting.
   *        
   * @throws IOException The underlying {@link Buffer} operation fails
   *         with an error.
   */
  public int available()
    throws IOException {
    long numOfBitsReadable = buffer.readable();
    int numOfBytesReadable = (int )(numOfBitsReadable / Byte.SIZE);
    return numOfBytesReadable;
  }

  /**
   * Closes the I/O stream. This method is merely a stub and does not
   * do anything.
   *
   * @throws IOException the underlying stream I/O error occurs.
   */
  public void close()
    throws IOException {
  }

  /**
   * Marks the current read position of the underlying
   * {@link Buffer} so that it can be reverted later
   * after a corresponding call to {@link #reset()}.
   * The parameter {@code readLimit} is ignored because
   * the underlying {@link Buffer} no longer needs this
   * limit, but in order to keep the methods compliant,
   * this parameter is kept. This method will simply call
   * {@link Buffer#mark()}.
   *
   * @param readLimit The maximum number of bytes that
   *        can be read without the mark becoming valid.
   *        This parameter is never used so it is ignored.
   *        This parameter is kept for compliance reasons.
   *       
   * @throws IOException the underlying {@link Buffer}
   *         fails with an error.
   *       
   * @see Buffer#mark()
   */
  public synchronized void mark(int readLimit)
    throws IOException {
    buffer.mark();
  }

  /**
   * Resets from the previously marked position so that
   * the {@link Buffer} reverts to a previous location.
   *
   * @throws IOException the underlying {@link Buffer}
   *         fails with an error.
   *        
   * @see Buffer#reset();
   */
  public synchronized void reset()
    throws IOException {
    buffer.reset();
  }

  /**
   * Returns whether the mark / reset operation is supported
   * by the underlying stream. This method is a stub because
   * mark / reset operation is always supported by the underlying
   * {@link Buffer} object.
   *
   * @return This method always returns {@code true} as the
   *         underlying {@link Buffer} always supports marking /
   *         resetting.
   *        
   * @throws IOException the underlying {@link Buffer} fails
   *         with an error.
   *
   * @see Buffer#mark()
   * @see Buffer#reset()
   */
  public boolean markSupported()
    throws IOException {
    return true;
  }

  /**
   * Reads the next {@code byte} of data to the underlying
   * {@link Buffer} object. The parameter {@code dataValue}
   * is an {@code unsigned byte} representation of the data
   * (value range between {@code zero} and {@code 255}).
   * The underlying {@link Buffer} will append the data
   * and forward the write cursor. Please note that
   * re-alloc-expansion may occur if the current buffer
   * is not big enough.
   *
   * @param dataVaue The value of the data between
   *        {@code zero} and {@code 255}.
   *       
   * @throws IllegalArgumentExcecption the value of he data
   *         is either negative or greater than {@code 255}.
   * @throws IOException thrown when the underlying
   *        {@link Buffer} encounters an error writing
   *         the required amount of data.
   */
  public void write(int dataValue)
    throws IllegalArgumentException,
         IOException {
    if (dataValue < 0) {
      throw new IllegalArgumentException();
    }
   
    if (dataValue > 0xff) {
      throw new IllegalArgumentException();
    }
   
    BigInteger bitsValue = BigInteger.valueOf(dataValue);
    buffer.write(Byte.SIZE, bitsValue, Buffer.BIG_ENDIAN);
  }

  /**
   * Writes all the data from {@code byteArray} into the underlying
   * {@link Buffer}.
   *
   * @param byteArray A {@code byte} array that contains the data
   *        to be written into the {@link Buffer}.
   *
   * @throws NullPointerException thrown of {@code byteArray} is
   *         {@code null}.
   * @throws IOException thrown when the underlying {@link Buffer}
   *        encounters an error while writing the dat.
   *
   * @see {@link #write(int)}
   */
  public void write(byte[] byteArray)
    throws IOException {
    if (byteArray == null) {
      throw new NullPointerException();
    }
   
    write(byteArray, 0, byteArray.length);
  }

  /**
   * Writes the subsequent {@code n byte}s of data of {@code byteArray},
   * specified by the parameter {@code numOfBytes}, into the underlying
   * {@link Buffer}, starting at index known as the parameter
   * {@code byteOffset}.
   *
   * @param byteArray The source {@code byte} array containing the data
   *        to write.
   * @param byteOffset The starting index at which the {@code byte}s are copied from.
   * @param numOfBytes The number of {@code byte}s to read, re-alloc-expansion may
   *        occur if  the underlying {@link Buffer} is not big enough.
   *
   * @throws NullPointerException if {@code byteArray} is {@code null}.
   * @throws IllegalArgumentException thrown when one of the following occurs:
   *         <ul>
   *             <li>{@code byteArray} is an empty ({@code zero}-length) array.</li>
   *             <li>{@code byteOffset} is negative.</li>
   *             <li>{@code numOfBytes} is negative.</li>
   *             <li>{@code numofBytes} is zero.</li>
   *             <li>{#code byteOffset} and {@code numOfBytes} went out of bounds
   *               so that {@code byteOffset + numOfBytes} is greater than the
   *               number of {@code byte}s the array {@code copyDataTo} can hold.</li>
   *         </ul>
   * @throws IOException thrown when the underlying {@link Buffer}
   *         encounters an error writing the required amount of data.
   *        
   * @see #write(int)
   */
  public void write(byte[] byteArray, int byteOffset, int numOfBytes)
    throws IOException {
    if (byteArray == null) {
      throw new NullPointerException();
    }
   
    if (byteArray.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 > byteArray.length) {
      throw new IllegalArgumentException();
    }
   
    for (int nthByteToWrite = byteOffset;
           nthByteToWrite < byteArray.length;
         nthByteToWrite = nthByteToWrite + 1) {
      write(byteArray[nthByteToWrite] & 0xff);
    }
  }

  /**
   * Flushes the underlying {@link Buffer} so that all
   * pending data updates are executed before this method returns.
   * To be compliant, this method exists, but it doesn't actually
   * do anything.
   *
   * @throws IOException The underlying {@link Buffer} object
   *         generates an error while flushing.
   */
  public void flush()
    throws IOException {
  }

  /**
   * Read from the underlying {@link Buffer} and copy the data
   * to the {@code byte} array provided as the parameter
   * {@code copyDataTo}. This method forces that the underlying
   * {@link Buffer} must have enough amount of data to be read
   * to completely fill the {@code copyDataTo}. Please note that
   * the behavior of {@link #readFully(byte[])} is slightly different
   * from that defined in {@link DataInput#readFully(byte[])}.
   * The difference is that in {@link DataInput#readFully(byte[])},
   * if the underlying data source doesn't have the required amount
   * of data to read, it will block until enough data has arrived.
   * In {@link #readFully(byte[])}, however, it will not block
   * if the underlying {@link Buffer} has insufficient data. Instead,
   * it will throw an {@link InsufficientBufferException} or an
   * {@link EOFException} to reflect this situation. The caller
   * should always catch this exception in the event that there
   * is insufficient data to be read completely. The implementation,
   * though, guarantees that if an {@link InsufficientBufferException}
   * or an {@link EOFException} is thrown, data won't be corrupted,
   * so that the state of the underlying {@link Buffer} remains intact.
   *
   * @param copyDataTo A {@code byte} array that will hold the data
   *        read from the underlying {@link Buffer}. This method
   *        guarantees that {@code copyDataTo} will be completely
   *        filled with the data. If not, an
   *        {@link InsufficientBufferException} or an
   *        {@link EOFException} will be thrown.
   *
   * @throws NullPointerException if {@code copyDataTo} is {@code null}.
   * @throws IllegalArgumentException thrown when one of the following occurs:
   *        <ul>
   *         <li>{@code copyDataTo} is an empty ({@code zero}-length) {@code byte} array.</li>
   *         <li>{@code byteOffset} is negative.</li>
   *         <li>{@code numOfBytes} is negative.</li>
   *         <li>{@code numOfBytes} is zero.</li>
   *         <li>{@code byteOffset} and {@code numOfBytes} went out of bounds
   *           so that {@code byteOffset + numOfBytes} is greater than the
   *           number of {@code byte}s the array {@code copyDataTo} can hold.</li>
   *        </ul>
   * @throws InsufficientBufferException the underlying {@link Buffer}
   *         does not have enough amount of data to be read.
   * @throws EOFException the underlying {@link Buffer}
   *         has no data left to read.
   * @throws IOException the underlying {@link Buffer} throws an
   *         error during the I/O operation.
   *
   * @see DataInput#readFully(byte[])
   *
   */
  public void readFully(byte[] copyDataTo)
    throws IOException {
    if (copyDataTo == null) {
      throw new NullPointerException();
    }
   
    readFully(copyDataTo, 0, copyDataTo.length);
  }

  /**
   * Read from the underlying {@link Buffer} and copy the
   * data to the {@code byte} array provided as the parameter
   * {@code copyDataTo}, starting from the offset parameter
   * {@code byteOffset}. The number of {@code byte}s to read
   * is defined via the parameter {@code numOfBytes}. This
   * method forces that the underlying {@link Buffer} must
   * have the amount of data no less than the amount defined
   * by the parameter {@code numOfBytes}. Please note that
   * the behavior of {@link #readFully(byte[], int, int)} is
   * slightly different from that defined in
   * {@link DataInput#readFully(byte[], int, int)}. The
   * difference is that in
   * {@link DataInput#readFully(byte[], int, int)}, if the
   * underlying data source doesn't have the required amount
   * of data to read, it will block until enough data has
   * arrived. In {@link #readFully(byte[], int, int)}, however,
   * it will not block if the underlying {@link Buffer} has
   * insufficient data. Instead, it will throw an
   * {@link InsufficientBufferException} or an {@link EOFException}
   * to reflect this situation. The caller should always catch
   * this exception in the event that there is insufficient data
   * to be read completely. The implementation, though,
   * guarantees that if an {@link InsufficientBufferException}
   * or an {@link EOFException} is thrown, data won't be corrupted, so
   * that the state of the underlying {@link Buffer} remains intact.
   *
   * @param copyDataTo A {@code byte} array that will hold the data read
   *        from the underlying {@link Buffer}. This method guarantees
   *        that {@code copyDataTo} will be filled completely with data
   *        starting at the index specified by {@code byteOffset},
   *        spanning a number of {@code bytes} specified by
   *        {@code numOfBytes}. If not, an
   *        {@link InsufficientBufferException} or an
   *        {@link EOFException} will be thrown.
   * @param byteOffset The starting index at which to read the data.
   * @param numOfBytes the number of {@code byte}s to be read.
   *
   * @throws NullPointerException if {@code copyDataTo} is {@code null}.
   * @throws IllegalArgumentException thrown when one of the following occurs:
   *        <ul>
   *         <li>{@code copyDataTo} is an empty ({@code zero}-length) {@code byte} array.</li>
   *         <li>{@code byteOffset} is negative.</li>
   *         <li>{@code numOfBytes} is negative.</li>
   *         <li>{@code numOfBytes} is zero.</li>
   *         <li>{@code byteOffset} and {@code numOfBytes} went out of bounds
   *           so that {@code byteOffset + numOfBytes} is greater than the
   *           number of {@code byte}s the array {@code copyDataTo} can hold.</li>
   *        </ul>
   * @throws InsuffientBufferException the underlying {@link Buffer}
   *         does not have enough amount of data (specified by
   *         {@code numOfBytes}, to read).
   * @throws EOFException the underlying {@link Buffer} has no data
   *         left to read.
   * @throws IOException the underlying {@link Buffer} throws an
   *         error during the I/O operation.
   *        
   * @see DataInput#readFully(byte[], int, int)
   */
  public void readFully(byte[] copyDataTo, int byteOffset, int numOfBytes)
    throws NullPointerException,
         IllegalArgumentException,
         IOException {
    if (copyDataTo == null) {
      throw new NullPointerException();
    }
   
    if (copyDataTo.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 > copyDataTo.length) {
      throw new IllegalArgumentException();
    }

    long readableBits = buffer.readable();
   
    if (readableBits == 0) {
      throw new EOFException();
    }
   
    int readableBytes = (int )(readableBits / Byte.SIZE);
   
    if (readableBytes < numOfBytes) {
      throw new InsufficientBufferException();
    }
   
    read(copyDataTo, byteOffset, numOfBytes);
  }

  /**
   * <p>Skips a specified number of bytes, specified by the
   * parameter {@code numOfBytesToSkip} to fast-forward
   * the read cursor of the underlying {@link Buffer}. It
   * is possible that the actual number of {@code byte}s
   * skipped may be smaller than the designated {@code numOfBytes}
   * parameter. Therefore, the actual number of {@code byte}s
   * skipped is shown in the return value.</p>
   *
   * <p>This method is just a delegate to {@link #skip(long)}. This
   * method exists because we need it to be compliant with
   * {@link DataInput}.</p>
   *
   * @param numOfBytes The number of {@code byte}s planned to skip. If
   *       {@code numOfBytes} is {@code zero}. This method does nothing and
   *        returns {@code zero}.
   *
   * @return The actual number of bytes skipped. The position of the
   *        {@link Buffer} is modified as a result of the skip operation,
   *         if the return value is {@code zero}, the backing {@link Buffer}
   *         is not modified.
   *
   * @throws IllegalArgumentException thrown when one of the following occurs:
   *        <ul>
   *         <li>{@code numOfBytes} is negative.</li>
   *         <li>{@code numOfBytes} is zero.</li>
   *        </ul>
   * @throws IOException if the underlying {@link Buffer} operation throws
   *        an error.
   *
   * @see #skip(long)
   * @see DataInput#skipBytes(int)
   */
  public int skipBytes(int numOfBytes)
    throws IOException {
    return (int )skip(numOfBytes);
  }
 
  /**
   * Reads the next {@code bit} from the underlying
   * {@link Buffer} and return the value as an {@code int}.
   * Possible values for the return value are {@code 0 zero}
   * or {@code 1 one}. Unlike it's counterpart method
   * {@link Buffer#read()}, if there is an {@link Buffer#EOF}
   * situation this method will not return {@link Buffer#EOF},
   * but instead throw an {@link EOFException}. This is because
   * we want to keep the method behavior compliant with other
   * {@link DataInput} methods.
   *
   * @return {@code 1 one} if the underlying {@link Buffer} returns
   *         a {@code bit} value of {@code 1 one}, otherwise, {@code 0 zero}
   *         if the underlying {@link Buffer} returns a {@code bit}
   *         value of {@code 0 zero}
   *
   * @throws EOFException Buffer{@link #read()} method returns
   *        {@link Buffer#EOF}.
   * @throws IOException other error occurs while the underlying
   *         {@link Buffer} is carrying out the I/O operation.
   */
  public int readBit()
    throws IOException {
    int data = buffer.read();
   
    if (data == Buffer.EOF) {
      throw new EOFException();
    }
   
    return data;
  }

  /**
   * Reads a {@code byte} of data and returns the data
   * as a representation of a {@code boolean}. The
   * returned value is a {@code boolean}, where {@code true}
   * means that the data is non-{@code zero}, and
   * {@code false} means that the data is {@code zero}.
   *
   * @return A {@code boolean} value where {@code true}
   *         means the {@code byte} data is non-{@code zero},
   *         and {@code false} means the {@code byte} data
   *         is zero.
   *        
   * @throws EOFException the underlying {@link Buffer} has
   *         no data to read.
   * @throws InsufficientBufferException the underlying {@link Buffer}
   *         has data, but it is not enough to form a complete
   *         {@code byte} value to return.
   * @throws IOException the underlying {@link Buffer} has
   *         an error during the I/O operation.
   *        
   * @see DataInput#readBoolean()
   */
  public boolean readBoolean()
    throws IOException {
    byte[] data = new byte[Byte.SIZE / 8];
    readFully(data);
    boolean isNonZero = data[0] != 0;
    return isNonZero;
  }
 
  /**
   * <p>Reads a {@code bit} instead of {@code byte} of data and
   * returns the data as a representation of a {@code boolean}.
   * The return value is such that {@code true} means the {@code bit}
   * value is {@code 1 one}, and {@code false} means the {@code bit}
   * value is {@code 0 zero}.</p>
   *
   * <p>This method is merely a delegate to {@link Buffer#readAsBoolean()}.</p>
   *
   * @return {@code true} if the next {@code bit} being read
   *         is {@code 1 one}, and otherwise, {@code false} if
   *         the next {@code bit} being read is {@code 0 zero}.
   *
   * @throws EOFException the underlying {@link Buffer} has
   *         no data to read.
   * @throws InsufficientBufferException the underlying {@link Buffer}
   *         has data, but it is not enough to form a complete
   *         {@code byte} value to return.
   * @throws IOException the underlying {@link Buffer} has
   *         an error during the I/O operation.
   *        
   * @see Buffer#readAsBoolean()
   */
  public boolean readBooleanAsBit()
    throws IOException {
    return buffer.readAsBoolean();
  }

  /**
   * Reads a {@code byte} of data and returns the data.
   *
   * @return A {@code byte} representing the data.
   *
   * @throws EOFException the underlying {@link Buffer} has
   *         no data to read.
   * @throws InsufficientBufferException the underlying
   *         {@link Buffer} has data, but it is not enough
   *         to form a complete {@code byte} value to return.
   * @throws IOException the underlying {@link Buffer} has
   *         an error during the I/O operation.
   *
   * @see DataInput#readByte()
   *
   */
  public byte readByte()
    throws IOException {
    byte[] data = new byte[Byte.SIZE / 8];
    readFully(data);
    return data[0];
  }

  /**
   * Reads a {@code byte} of data from the underlying {@link Buffer}
   * but returns the value as a representation of an {@code unsigned byte}.
   * Java does not have the {@code unsigned} keyword. The return values
   * of such will all default to {@code int} numbers, bearing no
   * negative values.
   *
   * @return An {@code unsigned byte} value between {@code 0x00}
   *        and {@code 0xff}. No negatives.
   *
   * @throws EOFException the underlying {@link Buffer} has
   *         no data to read.
   * @throws InsufficientBufferException the underlying
   *         {@link Buffer} has data, but it is not enough
   *         to form a complete {@code byte} value to return.
   * @throws IOException the underlying {@link Buffer} has
   *         an error during the I/O operation.
   *        
   * @see DataInput#readUnsignedByte()
   */
  public int readUnsignedByte()
    throws IOException {
    byte byteValue = readByte();
    int unsignedValue = ((int )byteValue) & 0xff;
    return unsignedValue;
   
  }

  /**
   * Reads a {@code 16 bit integer} of data from the underlying {@link Buffer}.
   *
   * @return A {@code short} representing the data.
   *
   * @throws EOFException the underlying {@link Buffer} has
   *         no data to read.
   * @throws InsufficientBufferException the underlying
   *         {@link Buffer} has data, but it is not enough
   *         to form a complete {@code short} value to return.
   * @throws IOException the underlying {@link Buffer} has
   *         an error during the I/O operation.
   *        
   * @see DataInput#readShort()
   *
   */
  public short readShort()
    throws IOException {
    return buffer.read(Short.SIZE, false, Buffer.BIG_ENDIAN).shortValue();
  }

  /**
   * Reads a {@code 16 bit integer} of data from the underlying {@link Buffer}
   * but returns the value as a representation of an {@code unsigned short}.
   * Java does not have the {@code unsigned} keyword. The return values
   * of such will all default to {@code int} numbers, bearing no
   * negative values.
   *
   * @return An {@code unsigned short} value between {@code 0x0000}
   *         and {@code 0xffff}. No negatives.
   *
   * @throws EOFException the underlying {@link Buffer} has
   *         no data to read.
   * @throws InsufficientBufferException the underlying
   *         {@link Buffer} has data, but it is not enough
   *         to form a complete {@code short} value to return.
   * @throws IOException the underlying {@link Buffer} has
   *         an error during the I/O operation.
   *        
   * @see DataInput#readUnsignedShort()
   *
   */
  public int readUnsignedShort()
    throws IOException {
    short shortValue = readShort();
    int unsignedValue = ((int )shortValue) & 0xffff;
    return unsignedValue;
  }

  /**
   * Returns a {@code unicode char} of data from the underlying {@link Buffer}.
   *
   * @return A {@code unicode char} representing the data.
   *
   * @throws EOFException the underlying {@link Buffer} has
   *         no data to read.
   * @throws InsufficientBufferException the underlying
   *         {@link Buffer} has data, but it is not enough
   *         to form a complete {@code char} value to return.
   * @throws IOException the underlying {@link Buffer} has
   *         an error during the I/O operation.
   *
   * @see DataInput#readChar()
   *
   */
  public char readChar()
    throws IOException {
    int unsignedValue = readUnsignedShort();
    return (char )unsignedValue;
  }

  /**
   * Reads a {@code 32 bit integer} of data from the underlying {@link Buffer}.
   *
   * @return An {@code int} representing the data.
   *
   * @throws EOFException the underlying {@link Buffer} has
   *         no data to read.
   * @throws InsufficientBufferException the underlying
   *         {@link Buffer} has data, but it is not enough
   *         to form a complete {@code int} value to return.
   * @throws IOException the underlying {@link Buffer} has
   *         an error during the I/O operation.
   *        
   * @see DataInput#readInt()
   *
   */
  public int readInt()
    throws IOException {
    return buffer.read(Integer.SIZE, false, Buffer.BIG_ENDIAN).intValue();
  }
 
  /**
   * Reads an arbitrary {@code int} from the underlying {@link Buffer}.
   * Java has a hard limit on the number of {@code bit}s an {@code int}
   * can hold, so the parameter {@code numOfBits} should be
   * {@code > 0 zero} and {@code <=} {@link Integer#SIZE}. The return
   * value is a {@code signed integer}.
   *
   * @param numOfBits The number of {@code bits} to read. Must be
   *        {@code > 0 zero} and {@code <=} {@link Integer#SIZE}.
   *       
   * @return An {@code signed int} value that represents the data
   *         spanning {@code numOfBits} bits.
   *        
   * @throws IllegalArgumentException one of the following occurs:
   *        <ul>
   *         <li>{@code numOfBits} is negative.</li>
   *         <li>{@code numOfBits} is zero.</li>
   *         <li>{@code numOfBits} is greater than {@link Integer#SIZE}</li>
   *        </ul>
   * @throws EOFException the underlying {@link Buffer} has
   *         no data to read.
   * @throws InsufficientBufferException the underlying
   *         {@link Buffer} has data, but its size is less than
   *         the number of bits specified by the parameter
   *         {@code numOfBits}.
   * @throws IOException the underlying {@link Buffer} has
   *         an error during the I/O operation.
   *        
   */
  public int readInt(int numOfBits)
    throws IllegalArgumentException,
         IOException {
    if (numOfBits > Integer.SIZE) {
      throw new IllegalArgumentException();
    }
   
    return (int )readLong(numOfBits);
  }
 
  /**
   * Reads a {@code 32 bit integer} of data from the underlying {@link Buffer}
   * but returns the value as a representation of an {@code unsigned int}.
   * Java does not have the {@code unsigned} keyword, and because all Java's
   * primitive types are signed (of course except {@code char}), we can only
   * use a {@code long} to hold the values returned. The returned value will
   * bear no negative values.
   *
   * @return An {@code unsigned int} value between {@code 0x00000000}
   *         and {@code 0xffffffffL}.
   *        
   * @throws EOFException the underlying {@link Buffer} has
   *         no data to read.
   * @throws InsufficientBufferException the underlying
   *         {@link Buffer} has data, but it is not enough
   *         to form a complete {@code int} value to return.
   * @throws IOException the underlying {@link Buffer} has
   *         an error during the I/O operation.
   */
  public long readUnsignedInt()
    throws IOException {
    int intValue = readInt();
    long unsignedValue = ((long )intValue) & 0xffffffffL;
    return unsignedValue;
  }
 
  /**
   * Read an arbitrary {@code int} from the underlying {@link Buffer}
   * but returns the value as a representation of an {@code unsigned int}.
   * Java does not have the {@code unsigned} keyword, and because all Java's
   * primitive types are signed (of course except {@code char}), we can only
   * use a {@code long} to hold the values returned. Java also has a hard
   * limit on the number of {@code bit}s an {@code int} can hold, so the
   * parameter {@code numOfBits} should be {@code > 0 zero} and {@code <=}
   * {@link Integer#SIZE}. The return value is a {@code unsigned integer},
   * bearing no negative values.
   *
   * @param numOfBits The number of {@code bits} to read. Must be
   *       {@code > 0 zero} and {@code <=} {@link Integer#SIZE}.
   *
   * @return An {@code unsigned int} value that represents the data
   *         spanning {@code numOfBits} bits.
   *        
   * @throws IllegalArgumentException one of the following occurs:
   *        <ul>
   *         <li>{@code numOfBits} is negative.</li>
   *         <li>{@code numOfBits} is zero.</li>
   *         <li>{@code numOfBits} is greater than {@link Integer#SIZE}</li>
   *        </ul>
   * @throws EOFException the underlying {@link Buffer} has
   *         no data to read.
   * @throws InsufficientBufferException the underlying
   *         {@link Buffer} has data, but its size is less than
   *         the number of bits specified by the parameter
   *         {@code numOfBits}.
   * @throws IOException the underlying {@link Buffer} has
   *         an error during the I/O operation.
   */
  public long readUnsignedInt(int numOfBits)
    throws IllegalArgumentException,
         IOException {
    if (numOfBits > Integer.SIZE) {
      throw new IllegalArgumentException();
    }
   
    BigInteger unsignedValue = readUnsignedLong(numOfBits);
    return unsignedValue.longValue();
  }
 
  public long readVarInt()
    throws IOException {
    int varIntSerialId = (int )readUnsignedInt(RT.VAR_INT_SHIFT);
    int numOfDataBits = RT.INT62_DATA_LENGTH;
   
    if (varIntSerialId == RT.INT14_SERIAL_ID) {
      numOfDataBits = RT.INT14_DATA_LENGTH;
    } else if (varIntSerialId == RT.INT30_SERIAL_ID) {
      numOfDataBits = RT.INT30_DATA_LENGTH;
    } else if (varIntSerialId == RT.INT62_SERIAL_ID) {
      numOfDataBits = RT.INT62_DATA_LENGTH;
    }

    BigInteger varInt = buffer.read(numOfDataBits, false, Buffer.BIG_ENDIAN);
    return varInt.longValue();
  }
 
  public long readUnsignedVarInt()
    throws IOException {
    int varIntSerialId = (int )readUnsignedInt(RT.VAR_INT_SHIFT);
    int numOfDataBits = RT.INT6_DATA_LENGTH;
   
    if (varIntSerialId == RT.INT14_SERIAL_ID) {
      numOfDataBits = RT.INT14_DATA_LENGTH;
    } else if (varIntSerialId == RT.INT30_SERIAL_ID) {
      numOfDataBits = RT.INT30_DATA_LENGTH;
    } else if (varIntSerialId == RT.INT62_SERIAL_ID) {
      numOfDataBits = RT.INT62_DATA_LENGTH;
    }

    BigInteger varInt = buffer.read(numOfDataBits, true, Buffer.BIG_ENDIAN);
    return varInt.longValue();
  }
 
  /**
   * Reads a {@code 64 bit integer} of data from the underlying {@link Buffer}.
   *
   * @return A {@code long} representing the data.
   *
   * @throws EOFException the underlying {@link Buffer} has
   *         no data to read.
   * @throws InsufficientBufferException the underlying
   *         {@link Buffer} has data, but it is not enough
   *         to form a complete {@code long} value to return.
   * @throws IOException the underlying {@link Buffer} has
   *         an error during the I/O operation.
   *        
   * @see DataInput#readLong()
   *
   */
  public long readLong()
    throws IOException {
    return buffer.read(Long.SIZE, false, Buffer.BIG_ENDIAN).longValue();
  }
 
  /**
   * Reads an arbitrary {@code long} from the underlying {@link Buffer}.
   * Java has a hard limit on the number of {@code bit}s a {@code long}
   * can hold, so the parameter {@code numOfBits} should be
   * {@code > 0 zero} and {@code <=} {@link Long#SIZE}. The return value
   * is a {@code signed long}.
   *
   * @param numOfBits The number of {@code bit}s to read. Must be >
   *       {@code > 0 zero} and {@code <=} {@link Long#SIZE}.
   *
   * @return A {@code signed long} value that represents the data
   *        spanning {@code numOfBits} bits.
   *
   * @throws IllegalArgumentException one of the following occurs:
   *        <ul>
   *         <li>{@code numOfBits} is negative.</li>
   *         <li>{@code numOfBits} is zero.</li>
   *         <li>{@code numOfBits} is greater than {@link Long#SIZE}</li>
   *        </ul>
   * @throws EOFException the underlying {@link Buffer} has
   *         no data to read.
   * @throws InsufficientBufferException the underlying
   *         {@link Buffer} has data, but its size is less than
   *         the number of bits specified by the parameter
   *         {@code numOfBits}.
   * @throws IOException the underlying {@link Buffer} has
   *         an error during the I/O operation.
   *        
   */
  public long readLong(int numOfBits)
    throws IllegalArgumentException,
         IOException {
    if (numOfBits > Long.SIZE) {
      throw new IllegalArgumentException();
    }
   
    return buffer.read(numOfBits, false, Buffer.BIG_ENDIAN).longValue();
  }
 
  /**
   * Reads a {@code 64 bit integer} of data from the underlying {@link Buffer}
   * but returns the value as a representation of an {@code unsigned long}.
   * Java does not have the {@code unsigned} keyword, and because all Java's
   * primitive types are signed (exception {@code char}), we can only use
   * a {@link BigInteger} to hold the values returned. The return value will
   * bear no negative values.
   *
   * @return An {@code unsigned long} value between {@code 0x0000000000000000L}
   * and {@code 0xffffffffffffffffLL}
   *
   * @throws EOFException the underlying {@link Buffer} has
   *         no data to read.
   * @throws InsufficientBufferException the underlying
   *         {@link Buffer} has data, but it is not enough
   *         to form a complete {@code long} value to return.
   * @throws IOException the underlying {@link Buffer} has
   *         an error during the I/O operation.
   */
  public BigInteger readUnsignedLong()
    throws IOException {
    long longValue = readLong();
    BigInteger signedValue = BigInteger.valueOf(longValue);
    BigInteger unsignedValue = signedValue.and(UNSIGNED_LONG_MASK);
    return unsignedValue;
  }
 
  /**
   * Reads an arbitrary {@code long} from the underlying {@link Buffer}
   * but returns the value as a representation of an {@code unsigned long}.
   * Java does not have the {@code unsigned} keyword, and because all Java's
   * primitive types are signed (except {@code char}), we can only use a
   * {@code long} to hold the values returned. Java also has a hard limit
   * on the number of {@code bit}s a {@code long} can hold, so the
   * parameter {@code numOfBits} would be {@code > 0 zero} and {@code <=}
   * {@link Long#SIZE}. The return value is a {@link BigInteger}, bearing
   * no negative values.
   *
   * @param numOfBits The number of {@code bits} to read. Must be
   *        {@code > 0 zero} and {@code <=} {@link Long#SIZE}.
   *
   * @return An {@code unsigned long} value that represents the data
   *         spanning {@code numOfBits} bits.
   *
   * @throws IllegalArgumentException one of the following occurs:
   *        <ul>
   *         <li>{@code numOfBits} is negative.</li>
   *         <li>{@code numOfBits} is zero.</li>
   *         <li>{@code numOfBits} is greater than {@link Long#SIZE}</li>
   *        </ul>
   * @throws EOFException the underlying {@link Buffer} has
   *         no data to read.
   * @throws InsufficientBufferException the underlying
   *         {@link Buffer} has data, but its size is less than
   *         the number of bits specified by the parameter
   *         {@code numOfBits}.
   * @throws IOException the underlying {@link Buffer} has
   *         an error during the I/O operation.
   */
  public BigInteger readUnsignedLong(int numOfBits)
    throws IllegalArgumentException,
         IOException {
    if (numOfBits > Long.SIZE) {
      throw new IllegalArgumentException();
    }
   
    return buffer.read(numOfBits, false, Buffer.BIG_ENDIAN);
  }
 
  /**
   * Reads an arbitrary {@code signed} {@link BigInteger} from
   * the underlying {@link Buffer}. There is no limit on the
   * magnitude of the number.
   *
   * @param numOfBits The number of {@code bit}s to read. Must be
   *        a positive value.
   *       
   * @return A {@link BigInteger} value that represents the data
   *         spanning {@code numOfBits} bits.
   *
   * @throws IllegalArgumentException one of the following occurs:
   *        <ul>
   *         <li>{@code numOfBits} is negative.</li>
   *         <li>{@code numOfBits} is zero.</li>
   *        </ul>
   * @throws EOFException the underlying {@link Buffer} has
   *         no data to read.
   * @throws InsufficientBufferException the underlying
   *         {@link Buffer} has data, but its size is less than
   *         the number of bits specified by the parameter
   *         {@code numOfBits}.
   * @throws IOException the underlying {@link Buffer} has
   *         an error during the I/O operation.
   */
  public BigInteger readBigInteger(int numOfBits)
    throws IllegalArgumentException,
         IOException {
    return buffer.read(numOfBits, true, Buffer.BIG_ENDIAN);
  }
 
  public BigInteger readBigInteger()
    throws IOException {
   
    int numOfBits = readInt();
    boolean isNegative = readBooleanAsBit();
    int numOfBitsWithoutSignBit = numOfBits - 1;
    BigInteger finalValue = buffer.read(numOfBitsWithoutSignBit, false, Buffer.BIG_ENDIAN);
   
    if (isNegative) {
      finalValue = finalValue.negate();
    }
   
    return finalValue;
  }
 
  /**
   * Reads an arbitrary {@code bit}s from the underlying {@link Buffer}
   * but returns the value as a representation of an {@code unsigned} {@link BigInteger}.
   * There is no limit on the magnitude of the number, but the
   * parameter {@code numOfBits} must be positive. The return value is a
   * {@link BigInteger}, bearing no negative values.
   *
   * @param numOfBits The number of {@code bits} to read. Must be positive.
   *
   * @return An {@code unsigned} {@link BigInteger} value that represents
   *        the data spanning {@code numOfBits} bits.
   *
   * @throws IllegalArgumentException one of the following occurs:
   *        <ul>
   *         <li>{@code numOfBits} is negative.</li>
   *         <li>{@code numOfBits} is zero.</li>
   *        </ul>
   * @throws EOFException the underlying {@link Buffer} has
   *         no data to read.
   * @throws InsufficientBufferException the underlying
   *         {@link Buffer} has data, but its size is less than
   *         the number of bits specified by the parameter
   *         {@code numOfBits}.
   * @throws IOException the underlying {@link Buffer} has
   *         an error during the I/O operation.
   */
  public BigInteger readUnsignedBigInteger(int numOfBits)
    throws IllegalArgumentException,
         IOException {
    return buffer.read(numOfBits, false, Buffer.BIG_ENDIAN);
  }

  /**
   * Reads a 32 {@code bit} number as a floating number from
   * the underlying {@link Buffer}.
   *
   * @returns A {@code float} representing the 32 bit number.
   *
   * @see DataInput#readFloat()
   */
  public float readFloat()
    throws IOException {
    return Float.intBitsToFloat(readInt());
  }

  /**
   * Reads a 64 {@code bit} number as a double precision number
   * from the underlying {@link Buffer}.
   *
   * @returns A {@code double} representing the 64 bit number.
    *
   * @see DataInput#readDouble()
   */
  public double readDouble()
    throws IOException {
    return Double.longBitsToDouble(readLong());
  }
 
  /**
   * <p>Reads a double-precision number of arbitrary {@code bit}s
   * in size, and returns the number value as a {@link BigDecimal}.
   * This parameter {@code numOfBitsExcludingScale} is the
   * number of integral (unscaled) {@code bit}s that the number needs.
   * There's a 32-{@code bit} {@code integer} value that will
   * be read first. This value indicates the decimal point
   * position (i.e. the scale). The parameter {@code numOfBitsExcludingScale}
   * doesn't  count this 32 {@code bit} integer. Therefore,
   * the actual amount of data read will by four {@code byte}s greater
   * than that specified in {@code numOfBitsExcludingScale}.</p>
   *
   * <p>This method will first read the {@code int} scale value, then
   * read the data chunk specified by {@code numOfBitsExcludingScale},
   * and finally use these two pieces of data to generate the {@link BigDecimal}.</p>
   *
   * <p>This method is atomic, if read operation fails halfway, the
   * {@link Buffer} will revert.</p>
   *
   * @param numOfBitsExcludingScale The number of {@code bit}s to read
   *        to form the integral (unscaled) version of the number. This does
   *        not consider the size of the {@code int} that is needed to contain
   *        the decimal point (scale) information. The actual data read will
   *        be four {@code byte}s bigger as this extra four {@code byte}s contains
   *        the decimal point (scale) info.
   *       
   * @return The {@link BigDecimal} containing the data.
   *
   * @throws IllegalArgumentException thrown when one of the following occurs:
   *        <ul>
   *         <li>{@code numOfBitsExcludingScale} is negative.</li>
   *         <li>{@code numOfBitsExcludingScale} is zero.</li>
   *        </ul>
   * @throws EOFException the underlying {@link Buffer} has
   *         no data to read.
   * @throws InsufficientBufferException the underlying
   *         {@link Buffer} has data, but its size is less than
   *         the number of bits specified by the parameter
   *         {@code numOfBitsExcludingScale} plus the four {@code byte}s
   *         needed to hold the decimal point (scale) info.
   * @throws IOException the underlying {@link Buffer} has
   *         an error during the I/O operation.
   */
  public BigDecimal readBigDecimal(int numOfBitsExcludingScale)
    throws IllegalArgumentException,
         IOException {
   
    if (numOfBitsExcludingScale <= 0) {
      throw new IllegalArgumentException();
    }
   
    int scaleValue = readInt();
    BigInteger unscaledValue = readBigInteger(numOfBitsExcludingScale);
    return new BigDecimal(unscaledValue, scaleValue);
  }
 
  public BigDecimal readBigDecimal()
    throws IOException {
    int scaleValue = readInt();
    int numOfBits = readInt();
    boolean isNegative = readBooleanAsBit();
    int numOfBitsWithoutSign = numOfBits - 1;
    BigInteger unscaledValue = buffer.read(numOfBitsWithoutSign, false, Buffer.BIG_ENDIAN);
   
    if (isNegative) {
      unscaledValue = unscaledValue.negate();
    }
   
    return new BigDecimal(unscaledValue, scaleValue);
  }

  /**
   * @see DataInput#readLine()
   */
  public String readLine()
    throws IOException {
    buffer.mark();
   
    int numOfBytesToRead = 0;
   
    while (true) {
      int currentChar = read();
     
      if (currentChar == -1) {
        break;
      }
     
      numOfBytesToRead = numOfBytesToRead + 1;
     
      if (currentChar == '\n') {
        break;
      } else if (currentChar == '\r') {
        int nextChar = read();
       
        if (nextChar == -1) {
          throw new InsufficientBufferException();
        } else if (nextChar == '\n') {
          numOfBytesToRead = numOfBytesToRead + 1;
        } else {
          break;
        }
      }
    }
   
    buffer.reset();
    buffer.mark();
   
    byte[] charArray = new byte[numOfBytesToRead];
    readFully(charArray);
    String line = new String(charArray, Charset.forName("utf-8"));
    return line.trim();
  }
 
  public String readRawUTF(int numOfBytes)
    throws IOException {
    byte[] byteBuffer = new byte[numOfBytes];
    readFully(byteBuffer);
   
    StringBuilder builder = new StringBuilder();
    int byteCursor = 0;
   
    while (byteCursor < numOfBytes) {
      int firstByte = byteBuffer[byteCursor] & 0xff;
      int flag = firstByte >> 4;
   
      if (flag == 0 || flag == 1 || flag == 2 || flag == 3 ||
        flag == 4 || flag == 5 || flag == 6 || flag == 7) {
       
      } else if (flag == 12 || flag == 13) {
        if (byteCursor + 1 > numOfBytes) {
          throw new UTFDataFormatException();
        }
       
        int ch = firstByte;
        builder.append((char )ch);
       
        byteCursor = byteCursor + 1;
      } else if (flag == 14) {
        if (byteCursor + 2 > numOfBytes) {
          throw new UTFDataFormatException();
        }
       
        int secondByte = byteBuffer[byteCursor + 1] & 0xff;
       
        if ((secondByte & 0xc0) != 0x80) {
          throw new UTFDataFormatException();
        }
       
        int ch = (firstByte & 0x1f) << 6;
        ch = ch | (secondByte & 0x3f);
        builder.append((char )ch);
       
        byteCursor = byteCursor + 2;
      } else {
        if (byteCursor + 3 > numOfBytes) {
          throw new UTFDataFormatException();
        }
       
        int secondByte = byteBuffer[byteCursor + 1] & 0xff;
        int thirdByte = byteBuffer[byteCursor + 2] & 0xff;
       
        if ((secondByte & 0xc0) != 0x80) {
          throw new UTFDataFormatException();
        }
       
        if ((thirdByte & 0xc0) != 0x80) {
          throw new UTFDataFormatException();
        }
       
        int ch = (firstByte & 0x0f) << 12;
        ch = ch | ((secondByte & 0x3f) << 6);
        ch = ch | (thirdByte & 0x3f);
       
        builder.append((char )ch);
       
        byteCursor = byteCursor + 3;
      }
    }

    return builder.toString();
  }

  /**
   * @see DataInput#readUTF()
   */
  public String readUTF()
    throws IOException {
    int numOfBytes = readUnsignedShort();
    String utfString = readRawUTF(numOfBytes);
    return utfString;
  }

  public void writeBoolean(boolean booleanValue)
    throws IOException {
    if (booleanValue) {
      write(1);
    } else {
      write(0);
    }
  }
 
  public void writeBooleanAsBit(boolean booleanValue)
    throws IOException {
    buffer.writeAsBoolean(booleanValue);
  }

  @Override
  public void writeByte(int byteValue)
    throws IllegalArgumentException,
         IOException {
    if (byteValue < Byte.MIN_VALUE) {
      throw new IllegalArgumentException();
    }
   
    if (byteValue > Byte.MAX_VALUE) {
      throw new IllegalArgumentException();
    }
   
    int data = byteValue & 0xff;
   
    write(data);
  }

  @Override
  public void writeShort(int shortValue)
    throws IllegalArgumentException,
         IOException {
    if (shortValue < Short.MIN_VALUE) {
      throw new IllegalArgumentException();
    }
   
    if (shortValue > Short.MAX_VALUE) {
      throw new IllegalArgumentException();
    }
   
    buffer.write(Short.SIZE, BigInteger.valueOf(shortValue), Buffer.BIG_ENDIAN);
  }

  @Override
  public void writeChar(int charValue)
    throws IllegalArgumentException,
         IOException {
    if (charValue < Character.MIN_VALUE) {
      throw new IllegalArgumentException();
    }
   
    if (charValue > Character.MAX_VALUE) {
      throw new IllegalArgumentException();
    }
   
    buffer.write(Character.SIZE, BigInteger.valueOf(charValue), Buffer.BIG_ENDIAN);
  }

  @Override
  public void writeInt(int intValue)
    throws IOException {
    buffer.write(Integer.SIZE, BigInteger.valueOf(intValue), Buffer.BIG_ENDIAN);
  }
 
  public void writeInt(int intValue, int numOfBits)
    throws IllegalArgumentException,
         IOException {
    if (numOfBits > Integer.SIZE) {
      throw new IllegalArgumentException();
    }
   
    writeLong(intValue, numOfBits);
  }
 
  public int writeVarInt(long value)
    throws IllegalArgumentException,
         IOException {
   
    if (value < RT.INT62_MIN || value > RT.INT62_MAX_SIGNED) {
      throw new IllegalArgumentException();
    }
   
    int numOfDataBits = RT.INT6_DATA_LENGTH;
   
    if (value < RT.INT30_MIN || value > RT.INT30_MAX_SIGNED) {
      numOfDataBits = RT.INT62_DATA_LENGTH;
    } else if (value < RT.INT14_MIN || value > RT.INT14_MAX_SIGNED) {
      numOfDataBits = RT.INT30_DATA_LENGTH;
    } else if (value < RT.INT6_MIN || value > RT.INT62_MAX_SIGNED) {
      numOfDataBits = RT.INT14_DATA_LENGTH;
    }
   
    buffer.write(numOfDataBits, BigInteger.valueOf(value), Buffer.BIG_ENDIAN);
   
    return numOfDataBits;
  }
 
  public int writeUnsignedVarInt(long value)
    throws IllegalArgumentException,
         IOException {
    if (value < 0) {
      throw new IllegalArgumentException();
    }
   
    if (value > RT.INT62_MAX_UNSIGNED) {
      throw new IllegalArgumentException();
    }
   
    int numOfDataBits = RT.INT6_DATA_LENGTH;
   
    if (value > RT.INT30_MAX_UNSIGNED) {
      numOfDataBits = RT.INT62_DATA_LENGTH;
    } else if (value > RT.INT14_MAX_UNSIGNED) {
      numOfDataBits = RT.INT30_DATA_LENGTH;
    } else if (value > RT.INT6_MAX_UNSIGNED) {
      numOfDataBits = RT.INT14_DATA_LENGTH;
    }

    buffer.write(numOfDataBits, BigInteger.valueOf(value), Buffer.BIG_ENDIAN);
   
    return numOfDataBits;
  }

  @Override
  public void writeLong(long longValue)
    throws IOException {
    buffer.write(Long.SIZE, BigInteger.valueOf(longValue), Buffer.BIG_ENDIAN);
  }
 
  public void writeLong(long longValue, int numOfBits)
    throws IllegalArgumentException,
         IOException {
    if (numOfBits > Long.SIZE) {
      throw new IllegalArgumentException();
    }
   
    buffer.write(numOfBits, BigInteger.valueOf(longValue), Buffer.BIG_ENDIAN);
  }
 
  public void writeBigInteger(BigInteger bigIntValue, int numOfBits)
    throws IllegalArgumentException,
         IOException {
    buffer.write(numOfBits, bigIntValue, Buffer.BIG_ENDIAN);
  }
 
  public long writeBigInteger(BigInteger bigIntValue)
    throws NullPointerException,
        IOException {
   
    if (bigIntValue == null) {
      throw new NullPointerException();
    }
   
    int numOfBitsWithoutSignBit = bigIntValue.bitLength();
    int numOfBits = numOfBitsWithoutSignBit + 1;
    writeInt(numOfBits);
   
    if (bigIntValue.signum() == -1) {
      writeBooleanAsBit(true);
    } else {
      writeBooleanAsBit(false);
    }
   
    buffer.write(numOfBitsWithoutSignBit, bigIntValue, Buffer.BIG_ENDIAN);
    return Integer.SIZE + numOfBits;
  }

  @Override
  public void writeFloat(float floatValue) throws IOException {
    writeInt(Float.floatToIntBits(floatValue));
  }

  @Override
  public void writeDouble(double doubleValue) throws IOException {
    writeLong(Double.doubleToLongBits(doubleValue));
  }
 
  public long writeBigDecimal(BigDecimal bigDecValue, int numOfBitsExcludingScale)
    throws NullPointerException,
           IllegalArgumentException,
           IOException {
   
    if (bigDecValue == null) {
      throw new NullPointerException();
    }
   
    if (numOfBitsExcludingScale <= 0) {
      throw new IllegalArgumentException();
    }
   
    int scale = bigDecValue.scale();
    writeInt(scale);
    BigInteger unscaledValue = bigDecValue.unscaledValue();
    long written = writeBigInteger(unscaledValue);
    return written + Integer.SIZE;
  }
 
  public long writeBigDecimal(BigDecimal bigDecValue)
    throws NullPointerException,
         IOException {
    if (bigDecValue == null) {
      throw new NullPointerException();
    }
   
    int scale = bigDecValue.scale();
    writeInt(scale);
    BigInteger unscaledValue = bigDecValue.unscaledValue();
    int numOfBitsWithoutSign = unscaledValue.bitLength();
    int numOfBits = numOfBitsWithoutSign + 1;
    writeInt(numOfBits);
   
    if (unscaledValue.signum() == -1) {
      writeBooleanAsBit(true);
    } else {
      writeBooleanAsBit(false);
    }
   
    buffer.write(numOfBitsWithoutSign, unscaledValue, Buffer.BIG_ENDIAN);
    return Integer.SIZE + Integer.SIZE + numOfBits;
  }
 
  @Override
  public void writeBytes(String stringValue)
    throws NullPointerException,
         IllegalArgumentException,
         IOException {
    if (stringValue == null) {
      throw new NullPointerException();
    }
   
    if (stringValue.isEmpty()) {
      throw new IllegalArgumentException();
    }
   
    for (int charIndex = 0; charIndex < stringValue.length(); charIndex = charIndex + 1) {
      int charValue = stringValue.charAt(charIndex);
      write((byte )charValue);
    }
  }

  @Override
  public void writeChars(String stringValue)
    throws IOException {
    if (stringValue == null) {
      throw new NullPointerException();
    }
   
    if (stringValue.isEmpty()) {
      throw new IllegalArgumentException();
    }
   
    for (int charIndex = 0; charIndex < stringValue.length(); charIndex = charIndex + 1) {
      int charValue = stringValue.charAt(charIndex);
      writeChar(charValue);
    }
  }

  @Override
  public void writeUTF(String stringValue)
    throws NullPointerException,
         IllegalArgumentException,
         IOException {

    if (stringValue == null) {
      throw new NullPointerException();
    }
   
    if (stringValue.isEmpty()) {
      throw new IllegalArgumentException();
    }
   
    int numOfBytes = 0;
   
    for (int charIndex = 0; charIndex < stringValue.length(); charIndex = charIndex + 1) {
      int charValue = stringValue.charAt(charIndex);
     
      if ((charValue >= 0x0001) && (charValue <= 0x007f)) {
        numOfBytes = numOfBytes + 1;
      } else if (charValue > 0x07ff) {
        numOfBytes = numOfBytes + 3;
      } else {
        numOfBytes = numOfBytes + 2;
      }
    }
   
    if (numOfBytes > 0xffff) {
      throw new UTFDataFormatException();
    }
   
    writeShort((short )numOfBytes);
    writeRawUTF(stringValue, numOfBytes);
  }
 
  public void writeRawUTF(String stringValue, int numOfBytes)
    throws NullPointerException,
         IllegalArgumentException,
         IOException {
   
    if (stringValue == null) {
      throw new NullPointerException();
    }
   
    if (stringValue.isEmpty()) {
      throw new IllegalArgumentException();
    }
   
    byte[] byteArray = new byte[numOfBytes];
    int byteCursor = 0;
   
    for (int charIndex = 0; charIndex < stringValue.length(); charIndex = charIndex + 1) {
      int charValue = stringValue.charAt(charIndex);
     
      if ((charValue >= 0x0001) && (charValue <= 0x007f)) {
        byteArray[byteCursor++] = (byte )charValue;
      } else if ((charValue > 0x07ff)) {
        byteArray[byteCursor++] = (byte )(0xe0 | ((charValue >> 12) & 0x0f));
        byteArray[byteCursor++] = (byte )(0x80 | ((charValue >> 6) & 0x3f));
        byteArray[byteCursor++] = (byte )(0x80 | ((charValue >> 0) & 0x3f));
      } else {
        byteArray[byteCursor++] = (byte )(0xc0 | ((charValue >> 6) & 0x1f));
        byteArray[byteCursor++] = (byte )(0x80 | ((charValue >> 0) & 0x1f));
      }
    }
   
    write(byteArray);
  }
}
TOP

Related Classes of com.peterhi.runtime.BufferHelper

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.