Java tutorial
/* * 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 com.datastax.driver.core.exceptions.CodecNotFoundException; import com.google.common.base.Objects; import com.google.common.cache.*; import com.google.common.collect.ImmutableSet; import com.google.common.reflect.TypeToken; import com.google.common.util.concurrent.UncheckedExecutionException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.nio.ByteBuffer; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ExecutionException; import static com.datastax.driver.core.DataType.Name.*; import static com.google.common.base.Preconditions.checkNotNull; /** * A registry for {@link TypeCodec}s. When the driver needs to serialize or deserialize a Java type to/from CQL, * it will lookup in the registry for a suitable codec. The registry is initialized with default codecs that handle * basic conversions (e.g. CQL {@code text} to {@code java.lang.String}), and users can add their own. Complex * codecs can also be generated on-the-fly from simpler ones (more details below). * <h3> * Creating a registry * </h3> * By default, the driver uses {@link CodecRegistry#DEFAULT_INSTANCE}, a shareable, JVM-wide instance initialized with * built-in codecs for all the base CQL types. * The only reason to create your own instances is if you have multiple {@code Cluster} objects that use different * sets of codecs. In that case, use {@link com.datastax.driver.core.Cluster.Builder#withCodecRegistry(CodecRegistry)} * to associate the registry with the cluster: * <pre> * {@code * CodecRegistry myCodecRegistry = new CodecRegistry(); * myCodecRegistry.register(myCodec1, myCodec2, myCodec3); * Cluster cluster = Cluster.builder().withCodecRegistry(myCodecRegistry).build(); * * // To retrieve the registry later: * CodecRegistry registry = cluster.getConfiguration().getCodecRegistry();} * </pre> * {@code CodecRegistry} instances are thread-safe. * <p/> * It is possible to turn on log messages by setting the {@code com.datastax.driver.core.CodecRegistry} logger * level to {@code TRACE}. Beware that the registry can be very verbose at this log level. * <h3> * Registering and using custom codecs * </h3> * To create a custom codec, write a class that extends {@link TypeCodec}, create an instance, and pass it to one of * the {@link #register(TypeCodec) register} methods; for example, one could create a codec that maps CQL * timestamps to JDK8's {@code java.time.LocalDate}: * <pre> * {@code * class LocalDateCodec extends TypeCodec<java.time.LocalDate> { * ... * } * myCodecRegistry.register(new LocalDateCodec());} * </pre> * The conversion will be available to: * <ul> * <li>all driver types that implement {@link GettableByIndexData}, {@link GettableByNameData}, * {@link SettableByIndexData} and/or {@link SettableByNameData}. Namely: {@link Row}, * {@link BoundStatement}, {@link UDTValue} and {@link TupleValue};</li> * <li>{@link SimpleStatement#SimpleStatement(String, Object...) simple statements};</li> * <li>statements created with the {@link com.datastax.driver.core.querybuilder.QueryBuilder Query builder}.</li> * </ul> * <p/> * Example: * <pre> * {@code * Row row = session.executeQuery("select date from some_table where pk = 1").one(); * java.time.LocalDate date = row.get(0, java.time.LocalDate.class); // uses LocalDateCodec registered above} * </pre> * You can also bypass the codec registry by passing a standalone codec instance to methods such as * {@link GettableByIndexData#get(int, TypeCodec)}. * <h3> * Codec generation * </h3> * When a {@code CodecRegistry} cannot find a suitable codec among existing ones, it will attempt to create it on-the-fly. * It can manage: * <ul> * <li>collections (lists, sets and maps) of known types. For example, * if you registered a codec for JDK8's {@code java.time.LocalDate} like in the example above, you get * {@code List<LocalDate>>} and {@code Set<LocalDate>>} handled for free, * as well as all {@code Map} types whose keys and/or values are {@code java.time.LocalDate}. * This works recursively for nested collections;</li> * <li>{@link UserType user types}, mapped to {@link UDTValue} objects. Custom codecs are available recursively * to the UDT's fields, so if one of your fields is a {@code timestamp} you can use your {@code LocaDateCodec} to retrieve * it as a {@code java.time.LocalDate};</li> * <li>{@link TupleType tuple types}, mapped to {@link TupleValue} (with the same rules for nested fields);</li> * <li>{@link com.datastax.driver.core.DataType.CustomType custom types}, mapped to {@code ByteBuffer}.</li> * </ul> * If the codec registry encounters a mapping that it can't handle automatically, a {@link CodecNotFoundException} is thrown; * you'll need to register a custom codec for it. * <h3> * Performance and caching * </h3> * Whenever possible, the registry will cache the result of a codec lookup for a specific type mapping, including any generated * codec. For example, if you registered {@code LocalDateCodec} and ask the registry for a codec to convert a CQL * {@code list<timestamp>} to a Java {@code List<LocalDate>}: * <ol> * <li>the first lookup will generate a {@code TypeCodec<List<LocalDate>>} from {@code LocalDateCodec}, and put it in * the cache;</li> * <li>the second lookup will hit the cache directly, and reuse the previously generated instance.</li> * </ol> * The javadoc for each {@link #codecFor(DataType) codecFor} variant specifies whether the result can be cached or not. * <h3> * Codec order * </h3> * When the registry looks up a codec, the rules of precedence are: * <ul> * <li>if a result was previously cached for that mapping, it is returned;</li> * <li>otherwise, the registry checks the list of "basic" codecs: the default ones, and the ones that were explicitly * registered (in the order that they were registered). It calls each codec's {@code accepts} methods to determine if * it can handle the mapping, and if so returns it;</li> * <li>otherwise, the registry tries to generate a codec, according to the rules outlined above.</li> * </ul> * It is currently impossible to override an existing codec. If you try to do so, {@link #register(TypeCodec)} will log a * warning and ignore it. */ public final class CodecRegistry { private static final Logger logger = LoggerFactory.getLogger(CodecRegistry.class); @SuppressWarnings("unchecked") private static final ImmutableSet<TypeCodec<?>> PRIMITIVE_CODECS = ImmutableSet.of(TypeCodec.blob(), TypeCodec.cboolean(), TypeCodec.smallInt(), TypeCodec.tinyInt(), TypeCodec.cint(), TypeCodec.bigint(), TypeCodec.counter(), TypeCodec.cdouble(), TypeCodec.cfloat(), TypeCodec.varint(), TypeCodec.decimal(), TypeCodec.varchar(), // must be declared before AsciiCodec so it gets chosen when CQL type not available TypeCodec.ascii(), TypeCodec.timestamp(), TypeCodec.date(), TypeCodec.time(), TypeCodec.uuid(), // must be declared before TimeUUIDCodec so it gets chosen when CQL type not available TypeCodec.timeUUID(), TypeCodec.inet()); /** * The default {@code CodecRegistry} instance. * <p/> * It will be shared among all {@link Cluster} instances that were not explicitly built with a different instance. */ public static final CodecRegistry DEFAULT_INSTANCE = new CodecRegistry(); /** * Cache key for the codecs cache. */ private static final class CacheKey { private final DataType cqlType; private final TypeToken<?> javaType; public CacheKey(DataType cqlType, TypeToken<?> javaType) { this.javaType = javaType; this.cqlType = cqlType; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; CacheKey cacheKey = (CacheKey) o; return Objects.equal(cqlType, cacheKey.cqlType) && Objects.equal(javaType, cacheKey.javaType); } @Override public int hashCode() { return Objects.hashCode(cqlType, javaType); } } /** * Cache loader for the codecs cache. */ private class TypeCodecCacheLoader extends CacheLoader<CacheKey, TypeCodec<?>> { @Override public TypeCodec<?> load(CacheKey cacheKey) { return findCodec(cacheKey.cqlType, cacheKey.javaType); } } /** * A complexity-based weigher for the codecs cache. * Weights are computed mainly according to the CQL type: * <ol> * <li>Manually-registered codecs always weigh 0; * <li>Codecs for primtive types weigh 0; * <li>Codecs for collections weigh the total weight of their inner types + the weight of their level of deepness; * <li>Codecs for UDTs and tuples weigh the total weight of their inner types + the weight of their level of deepness, but cannot weigh less than 1; * <li>Codecs for custom (non-CQL) types weigh 1. * </ol> * A consequence of this algorithm is that codecs for primitive types and codecs for all "shallow" collections thereof * are never evicted. */ private class TypeCodecWeigher implements Weigher<CacheKey, TypeCodec<?>> { @Override public int weigh(CacheKey key, TypeCodec<?> value) { return codecs.contains(value) ? 0 : weigh(value.cqlType, 0); } private int weigh(DataType cqlType, int level) { switch (cqlType.getName()) { case LIST: case SET: case MAP: { int weight = level; for (DataType eltType : cqlType.getTypeArguments()) { weight += weigh(eltType, level + 1); } return weight; } case UDT: { int weight = level; for (UserType.Field field : ((UserType) cqlType)) { weight += weigh(field.getType(), level + 1); } return weight == 0 ? 1 : weight; } case TUPLE: { int weight = level; for (DataType componentType : ((TupleType) cqlType).getComponentTypes()) { weight += weigh(componentType, level + 1); } return weight == 0 ? 1 : weight; } case CUSTOM: return 1; default: return 0; } } } /** * Simple removal listener for the codec cache (can be used for debugging purposes * by setting the {@code com.datastax.driver.core.CodecRegistry} logger level to {@code TRACE}. */ private class TypeCodecRemovalListener implements RemovalListener<CacheKey, TypeCodec<?>> { @Override public void onRemoval(RemovalNotification<CacheKey, TypeCodec<?>> notification) { logger.trace("Evicting codec from cache: {} (cause: {})", notification.getValue(), notification.getCause()); } } /** * The list of registered codecs. * This list is initialized with the built-in codecs; * User-defined codecs are appended to the list. */ private final CopyOnWriteArrayList<TypeCodec<?>> codecs; /** * A LoadingCache to serve requests for codecs whenever possible. * The cache can be used as long as at least the CQL type is known. */ private final LoadingCache<CacheKey, TypeCodec<?>> cache; /** * Creates a new instance initialized with built-in codecs for all the base CQL types. */ public CodecRegistry() { this.codecs = new CopyOnWriteArrayList<TypeCodec<?>>(PRIMITIVE_CODECS); this.cache = defaultCacheBuilder().build(new TypeCodecCacheLoader()); } private CacheBuilder<CacheKey, TypeCodec<?>> defaultCacheBuilder() { CacheBuilder<CacheKey, TypeCodec<?>> builder = CacheBuilder.newBuilder() // 19 primitive codecs + collections thereof = 19*3 + 19*19 = 418 codecs, // so let's start with roughly 1/4 of that .initialCapacity(100).maximumWeight(1000).weigher(new TypeCodecWeigher()); if (logger.isTraceEnabled()) // do not bother adding a listener if it will be ineffective builder = builder.removalListener(new TypeCodecRemovalListener()); return builder; } /** * Register the given codec with this registry. * <p/> * This method will log a warning and ignore the codec if it collides with a previously registered one. * Note that this check is not done in a completely thread-safe manner; codecs should typically be registered * at application startup, not in a highly concurrent context (if a race condition occurs, the worst possible * outcome is that no warning gets logged, and the codec gets registered but will never actually be used). * * @param newCodec The codec to add to the registry. * @return this CodecRegistry (for method chaining). */ public CodecRegistry register(TypeCodec<?> newCodec) { for (TypeCodec<?> oldCodec : codecs) { if (oldCodec.accepts(newCodec.getCqlType()) && oldCodec.accepts(newCodec.getJavaType())) { logger.warn("Ignoring codec {} because it collides with previously registered codec {}", newCodec, oldCodec); return this; } } CacheKey key = new CacheKey(newCodec.getCqlType(), newCodec.getJavaType()); TypeCodec<?> existing = cache.getIfPresent(key); if (existing != null) { logger.warn("Ignoring codec {} because it collides with previously generated codec {}", newCodec, existing); return this; } this.codecs.add(newCodec); return this; } /** * Register the given codecs with this registry. * * @param codecs The codecs to add to the registry. * @return this CodecRegistry (for method chaining). * @see #register(TypeCodec) */ public CodecRegistry register(TypeCodec<?>... codecs) { for (TypeCodec<?> codec : codecs) register(codec); return this; } /** * Register the given codecs with this registry. * * @param codecs The codecs to add to the registry. * @return this CodecRegistry (for method chaining). * @see #register(TypeCodec) */ public CodecRegistry register(Iterable<? extends TypeCodec<?>> codecs) { for (TypeCodec<?> codec : codecs) register(codec); return this; } /** * Returns a {@link TypeCodec codec} that accepts the given value. * <p/> * This method takes an arbitrary Java object and tries to locate a suitable codec for it. * Codecs must perform a {@link TypeCodec#accepts(Object) runtime inspection} of the object to determine * if they can accept it or not, which, depending on the implementations, can be expensive; besides, the * resulting codec cannot be cached. * Therefore there might be a performance penalty when using this method. * <p/> * Furthermore, this method returns the first matching codec, regardless of its accepted CQL type. * It should be reserved for situations where the target CQL type is not available or unknown. * In the Java driver, this happens mainly when serializing a value in a * {@link SimpleStatement#SimpleStatement(String, Object...) SimpleStatement} or in the * {@link com.datastax.driver.core.querybuilder.QueryBuilder}, where no CQL type information is available. * <p/> * Codecs returned by this method are <em>NOT</em> cached (see the {@link CodecRegistry top-level documentation} * of this class for more explanations about caching). * * @param value The value the codec should accept; must not be {@code null}. * @return A suitable codec. * @throws CodecNotFoundException if a suitable codec cannot be found. */ public <T> TypeCodec<T> codecFor(T value) { return findCodec(null, value); } /** * Returns a {@link TypeCodec codec} that accepts the given {@link DataType CQL type}. * <p/> * This method returns the first matching codec, regardless of its accepted Java type. * It should be reserved for situations where the Java type is not available or unknown. * In the Java driver, this happens mainly when deserializing a value using the * {@link GettableByIndexData#getObject(int) getObject} method. * <p/> * Codecs returned by this method are cached (see the {@link CodecRegistry top-level documentation} * of this class for more explanations about caching). * * @param cqlType The {@link DataType CQL type} the codec should accept; must not be {@code null}. * @return A suitable codec. * @throws CodecNotFoundException if a suitable codec cannot be found. */ public <T> TypeCodec<T> codecFor(DataType cqlType) throws CodecNotFoundException { return lookupCodec(cqlType, null); } /** * Returns a {@link TypeCodec codec} that accepts the given {@link DataType CQL type} * and the given Java class. * <p/> * This method can only handle raw (non-parameterized) Java types. * For parameterized types, use {@link #codecFor(DataType, TypeToken)} instead. * <p/> * Codecs returned by this method are cached (see the {@link CodecRegistry top-level documentation} * of this class for more explanations about caching). * * @param cqlType The {@link DataType CQL type} the codec should accept; must not be {@code null}. * @param javaType The Java type the codec should accept; can be {@code null}. * @return A suitable codec. * @throws CodecNotFoundException if a suitable codec cannot be found. */ public <T> TypeCodec<T> codecFor(DataType cqlType, Class<T> javaType) throws CodecNotFoundException { return codecFor(cqlType, TypeToken.of(javaType)); } /** * Returns a {@link TypeCodec codec} that accepts the given {@link DataType CQL type} * and the given Java type. * <p/> * This method handles parameterized types thanks to Guava's {@link TypeToken} API. * <p/> * Codecs returned by this method are cached (see the {@link CodecRegistry top-level documentation} * of this class for more explanations about caching). * * @param cqlType The {@link DataType CQL type} the codec should accept; must not be {@code null}. * @param javaType The {@link TypeToken Java type} the codec should accept; can be {@code null}. * @return A suitable codec. * @throws CodecNotFoundException if a suitable codec cannot be found. */ public <T> TypeCodec<T> codecFor(DataType cqlType, TypeToken<T> javaType) throws CodecNotFoundException { return lookupCodec(cqlType, javaType); } /** * Returns a {@link TypeCodec codec} that accepts the given {@link DataType CQL type} * and the given value. * <p/> * This method takes an arbitrary Java object and tries to locate a suitable codec for it. * Codecs must perform a {@link TypeCodec#accepts(Object) runtime inspection} of the object to determine * if they can accept it or not, which, depending on the implementations, can be expensive; besides, the * resulting codec cannot be cached. * Therefore there might be a performance penalty when using this method. * <p/> * Codecs returned by this method are <em>NOT</em> cached (see the {@link CodecRegistry top-level documentation} * of this class for more explanations about caching). * * @param cqlType The {@link DataType CQL type} the codec should accept; can be {@code null}. * @param value The value the codec should accept; must not be {@code null}. * @return A suitable codec. * @throws CodecNotFoundException if a suitable codec cannot be found. */ public <T> TypeCodec<T> codecFor(DataType cqlType, T value) { return findCodec(cqlType, value); } @SuppressWarnings("unchecked") private <T> TypeCodec<T> lookupCodec(DataType cqlType, TypeToken<T> javaType) { checkNotNull(cqlType, "Parameter cqlType cannot be null"); if (logger.isTraceEnabled()) logger.trace("Querying cache for codec [{} <-> {}]", toString(cqlType), toString(javaType)); CacheKey cacheKey = new CacheKey(cqlType, javaType); try { TypeCodec<?> codec = cache.get(cacheKey); logger.trace("Returning cached codec {}", codec); return (TypeCodec<T>) codec; } catch (UncheckedExecutionException e) { if (e.getCause() instanceof CodecNotFoundException) { throw (CodecNotFoundException) e.getCause(); } throw new CodecNotFoundException(e.getCause(), cqlType, javaType); } catch (RuntimeException e) { throw new CodecNotFoundException(e.getCause(), cqlType, javaType); } catch (ExecutionException e) { throw new CodecNotFoundException(e.getCause(), cqlType, javaType); } } @SuppressWarnings("unchecked") private <T> TypeCodec<T> findCodec(DataType cqlType, TypeToken<T> javaType) { checkNotNull(cqlType, "Parameter cqlType cannot be null"); if (logger.isTraceEnabled()) logger.trace("Looking for codec [{} <-> {}]", toString(cqlType), toString(javaType)); for (TypeCodec<?> codec : codecs) { if (codec.accepts(cqlType) && (javaType == null || codec.accepts(javaType))) { logger.trace("Codec found: {}", codec); return (TypeCodec<T>) codec; } } return createCodec(cqlType, javaType); } @SuppressWarnings("unchecked") private <T> TypeCodec<T> findCodec(DataType cqlType, T value) { checkNotNull(value, "Parameter value cannot be null"); if (logger.isTraceEnabled()) logger.trace("Looking for codec [{} <-> {}]", toString(cqlType), value.getClass()); for (TypeCodec<?> codec : codecs) { if ((cqlType == null || codec.accepts(cqlType)) && codec.accepts(value)) { logger.trace("Codec found: {}", codec); return (TypeCodec<T>) codec; } } return createCodec(cqlType, value); } private <T> TypeCodec<T> createCodec(DataType cqlType, TypeToken<T> javaType) { TypeCodec<T> codec = maybeCreateCodec(cqlType, javaType); if (codec == null) throw notFound(cqlType, javaType); // double-check that the created codec satisfies the initial request // this check can fail specially when creating codecs for collections // e.g. if B extends A and there is a codec registered for A and // we request a codec for List<B>, the registry would generate a codec for List<A> if (!codec.accepts(cqlType) || (javaType != null && !codec.accepts(javaType))) throw notFound(cqlType, javaType); logger.trace("Codec created: {}", codec); return codec; } private <T> TypeCodec<T> createCodec(DataType cqlType, T value) { TypeCodec<T> codec = maybeCreateCodec(cqlType, value); if (codec == null) throw notFound(cqlType, TypeToken.of(value.getClass())); // double-check that the created codec satisfies the initial request if ((cqlType != null && !codec.accepts(cqlType)) || !codec.accepts(value)) throw notFound(cqlType, TypeToken.of(value.getClass())); logger.trace("Codec created: {}", codec); return codec; } @SuppressWarnings("unchecked") private <T> TypeCodec<T> maybeCreateCodec(DataType cqlType, TypeToken<T> javaType) { checkNotNull(cqlType); if (cqlType.getName() == LIST && (javaType == null || List.class.isAssignableFrom(javaType.getRawType()))) { TypeToken<?> elementType = null; if (javaType != null && javaType.getType() instanceof ParameterizedType) { Type[] typeArguments = ((ParameterizedType) javaType.getType()).getActualTypeArguments(); elementType = TypeToken.of(typeArguments[0]); } TypeCodec<?> eltCodec = findCodec(cqlType.getTypeArguments().get(0), elementType); return (TypeCodec<T>) TypeCodec.list(eltCodec); } if (cqlType.getName() == SET && (javaType == null || Set.class.isAssignableFrom(javaType.getRawType()))) { TypeToken<?> elementType = null; if (javaType != null && javaType.getType() instanceof ParameterizedType) { Type[] typeArguments = ((ParameterizedType) javaType.getType()).getActualTypeArguments(); elementType = TypeToken.of(typeArguments[0]); } TypeCodec<?> eltCodec = findCodec(cqlType.getTypeArguments().get(0), elementType); return (TypeCodec<T>) TypeCodec.set(eltCodec); } if (cqlType.getName() == MAP && (javaType == null || Map.class.isAssignableFrom(javaType.getRawType()))) { TypeToken<?> keyType = null; TypeToken<?> valueType = null; if (javaType != null && javaType.getType() instanceof ParameterizedType) { Type[] typeArguments = ((ParameterizedType) javaType.getType()).getActualTypeArguments(); keyType = TypeToken.of(typeArguments[0]); valueType = TypeToken.of(typeArguments[1]); } TypeCodec<?> keyCodec = findCodec(cqlType.getTypeArguments().get(0), keyType); TypeCodec<?> valueCodec = findCodec(cqlType.getTypeArguments().get(1), valueType); return (TypeCodec<T>) TypeCodec.map(keyCodec, valueCodec); } if (cqlType instanceof TupleType && (javaType == null || TupleValue.class.isAssignableFrom(javaType.getRawType()))) { return (TypeCodec<T>) TypeCodec.tuple((TupleType) cqlType); } if (cqlType instanceof UserType && (javaType == null || UDTValue.class.isAssignableFrom(javaType.getRawType()))) { return (TypeCodec<T>) TypeCodec.userType((UserType) cqlType); } if (cqlType instanceof DataType.CustomType && (javaType == null || ByteBuffer.class.isAssignableFrom(javaType.getRawType()))) { return (TypeCodec<T>) TypeCodec.custom((DataType.CustomType) cqlType); } return null; } @SuppressWarnings("unchecked") private <T> TypeCodec<T> maybeCreateCodec(DataType cqlType, T value) { checkNotNull(value); if ((cqlType == null || cqlType.getName() == LIST) && value instanceof List) { List list = (List) value; if (list.isEmpty()) { DataType elementType = (cqlType == null || cqlType.getTypeArguments().isEmpty()) ? DataType.blob() : cqlType.getTypeArguments().get(0); return TypeCodec.list(findCodec(elementType, (TypeToken) null)); } else { DataType elementType = (cqlType == null || cqlType.getTypeArguments().isEmpty()) ? null : cqlType.getTypeArguments().get(0); return (TypeCodec<T>) TypeCodec.list(findCodec(elementType, list.iterator().next())); } } if ((cqlType == null || cqlType.getName() == SET) && value instanceof Set) { Set set = (Set) value; if (set.isEmpty()) { DataType elementType = (cqlType == null || cqlType.getTypeArguments().isEmpty()) ? DataType.blob() : cqlType.getTypeArguments().get(0); return TypeCodec.set(findCodec(elementType, (TypeToken) null)); } else { DataType elementType = (cqlType == null || cqlType.getTypeArguments().isEmpty()) ? null : cqlType.getTypeArguments().get(0); return (TypeCodec<T>) TypeCodec.set(findCodec(elementType, set.iterator().next())); } } if ((cqlType == null || cqlType.getName() == MAP) && value instanceof Map) { Map map = (Map) value; if (map.isEmpty()) { DataType keyType = (cqlType == null || cqlType.getTypeArguments().size() < 1) ? DataType.blob() : cqlType.getTypeArguments().get(0); DataType valueType = (cqlType == null || cqlType.getTypeArguments().size() < 2) ? DataType.blob() : cqlType.getTypeArguments().get(1); return TypeCodec.map(findCodec(keyType, (TypeToken) null), findCodec(valueType, (TypeToken) null)); } else { DataType keyType = (cqlType == null || cqlType.getTypeArguments().size() < 1) ? null : cqlType.getTypeArguments().get(0); DataType valueType = (cqlType == null || cqlType.getTypeArguments().size() < 2) ? null : cqlType.getTypeArguments().get(1); Map.Entry entry = (Map.Entry) map.entrySet().iterator().next(); return (TypeCodec<T>) TypeCodec.map(findCodec(keyType, entry.getKey()), findCodec(valueType, entry.getValue())); } } if ((cqlType == null || cqlType.getName() == DataType.Name.TUPLE) && value instanceof TupleValue) { return (TypeCodec<T>) TypeCodec .tuple(cqlType == null ? ((TupleValue) value).getType() : (TupleType) cqlType); } if ((cqlType == null || cqlType.getName() == DataType.Name.UDT) && value instanceof UDTValue) { return (TypeCodec<T>) TypeCodec .userType(cqlType == null ? ((UDTValue) value).getType() : (UserType) cqlType); } if ((cqlType != null && cqlType instanceof DataType.CustomType) && value instanceof ByteBuffer) { return (TypeCodec<T>) TypeCodec.custom((DataType.CustomType) cqlType); } return null; } private static CodecNotFoundException notFound(DataType cqlType, TypeToken<?> javaType) { String msg = String.format("Codec not found for requested operation: [%s <-> %s]", toString(cqlType), toString(javaType)); return new CodecNotFoundException(msg, cqlType, javaType); } private static String toString(Object value) { return value == null ? "ANY" : value.toString(); } }