Skip to content

Commit 279e79e

Browse files
committed
Fix collection codec creation if first element is a subtype
1 parent 59417d3 commit 279e79e

2 files changed

Lines changed: 84 additions & 11 deletions

File tree

core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/registry/CachingCodecRegistry.java

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -158,17 +158,17 @@ public <T> TypeCodec<T> codecFor(T value) {
158158
return safeCast(getCachedCodec(null, javaType));
159159
}
160160

161-
// Not exposed publicly, this is only used for the recursion from createCodec(GenericType)
162-
private TypeCodec<?> codecFor(GenericType<?> javaType) {
161+
// Not exposed publicly, this is only used for the recursion from createCovariantCodec(GenericType)
162+
private TypeCodec<?> covariantCodecFor(GenericType<?> javaType) {
163163
LOG.trace("[{}] Looking up codec for Java type {}", logPrefix, javaType);
164164
for (TypeCodec<?> primitiveCodec : PRIMITIVE_CODECS) {
165-
if (primitiveCodec.canEncode(javaType)) {
165+
if (primitiveCodec.getJavaType().__getToken().isSupertypeOf(javaType.__getToken())) {
166166
LOG.trace("[{}] Found matching primitive codec {}", logPrefix, primitiveCodec);
167167
return safeCast(primitiveCodec);
168168
}
169169
}
170170
for (TypeCodec<?> userCodec : userCodecs) {
171-
if (userCodec.canEncode(javaType)) {
171+
if (userCodec.getJavaType().__getToken().isSupertypeOf(javaType.__getToken())) {
172172
LOG.trace("[{}] Found matching user codec {}", logPrefix, userCodec);
173173
return safeCast(userCodec);
174174
}
@@ -218,7 +218,7 @@ protected TypeCodec<?> createCodec(DataType cqlType, GenericType<?> javaType) {
218218
assert cqlType != null;
219219
return createCodec(cqlType);
220220
} else if (cqlType == null) {
221-
return createCodec(javaType);
221+
return createCovariantCodec(javaType);
222222
}
223223
TypeToken<?> token = javaType.__getToken();
224224
if (cqlType instanceof ListType && List.class.isAssignableFrom(token.getRawType())) {
@@ -273,28 +273,29 @@ protected TypeCodec<?> createCodec(DataType cqlType, GenericType<?> javaType) {
273273
}
274274

275275
// Try to create a codec when we haven't found it in the cache.
276-
// Variant where the CQL type is unknown.
277-
private TypeCodec<?> createCodec(GenericType<?> javaType) {
276+
// Variant where the CQL type is unknown. Note that this is only used for lookups by Java value,
277+
// and therefore covariance is allowed.
278+
private TypeCodec<?> createCovariantCodec(GenericType<?> javaType) {
278279
TypeToken<?> token = javaType.__getToken();
279280
if (List.class.isAssignableFrom(token.getRawType())
280281
&& token.getType() instanceof ParameterizedType) {
281282
Type[] typeArguments = ((ParameterizedType) token.getType()).getActualTypeArguments();
282283
GenericType<?> elementType = GenericType.of(typeArguments[0]);
283-
TypeCodec<?> elementCodec = codecFor(elementType);
284+
TypeCodec<?> elementCodec = covariantCodecFor(elementType);
284285
return TypeCodecs.listOf(elementCodec);
285286
} else if (Set.class.isAssignableFrom(token.getRawType())
286287
&& token.getType() instanceof ParameterizedType) {
287288
Type[] typeArguments = ((ParameterizedType) token.getType()).getActualTypeArguments();
288289
GenericType<?> elementType = GenericType.of(typeArguments[0]);
289-
TypeCodec<?> elementCodec = codecFor(elementType);
290+
TypeCodec<?> elementCodec = covariantCodecFor(elementType);
290291
return TypeCodecs.setOf(elementCodec);
291292
} else if (Map.class.isAssignableFrom(token.getRawType())
292293
&& token.getType() instanceof ParameterizedType) {
293294
Type[] typeArguments = ((ParameterizedType) token.getType()).getActualTypeArguments();
294295
GenericType<?> keyType = GenericType.of(typeArguments[0]);
295296
GenericType<?> valueType = GenericType.of(typeArguments[1]);
296-
TypeCodec<?> keyCodec = codecFor(keyType);
297-
TypeCodec<?> valueCodec = codecFor(valueType);
297+
TypeCodec<?> keyCodec = covariantCodecFor(keyType);
298+
TypeCodec<?> valueCodec = covariantCodecFor(valueType);
298299
return TypeCodecs.mapOf(keyCodec, valueCodec);
299300
}
300301
throw new CodecNotFoundException(null, javaType);

core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/registry/CachingCodecRegistryTest.java

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,29 @@ public void should_create_list_codec_for_java_value() throws UnknownHostExceptio
229229
inOrder.verify(onCacheLookup).accept(null, GenericType.listOf(GenericType.INTEGER));
230230
}
231231

232+
@Test
233+
public void should_create_list_codec_for_java_value_when_first_element_is_a_subtype()
234+
throws UnknownHostException {
235+
ListType cqlType = DataTypes.listOf(DataTypes.INET);
236+
GenericType<List<InetAddress>> javaType = new GenericType<List<InetAddress>>() {};
237+
InetAddress address = InetAddress.getByAddress(new byte[] {127, 0, 0, 1});
238+
// Because the actual implementation is a subclass, there is no exact match with the codec's
239+
// declared type
240+
assertThat(address).isInstanceOf(Inet4Address.class);
241+
List<InetAddress> value = ImmutableList.of(address);
242+
243+
TestCachingCodecRegistry registry = new TestCachingCodecRegistry(onCacheLookup);
244+
InOrder inOrder = Mockito.inOrder(onCacheLookup);
245+
246+
TypeCodec<List<InetAddress>> codec = registry.codecFor(value);
247+
assertThat(codec).isNotNull();
248+
assertThat(codec.canDecode(cqlType)).isTrue();
249+
assertThat(codec.canEncode(javaType)).isTrue();
250+
assertThat(codec.canEncode(value)).isTrue();
251+
252+
inOrder.verify(onCacheLookup).accept(null, GenericType.listOf(Inet4Address.class));
253+
}
254+
232255
@Test
233256
public void should_create_set_codec_for_cql_and_java_types() {
234257
SetType cqlType = DataTypes.setOf(DataTypes.setOf(DataTypes.INT));
@@ -286,6 +309,29 @@ public void should_create_set_codec_for_java_value() {
286309
inOrder.verify(onCacheLookup).accept(null, GenericType.setOf(GenericType.INTEGER));
287310
}
288311

312+
@Test
313+
public void should_create_set_codec_for_java_value_when_first_element_is_a_subtype()
314+
throws UnknownHostException {
315+
SetType cqlType = DataTypes.setOf(DataTypes.INET);
316+
GenericType<Set<InetAddress>> javaType = new GenericType<Set<InetAddress>>() {};
317+
InetAddress address = InetAddress.getByAddress(new byte[] {127, 0, 0, 1});
318+
// Because the actual implementation is a subclass, there is no exact match with the codec's
319+
// declared type
320+
assertThat(address).isInstanceOf(Inet4Address.class);
321+
Set<InetAddress> value = ImmutableSet.of(address);
322+
323+
TestCachingCodecRegistry registry = new TestCachingCodecRegistry(onCacheLookup);
324+
InOrder inOrder = Mockito.inOrder(onCacheLookup);
325+
326+
TypeCodec<Set<InetAddress>> codec = registry.codecFor(value);
327+
assertThat(codec).isNotNull();
328+
assertThat(codec.canDecode(cqlType)).isTrue();
329+
assertThat(codec.canEncode(javaType)).isTrue();
330+
assertThat(codec.canEncode(value)).isTrue();
331+
332+
inOrder.verify(onCacheLookup).accept(null, GenericType.setOf(Inet4Address.class));
333+
}
334+
289335
@Test
290336
public void should_create_map_codec_for_cql_and_java_types() {
291337
MapType cqlType = DataTypes.mapOf(DataTypes.INT, DataTypes.mapOf(DataTypes.INT, DataTypes.INT));
@@ -350,6 +396,32 @@ public void should_create_map_codec_for_java_value() {
350396
.accept(null, GenericType.mapOf(GenericType.INTEGER, GenericType.INTEGER));
351397
}
352398

399+
@Test
400+
public void should_create_map_codec_for_java_value_when_first_element_is_a_subtype()
401+
throws UnknownHostException {
402+
MapType cqlType = DataTypes.mapOf(DataTypes.INET, DataTypes.INET);
403+
GenericType<Map<InetAddress, InetAddress>> javaType =
404+
new GenericType<Map<InetAddress, InetAddress>>() {};
405+
InetAddress address = InetAddress.getByAddress(new byte[] {127, 0, 0, 1});
406+
// Because the actual implementation is a subclass, there is no exact match with the codec's
407+
// declared type
408+
assertThat(address).isInstanceOf(Inet4Address.class);
409+
Map<InetAddress, InetAddress> value = ImmutableMap.of(address, address);
410+
411+
TestCachingCodecRegistry registry = new TestCachingCodecRegistry(onCacheLookup);
412+
InOrder inOrder = Mockito.inOrder(onCacheLookup);
413+
414+
TypeCodec<Map<InetAddress, InetAddress>> codec = registry.codecFor(value);
415+
assertThat(codec).isNotNull();
416+
assertThat(codec.canDecode(cqlType)).isTrue();
417+
assertThat(codec.canEncode(javaType)).isTrue();
418+
assertThat(codec.canEncode(value)).isTrue();
419+
420+
inOrder
421+
.verify(onCacheLookup)
422+
.accept(null, GenericType.mapOf(Inet4Address.class, Inet4Address.class));
423+
}
424+
353425
@Test
354426
public void should_create_tuple_codec_for_cql_and_java_types() {
355427
TupleType cqlType = DataTypes.tupleOf(DataTypes.INT, DataTypes.listOf(DataTypes.TEXT));

0 commit comments

Comments
 (0)