1818import com .datastax .driver .core .exceptions .CodecNotFoundException ;
1919import com .google .common .base .Objects ;
2020import com .google .common .cache .*;
21- import com .google .common .collect .ImmutableSet ;
2221import com .google .common .reflect .TypeToken ;
2322import com .google .common .util .concurrent .UncheckedExecutionException ;
2423import org .slf4j .Logger ;
2726import java .lang .reflect .ParameterizedType ;
2827import java .lang .reflect .Type ;
2928import java .nio .ByteBuffer ;
29+ import java .util .EnumMap ;
3030import java .util .List ;
3131import java .util .Map ;
3232import java .util .Set ;
4949 * The only reason to create your own instances is if you have multiple {@code Cluster} objects that use different
5050 * sets of codecs. In that case, use {@link com.datastax.driver.core.Cluster.Builder#withCodecRegistry(CodecRegistry)}
5151 * to associate the registry with the cluster:
52- * <pre>
53- * {@code
52+ * <pre>{@code
5453 * CodecRegistry myCodecRegistry = new CodecRegistry();
5554 * myCodecRegistry.register(myCodec1, myCodec2, myCodec3);
5655 * Cluster cluster = Cluster.builder().withCodecRegistry(myCodecRegistry).build();
6867 * To create a custom codec, write a class that extends {@link TypeCodec}, create an instance, and pass it to one of
6968 * the {@link #register(TypeCodec) register} methods; for example, one could create a codec that maps CQL
7069 * timestamps to JDK8's {@code java.time.LocalDate}:
71- * <pre>
72- * {@code
70+ * <pre>{@code
7371 * class LocalDateCodec extends TypeCodec<java.time.LocalDate> {
7472 * ...
7573 * }
8583 * </ul>
8684 * <p/>
8785 * Example:
88- * <pre>
89- * {@code
86+ * <pre>{@code
9087 * Row row = session.executeQuery("select date from some_table where pk = 1").one();
9188 * java.time.LocalDate date = row.get(0, java.time.LocalDate.class); // uses LocalDateCodec registered above}
9289 * </pre>
104101 * as well as all {@code Map} types whose keys and/or values are {@code java.time.LocalDate}.
105102 * This works recursively for nested collections;</li>
106103 * <li>{@link UserType user types}, mapped to {@link UDTValue} objects. Custom codecs are available recursively
107- * to the UDT's fields, so if one of your fields is a {@code timestamp} you can use your {@code LocaDateCodec } to retrieve
104+ * to the UDT's fields, so if one of your fields is a {@code timestamp} you can use your {@code LocalDateCodec } to retrieve
108105 * it as a {@code java.time.LocalDate};</li>
109106 * <li>{@link TupleType tuple types}, mapped to {@link TupleValue} (with the same rules for nested fields);</li>
110107 * <li>{@link com.datastax.driver.core.DataType.CustomType custom types}, mapped to {@code ByteBuffer}.</li>
129126 * When the registry looks up a codec, the rules of precedence are:
130127 * <ul>
131128 * <li>if a result was previously cached for that mapping, it is returned;</li>
132- * <li>otherwise, the registry checks the list of "basic" codecs: the default ones, and the ones that were explicitly
129+ * <li>otherwise, the registry checks the list of built-in codecs – the default ones – and the ones that were explicitly
133130 * registered (in the order that they were registered). It calls each codec's {@code accepts} methods to determine if
134131 * it can handle the mapping, and if so returns it;</li>
135132 * <li>otherwise, the registry tries to generate a codec, according to the rules outlined above.</li>
@@ -141,29 +138,55 @@ public final class CodecRegistry {
141138
142139 private static final Logger logger = LoggerFactory .getLogger (CodecRegistry .class );
143140
144- @ SuppressWarnings ("unchecked" )
145- private static final ImmutableSet <TypeCodec <?>> PRIMITIVE_CODECS = ImmutableSet .of (
146- TypeCodec .blob (),
147- TypeCodec .cboolean (),
148- TypeCodec .smallInt (),
149- TypeCodec .tinyInt (),
141+ private static final Map <DataType .Name , TypeCodec <?>> BUILT_IN_CODECS_MAP = new EnumMap <DataType .Name , TypeCodec <?>>(DataType .Name .class );
142+
143+ static {
144+ BUILT_IN_CODECS_MAP .put (DataType .Name .ASCII , TypeCodec .ascii ());
145+ BUILT_IN_CODECS_MAP .put (DataType .Name .BIGINT , TypeCodec .bigint ());
146+ BUILT_IN_CODECS_MAP .put (DataType .Name .BLOB , TypeCodec .blob ());
147+ BUILT_IN_CODECS_MAP .put (DataType .Name .BOOLEAN , TypeCodec .cboolean ());
148+ BUILT_IN_CODECS_MAP .put (DataType .Name .COUNTER , TypeCodec .counter ());
149+ BUILT_IN_CODECS_MAP .put (DataType .Name .DECIMAL , TypeCodec .decimal ());
150+ BUILT_IN_CODECS_MAP .put (DataType .Name .DOUBLE , TypeCodec .cdouble ());
151+ BUILT_IN_CODECS_MAP .put (DataType .Name .FLOAT , TypeCodec .cfloat ());
152+ BUILT_IN_CODECS_MAP .put (DataType .Name .INET , TypeCodec .inet ());
153+ BUILT_IN_CODECS_MAP .put (DataType .Name .INT , TypeCodec .cint ());
154+ BUILT_IN_CODECS_MAP .put (DataType .Name .TEXT , TypeCodec .varchar ());
155+ BUILT_IN_CODECS_MAP .put (DataType .Name .TIMESTAMP , TypeCodec .timestamp ());
156+ BUILT_IN_CODECS_MAP .put (DataType .Name .UUID , TypeCodec .uuid ());
157+ BUILT_IN_CODECS_MAP .put (DataType .Name .VARCHAR , TypeCodec .varchar ());
158+ BUILT_IN_CODECS_MAP .put (DataType .Name .VARINT , TypeCodec .varint ());
159+ BUILT_IN_CODECS_MAP .put (DataType .Name .TIMEUUID , TypeCodec .timeUUID ());
160+ BUILT_IN_CODECS_MAP .put (DataType .Name .SMALLINT , TypeCodec .smallInt ());
161+ BUILT_IN_CODECS_MAP .put (DataType .Name .TINYINT , TypeCodec .tinyInt ());
162+ BUILT_IN_CODECS_MAP .put (DataType .Name .DATE , TypeCodec .date ());
163+ BUILT_IN_CODECS_MAP .put (DataType .Name .TIME , TypeCodec .time ());
164+ BUILT_IN_CODECS_MAP .put (DataType .Name .DURATION , TypeCodec .duration ());
165+ }
166+
167+ // roughly sorted by popularity
168+ private static final TypeCodec <?>[] BUILT_IN_CODECS = new TypeCodec <?>[]{
169+ TypeCodec .varchar (), // must be declared before AsciiCodec so it gets chosen when CQL type not available
170+ TypeCodec .uuid (), // must be declared before TimeUUIDCodec so it gets chosen when CQL type not available
171+ TypeCodec .timeUUID (),
172+ TypeCodec .timestamp (),
150173 TypeCodec .cint (),
151174 TypeCodec .bigint (),
152- TypeCodec .counter (),
175+ TypeCodec .blob (),
153176 TypeCodec .cdouble (),
154177 TypeCodec .cfloat (),
155- TypeCodec .varint (),
156178 TypeCodec .decimal (),
157- TypeCodec .varchar (), // must be declared before AsciiCodec so it gets chosen when CQL type not available
158- TypeCodec .ascii (),
159- TypeCodec .timestamp (),
179+ TypeCodec .varint (),
180+ TypeCodec .inet (),
181+ TypeCodec .cboolean (),
182+ TypeCodec .smallInt (),
183+ TypeCodec .tinyInt (),
160184 TypeCodec .date (),
161185 TypeCodec .time (),
162- TypeCodec .uuid (), // must be declared before TimeUUIDCodec so it gets chosen when CQL type not available
163- TypeCodec .timeUUID (),
164- TypeCodec .inet (),
165- TypeCodec .duration ()
166- );
186+ TypeCodec .duration (),
187+ TypeCodec .counter (),
188+ TypeCodec .ascii ()
189+ };
167190
168191 /**
169192 * The default {@code CodecRegistry} instance.
@@ -181,7 +204,7 @@ private static final class CacheKey {
181204
182205 private final TypeToken <?> javaType ;
183206
184- public CacheKey (DataType cqlType , TypeToken <?> javaType ) {
207+ CacheKey (DataType cqlType , TypeToken <?> javaType ) {
185208 this .javaType = javaType ;
186209 this .cqlType = cqlType ;
187210 }
@@ -209,7 +232,18 @@ public int hashCode() {
209232 private class TypeCodecCacheLoader extends CacheLoader <CacheKey , TypeCodec <?>> {
210233 @ Override
211234 public TypeCodec <?> load (CacheKey cacheKey ) {
212- return findCodec (cacheKey .cqlType , cacheKey .javaType );
235+ checkNotNull (cacheKey .cqlType , "Parameter cqlType cannot be null" );
236+ if (logger .isTraceEnabled ())
237+ logger .trace ("Loading codec into cache: [{} <-> {}]" ,
238+ CodecRegistry .toString (cacheKey .cqlType ),
239+ CodecRegistry .toString (cacheKey .javaType ));
240+ for (TypeCodec <?> codec : codecs ) {
241+ if (codec .accepts (cacheKey .cqlType ) && (cacheKey .javaType == null || codec .accepts (cacheKey .javaType ))) {
242+ logger .trace ("Already existing codec found: {}" , codec );
243+ return codec ;
244+ }
245+ }
246+ return createCodec (cacheKey .cqlType , cacheKey .javaType );
213247 }
214248 }
215249
@@ -218,7 +252,7 @@ public TypeCodec<?> load(CacheKey cacheKey) {
218252 * Weights are computed mainly according to the CQL type:
219253 * <ol>
220254 * <li>Manually-registered codecs always weigh 0;
221- * <li>Codecs for primtive types weigh 0;
255+ * <li>Codecs for primitive types weigh 0;
222256 * <li>Codecs for collections weigh the total weight of their inner types + the weight of their level of deepness;
223257 * <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;
224258 * <li>Codecs for custom (non-CQL) types weigh 1.
@@ -278,9 +312,7 @@ public void onRemoval(RemovalNotification<CacheKey, TypeCodec<?>> notification)
278312 }
279313
280314 /**
281- * The list of registered codecs.
282- * This list is initialized with the built-in codecs;
283- * User-defined codecs are appended to the list.
315+ * The list of user-registered codecs.
284316 */
285317 private final CopyOnWriteArrayList <TypeCodec <?>> codecs ;
286318
@@ -294,13 +326,13 @@ public void onRemoval(RemovalNotification<CacheKey, TypeCodec<?>> notification)
294326 * Creates a new instance initialized with built-in codecs for all the base CQL types.
295327 */
296328 public CodecRegistry () {
297- this .codecs = new CopyOnWriteArrayList <TypeCodec <?>>(PRIMITIVE_CODECS );
329+ this .codecs = new CopyOnWriteArrayList <TypeCodec <?>>();
298330 this .cache = defaultCacheBuilder ().build (new TypeCodecCacheLoader ());
299331 }
300332
301333 private CacheBuilder <CacheKey , TypeCodec <?>> defaultCacheBuilder () {
302334 CacheBuilder <CacheKey , TypeCodec <?>> builder = CacheBuilder .newBuilder ()
303- // 19 primitive codecs + collections thereof = 19*3 + 19*19 = 418 codecs,
335+ // lists, sets and maps of 20 primitive types = 20 + 20 + 20*20 = 440 codecs,
304336 // so let's start with roughly 1/4 of that
305337 .initialCapacity (100 )
306338 .maximumWeight (1000 )
@@ -323,20 +355,24 @@ private CacheBuilder<CacheKey, TypeCodec<?>> defaultCacheBuilder() {
323355 * @return this CodecRegistry (for method chaining).
324356 */
325357 public CodecRegistry register (TypeCodec <?> newCodec ) {
358+ for (TypeCodec <?> oldCodec : BUILT_IN_CODECS ) {
359+ if (oldCodec .accepts (newCodec .getCqlType ()) && oldCodec .accepts (newCodec .getJavaType ())) {
360+ logger .warn ("Ignoring codec {} because it collides with previously registered codec {}" , newCodec , oldCodec );
361+ return this ;
362+ }
363+ }
326364 for (TypeCodec <?> oldCodec : codecs ) {
327365 if (oldCodec .accepts (newCodec .getCqlType ()) && oldCodec .accepts (newCodec .getJavaType ())) {
328366 logger .warn ("Ignoring codec {} because it collides with previously registered codec {}" , newCodec , oldCodec );
329367 return this ;
330368 }
331369 }
332-
333370 CacheKey key = new CacheKey (newCodec .getCqlType (), newCodec .getJavaType ());
334371 TypeCodec <?> existing = cache .getIfPresent (key );
335372 if (existing != null ) {
336373 logger .warn ("Ignoring codec {} because it collides with previously generated codec {}" , newCodec , existing );
337374 return this ;
338375 }
339-
340376 this .codecs .add (newCodec );
341377 return this ;
342378 }
@@ -474,13 +510,16 @@ public <T> TypeCodec<T> codecFor(DataType cqlType, T value) {
474510 @ SuppressWarnings ("unchecked" )
475511 private <T > TypeCodec <T > lookupCodec (DataType cqlType , TypeToken <T > javaType ) {
476512 checkNotNull (cqlType , "Parameter cqlType cannot be null" );
513+ TypeCodec <?> codec = BUILT_IN_CODECS_MAP .get (cqlType .getName ());
514+ if (codec != null && (javaType == null || codec .accepts (javaType ))) {
515+ logger .trace ("Returning built-in codec {}" , codec );
516+ return (TypeCodec <T >) codec ;
517+ }
477518 if (logger .isTraceEnabled ())
478519 logger .trace ("Querying cache for codec [{} <-> {}]" , toString (cqlType ), toString (javaType ));
479- CacheKey cacheKey = new CacheKey (cqlType , javaType );
480520 try {
481- TypeCodec <?> codec = cache .get (cacheKey );
482- logger .trace ("Returning cached codec {}" , codec );
483- return (TypeCodec <T >) codec ;
521+ CacheKey cacheKey = new CacheKey (cqlType , javaType );
522+ codec = cache .get (cacheKey );
484523 } catch (UncheckedExecutionException e ) {
485524 if (e .getCause () instanceof CodecNotFoundException ) {
486525 throw (CodecNotFoundException ) e .getCause ();
@@ -491,16 +530,28 @@ private <T> TypeCodec<T> lookupCodec(DataType cqlType, TypeToken<T> javaType) {
491530 } catch (ExecutionException e ) {
492531 throw new CodecNotFoundException (e .getCause (), cqlType , javaType );
493532 }
533+ logger .trace ("Returning cached codec {}" , codec );
534+ return (TypeCodec <T >) codec ;
494535 }
495536
496537 @ SuppressWarnings ("unchecked" )
497538 private <T > TypeCodec <T > findCodec (DataType cqlType , TypeToken <T > javaType ) {
498539 checkNotNull (cqlType , "Parameter cqlType cannot be null" );
499540 if (logger .isTraceEnabled ())
500541 logger .trace ("Looking for codec [{} <-> {}]" , toString (cqlType ), toString (javaType ));
542+
543+ // Look at the built-in codecs first
544+ for (TypeCodec <?> codec : BUILT_IN_CODECS ) {
545+ if (codec .accepts (cqlType ) && (javaType == null || codec .accepts (javaType ))) {
546+ logger .trace ("Built-in codec found: {}" , codec );
547+ return (TypeCodec <T >) codec ;
548+ }
549+ }
550+
551+ // Look at the user-registered codecs next
501552 for (TypeCodec <?> codec : codecs ) {
502553 if (codec .accepts (cqlType ) && (javaType == null || codec .accepts (javaType ))) {
503- logger .trace ("Codec found: {}" , codec );
554+ logger .trace ("Already registered codec found: {}" , codec );
504555 return (TypeCodec <T >) codec ;
505556 }
506557 }
@@ -512,9 +563,19 @@ private <T> TypeCodec<T> findCodec(DataType cqlType, T value) {
512563 checkNotNull (value , "Parameter value cannot be null" );
513564 if (logger .isTraceEnabled ())
514565 logger .trace ("Looking for codec [{} <-> {}]" , toString (cqlType ), value .getClass ());
566+
567+ // Look at the built-in codecs first
568+ for (TypeCodec <?> codec : BUILT_IN_CODECS ) {
569+ if ((cqlType == null || codec .accepts (cqlType )) && codec .accepts (value )) {
570+ logger .trace ("Built-in codec found: {}" , codec );
571+ return (TypeCodec <T >) codec ;
572+ }
573+ }
574+
575+ // Look at the user-registered codecs next
515576 for (TypeCodec <?> codec : codecs ) {
516577 if ((cqlType == null || codec .accepts (cqlType )) && codec .accepts (value )) {
517- logger .trace ("Codec found: {}" , codec );
578+ logger .trace ("Already registered codec found: {}" , codec );
518579 return (TypeCodec <T >) codec ;
519580 }
520581 }
@@ -598,7 +659,7 @@ private <T> TypeCodec<T> maybeCreateCodec(DataType cqlType, TypeToken<T> javaTyp
598659 return null ;
599660 }
600661
601- @ SuppressWarnings ("unchecked" )
662+ @ SuppressWarnings ({ "unchecked" , "rawtypes" } )
602663 private <T > TypeCodec <T > maybeCreateCodec (DataType cqlType , T value ) {
603664 checkNotNull (value );
604665
0 commit comments