blob: 22ca4ed2a510e3fbf5a7d25dd80eb63eee36c387 [file] [log] [blame]
/*
* 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);
}
}