blob: 65b116f7dc7c43b58be16a0472759e62f5158dc5 [file] [log] [blame]
/*-
* Copyright (C) 2002, 2018, Oracle and/or its affiliates. All rights reserved.
*
* This file was distributed by Oracle as part of a version of Oracle Berkeley
* DB Java Edition made available at:
*
* http://www.oracle.com/technetwork/database/database-technologies/berkeleydb/downloads/index.html
*
* Please see the LICENSE file included in the top-level directory of the
* appropriate version of Oracle Berkeley DB Java Edition for a copy of the
* license and additional information.
*/
package com.sleepycat.bind.tuple;
import java.math.BigDecimal;
import java.math.BigInteger;
import com.sleepycat.util.FastOutputStream;
import com.sleepycat.util.PackedInteger;
import com.sleepycat.util.UtfOps;
/**
* An <code>OutputStream</code> with <code>DataOutput</code>-like methods for
* writing 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.DataOutput} interface. The reason this class does not
* implement {@link java.io.DataOutput} is because it would break the interface
* contract for those methods because of data format differences.</p>
*
* @see <a href="package-summary.html#formats">Tuple Formats</a>
*
* @author Mark Hayes
*/
public class TupleOutput extends FastOutputStream {
/**
* We represent a null string as a single FF UTF character, which cannot
* occur in a UTF encoded string.
*/
static final int NULL_STRING_UTF_VALUE = ((byte) 0xFF);
/**
* Creates a tuple output object for writing a byte array of tuple data.
*/
public TupleOutput() {
super();
}
/**
* Creates a tuple output object for writing a byte array of tuple data,
* using a given buffer. A new buffer will be allocated only if the number
* of bytes needed is greater than the length of this buffer. A reference
* to the byte array will be kept by this object and therefore the byte
* array should not be modified while this object is in use.
*
* @param buffer is the byte array to use as the buffer.
*/
public TupleOutput(byte[] buffer) {
super(buffer);
}
// --- begin DataOutput compatible methods ---
/**
* Writes the specified bytes to the buffer, converting each character to
* an unsigned byte value.
* Writes values that can be read using {@link TupleInput#readBytes}.
*
* @param val is the string containing the values to be written.
* Only characters with values below 0x100 may be written using this
* method, since the high-order 8 bits of all characters are discarded.
*
* @return this tuple output object.
*
* @throws NullPointerException if the val parameter is null.
*
* @see <a href="package-summary.html#integerFormats">Integer Formats</a>
*/
public final TupleOutput writeBytes(String val) {
writeBytes(val.toCharArray());
return this;
}
/**
* Writes the specified characters to the buffer, converting each character
* to a two byte unsigned value.
* Writes values that can be read using {@link TupleInput#readChars}.
*
* @param val is the string containing the characters to be written.
*
* @return this tuple output object.
*
* @throws NullPointerException if the val parameter is null.
*
* @see <a href="package-summary.html#integerFormats">Integer Formats</a>
*/
public final TupleOutput writeChars(String val) {
writeChars(val.toCharArray());
return this;
}
/**
* Writes the specified characters to the buffer, converting each character
* to UTF format, and adding a null terminator byte.
* Writes values that can be read using {@link TupleInput#readString()}.
*
* @param val is the string containing the characters to be written.
*
* @return this tuple output object.
*
* @see <a href="package-summary.html#stringFormats">String Formats</a>
*/
public final TupleOutput writeString(String val) {
if (val != null) {
writeString(val.toCharArray());
} else {
writeFast(NULL_STRING_UTF_VALUE);
}
writeFast(0);
return this;
}
/**
* Writes a char (two byte) unsigned value to the buffer.
* Writes values that can be read using {@link TupleInput#readChar}.
*
* @param val is the value to write to the buffer.
*
* @return this tuple output object.
*
* @see <a href="package-summary.html#integerFormats">Integer Formats</a>
*/
public final TupleOutput writeChar(int val) {
writeFast((byte) (val >>> 8));
writeFast((byte) val);
return this;
}
/**
* Writes a boolean (one byte) unsigned value to the buffer, writing one
* if the value is true and zero if it is false.
* Writes values that can be read using {@link TupleInput#readBoolean}.
*
* @param val is the value to write to the buffer.
*
* @return this tuple output object.
*
* @see <a href="package-summary.html#integerFormats">Integer Formats</a>
*/
public final TupleOutput writeBoolean(boolean val) {
writeFast(val ? (byte)1 : (byte)0);
return this;
}
/**
* Writes an signed byte (one byte) value to the buffer.
* Writes values that can be read using {@link TupleInput#readByte}.
*
* @param val is the value to write to the buffer.
*
* @return this tuple output object.
*
* @see <a href="package-summary.html#integerFormats">Integer Formats</a>
*/
public final TupleOutput writeByte(int val) {
writeUnsignedByte(val ^ 0x80);
return this;
}
/**
* Writes an signed short (two byte) value to the buffer.
* Writes values that can be read using {@link TupleInput#readShort}.
*
* @param val is the value to write to the buffer.
*
* @return this tuple output object.
*
* @see <a href="package-summary.html#integerFormats">Integer Formats</a>
*/
public final TupleOutput writeShort(int val) {
writeUnsignedShort(val ^ 0x8000);
return this;
}
/**
* Writes an signed int (four byte) value to the buffer.
* Writes values that can be read using {@link TupleInput#readInt}.
*
* @param val is the value to write to the buffer.
*
* @return this tuple output object.
*
* @see <a href="package-summary.html#integerFormats">Integer Formats</a>
*/
public final TupleOutput writeInt(int val) {
writeUnsignedInt(val ^ 0x80000000);
return this;
}
/**
* Writes an signed long (eight byte) value to the buffer.
* Writes values that can be read using {@link TupleInput#readLong}.
*
* @param val is the value to write to the buffer.
*
* @return this tuple output object.
*
* @see <a href="package-summary.html#integerFormats">Integer Formats</a>
*/
public final TupleOutput writeLong(long val) {
writeUnsignedLong(val ^ 0x8000000000000000L);
return this;
}
/**
* Writes an unsorted float (four byte) value to the buffer.
* Writes values that can be read using {@link TupleInput#readFloat}.
*
* @param val is the value to write to the buffer.
*
* @return this tuple output object.
*
* @see <a href="package-summary.html#floatFormats">Floating Point
* Formats</a>
*/
public final TupleOutput writeFloat(float val) {
writeUnsignedInt(Float.floatToIntBits(val));
return this;
}
/**
* Writes an unsorted double (eight byte) value to the buffer.
* Writes values that can be read using {@link TupleInput#readDouble}.
*
* @param val is the value to write to the buffer.
*
* @return this tuple output object.
*
* @see <a href="package-summary.html#floatFormats">Floating Point
* Formats</a>
*/
public final TupleOutput writeDouble(double val) {
writeUnsignedLong(Double.doubleToLongBits(val));
return this;
}
/**
* Writes a sorted float (four byte) value to the buffer.
* Writes values that can be read using {@link TupleInput#readSortedFloat}.
*
* @param val is the value to write to the buffer.
*
* @return this tuple output object.
*
* @see <a href="package-summary.html#floatFormats">Floating Point
* Formats</a>
*/
public final TupleOutput writeSortedFloat(float val) {
int intVal = Float.floatToIntBits(val);
intVal ^= (intVal < 0) ? 0xffffffff : 0x80000000;
writeUnsignedInt(intVal);
return this;
}
/**
* Writes a sorted double (eight byte) value to the buffer.
* Writes values that can be read using {@link TupleInput#readSortedDouble}.
*
* @param val is the value to write to the buffer.
*
* @return this tuple output object.
*
* @see <a href="package-summary.html#floatFormats">Floating Point
* Formats</a>
*/
public final TupleOutput writeSortedDouble(double val) {
long longVal = Double.doubleToLongBits(val);
longVal ^= (longVal < 0) ? 0xffffffffffffffffL : 0x8000000000000000L;
writeUnsignedLong(longVal);
return this;
}
// --- end DataOutput compatible methods ---
/**
* Writes the specified bytes to the buffer, converting each character to
* an unsigned byte value.
* Writes values that can be read using {@link TupleInput#readBytes}.
*
* @param chars is the array of values to be written.
* Only characters with values below 0x100 may be written using this
* method, since the high-order 8 bits of all characters are discarded.
*
* @return this tuple output object.
*
* @throws NullPointerException if the chars parameter is null.
*
* @see <a href="package-summary.html#integerFormats">Integer Formats</a>
*/
public final TupleOutput writeBytes(char[] chars) {
for (int i = 0; i < chars.length; i++) {
writeFast((byte) chars[i]);
}
return this;
}
/**
* Writes the specified characters to the buffer, converting each character
* to a two byte unsigned value.
* Writes values that can be read using {@link TupleInput#readChars}.
*
* @param chars is the array of characters to be written.
*
* @return this tuple output object.
*
* @throws NullPointerException if the chars parameter is null.
*
* @see <a href="package-summary.html#integerFormats">Integer Formats</a>
*/
public final TupleOutput writeChars(char[] chars) {
for (int i = 0; i < chars.length; i++) {
writeFast((byte) (chars[i] >>> 8));
writeFast((byte) chars[i]);
}
return this;
}
/**
* Writes the specified characters to the buffer, converting each character
* to UTF format.
* Writes values that can be read using {@link TupleInput#readString(int)}
* or {@link TupleInput#readString(char[])}.
*
* @param chars is the array of characters to be written.
*
* @return this tuple output object.
*
* @throws NullPointerException if the chars parameter is null.
*
* @see <a href="package-summary.html#stringFormats">String Formats</a>
*/
public final TupleOutput writeString(char[] chars) {
if (chars.length == 0) return this;
int utfLength = UtfOps.getByteLength(chars);
makeSpace(utfLength);
UtfOps.charsToBytes(chars, 0, getBufferBytes(), getBufferLength(),
chars.length);
addSize(utfLength);
return this;
}
/**
* Writes an unsigned byte (one byte) value to the buffer.
* Writes values that can be read using {@link
* TupleInput#readUnsignedByte}.
*
* @param val is the value to write to the buffer.
*
* @return this tuple output object.
*
* @see <a href="package-summary.html#integerFormats">Integer Formats</a>
*/
public final TupleOutput writeUnsignedByte(int val) {
writeFast(val);
return this;
}
/**
* Writes an unsigned short (two byte) value to the buffer.
* Writes values that can be read using {@link
* TupleInput#readUnsignedShort}.
*
* @param val is the value to write to the buffer.
*
* @return this tuple output object.
*
* @see <a href="package-summary.html#integerFormats">Integer Formats</a>
*/
public final TupleOutput writeUnsignedShort(int val) {
writeFast((byte) (val >>> 8));
writeFast((byte) val);
return this;
}
/**
* Writes an unsigned int (four byte) value to the buffer.
* Writes values that can be read using {@link
* TupleInput#readUnsignedInt}.
*
* @param val is the value to write to the buffer.
*
* @return this tuple output object.
*
* @see <a href="package-summary.html#integerFormats">Integer Formats</a>
*/
public final TupleOutput writeUnsignedInt(long val) {
writeFast((byte) (val >>> 24));
writeFast((byte) (val >>> 16));
writeFast((byte) (val >>> 8));
writeFast((byte) val);
return this;
}
/**
* 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 TupleOutput writeUnsignedLong(long val) {
writeFast((byte) (val >>> 56));
writeFast((byte) (val >>> 48));
writeFast((byte) (val >>> 40));
writeFast((byte) (val >>> 32));
writeFast((byte) (val >>> 24));
writeFast((byte) (val >>> 16));
writeFast((byte) (val >>> 8));
writeFast((byte) val);
return this;
}
/**
* Writes an unsorted packed integer.
*
* @param val is the value to write to the buffer.
*
* @return this tuple output object.
*
* @see <a href="package-summary.html#integerFormats">Integer Formats</a>
*/
public final TupleOutput writePackedInt(int val) {
makeSpace(PackedInteger.MAX_LENGTH);
int oldLen = getBufferLength();
int newLen = PackedInteger.writeInt(getBufferBytes(), oldLen, val);
addSize(newLen - oldLen);
return this;
}
/**
* Writes an unsorted packed long integer.
*
* @param val is the value to write to the buffer.
*
* @return this tuple output object.
*
* @see <a href="package-summary.html#integerFormats">Integer Formats</a>
*/
public final TupleOutput writePackedLong(long val) {
makeSpace(PackedInteger.MAX_LONG_LENGTH);
int oldLen = getBufferLength();
int newLen = PackedInteger.writeLong(getBufferBytes(), oldLen, val);
addSize(newLen - oldLen);
return this;
}
/**
* Writes a sorted packed integer.
*
* @param val is the value to write to the buffer.
*
* @return this tuple output object.
*
* @see <a href="package-summary.html#integerFormats">Integer Formats</a>
*/
public final TupleOutput writeSortedPackedInt(int val) {
makeSpace(PackedInteger.MAX_LENGTH);
int oldLen = getBufferLength();
int newLen = PackedInteger.writeSortedInt(getBufferBytes(), oldLen,
val);
addSize(newLen - oldLen);
return this;
}
/**
* Writes a sorted packed long integer.
*
* @param val is the value to write to the buffer.
*
* @return this tuple output object.
*
* @see <a href="package-summary.html#integerFormats">Integer Formats</a>
*/
public final TupleOutput writeSortedPackedLong(long val) {
makeSpace(PackedInteger.MAX_LONG_LENGTH);
int oldLen = getBufferLength();
int newLen = PackedInteger.writeSortedLong(getBufferBytes(), oldLen,
val);
addSize(newLen - oldLen);
return this;
}
/**
* Writes a {@code BigInteger}.
*
* @param val is the value to write to the buffer.
*
* @return this tuple output object.
*
* @throws NullPointerException if val is null.
*
* @throws IllegalArgumentException if the byte array representation of val
* is larger than 0x7fff bytes.
*
* @see <a href="package-summary.html#integerFormats">Integer Formats</a>
*/
public final TupleOutput writeBigInteger(BigInteger val) {
byte[] a = val.toByteArray();
if (a.length > Short.MAX_VALUE) {
throw new IllegalArgumentException
("BigInteger byte array is larger than 0x7fff bytes");
}
int firstByte = a[0];
writeShort((firstByte < 0) ? (- a.length) : a.length);
writeByte(firstByte);
writeFast(a, 1, a.length - 1);
return this;
}
/**
* Returns the exact byte length that would would be output for a given
* {@code BigInteger} value if {@link TupleOutput#writeBigInteger} were
* called.
*
* @param val the BigInteger
*
* @return the byte length.
*
* @see <a href="package-summary.html#integerFormats">Integer Formats</a>
*/
public static int getBigIntegerByteLength(BigInteger val) {
return 2 /* length bytes */ +
(val.bitLength() + 1 /* sign bit */ + 7 /* round up */) / 8;
}
/**
* Writes an unsorted {@code BigDecimal}.
*
* @param val is the value to write to the buffer.
*
* @return this tuple output object.
*
* @throws NullPointerException if val is null.
*
* @see <a href="package-summary.html#bigDecimalFormats">BigDecimal
* Formats</a>
*/
public final TupleOutput writeBigDecimal(BigDecimal val) {
/*
* The byte format for a BigDecimal value is:
* Byte 0 ~ L: The scale part written as a PackedInteger.
* Byte L+1 ~ M: The length of the unscaled value written as a
* PackedInteger.
* Byte M+1 ~ N: The BigDecimal.toByteArray array, written
* without modification.
*
* Get the scale and the unscaled value of this BigDecimal.
*/
int scale = val.scale();
BigInteger unscaledVal = val.unscaledValue();
/* Store the scale. */
writePackedInt(scale);
byte[] a = unscaledVal.toByteArray();
int len = a.length;
/* Store the length of the following bytes. */
writePackedInt(len);
/* Store the bytes of the BigDecimal, without modification. */
writeFast(a, 0, len);
return this;
}
/**
* Returns the maximum byte length that would be output for a given {@code
* BigDecimal} value if {@link TupleOutput#writeBigDecimal} were called.
*
* @param val the BigDecimal.
*
* @return the byte length.
*
* @see <a href="package-summary.html#bigDecimalFormats">BigDecimal
* Formats</a>
*/
public static int getBigDecimalMaxByteLength(BigDecimal val) {
BigInteger unscaledVal = val.unscaledValue();
return PackedInteger.MAX_LENGTH * 2 +
unscaledVal.toByteArray().length;
}
/**
* Writes a sorted {@code BigDecimal}.
*
* @param val is the value to write to the buffer.
*
* @return this tuple output object.
*
* @see <a href="package-summary.html#bigDecimalFormats">BigDecimal
* Formats</a>
*/
public final TupleOutput writeSortedBigDecimal(BigDecimal val) {
/*
* We have several options for the serialization of sorted BigDecimal.
* The reason for choosing this method is that it is simpler and more
* compact, and in some cases, comparison time will be less. For other
* methods and detailed discussion, please refer to [#18379].
*
* First, we need to do the normalization, which means we normalize a
* given BigDecimal into two parts: decimal part and the exponent part.
* The decimal part contains one integer (non zero). For example,
* 1234.56 will be normalized to 1.23456E3;
* 123.4E100 will be normalized to 1.234E102;
* -123.4E-100 will be normalized to -1.234E-98.
*
* After the normalization, the byte format is:
* Byte 0: sign (-1 represents negative, 0 represents zero, and 1
* represents positive).
* Byte 1 ~ 5: the exponent with sign, and written as a
* SortedPackedInteger value.
* Byte 6 ~ N: the normalized decimal part with sign.
*
* Get the scale and the unscaled value of this BigDecimal..
*/
BigDecimal valNoTrailZeros = val.stripTrailingZeros();
int scale = valNoTrailZeros.scale();
BigInteger unscaledVal = valNoTrailZeros.unscaledValue();
int sign = valNoTrailZeros.signum();
/* Then do the normalization. */
String unscaledValStr = unscaledVal.abs().toString();
int normalizedScale = unscaledValStr.length() - 1;
BigDecimal normalizedVal = new BigDecimal(unscaledVal,
normalizedScale);
int exponent = (normalizedScale - scale) * sign;
/* Start serializing each part. */
writeByte(sign);
writeSortedPackedInt(exponent);
writeSortedNormalizedBigDecimal(normalizedVal);
return this;
}
/**
* Writes a normalized {@code BigDecimal}.
*/
private final TupleOutput writeSortedNormalizedBigDecimal(BigDecimal val) {
/*
* The byte format for a sorted normalized {@code BigDecimal} value is:
* Byte 0 ~ N: Store all digits with sign. Each 9 digits is
* regarded as one integer, and written as a
* SortedPackedInteger value. If there are not enough
* 9 digits, pad trailing zeros. Since we may pad
* trailing zeros for serialization, when doing
* de-serialization, we need to delete the trailing
* zeros. In order to designate a special value as the
* terminator byte, we set
* val = (val < 0) ? (val - 1) : val.
* Byte N + 1: Terminator byte. The terminator byte is -1, and
* written as a SortedPackedInteger value.
*/
/* get the precision, scale and sign of the BigDecimal. */
int precision = val.precision();
int scale = val.scale();
int sign = val.signum();
/* Start the serialization of the whole digits. */
String digitsStr = val.abs().toPlainString();
/*
* The default capacity of a StringBuilder is 16 chars, which is
* enough to hold a group of digits having 9 digits.
*/
StringBuilder groupDigits = new StringBuilder();
for (int i = 0; i < digitsStr.length();) {
char digit = digitsStr.charAt(i++);
/* Ignore the decimal. */
if (digit != '.') {
groupDigits.append(digit);
}
/*
* For the last group of the digits, if there are not 9 digits, pad
* trailing zeros.
*/
if (i == digitsStr.length() && groupDigits.length() < 9) {
final int insertLen = 9 - groupDigits.length();
for (int k = 0; k < insertLen; k++) {
groupDigits.append("0");
}
}
/* Group every 9 digits as an Integer. */
if (groupDigits.length() == 9) {
int subVal = Integer.valueOf(groupDigits.toString());
if (sign < 0) {
subVal = -subVal;
}
/*
* Reset the sub-value, so the value -1 will be designated as
* the terminator byte.
*/
subVal = subVal < 0 ? subVal - 1 : subVal;
writeSortedPackedInt(subVal);
groupDigits.setLength(0);
}
}
/* Write the terminator byte. */
writeSortedPackedInt(-1);
return this;
}
/**
* Returns the maximum byte length that would be output for a given {@code
* BigDecimal} value if {@link TupleOutput#writeSortedBigDecimal} were
* called.
*
* @param val the BigDecimal.
*
* @return the byte length.
*
* @see <a href="package-summary.html#bigDecimalFormats">BigDecimal
* Formats</a>
*/
public static int getSortedBigDecimalMaxByteLength(BigDecimal val) {
String digitsStr = val.stripTrailingZeros().unscaledValue().abs().
toString();
int numOfGroups = (digitsStr.length() + 8 /* round up */) / 9;
return 1 /* sign */ +
PackedInteger.MAX_LENGTH /* exponent */ +
PackedInteger.MAX_LENGTH * numOfGroups /* all the digits */ +
1; /* terminator byte */
}
}