/* * Copyright (C) 2012-2015 DataStax Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ 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: 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: 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: 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. * <p/> * The protocol encodes DATE values as <em>unsigned</em> 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. * <p/> * The protocol encodes DATE values as <em>unsigned</em> 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. * <p/> * 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: 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: 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); } }