oracle.nosql.driver.util.SerializationUtil Maven / Gradle / Ivy
Show all versions of nosqldriver Show documentation
/*-
* Copyright (c) 2011, 2020 Oracle and/or its affiliates. All rights reserved.
*
* Licensed under the Universal Permissive License v 1.0 as shown at
* https://oss.oracle.com/licenses/upl/
*/
package oracle.nosql.driver.util;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.lang.reflect.Array;
import java.math.MathContext;
import java.math.RoundingMode;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
/**
* Utility methods to facilitate serialization/deserialization
*
* The numeric methods use PackedInteger class which uses a format
* that is always sorted.
*/
public class SerializationUtil {
public static final String EMPTY_STRING = new String();
public static final byte[] EMPTY_BYTES = { };
/**
* Reads a packed integer from the input and returns it.
*
* @param in the data input
* @return the integer that was read
*/
public static int readPackedInt(DataInput in) throws IOException {
final byte[] bytes = new byte[PackedInteger.MAX_LENGTH];
in.readFully(bytes, 0, 1);
final int len = PackedInteger.getReadSortedIntLength(bytes, 0);
try {
in.readFully(bytes, 1, len - 1);
} catch (IndexOutOfBoundsException e) {
throw new IOException("Invalid packed int", e);
}
return PackedInteger.readSortedInt(bytes, 0);
}
/**
* Skips over a packed integer from this input stream.
*
* @param in the data input
* @return the number of bytes skipped.
*/
public static int skipPackedInt(ByteInputStream in)
throws IOException {
byte b = in.readByte();
int len = PackedInteger.getReadSortedIntLength(new byte[]{b}, 0);
if (len > 1) {
in.skip(len - 1);
}
return len;
}
/**
* Writes a packed integer to the output.
*
* @param out the data output
* @param value the integer to be written
* @return the length of bytes written
*/
public static int writePackedInt(DataOutput out, int value)
throws IOException {
final byte[] buf = new byte[PackedInteger.MAX_LENGTH];
final int offset = PackedInteger.writeSortedInt(buf, 0, value);
out.write(buf, 0, offset);
return offset;
}
/**
* Reads a packed long from the input and returns it.
*
* @param in the data input
* @return the long that was read
*/
public static long readPackedLong(ByteInputStream in) throws IOException {
final byte[] bytes = new byte[PackedInteger.MAX_LONG_LENGTH];
in.readFully(bytes, 0, 1);
final int len = PackedInteger.getReadSortedLongLength(bytes, 0);
try {
in.readFully(bytes, 1, len - 1);
} catch (IndexOutOfBoundsException e) {
throw new IOException("Invalid packed long", e);
}
return PackedInteger.readSortedLong(bytes, 0);
}
/**
* Skips over a packed long from this input stream.
*
* @param in the data input
* @return the number of bytes skipped.
*/
public static int skipPackedLong(ByteInputStream in) throws IOException {
byte b = in.readByte();
int len = PackedInteger.getReadSortedLongLength(new byte[]{b}, 0);
if (len > 1) {
in.skip(len - 1);
}
return len;
}
/**
* Writes a packed long to the output.
*
* @param out the data output
* @param value the long to be written
*/
public static void writePackedLong(DataOutput out, long value)
throws IOException {
final byte[] buf = new byte[PackedInteger.MAX_LONG_LENGTH];
final int offset = PackedInteger.writeSortedLong(buf, 0, value);
out.write(buf, 0, offset);
}
/**
* Reads a string written by {@link #writeString}, using standard UTF-8
*
* @param in the input stream
* @return a string or null
* @throws IOException if an I/O error occurs or if the input UTF-8
* encoding is invalid
*/
public static String readString(ByteInputStream in)
throws IOException {
return readStdUTF8String(in);
}
/**
* Skips over a string from this input stream.
*
* @param in the data input
* @return the number of bytes skipped.
*/
public static int skipString(ByteInputStream in)
throws IOException {
int start = in.getOffset();
int len = readPackedInt(in);
if (len > 0) {
in.skip(len);
}
return in.getOffset() - start;
}
/**
* Reads a non-null string written by {@link #writeNonNullString}, using
* standard UTF-8 or Java's modified UTF-8 format, depending on the serial
* version.
*
* @param in the input stream
* @return a string
* @throws IOException if an I/O error occurs, if the input represents a
* null value, or if the input UTF-8 encoding is invalid
*/
public static String readNonNullString(ByteInputStream in)
throws IOException {
final String result = readString(in);
if (result == null) {
throw new IOException("Found null value for non-null string");
}
return result;
}
/**
* Reads a possibly null string from an input stream in standard UTF-8
* format.
*
* First reads a {@link #readSortedInt packedInt} representing the
* length of the UTF-8 encoding of the string, or a negative value for
* null, followed by the string contents in UTF-8 format for a non-empty
* string, if any.
*
* @param in the input stream
* @return the string
* @throws IOException if an I/O error occurs or the input UTF-8 encoding
* is invalid
*/
private static String readStdUTF8String(ByteInputStream in)
throws IOException {
final int length = readPackedInt(in);
if (length < -1) {
throw new IOException("Invalid length of String: " + length);
}
if (length == -1) {
return null;
}
if (length == 0) {
return EMPTY_STRING;
}
final byte[] bytes = new byte[length];
in.readFully(bytes);
return StandardCharsets.UTF_8.decode(ByteBuffer.wrap(bytes)).toString();
}
/**
* Writes a string for reading by {@link #readString}, using standard UTF-8
* format. The string may be null or empty. This code differentiates
* between the two, maintaining the ability to round-trip null and empty
* string values.
*
*
This format is used rather than that of {@link ByteOutputStream#writeUTF}
* to allow packing of the size of the string. For shorter strings this
* size savings is a significant percentage of the space used.
*
* The format is the standard UTF-8 format documented by RFC 2279 and implemented
* by the {@link Charset} class using the "UTF-8" standard charset.
*
*
Format:
*
* - ({@link #writePackedInt packed int}) string length, or -1
* for null
*
- [Optional] ({@code byte[]}) UTF-8 bytes
*
*
* @param out the output stream
* @param value the string or null
* @throws IOException if an I/O error occurs
*/
public static int writeString(DataOutput out, String value)
throws IOException {
return writeStdUTF8String(out, value);
}
/**
* Writes a non-null string for reading by {@link #readNonNullString},
* using the same format as {@link #writeString}, but not permitting a null
* value to be written.
*
* @param out the output stream
* @param value the string
* @throws IOException if an I/O error occurs
* @throws IllegalArgumentException if {@code value} is {@code null}
*/
public static void writeNonNullString(DataOutput out, String value)
throws IOException {
checkNull("value", value);
writeString(out, value);
}
/**
* Writes a possibly null or empty string to an output stream using
* standard UTF-8 format.
*
* First writes a {@link #writeSortedInt packedInt} representing the
* length of the UTF-8 encoding of the string, or {@code -1} if the string
* is null, followed by the UTF-8 encoding for non-empty strings.
*
* @param out the output stream
* @param value the string or null
* @throws IOException if an I/O error occurs
*/
private static int writeStdUTF8String(DataOutput out, String value)
throws IOException {
if (value == null) {
return writePackedInt(out, -1);
}
final ByteBuffer buffer = StandardCharsets.UTF_8.encode(value);
final int length = buffer.limit();
int len = writePackedInt(out, length);
if (length > 0) {
out.write(buffer.array(), 0, length);
}
return len + length;
}
/**
* Reads the length of a possibly null sequence. The length is represented
* as a {@link #readPackedInt packed int}, with -1 interpreted as meaning
* null, and other negative values not permitted. Although we don't
* enforce maximum sequence lengths yet, this entrypoint provides a place
* to do that.
*
* @param in the input stream
* @return the sequence length or -1 for null
* @throws IOException if an I/O error occurs or the input format is
* invalid
*/
public static int readSequenceLength(ByteInputStream in)
throws IOException {
final int result = readPackedInt(in);
if (result < -1) {
throw new IOException("Invalid sequence length: " + result);
}
return result;
}
/**
* Reads the length of a non-null sequence. The length is represented as a
* non-negative {@link #readPackedInt packed int}. Although we don't
* enforce maximum sequence lengths yet, this entrypoint provides a place
* to do that.
*
* @param in the input stream
* @return the sequence length
* @throws IOException if an I/O error occurs, if the input represents a
* null sequence, or if the input format is invalid
*/
public static int readNonNullSequenceLength(ByteInputStream in)
throws IOException {
final int length = readSequenceLength(in);
if (length == -1) {
throw new IOException("Read null length for non-null sequence");
}
return length;
}
/**
* Writes a sequence length. The length is represented as a {@link
* #readPackedInt packed int}, with -1 representing null. Although we
* don't enforce maximum sequence lengths yet, this entrypoint provides a
* place to do that.
*
* @param out the output stream
* @param length the sequence length or -1
* @throws IOException if an I/O error occurs
* @throws IllegalArgumentException if length is less than -1
*/
public static void writeSequenceLength(DataOutput out, int length)
throws IOException {
if (length < -1) {
throw new IllegalArgumentException(
"Invalid sequence length: " + length);
}
writePackedInt(out, length);
}
/**
* Writes the length of a non-null sequence. The length is represented as
* a non-negative {@link #readPackedInt packed int}. Although we don't
* enforce maximum sequence lengths yet, this entrypoint provides a place
* to do that.
*
* @param out the output stream
* @param length the sequence length
* @throws IOException if an I/O error occurs
* @throws IllegalArgumentException if length is less than 0
*/
public static void writeNonNullSequenceLength(DataOutput out, int length)
throws IOException {
if (length < 0) {
throw new IllegalArgumentException(
"Invalid non-null sequence length: " + length);
}
writePackedInt(out, length);
}
/**
* Reads a possibly null byte array as a {@link #readSequenceLength
* sequence length} followed by the array contents.
*
* @param in the input stream
* @return array the array or null
* @throws IOException if an I/O error occurs or if the input format is
* invalid
*/
public static byte[] readByteArray(ByteInputStream in)
throws IOException {
final int len = readSequenceLength(in);
if (len < -1) {
throw new IOException("Invalid length of byte array: " + len);
}
if (len == -1) {
return null;
}
if (len == 0) {
return EMPTY_BYTES;
}
final byte[] array = new byte[len];
in.readFully(array);
return array;
}
/**
* Skips over a byte array from this input stream.
*
* @param in the data input
* @return the number of bytes skipped.
*/
public static int skipByteArray(ByteInputStream in)
throws IOException {
int start = in.getOffset();
final int len = readSequenceLength(in);
if (len > 0) {
in.skip(len);
}
return in.getOffset() - start;
}
/**
* Writes a possibly null byte array as a {@link #writeSequenceLength
* sequence length} followed by the array contents.
*
* @param out the output stream
* @param array the byte array or null
* @throws IOException if an I/O error occurs
*/
public static void writeByteArray(DataOutput out, byte[] array)
throws IOException {
final int length = (array == null) ? -1 : Array.getLength(array);
writeSequenceLength(out, length);
if (length > 0) {
out.write(array);
}
}
/**
* Reads a non-null byte array as a {@link #readNonNullSequenceLength
* non-null sequence length} followed by the array contents.
*
* @param in the input stream
* @return array the array
* @throws IOException if an I/O error occurs, if the input represents a
* null array, or if the input format is invalid
*/
public static byte[] readNonNullByteArray(ByteInputStream in)
throws IOException {
final byte[] array = readByteArray(in);
if (array == null) {
throw new IOException("Read unexpected null array");
}
return array;
}
/**
* Writes a non-null byte array as a {@link #writeNonNullSequenceLength
* non-null sequence length} followed by the array contents.
*
* @param out the output stream
* @param array the byte array
* @throws IOException if an I/O error occurs
*/
public static void writeNonNullByteArray(DataOutput out, byte[] array)
throws IOException {
checkNull("array", array);
writeByteArray(out, array);
}
/**
* Writes a possibly null int array as a {@link #writeSequenceLength
* sequence length} followed by the array contents.
*
* @param out the output stream
* @param array the int array or null
* @throws IOException if an I/O error occurs
*/
public static void writePackedIntArray(DataOutput out, int[] array)
throws IOException {
final int len = (array == null ? -1 : array.length);
writeSequenceLength(out, len);
if (array != null) {
for (int v : array) {
writePackedInt(out, v);
}
}
}
/**
* Reads a possibly null int array as a {@link #readSequenceLength
* sequence length} followed by the array contents.
*
* @param in the input stream
* @return array the array or null
* @throws IOException if an I/O error occurs or if the input format is
* invalid
*/
public static int[] readPackedIntArray(ByteInputStream in)
throws IOException {
final int len = readSequenceLength(in);
if (len < -1) {
throw new IOException("Invalid length of byte array: " + len);
}
if (len == -1) {
return null;
}
final int[] array = new int[len];
for (int i = 0; i < len; ++i) {
array[i] = readPackedInt(in);
}
return array;
}
/**
* Writes a possibly null int array as a {@link #writeSequenceLength
* sequence length} followed by the array contents.
*
* @param out the output stream
* @param array the int array or null
* @throws IOException if an I/O error occurs
*/
public static void writeIntArray(DataOutput out, int[] array)
throws IOException {
final int len = (array == null ? -1 : array.length);
writeSequenceLength(out, len);
if (array != null) {
for (int v : array) {
out.writeInt(v);
}
}
}
/**
* Reads a possibly null int array as a {@link #readSequenceLength
* sequence length} followed by the array contents.
*
* @param in the input stream
* @return array the array or null
* @throws IOException if an I/O error occurs or if the input format is
* invalid
*/
public static int[] readIntArray(ByteInputStream in)
throws IOException {
final int len = readSequenceLength(in);
if (len < -1) {
throw new IOException("Invalid length of byte array: " + len);
}
if (len == -1) {
return null;
}
final int[] array = new int[len];
for (int i = 0; i < len; ++i) {
array[i] = in.readInt();
}
return array;
}
public static String[] readStringArray(ByteInputStream in)
throws IOException {
final int len = readSequenceLength(in);
if (len < -1) {
throw new IOException("Invalid length of byte array: " + len);
}
if (len == -1) {
return null;
}
final String[] array = new String[len];
for (int i = 0; i < len; ++i) {
array[i] = readString(in);
}
return array;
}
public static void writeMathContext(
MathContext mathContext,
DataOutput out)
throws IOException {
if (mathContext == null) {
out.writeByte(0);
} else if (MathContext.DECIMAL32.equals(mathContext)) {
out.writeByte(1);
} else if (MathContext.DECIMAL64.equals(mathContext)) {
out.writeByte(2);
} else if (MathContext.DECIMAL128.equals(mathContext)) {
out.writeByte(3);
} else if (MathContext.UNLIMITED.equals(mathContext)) {
out.writeByte(4);
} else {
out.writeByte(5);
out.writeInt(mathContext.getPrecision());
out.writeInt(mathContext.getRoundingMode().ordinal());
}
}
public static MathContext readMathContext(DataInput in)
throws IOException {
int code = in.readByte();
switch (code) {
case 0:
return null;
case 1:
return MathContext.DECIMAL32;
case 2:
return MathContext.DECIMAL64;
case 3:
return MathContext.DECIMAL128;
case 4:
return MathContext.UNLIMITED;
case 5:
int precision = in.readInt();
int roundingMode = in.readInt();
return
new MathContext(precision, RoundingMode.valueOf(roundingMode));
default:
throw new IOException("Unknown MathContext code.");
}
}
private static void checkNull(final String variableName,
final Object value) {
if (value == null) {
throw new IllegalArgumentException(
"The value of " + variableName + " must not be null");
}
}
}