com.datastax.oss.driver.api.core.type.codec.TypeCodec Maven / Gradle / Ivy
/*
* 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 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 {@code null} values and empty byte buffers (i.e.
* {@link ByteBuffer#remaining()} == 0
) in a reasonable 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.
* - for Java types that have a primitive equivalent, should 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)}.
*
- when decoding, must 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.
*
*/
public interface TypeCodec {
@NonNull
GenericType 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 {@link 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.
*/
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 {@link Class#equals(Object)} and it's strongly
* recommended not to modify this behavior. This means that a codec will only ever return
* {@code 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 {@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 {@link
* #accepts(GenericType)} which is capable of handling generic types.
*
*
Contrary to other {@code accept} methods, this method's default implementation is
* covariant with respect to the passed argument (through the usage of {@link
* 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 {@linkplain #encode(Object, ProtocolVersion) encode} method is covariant with {@code
* 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 {@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. */
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 {@code null} input as the equivalent of an
* empty collection.
*
*/
@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 {@code 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 {@code null}; they should return
* empty collections instead (the driver's default collection codecs all comply with this
* rule);
*
- 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.
*
*/
@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 {@code "NULL"} for {@code 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 {@link AggregateMetadata#describe(boolean)};
*
- in the {@code toString()} representation of some driver objects (such as {@link UdtValue}
* and {@link TupleValue}), which is only used in driver logs;
*
- for literal values in the query builder (see {@code QueryBuilder#literal(Object,
* CodecRegistry)} and {@code 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").
*/
@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 {@code
* "NULL"}; in most cases, implementations should interpret these inputs has equivalent to a
* {@code null} reference.
*
*
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).
*
*
If you choose not to implement this method, don't throw an exception but instead return
* {@code null}.
*/
@Nullable
JavaTypeT parse(@Nullable String value);
}