summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/main/java/com/amazon/carbonado/raw/KeyDecoder.java222
-rw-r--r--src/main/java/com/amazon/carbonado/raw/KeyEncoder.java244
2 files changed, 423 insertions, 43 deletions
diff --git a/src/main/java/com/amazon/carbonado/raw/KeyDecoder.java b/src/main/java/com/amazon/carbonado/raw/KeyDecoder.java
index 8c4bf02..dc838e6 100644
--- a/src/main/java/com/amazon/carbonado/raw/KeyDecoder.java
+++ b/src/main/java/com/amazon/carbonado/raw/KeyDecoder.java
@@ -18,6 +18,7 @@
package com.amazon.carbonado.raw;
+import java.math.BigDecimal;
import java.math.BigInteger;
import com.amazon.carbonado.CorruptEncodingException;
@@ -346,30 +347,37 @@ public class KeyDecoder {
public static int decode(byte[] src, int srcOffset, BigInteger[] valueRef)
throws CorruptEncodingException
{
- int header = src[srcOffset];
- if (header == NULL_BYTE_HIGH || header == NULL_BYTE_LOW) {
- valueRef[0] = null;
- return 1;
- }
-
- header &= 0xff;
-
int headerSize;
int bytesLength;
- if (header > 1 && header < 0xfe) {
- if (header < 0x80) {
- bytesLength = 0x80 - header;
+ byte[] bytes;
+
+ try {
+ int header = src[srcOffset];
+ if (header == NULL_BYTE_HIGH || header == NULL_BYTE_LOW) {
+ valueRef[0] = null;
+ return 1;
+ }
+
+ header &= 0xff;
+
+ if (header > 1 && header < 0xfe) {
+ if (header < 0x80) {
+ bytesLength = 0x80 - header;
+ } else {
+ bytesLength = header - 0x7f;
+ }
+ headerSize = 1;
} else {
- bytesLength = header - 0x7f;
+ bytesLength = Math.abs(DataDecoder.decodeInt(src, srcOffset + 1));
+ headerSize = 5;
}
- headerSize = 1;
- } else {
- bytesLength = Math.abs(DataDecoder.decodeInt(src, srcOffset + 1));
- headerSize = 5;
+
+ bytes = new byte[bytesLength];
+ System.arraycopy(src, srcOffset + headerSize, bytes, 0, bytesLength);
+ } catch (IndexOutOfBoundsException e) {
+ throw new CorruptEncodingException(null, e);
}
- byte[] bytes = new byte[bytesLength];
- System.arraycopy(src, srcOffset + headerSize, bytes, 0, bytesLength);
valueRef[0] = new BigInteger(bytes);
return headerSize + bytesLength;
}
@@ -387,33 +395,39 @@ public class KeyDecoder {
public static int decodeDesc(byte[] src, int srcOffset, BigInteger[] valueRef)
throws CorruptEncodingException
{
- int header = src[srcOffset];
- if (header == NULL_BYTE_HIGH || header == NULL_BYTE_LOW) {
- valueRef[0] = null;
- return 1;
- }
-
- header &= 0xff;
-
int headerSize;
int bytesLength;
- if (header > 1 && header < 0xfe) {
- if (header < 0x80) {
- bytesLength = 0x80 - header;
+ byte[] bytes;
+
+ try {
+ int header = src[srcOffset];
+ if (header == NULL_BYTE_HIGH || header == NULL_BYTE_LOW) {
+ valueRef[0] = null;
+ return 1;
+ }
+
+ header &= 0xff;
+
+ if (header > 1 && header < 0xfe) {
+ if (header < 0x80) {
+ bytesLength = 0x80 - header;
+ } else {
+ bytesLength = header - 0x7f;
+ }
+ headerSize = 1;
} else {
- bytesLength = header - 0x7f;
+ bytesLength = Math.abs(DataDecoder.decodeInt(src, srcOffset + 1));
+ headerSize = 5;
}
- headerSize = 1;
- } else {
- bytesLength = Math.abs(DataDecoder.decodeInt(src, srcOffset + 1));
- headerSize = 5;
- }
- byte[] bytes = new byte[bytesLength];
+ bytes = new byte[bytesLength];
- srcOffset += headerSize;
- for (int i=0; i<bytesLength; i++) {
- bytes[i] = (byte) ~src[srcOffset + i];
+ srcOffset += headerSize;
+ for (int i=0; i<bytesLength; i++) {
+ bytes[i] = (byte) ~src[srcOffset + i];
+ }
+ } catch (IndexOutOfBoundsException e) {
+ throw new CorruptEncodingException(null, e);
}
valueRef[0] = new BigInteger(bytes);
@@ -421,6 +435,136 @@ public class KeyDecoder {
}
/**
+ * Decodes the given BigDecimal as originally encoded for ascending order.
+ *
+ * @param src source of encoded data
+ * @param srcOffset offset into encoded data
+ * @param valueRef decoded BigDecimal is stored in element 0, which may be null
+ * @return amount of bytes read from source
+ * @throws CorruptEncodingException if source data is corrupt
+ * @since 1.2
+ */
+ public static int decode(byte[] src, int srcOffset, BigDecimal[] valueRef)
+ throws CorruptEncodingException
+ {
+ final int originalOffset;
+ BigInteger unscaledValue;
+ int scale;
+
+ try {
+ int header = src[srcOffset] & 0xff;
+
+ int headerSize;
+ switch (header) {
+ case (NULL_BYTE_HIGH & 0xff): case (NULL_BYTE_LOW & 0xff):
+ valueRef[0] = null;
+ return 1;
+
+ case 0x7f: case 0x80:
+ valueRef[0] = BigDecimal.ZERO;
+ return 1;
+
+ case 1: case 0x7e: case 0x81: case 0xfe:
+ headerSize = 5;
+ break;
+
+ default:
+ headerSize = 1;
+ break;
+ }
+
+ originalOffset = srcOffset;
+ srcOffset += headerSize;
+
+ BigInteger[] unscaledRef = new BigInteger[1];
+ srcOffset += decode(src, srcOffset, unscaledRef);
+
+ header = src[srcOffset++] & 0xff;
+ if (header > 0 && header < 0xff) {
+ scale = header - 0x80;
+ } else {
+ scale = DataDecoder.decodeInt(src, srcOffset);
+ srcOffset += 4;
+ }
+
+ unscaledValue = unscaledRef[0];
+ if (unscaledValue.signum() < 0) {
+ scale = ~scale;
+ }
+ } catch (IndexOutOfBoundsException e) {
+ throw new CorruptEncodingException(null, e);
+ }
+
+ valueRef[0] = new BigDecimal(unscaledValue, scale);
+ return srcOffset - originalOffset;
+ }
+
+ /**
+ * Decodes the given BigDecimal as originally encoded for descending order.
+ *
+ * @param src source of encoded data
+ * @param srcOffset offset into encoded data
+ * @param valueRef decoded BigDecimal is stored in element 0, which may be null
+ * @return amount of bytes read from source
+ * @throws CorruptEncodingException if source data is corrupt
+ * @since 1.2
+ */
+ public static int decodeDesc(byte[] src, int srcOffset, BigDecimal[] valueRef)
+ throws CorruptEncodingException
+ {
+ final int originalOffset;
+ BigInteger unscaledValue;
+ int scale;
+
+ try {
+ int header = src[srcOffset] & 0xff;
+
+ int headerSize;
+ switch (header) {
+ case (NULL_BYTE_HIGH & 0xff): case (NULL_BYTE_LOW & 0xff):
+ valueRef[0] = null;
+ return 1;
+
+ case 0x7f: case 0x80:
+ valueRef[0] = BigDecimal.ZERO;
+ return 1;
+
+ case 1: case 0x7e: case 0x81: case 0xfe:
+ headerSize = 5;
+ break;
+
+ default:
+ headerSize = 1;
+ break;
+ }
+
+ originalOffset = srcOffset;
+ srcOffset += headerSize;
+
+ BigInteger[] unscaledRef = new BigInteger[1];
+ srcOffset += decodeDesc(src, srcOffset, unscaledRef);
+
+ header = src[srcOffset++] & 0xff;
+ if (header > 0 && header < 0xff) {
+ scale = 0x7f - header;
+ } else {
+ scale = ~DataDecoder.decodeInt(src, srcOffset);
+ srcOffset += 4;
+ }
+
+ unscaledValue = unscaledRef[0];
+ if (unscaledValue.signum() < 0) {
+ scale = ~scale;
+ }
+ } catch (IndexOutOfBoundsException e) {
+ throw new CorruptEncodingException(null, e);
+ }
+
+ valueRef[0] = new BigDecimal(unscaledValue, scale);
+ return srcOffset - originalOffset;
+ }
+
+ /**
* Decodes the given byte array as originally encoded for ascending order.
* The decoding stops when any kind of terminator or illegal byte has been
* read. The decoded bytes are stored in valueRef.
diff --git a/src/main/java/com/amazon/carbonado/raw/KeyEncoder.java b/src/main/java/com/amazon/carbonado/raw/KeyEncoder.java
index 2e61978..07fb94e 100644
--- a/src/main/java/com/amazon/carbonado/raw/KeyEncoder.java
+++ b/src/main/java/com/amazon/carbonado/raw/KeyEncoder.java
@@ -18,6 +18,7 @@
package com.amazon.carbonado.raw;
+import java.math.BigDecimal;
import java.math.BigInteger;
import static com.amazon.carbonado.raw.EncodingConstants.*;
@@ -311,6 +312,16 @@ public class KeyEncoder {
* @since 1.2
*/
public static int encode(BigInteger value, byte[] dst, int dstOffset) {
+ /* Encoding of first byte:
+
+ 0x00: null low (unused)
+ 0x01: negative signum; four bytes follow for value length
+ 0x02..0x7f: negative signum; value length 7e range, 1..126
+ 0x80..0xfd: positive signum; value length 7e range, 1..126
+ 0xfe: positive signum; four bytes follow for value length
+ 0xff: null high
+ */
+
if (value == null) {
dst[dstOffset] = NULL_BYTE_HIGH;
return 1;
@@ -322,8 +333,6 @@ public class KeyEncoder {
int headerSize;
if (bytesLength < 0x7f) {
- // First byte is 0x02..0x7f for negative values and 0x80..0xfd for
- // positive values.
if (value.signum() < 0) {
dst[dstOffset] = (byte) (0x80 - bytesLength);
} else {
@@ -355,6 +364,16 @@ public class KeyEncoder {
* @since 1.2
*/
public static int encodeDesc(BigInteger value, byte[] dst, int dstOffset) {
+ /* Encoding of first byte:
+
+ 0x00: null high (unused)
+ 0x01: positive signum; four bytes follow for value length
+ 0x02..0x7f: positive signum; value length 7e range, 1..126
+ 0x80..0xfd: negative signum; value length 7e range, 1..126
+ 0xfe: negative signum; four bytes follow for value length
+ 0xff: null low
+ */
+
if (value == null) {
dst[dstOffset] = NULL_BYTE_LOW;
return 1;
@@ -366,8 +385,6 @@ public class KeyEncoder {
int headerSize;
if (bytesLength < 0x7f) {
- // First byte is 0x02..0x7f for negative values and 0x80..0xfd for
- // positive values.
if (value.signum() < 0) {
dst[dstOffset] = (byte) (bytesLength + 0x7f);
} else {
@@ -405,6 +422,225 @@ public class KeyEncoder {
}
/**
+ * Encodes the given optional BigDecimal into a variable amount of
+ * bytes. If the BigDecimal is null, exactly 1 byte is written. Otherwise,
+ * the amount written can be determined by calling calculateEncodedLength.
+ *
+ * <p><i>Note:</i> It is recommended that value be normalized by stripping
+ * trailing zeros. This makes searching by value much simpler.
+ *
+ * @param value BigDecimal value to encode, may be null
+ * @param dst destination for encoded bytes
+ * @param dstOffset offset into destination array
+ * @return amount of bytes written
+ * @since 1.2
+ */
+ public static int encode(BigDecimal value, byte[] dst, int dstOffset) {
+ /* Encoding of first byte:
+
+ 0x00: null low (unused)
+ 0x01: negative signum; four bytes follow for positive exponent
+ 0x02..0x3f: negative signum; positive exponent; 3e range, 61..0
+ 0x40..0x7d: negative signum; negative exponent; 3e range, -1..-62
+ 0x7e: negative signum; four bytes follow for negative exponent
+ 0x7f: negative zero (unused)
+ 0x80: zero
+ 0x81: positive signum; four bytes follow for negative exponent
+ 0x82..0xbf: positive signum; negative exponent; 3e range, -62..-1
+ 0xc0..0xfd: positive signum; positive exponent; 3e range, 0..61
+ 0xfe: positive signum; four bytes follow for positive exponent
+ 0xff: null high
+ */
+
+ if (value == null) {
+ dst[dstOffset] = NULL_BYTE_HIGH;
+ return 1;
+ }
+
+ int signum = value.signum();
+ if (signum == 0) {
+ dst[dstOffset] = (byte) 0x80;
+ return 1;
+ }
+
+ final int originalOffset = dstOffset;
+
+ int scale = value.scale();
+ int exponent = value.precision() - scale;
+
+ if (signum < 0) {
+ if (exponent >= -0x3e && exponent < 0x3e) {
+ dst[dstOffset++] = (byte) (0x3f - exponent);
+ } else {
+ if (exponent < 0) {
+ dst[dstOffset++] = (byte) 0x7e;
+ } else {
+ dst[dstOffset++] = (byte) 1;
+ }
+ DataEncoder.encode(~exponent, dst, dstOffset);
+ dstOffset += 4;
+ }
+ } else {
+ if (exponent >= -0x3e && exponent < 0x3e) {
+ dst[dstOffset++] = (byte) (exponent + 0xc0);
+ } else {
+ if (exponent < 0) {
+ dst[dstOffset++] = (byte) 0x81;
+ } else {
+ dst[dstOffset++] = (byte) 0xfe;
+ }
+ DataEncoder.encode(exponent, dst, dstOffset);
+ dstOffset += 4;
+ }
+ }
+
+ dstOffset += encode(value.unscaledValue(), dst, dstOffset);
+
+ /* Encoding of scale:
+
+ 0x00: negative scale; four bytes follow for scale
+ 0x01..0x7f: negative scale; 7f range, -127..-1
+ 0x80..0xfe: positive scale; 7f range, 0..126
+ 0xff: positive scale; four bytes follow for scale
+ */
+
+ if (signum < 0) {
+ scale = ~scale;
+ }
+ if (scale >= -0x7f && scale < 0x7f) {
+ dst[dstOffset++] = (byte) (scale + 0x80);
+ } else {
+ dst[dstOffset++] = scale < 0 ? ((byte) 0) : ((byte) 0xff);
+ DataEncoder.encode(scale, dst, dstOffset);
+ dstOffset += 4;
+ }
+
+ return dstOffset - originalOffset;
+ }
+
+ /**
+ * Encodes the given optional BigDecimal into a variable amount of bytes
+ * for descending order. If the BigDecimal is null, exactly 1 byte is
+ * written. Otherwise, the amount written can be determined by calling
+ * calculateEncodedLength.
+ *
+ * <p><i>Note:</i> It is recommended that value be normalized by stripping
+ * trailing zeros. This makes searching by value much simpler.
+ *
+ * @param value BigDecimal value to encode, may be null
+ * @param dst destination for encoded bytes
+ * @param dstOffset offset into destination array
+ * @return amount of bytes written
+ * @since 1.2
+ */
+ public static int encodeDesc(BigDecimal value, byte[] dst, int dstOffset) {
+ /* Encoding of first byte:
+
+ 0x00: null high (unused)
+ 0x01: positive signum; four bytes follow for positive exponent
+ 0x02..0x3f: positive signum; positive exponent; 3e range, 0..61
+ 0x40..0x7d: positive signum; negative exponent; 3e range, -62..-1
+ 0x7e: positive signum; four bytes follow for negative exponent
+ 0x7f: zero
+ 0x80: negative zero (unused)
+ 0x81: negative signum; four bytes follow for negative exponent
+ 0x82..0xbf: negative signum; negative exponent; 3e range, -1..-62
+ 0xc0..0xfd: negative signum; positive exponent; 3e range, 61..0
+ 0xfe: negative signum; four bytes follow for positive exponent
+ 0xff: null low
+ */
+
+ if (value == null) {
+ dst[dstOffset] = NULL_BYTE_LOW;
+ return 1;
+ }
+
+ int signum = value.signum();
+ if (signum == 0) {
+ dst[dstOffset] = (byte) 0x7f;
+ return 1;
+ }
+
+ final int originalOffset = dstOffset;
+
+ int scale = value.scale();
+ int exponent = value.precision() - scale;
+
+ if (signum < 0) {
+ if (exponent >= -0x3e && exponent < 0x3e) {
+ dst[dstOffset++] = (byte) (exponent + 0xc0);
+ } else {
+ if (exponent < 0) {
+ dst[dstOffset++] = (byte) 0x81;
+ } else {
+ dst[dstOffset++] = (byte) 0xfe;
+ }
+ DataEncoder.encode(exponent, dst, dstOffset);
+ dstOffset += 4;
+ }
+ } else {
+ if (exponent >= -0x3e && exponent < 0x3e) {
+ dst[dstOffset++] = (byte) (0x3f - exponent);
+ } else {
+ if (exponent < 0) {
+ dst[dstOffset++] = (byte) 0x7e;
+ } else {
+ dst[dstOffset++] = (byte) 1;
+ }
+ DataEncoder.encode(~exponent, dst, dstOffset);
+ dstOffset += 4;
+ }
+ }
+
+ dstOffset += encodeDesc(value.unscaledValue(), dst, dstOffset);
+
+ /* Encoding of scale:
+
+ 0x00: positive scale; four bytes follow for scale
+ 0x01..0x7f: positive scale; 7f range, 0..126
+ 0x80..0xfe: negative scale; 7f range, -127..-1
+ 0xff: negative scale; four bytes follow for scale
+ */
+
+ if (signum < 0) {
+ scale = ~scale;
+ }
+ if (scale >= -0x7f && scale < 0x7f) {
+ dst[dstOffset++] = (byte) (0x7f - scale);
+ } else {
+ dst[dstOffset++] = scale < 0 ? ((byte) 0xff) : ((byte) 0);
+ DataEncoder.encode(~scale, dst, dstOffset);
+ dstOffset += 4;
+ }
+
+ return dstOffset - originalOffset;
+ }
+
+ /**
+ * Returns the amount of bytes required to encode a BigDecimal.
+ *
+ * <p><i>Note:</i> It is recommended that value be normalized by stripping
+ * trailing zeros. This makes searching by value much simpler.
+ *
+ * @param value BigDecimal value to encode, may be null
+ * @return amount of bytes needed to encode
+ * @since 1.2
+ */
+ public static int calculateEncodedLength(BigDecimal value) {
+ if (value == null || value.signum() == 0) {
+ return 1;
+ }
+
+ int scale = value.scale();
+ int exponent = value.precision() - value.scale();
+
+ int headerSize = (exponent >= -0x3e && exponent < 0x3e) ? 1 : 5;
+ int scaleSize = (scale >= -0x7f && scale < 0x7f) ? 1 : 5;
+
+ return headerSize + calculateEncodedLength(value.unscaledValue()) + scaleSize;
+ }
+
+ /**
* Encodes the given optional unsigned byte array into a variable amount of
* bytes. If the byte array is null, exactly 1 byte is written. Otherwise,
* the amount written can be determined by calling calculateEncodedLength.