/*
* Copyright 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.oss.driver.api.core.type.codec;
import com.datastax.oss.driver.api.core.ProtocolVersion;
import com.datastax.oss.driver.api.core.cql.Row;
import com.datastax.oss.driver.api.core.data.TupleValue;
import com.datastax.oss.driver.api.core.data.UdtValue;
import com.datastax.oss.driver.api.core.metadata.schema.AggregateMetadata;
import com.datastax.oss.driver.api.core.type.DataType;
import com.datastax.oss.driver.api.core.type.reflect.GenericType;
import com.datastax.oss.driver.shaded.guava.common.base.Preconditions;
import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.Nullable;
import java.nio.ByteBuffer;
Manages the two-way conversion between a CQL type and a Java type.
Type codec implementations:
- must be thread-safe.
- must perform fast and never block.
- must support all native protocol versions; it is not possible to use different
codecs for the same types but under different protocol versions.
- must comply with the native protocol specifications; failing to do so will result
in unexpected results and could cause the driver to crash.
- should be stateless and immutable.
- should interpret
null
values and empty byte buffers (i.e. Buffer.remaining()
== 0
) in a reasonable way; usually,
NULL
CQL values should map to null
references, but exceptions exist; e.g. for varchar types, a NULL
CQL value maps to a null
reference, whereas an empty buffer maps to an empty String. For collection types, it is also admitted that NULL
CQL values map to empty Java collections instead of null
references. In any case, the codec's behavior with respect to null
values and empty ByteBuffers should be clearly documented. - for Java types that have a primitive equivalent, should implement the appropriate "primitive" codec interface, e.g.
PrimitiveBooleanCodec
for boolean
. This allows the driver to avoid the overhead of boxing when using primitive accessors such as GettableByIndex.getBoolean(int)
. - when decoding, must not consume
ByteBuffer
instances by performing relative read operations that modify their current position; codecs should instead prefer absolute read methods or, if necessary, duplicate
their byte buffers prior to reading them.
/**
* Manages the two-way conversion between a CQL type and a Java type.
*
* <p>Type codec implementations:
*
* <ol>
* <li><em>must</em> be thread-safe.
* <li><em>must</em> perform fast and never block.
* <li><em>must</em> support all native protocol versions; it is not possible to use different
* codecs for the same types but under different protocol versions.
* <li><em>must</em> comply with the native protocol specifications; failing to do so will result
* in unexpected results and could cause the driver to crash.
* <li><em>should</em> be stateless and immutable.
* <li><em>should</em> interpret {@code null} values and empty byte buffers (i.e. <code>
* {@link ByteBuffer#remaining()} == 0</code>) in a <em>reasonable</em> way; usually, {@code
* NULL} CQL values should map to {@code null} references, but exceptions exist; e.g. for
* varchar types, a {@code NULL} CQL value maps to a {@code null} reference, whereas an empty
* buffer maps to an empty String. For collection types, it is also admitted that {@code NULL}
* CQL values map to empty Java collections instead of {@code null} references. In any case,
* the codec's behavior with respect to {@code null} values and empty ByteBuffers should be
* clearly documented.
* <li>for Java types that have a primitive equivalent, <em>should</em> implement the appropriate
* "primitive" codec interface, e.g. {@link PrimitiveBooleanCodec} for {@code boolean}. This
* allows the driver to avoid the overhead of boxing when using primitive accessors such as
* {@link Row#getBoolean(int)}.
* <li>when decoding, <em>must</em> not consume {@link ByteBuffer} instances by performing
* relative read operations that modify their current position; codecs should instead prefer
* absolute read methods or, if necessary, {@link ByteBuffer#duplicate() duplicate} their byte
* buffers prior to reading them.
* </ol>
*/
public interface TypeCodec<JavaTypeT> {
@NonNull
GenericType<JavaTypeT> getJavaType();
@NonNull
DataType getCqlType();
Whether this codec is capable of processing the given Java type.
The default implementation is invariant with respect to the passed argument (through the usage of GenericType.equals(Object)
) and it's strongly recommended not
to modify this behavior. This means that a codec will only ever accept the exact
Java type that it has been created for.
If the argument represents a Java primitive type, its wrapper type is considered instead.
/**
* Whether this codec is capable of processing the given Java type.
*
* <p>The default implementation is <em>invariant</em> with respect to the passed argument
* (through the usage of {@link GenericType#equals(Object)}) and <em>it's strongly recommended not
* to modify this behavior</em>. This means that a codec will only ever accept the <em>exact</em>
* Java type that it has been created for.
*
* <p>If the argument represents a Java primitive type, its wrapper type is considered instead.
*/
default boolean accepts(@NonNull GenericType<?> javaType) {
Preconditions.checkNotNull(javaType);
return getJavaType().equals(javaType.wrap());
}
Whether this codec is capable of processing the given Java class.
This implementation simply compares the given class (or its wrapper type if it is a
primitive type) against this codec's runtime (raw) class; it is invariant with respect to the passed argument (through the usage of Object.equals(Object)
and it's strongly
recommended not to modify this behavior. This means that a codec will only ever return true
for the exact runtime (raw) Java class that it has been created for.
Implementors are encouraged to override this method if there is a more efficient way. In particular, if the codec targets a final class, the check can be done with a simple ==
.
/**
* Whether this codec is capable of processing the given Java class.
*
* <p>This implementation simply compares the given class (or its wrapper type if it is a
* primitive type) against this codec's runtime (raw) class; it is <em>invariant</em> with respect
* to the passed argument (through the usage of {@link Class#equals(Object)} and <em>it's strongly
* recommended not to modify this behavior</em>. This means that a codec will only ever return
* {@code true} for the <em>exact</em> runtime (raw) Java class that it has been created for.
*
* <p>Implementors are encouraged to override this method if there is a more efficient way. In
* particular, if the codec targets a final class, the check can be done with a simple {@code ==}.
*/
default boolean accepts(@NonNull Class<?> javaClass) {
Preconditions.checkNotNull(javaClass);
if (javaClass.isPrimitive()) {
if (javaClass == Boolean.TYPE) {
javaClass = Boolean.class;
} else if (javaClass == Character.TYPE) {
javaClass = Character.class;
} else if (javaClass == Byte.TYPE) {
javaClass = Byte.class;
} else if (javaClass == Short.TYPE) {
javaClass = Short.class;
} else if (javaClass == Integer.TYPE) {
javaClass = Integer.class;
} else if (javaClass == Long.TYPE) {
javaClass = Long.class;
} else if (javaClass == Float.TYPE) {
javaClass = Float.class;
} else if (javaClass == Double.TYPE) {
javaClass = Double.class;
}
}
return getJavaType().getRawType().equals(javaClass);
}
Whether this codec is capable of encoding the given Java object.
The object's Java type is inferred from its runtime (raw) type, contrary to accepts(GenericType)
which is capable of handling generic types.
Contrary to other accept
methods, this method's default implementation is covariant with respect to the passed argument (through the usage of Class.isAssignableFrom(Class)
) and it's strongly recommended not to modify this
behavior. This means that, by default, a codec will accept any subtype of the Java type that it has been created for. This is so because codec lookups by arbitrary Java objects only make sense when attempting to encode, never when attempting to decode, and indeed the encode method is covariant with
JavaTypeT
.
It can only handle non-parameterized types; codecs handling parameterized types, such as
collection types, must override this method and perform some sort of "manual" inspection of the
actual type parameters.
Similarly, codecs that only accept a partial subset of all possible values must override
this method and manually inspect the object to check if it complies or not with the codec's
limitations.
Finally, if the codec targets a non-generic Java class, it might be possible to implement this method with a simple instanceof
check.
/**
* Whether this codec is capable of encoding the given Java object.
*
* <p>The object's Java type is inferred from its runtime (raw) type, contrary to {@link
* #accepts(GenericType)} which is capable of handling generic types.
*
* <p>Contrary to other {@code accept} methods, this method's default implementation is
* <em>covariant</em> with respect to the passed argument (through the usage of {@link
* Class#isAssignableFrom(Class)}) and <em>it's strongly recommended not to modify this
* behavior</em>. This means that, by default, a codec will accept <em>any subtype</em> of the
* Java type that it has been created for. This is so because codec lookups by arbitrary Java
* objects only make sense when attempting to encode, never when attempting to decode, and indeed
* the {@linkplain #encode(Object, ProtocolVersion) encode} method is covariant with {@code
* JavaTypeT}.
*
* <p>It can only handle non-parameterized types; codecs handling parameterized types, such as
* collection types, must override this method and perform some sort of "manual" inspection of the
* actual type parameters.
*
* <p>Similarly, codecs that only accept a partial subset of all possible values must override
* this method and manually inspect the object to check if it complies or not with the codec's
* limitations.
*
* <p>Finally, if the codec targets a non-generic Java class, it might be possible to implement
* this method with a simple {@code instanceof} check.
*/
default boolean accepts(@NonNull Object value) {
Preconditions.checkNotNull(value);
return getJavaType().getRawType().isAssignableFrom(value.getClass());
}
Whether this codec is capable of processing the given CQL type. /** Whether this codec is capable of processing the given CQL type. */
default boolean accepts(@NonNull DataType cqlType) {
Preconditions.checkNotNull(cqlType);
return this.getCqlType().equals(cqlType);
}
Encodes the given value in the binary format of the CQL type handled by this codec.
- Null values should be gracefully handled and no exception should be raised; they should
be considered as the equivalent of a NULL CQL value;
- Codecs for CQL collection types should not permit null elements;
- Codecs for CQL collection types should treat a
null
input as the equivalent of an empty collection.
/**
* Encodes the given value in the binary format of the CQL type handled by this codec.
*
* <ul>
* <li>Null values should be gracefully handled and no exception should be raised; they should
* be considered as the equivalent of a NULL CQL value;
* <li>Codecs for CQL collection types should not permit null elements;
* <li>Codecs for CQL collection types should treat a {@code null} input as the equivalent of an
* empty collection.
* </ul>
*/
@Nullable
ByteBuffer encode(@Nullable JavaTypeT value, @NonNull ProtocolVersion protocolVersion);
Decodes a value from the binary format of the CQL type handled by this codec.
- Null or empty buffers should be gracefully handled and no exception should be raised; they should be considered as the equivalent of a NULL CQL value and, in most cases, should map to
null
or a default value for the corresponding Java type, if applicable; - Codecs for CQL collection types should clearly document whether they return immutable
collections or not (note that the driver's default collection codecs return
mutable collections);
- Codecs for CQL collection types should avoid returning
null
; they should return empty collections instead (the driver's default collection codecs all comply with this rule); - The provided
ByteBuffer
should never be consumed by read operations that modify its current position; if necessary, ByteBuffer.duplicate()
duplicate} it before consuming.
/**
* Decodes a value from the binary format of the CQL type handled by this codec.
*
* <ul>
* <li>Null or empty buffers should be gracefully handled and no exception should be raised;
* they should be considered as the equivalent of a NULL CQL value and, in most cases,
* should map to {@code null} or a default value for the corresponding Java type, if
* applicable;
* <li>Codecs for CQL collection types should clearly document whether they return immutable
* collections or not (note that the driver's default collection codecs return
* <em>mutable</em> collections);
* <li>Codecs for CQL collection types should avoid returning {@code null}; they should return
* empty collections instead (the driver's default collection codecs all comply with this
* rule);
* <li>The provided {@link ByteBuffer} should never be consumed by read operations that modify
* its current position; if necessary, {@link ByteBuffer#duplicate()} duplicate} it before
* consuming.
* </ul>
*/
@Nullable
JavaTypeT decode(@Nullable ByteBuffer bytes, @NonNull ProtocolVersion protocolVersion);
Formats the given value as a valid CQL literal according to the CQL type handled by this codec.
Implementors should take care of quoting and escaping the resulting CQL literal where applicable. Null values should be accepted; in most cases, implementations should return the CQL keyword "NULL"
for null
inputs.
Implementing this method is not strictly mandatory. It is used:
- by the request logger, if parameter logging is enabled;
- to format the INITCOND in
AggregateMetadata.describe(boolean)
; - in the
toString()
representation of some driver objects (such as UdtValue
and TupleValue
), which is only used in driver logs; - for literal values in the query builder (see
QueryBuilder#literal(Object,
CodecRegistry)
and QueryBuilder#literal(Object, TypeCodec)
).
If you choose not to implement this method, don't throw an exception but instead return a
constant string (for example "XxxCodec.format not implemented").
/**
* Formats the given value as a valid CQL literal according to the CQL type handled by this codec.
*
* <p>Implementors should take care of quoting and escaping the resulting CQL literal where
* applicable. Null values should be accepted; in most cases, implementations should return the
* CQL keyword {@code "NULL"} for {@code null} inputs.
*
* <p>Implementing this method is not strictly mandatory. It is used:
*
* <ol>
* <li>by the request logger, if parameter logging is enabled;
* <li>to format the INITCOND in {@link AggregateMetadata#describe(boolean)};
* <li>in the {@code toString()} representation of some driver objects (such as {@link UdtValue}
* and {@link TupleValue}), which is only used in driver logs;
* <li>for literal values in the query builder (see {@code QueryBuilder#literal(Object,
* CodecRegistry)} and {@code QueryBuilder#literal(Object, TypeCodec)}).
* </ol>
*
* If you choose not to implement this method, don't throw an exception but instead return a
* constant string (for example "XxxCodec.format not implemented").
*/
@NonNull
String format(@Nullable JavaTypeT value);
Parse the given CQL literal into an instance of the Java type handled by this codec.
Implementors should take care of unquoting and unescaping the given CQL string where applicable. Null values and empty strings should be accepted, as well as the string
"NULL"
; in most cases, implementations should interpret these inputs has equivalent to a null
reference.
Implementing this method is not strictly mandatory: internally, the driver only uses it to parse the INITCOND when building the metadata of an aggregate
function
(and in most cases it will use a built-in codec, unless the INITCOND has a custom type).
If you choose not to implement this method, don't throw an exception but instead return null
.
/**
* Parse the given CQL literal into an instance of the Java type handled by this codec.
*
* <p>Implementors should take care of unquoting and unescaping the given CQL string where
* applicable. Null values and empty strings should be accepted, as well as the string {@code
* "NULL"}; in most cases, implementations should interpret these inputs has equivalent to a
* {@code null} reference.
*
* <p>Implementing this method is not strictly mandatory: internally, the driver only uses it to
* parse the INITCOND when building the {@link AggregateMetadata metadata of an aggregate
* function} (and in most cases it will use a built-in codec, unless the INITCOND has a custom
* type).
*
* <p>If you choose not to implement this method, don't throw an exception but instead return
* {@code null}.
*/
@Nullable
JavaTypeT parse(@Nullable String value);
}