com.datastax.driver.core.CodecUtils Maven / Gradle / Ivy
/*
* Copyright DataStax, Inc.
*
* This software can be used solely with DataStax Enterprise. Please consult the license at
* http://www.datastax.com/terms/datastax-dse-driver-license-terms
*/
package com.datastax.driver.core;
import java.nio.ByteBuffer;
/** A set of utility methods to deal with type conversion and serialization. */
public final class CodecUtils {
private static final long MAX_CQL_LONG_VALUE = ((1L << 32) - 1);
private static final long EPOCH_AS_CQL_LONG = (1L << 31);
private CodecUtils() {}
/**
* Utility method that "packs" together a list of {@link ByteBuffer}s containing serialized
* collection elements. Mainly intended for use with collection codecs when serializing
* collections.
*
* @param buffers the collection elements
* @param elements the total number of elements
* @param version the protocol version to use
* @return The serialized collection
*/
public static ByteBuffer pack(ByteBuffer[] buffers, int elements, ProtocolVersion version) {
int size = 0;
for (ByteBuffer bb : buffers) {
int elemSize = sizeOfValue(bb, version);
size += elemSize;
}
ByteBuffer result = ByteBuffer.allocate(sizeOfCollectionSize(version) + size);
writeSize(result, elements, version);
for (ByteBuffer bb : buffers) writeValue(result, bb, version);
return (ByteBuffer) result.flip();
}
/**
* Utility method that reads a size value. Mainly intended for collection codecs when
* deserializing CQL collections.
*
* @param input The ByteBuffer to read from.
* @param version The protocol version to use.
* @return The size value.
*/
public static int readSize(ByteBuffer input, ProtocolVersion version) {
switch (version) {
case V1:
case V2:
return getUnsignedShort(input);
case V3:
case V4:
case V5:
case DSE_V1:
case DSE_V2:
return input.getInt();
default:
throw version.unsupported();
}
}
/**
* Utility method that writes a size value. Mainly intended for collection codecs when serializing
* CQL collections.
*
* @param output The ByteBuffer to write to.
* @param size The collection size.
* @param version The protocol version to use.
*/
public static void writeSize(ByteBuffer output, int size, ProtocolVersion version) {
switch (version) {
case V1:
case V2:
if (size > 65535)
throw new IllegalArgumentException(
String.format(
"Native protocol version %d supports up to 65535 elements in any collection - but collection contains %d elements",
version.toInt(), size));
output.putShort((short) size);
break;
case V3:
case V4:
case V5:
case DSE_V1:
case DSE_V2:
output.putInt(size);
break;
default:
throw version.unsupported();
}
}
/**
* Utility method that reads a value. Mainly intended for collection codecs when deserializing CQL
* collections.
*
* @param input The ByteBuffer to read from.
* @param version The protocol version to use.
* @return The collection element.
*/
public static ByteBuffer readValue(ByteBuffer input, ProtocolVersion version) {
int size = readSize(input, version);
return size < 0 ? null : readBytes(input, size);
}
/**
* Utility method that writes a value. Mainly intended for collection codecs when deserializing
* CQL collections.
*
* @param output The ByteBuffer to write to.
* @param value The value to write.
* @param version The protocol version to use.
*/
public static void writeValue(ByteBuffer output, ByteBuffer value, ProtocolVersion version) {
switch (version) {
case V1:
case V2:
assert value != null;
output.putShort((short) value.remaining());
output.put(value.duplicate());
break;
case V3:
case V4:
case V5:
case DSE_V1:
case DSE_V2:
if (value == null) {
output.putInt(-1);
} else {
output.putInt(value.remaining());
output.put(value.duplicate());
}
break;
default:
throw version.unsupported();
}
}
/**
* Read {@code length} bytes from {@code bb} into a new ByteBuffer.
*
* @param bb The ByteBuffer to read.
* @param length The number of bytes to read.
* @return The read bytes.
*/
public static ByteBuffer readBytes(ByteBuffer bb, int length) {
ByteBuffer copy = bb.duplicate();
copy.limit(copy.position() + length);
bb.position(bb.position() + length);
return copy;
}
/**
* Converts an "unsigned" int read from a DATE value into a signed int.
*
* The protocol encodes DATE values as unsigned ints with the Epoch in the middle of
* the range (2^31). This method handles the conversion from an "unsigned" to a signed int.
*/
public static int fromUnsignedToSignedInt(int unsigned) {
return unsigned + Integer.MIN_VALUE; // this relies on overflow for "negative" values
}
/**
* Converts an int into an "unsigned" int suitable to be written as a DATE value.
*
*
The protocol encodes DATE values as unsigned ints with the Epoch in the middle of
* the range (2^31). This method handles the conversion from a signed to an "unsigned" int.
*/
public static int fromSignedToUnsignedInt(int signed) {
return signed - Integer.MIN_VALUE;
}
/**
* Convert from a raw CQL long representing a numeric DATE literal to the number of days since the
* Epoch. In CQL, numeric DATE literals are longs (unsigned integers actually) between 0 and 2^32
* - 1, with the epoch in the middle; this method re-centers the epoch at 0.
*
* @param raw The CQL date value to convert.
* @return The number of days since the Epoch corresponding to the given raw value.
* @throws IllegalArgumentException if the value is out of range.
*/
public static int fromCqlDateToDaysSinceEpoch(long raw) {
if (raw < 0 || raw > MAX_CQL_LONG_VALUE)
throw new IllegalArgumentException(
String.format(
"Numeric literals for DATE must be between 0 and %d (got %d)",
MAX_CQL_LONG_VALUE, raw));
return (int) (raw - EPOCH_AS_CQL_LONG);
}
/**
* Convert the number of days since the Epoch into a raw CQL long representing a numeric DATE
* literal.
*
*
In CQL, numeric DATE literals are longs (unsigned integers actually) between 0 and 2^32 - 1,
* with the epoch in the middle; this method re-centers the epoch at 2^31.
*
* @param days The number of days since the Epoch convert.
* @return The CQL date value corresponding to the given value.
*/
public static long fromDaysSinceEpochToCqlDate(int days) {
return ((long) days + EPOCH_AS_CQL_LONG);
}
private static int sizeOfCollectionSize(ProtocolVersion version) {
switch (version) {
case V1:
case V2:
return 2;
case V3:
case V4:
case V5:
case DSE_V1:
case DSE_V2:
return 4;
default:
throw version.unsupported();
}
}
private static int sizeOfValue(ByteBuffer value, ProtocolVersion version) {
switch (version) {
case V1:
case V2:
int elemSize = value.remaining();
if (elemSize > 65535)
throw new IllegalArgumentException(
String.format(
"Native protocol version %d supports only elements with size up to 65535 bytes - but element size is %d bytes",
version.toInt(), elemSize));
return 2 + elemSize;
case V3:
case V4:
case V5:
case DSE_V1:
case DSE_V2:
return value == null ? 4 : 4 + value.remaining();
default:
throw version.unsupported();
}
}
private static int getUnsignedShort(ByteBuffer bb) {
int length = (bb.get() & 0xFF) << 8;
return length | (bb.get() & 0xFF);
}
}