| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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 org.apache.cassandra.cql3.functions.types; |
| |
| import java.nio.ByteBuffer; |
| |
| import org.apache.cassandra.transport.ProtocolVersion; |
| |
| /** |
| * 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. |
| */ |
| static int readSize(ByteBuffer input, ProtocolVersion version) |
| { |
| switch (version) |
| { |
| case V1: |
| case V2: |
| return getUnsignedShort(input); |
| case V3: |
| case V4: |
| case V5: |
| case V6: |
| return input.getInt(); |
| default: |
| throw new IllegalArgumentException(String.valueOf(version)); |
| } |
| } |
| |
| /** |
| * 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. |
| */ |
| private 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.asInt(), size)); |
| output.putShort((short) size); |
| break; |
| case V3: |
| case V4: |
| case V5: |
| case V6: |
| output.putInt(size); |
| break; |
| default: |
| throw new IllegalArgumentException(String.valueOf(version)); |
| } |
| } |
| |
| /** |
| * 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 V6: |
| if (value == null) |
| { |
| output.putInt(-1); |
| } |
| else |
| { |
| output.putInt(value.remaining()); |
| output.put(value.duplicate()); |
| } |
| break; |
| default: |
| throw new IllegalArgumentException(String.valueOf(version)); |
| } |
| } |
| |
| /** |
| * 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. |
| */ |
| 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. |
| */ |
| 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. |
| */ |
| 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); |
| } |
| |
| private static int sizeOfCollectionSize(ProtocolVersion version) |
| { |
| switch (version) |
| { |
| case V1: |
| case V2: |
| return 2; |
| case V3: |
| case V4: |
| case V5: |
| case V6: |
| return 4; |
| default: |
| throw new IllegalArgumentException(String.valueOf(version)); |
| } |
| } |
| |
| 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.asInt(), elemSize)); |
| return 2 + elemSize; |
| case V3: |
| case V4: |
| case V5: |
| case V6: |
| return value == null ? 4 : 4 + value.remaining(); |
| default: |
| throw new IllegalArgumentException(String.valueOf(version)); |
| } |
| } |
| |
| private static int getUnsignedShort(ByteBuffer bb) |
| { |
| int length = (bb.get() & 0xFF) << 8; |
| return length | (bb.get() & 0xFF); |
| } |
| } |