package com.sleepycat.bind.tuple;
import com.sleepycat.util.FastInputStream;
import com.sleepycat.util.UtfOps;
import de.ovgu.cide.jakutil.*;
/** 
 * An <code>InputStream</code> with <code>DataInput</code>-like methods for
 * reading tuple fields. It is used by <code>TupleBinding</code>.
 * <p>
 * This class has many methods that have the same signatures as methods in the{@link java.io.DataInput} interface. The reason this class does not implement{@link java.io.DataInput} is because it would break the interface contract
 * for those methods because of data format differences.
 * </p>
 * <p>
 * Signed numbers are stored in the buffer in MSB (most significant byte first)
 * order with their sign bit (high-order bit) inverted to cause negative numbers
 * to be sorted first when comparing values as unsigned byte arrays, as done in
 * a database. Unsigned numbers, including characters, are stored in MSB order
 * with no change to their sign bit.
 * </p>
 * <p>
 * Strings and character arrays are stored either as a fixed length array of
 * unicode characters, where the length must be known by the application, or as
 * a null-terminated UTF byte array.
 * </p>
 * <ul>
 * <li>Null strings are UTF encoded as { 0xFF }, which is not allowed in a
 * standard UTF encoding. This allows null strings, as distinct from empty or
 * zero length strings, to be represented in a tuple. Using the default
 * comparator, null strings will be ordered last.</li>
 * <li>Zero (0x0000) character values are UTF encoded as non-zero values, and
 * therefore embedded zeros in the string are supported. The sequence { 0xC0,
 * 0x80 } is used to encode a zero character. This UTF encoding is the same one
 * used by native Java UTF libraries. However, this encoding of zero does impact
 * the lexicographical ordering, and zeros will not be sorted first (the natural
 * order) or last. For all character values other than zero, the default UTF
 * byte ordering is the same as the Unicode lexicographical character ordering.</li>
 * </ul>
 * <p>
 * Floats and doubles are stored in standard Java integer-bit representation
 * (IEEE 754). Non-negative numbers are correctly ordered by numeric value.
 * However, negative numbers are not correctly ordered; therefore, if you use
 * negative floating point numbers in a key, you'll need to implement and
 * configure a custom comparator to get correct numeric ordering.
 * </p>
 * @author Mark Hayes
 */
public class TupleInput extends FastInputStream {
  /** 
 * Creates a tuple input object for reading a byte array of tuple data. A
 * reference to the byte array will be kept by this object (it will not be
 * copied) and therefore the byte array should not be modified while this
 * object is in use.
 * @param bufferis the byte array to be read and should contain data in tuple
 * format.
 */
  public TupleInput(  byte[] buffer){
    super(buffer);
  }
  /** 
 * Creates a tuple input object for reading a byte array of tuple data at a
 * given offset for a given length. A reference to the byte array will be
 * kept by this object (it will not be copied) and therefore the byte array
 * should not be modified while this object is in use.
 * @param bufferis the byte array to be read and should contain data in tuple
 * format.
 * @param offsetis the byte offset at which to begin reading.
 * @param lengthis the number of bytes to be read.
 */
  public TupleInput(  byte[] buffer,  int offset,  int length){
    super(buffer,offset,length);
  }
  /** 
 * Creates a tuple input object from the data contained in a tuple output
 * object. A reference to the tuple output's byte array will be kept by this
 * object (it will not be copied) and therefore the tuple output object
 * should not be modified while this object is in use.
 * @param outputis the tuple output object containing the data to be read.
 */
  public TupleInput(  TupleOutput output){
    super(output.getBufferBytes(),output.getBufferOffset(),output.getBufferLength());
  }
  /** 
 * Reads a null-terminated UTF string from the data buffer and converts the
 * data from UTF to Unicode. Reads values that were written using {@link TupleOutput#writeString(String)}.
 * @return the converted string.
 * @throws IndexOutOfBoundsExceptionif no null terminating byte is found in the buffer.
 * @throws IllegalArgumentExceptionmalformed UTF data is encountered.
 */
  public final String readString() throws IndexOutOfBoundsException, IllegalArgumentException {
    byte[] buf=getBufferBytes();
    int off=getBufferOffset();
    if (available() >= 2 && buf[off] == TupleOutput.NULL_STRING_UTF_VALUE && buf[off + 1] == 0) {
      skip(2);
      return null;
    }
 else {
      int byteLen=UtfOps.getZeroTerminatedByteLength(buf,off);
      skip(byteLen + 1);
      return UtfOps.bytesToString(buf,off,byteLen);
    }
  }
  /** 
 * Reads a char (two byte) unsigned value from the buffer. Reads values that
 * were written using {@link TupleOutput#writeChar}.
 * @return the value read from the buffer.
 * @throws IndexOutOfBoundsExceptionif not enough bytes are available in the buffer.
 */
  public final char readChar() throws IndexOutOfBoundsException {
    return (char)readUnsignedShort();
  }
  /** 
 * Reads a boolean (one byte) unsigned value from the buffer and returns
 * true if it is non-zero and false if it is zero. Reads values that were
 * written using {@link TupleOutput#writeBoolean}.
 * @return the value read from the buffer.
 * @throws IndexOutOfBoundsExceptionif not enough bytes are available in the buffer.
 */
  public final boolean readBoolean() throws IndexOutOfBoundsException {
    int c=readFast();
    if (c < 0) {
      throw new IndexOutOfBoundsException();
    }
    return (c != 0);
  }
  /** 
 * Reads a signed byte (one byte) value from the buffer. Reads values that
 * were written using {@link TupleOutput#writeByte}.
 * @return the value read from the buffer.
 * @throws IndexOutOfBoundsExceptionif not enough bytes are available in the buffer.
 */
  public final byte readByte() throws IndexOutOfBoundsException {
    return (byte)(readUnsignedByte() ^ 0x80);
  }
  /** 
 * Reads a signed short (two byte) value from the buffer. Reads values that
 * were written using {@link TupleOutput#writeShort}.
 * @return the value read from the buffer.
 * @throws IndexOutOfBoundsExceptionif not enough bytes are available in the buffer.
 */
  public final short readShort() throws IndexOutOfBoundsException {
    return (short)(readUnsignedShort() ^ 0x8000);
  }
  /** 
 * Reads a signed int (four byte) value from the buffer. Reads values that
 * were written using {@link TupleOutput#writeInt}.
 * @return the value read from the buffer.
 * @throws IndexOutOfBoundsExceptionif not enough bytes are available in the buffer.
 */
  public final int readInt() throws IndexOutOfBoundsException {
    return (int)(readUnsignedInt() ^ 0x80000000);
  }
  /** 
 * Reads a signed long (eight byte) value from the buffer. Reads values that
 * were written using {@link TupleOutput#writeLong}.
 * @return the value read from the buffer.
 * @throws IndexOutOfBoundsExceptionif not enough bytes are available in the buffer.
 */
  public final long readLong() throws IndexOutOfBoundsException {
    return readUnsignedLong() ^ 0x8000000000000000L;
  }
  /** 
 * Reads a signed float (four byte) value from the buffer. Reads values that
 * were written using {@link TupleOutput#writeFloat}.
 * <code>Float.intBitsToFloat</code> is used to convert the signed int
 * value.
 * @return the value read from the buffer.
 * @throws IndexOutOfBoundsExceptionif not enough bytes are available in the buffer.
 */
  public final float readFloat() throws IndexOutOfBoundsException {
    return Float.intBitsToFloat((int)readUnsignedInt());
  }
  /** 
 * Reads a signed double (eight byte) value from the buffer. Reads values
 * that were written using {@link TupleOutput#writeDouble}.
 * <code>Double.longBitsToDouble</code> is used to convert the signed long
 * value.
 * @return the value read from the buffer.
 * @throws IndexOutOfBoundsExceptionif not enough bytes are available in the buffer.
 */
  public final double readDouble() throws IndexOutOfBoundsException {
    return Double.longBitsToDouble(readUnsignedLong());
  }
  /** 
 * Reads an unsigned byte (one byte) value from the buffer. Reads values
 * that were written using {@link TupleOutput#writeUnsignedByte}.
 * @return the value read from the buffer.
 * @throws IndexOutOfBoundsExceptionif not enough bytes are available in the buffer.
 */
  public final int readUnsignedByte() throws IndexOutOfBoundsException {
    int c=readFast();
    if (c < 0) {
      throw new IndexOutOfBoundsException();
    }
    return c;
  }
  /** 
 * Reads an unsigned short (two byte) value from the buffer. Reads values
 * that were written using {@link TupleOutput#writeUnsignedShort}.
 * @return the value read from the buffer.
 * @throws IndexOutOfBoundsExceptionif not enough bytes are available in the buffer.
 */
  public final int readUnsignedShort() throws IndexOutOfBoundsException {
    int c1=readFast();
    int c2=readFast();
    if ((c1 | c2) < 0) {
      throw new IndexOutOfBoundsException();
    }
    return ((c1 << 8) | c2);
  }
  /** 
 * Reads an unsigned int (four byte) value from the buffer. Reads values
 * that were written using {@link TupleOutput#writeUnsignedInt}.
 * @return the value read from the buffer.
 * @throws IndexOutOfBoundsExceptionif not enough bytes are available in the buffer.
 */
  public final long readUnsignedInt() throws IndexOutOfBoundsException {
    long c1=readFast();
    long c2=readFast();
    long c3=readFast();
    long c4=readFast();
    if ((c1 | c2 | c3| c4) < 0) {
      throw new IndexOutOfBoundsException();
    }
    return ((c1 << 24) | (c2 << 16) | (c3 << 8)| c4);
  }
  /** 
 * This method is private since an unsigned long cannot be treated as such
 * in Java, nor converted to a BigInteger of the same value.
 */
  private final long readUnsignedLong() throws IndexOutOfBoundsException {
    long c1=readFast();
    long c2=readFast();
    long c3=readFast();
    long c4=readFast();
    long c5=readFast();
    long c6=readFast();
    long c7=readFast();
    long c8=readFast();
    if ((c1 | c2 | c3| c4| c5| c6| c7| c8) < 0) {
      throw new IndexOutOfBoundsException();
    }
    return ((c1 << 56) | (c2 << 48) | (c3 << 40)| (c4 << 32)| (c5 << 24)| (c6 << 16)| (c7 << 8)| c8);
  }
  /** 
 * Reads the specified number of bytes from the buffer, converting each
 * unsigned byte value to a character of the resulting string. Reads values
 * that were written using {@link TupleOutput#writeBytes}. Only characters
 * with values below 0x100 may be read using this method.
 * @param lengthis the number of bytes to be read.
 * @return the value read from the buffer.
 * @throws IndexOutOfBoundsExceptionif not enough bytes are available in the buffer.
 */
  public final String readBytes(  int length) throws IndexOutOfBoundsException {
    StringBuffer buf=new StringBuffer(length);
    for (int i=0; i < length; i++) {
      int c=readFast();
      if (c < 0) {
        throw new IndexOutOfBoundsException();
      }
      buf.append((char)c);
    }
    return buf.toString();
  }
  /** 
 * Reads the specified number of characters from the buffer, converting each
 * two byte unsigned value to a character of the resulting string. Reads
 * values that were written using {@link TupleOutput#writeChars}.
 * @param lengthis the number of characters to be read.
 * @return the value read from the buffer.
 * @throws IndexOutOfBoundsExceptionif not enough bytes are available in the buffer.
 */
  public final String readChars(  int length) throws IndexOutOfBoundsException {
    StringBuffer buf=new StringBuffer(length);
    for (int i=0; i < length; i++) {
      buf.append(readChar());
    }
    return buf.toString();
  }
  /** 
 * Reads the specified number of bytes from the buffer, converting each
 * unsigned byte value to a character of the resulting array. Reads values
 * that were written using {@link TupleOutput#writeBytes}. Only characters
 * with values below 0x100 may be read using this method.
 * @param charsis the array to receive the data and whose length is used to
 * determine the number of bytes to be read.
 * @throws IndexOutOfBoundsExceptionif not enough bytes are available in the buffer.
 */
  public final void readBytes(  char[] chars) throws IndexOutOfBoundsException {
    for (int i=0; i < chars.length; i++) {
      int c=readFast();
      if (c < 0) {
        throw new IndexOutOfBoundsException();
      }
      chars[i]=(char)c;
    }
  }
  /** 
 * Reads the specified number of characters from the buffer, converting each
 * two byte unsigned value to a character of the resulting array. Reads
 * values that were written using {@link TupleOutput#writeChars}.
 * @param charsis the array to receive the data and whose length is used to
 * determine the number of characters to be read.
 * @throws IndexOutOfBoundsExceptionif not enough bytes are available in the buffer.
 */
  public final void readChars(  char[] chars) throws IndexOutOfBoundsException {
    for (int i=0; i < chars.length; i++) {
      chars[i]=readChar();
    }
  }
  /** 
 * Reads the specified number of UTF characters string from the data buffer
 * and converts the data from UTF to Unicode. Reads values that were written
 * using {@link TupleOutput#writeString(char[])}.
 * @param lengthis the number of characters to be read.
 * @return the converted string.
 * @throws IndexOutOfBoundsExceptionif no null terminating byte is found in the buffer.
 * @throws IllegalArgumentExceptionmalformed UTF data is encountered.
 */
  public final String readString(  int length) throws IndexOutOfBoundsException, IllegalArgumentException {
    char[] chars=new char[length];
    readString(chars);
    return new String(chars);
  }
  /** 
 * Reads the specified number of UTF characters string from the data buffer
 * and converts the data from UTF to Unicode. Reads values that were written
 * using {@link TupleOutput#writeString(char[])}.
 * @param charsis the array to receive the data and whose length is used to
 * determine the number of characters to be read.
 * @throws IndexOutOfBoundsExceptionif no null terminating byte is found in the buffer.
 * @throws IllegalArgumentExceptionmalformed UTF data is encountered.
 */
  public final void readString(  char[] chars) throws IndexOutOfBoundsException, IllegalArgumentException {
    byte[] buf=getBufferBytes();
    off=UtfOps.bytesToChars(buf,off,chars,0,chars.length,false);
  }
}
