Skip to content

Commit

Permalink
Fix collection codec creation if first element is a subtype
Browse files Browse the repository at this point in the history
  • Loading branch information
olim7t committed Jul 21, 2017
1 parent 59417d3 commit 279e79e
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 11 deletions.
Expand Up @@ -158,17 +158,17 @@ public <T> TypeCodec<T> codecFor(T value) {
return safeCast(getCachedCodec(null, javaType));
}

// Not exposed publicly, this is only used for the recursion from createCodec(GenericType)
private TypeCodec<?> codecFor(GenericType<?> javaType) {
// Not exposed publicly, this is only used for the recursion from createCovariantCodec(GenericType)
private TypeCodec<?> covariantCodecFor(GenericType<?> javaType) {
LOG.trace("[{}] Looking up codec for Java type {}", logPrefix, javaType);
for (TypeCodec<?> primitiveCodec : PRIMITIVE_CODECS) {
if (primitiveCodec.canEncode(javaType)) {
if (primitiveCodec.getJavaType().__getToken().isSupertypeOf(javaType.__getToken())) {
LOG.trace("[{}] Found matching primitive codec {}", logPrefix, primitiveCodec);
return safeCast(primitiveCodec);
}
}
for (TypeCodec<?> userCodec : userCodecs) {
if (userCodec.canEncode(javaType)) {
if (userCodec.getJavaType().__getToken().isSupertypeOf(javaType.__getToken())) {
LOG.trace("[{}] Found matching user codec {}", logPrefix, userCodec);
return safeCast(userCodec);
}
Expand Down Expand Up @@ -218,7 +218,7 @@ protected TypeCodec<?> createCodec(DataType cqlType, GenericType<?> javaType) {
assert cqlType != null;
return createCodec(cqlType);
} else if (cqlType == null) {
return createCodec(javaType);
return createCovariantCodec(javaType);
}
TypeToken<?> token = javaType.__getToken();
if (cqlType instanceof ListType && List.class.isAssignableFrom(token.getRawType())) {
Expand Down Expand Up @@ -273,28 +273,29 @@ protected TypeCodec<?> createCodec(DataType cqlType, GenericType<?> javaType) {
}

// Try to create a codec when we haven't found it in the cache.
// Variant where the CQL type is unknown.
private TypeCodec<?> createCodec(GenericType<?> javaType) {
// Variant where the CQL type is unknown. Note that this is only used for lookups by Java value,
// and therefore covariance is allowed.
private TypeCodec<?> createCovariantCodec(GenericType<?> javaType) {
TypeToken<?> token = javaType.__getToken();
if (List.class.isAssignableFrom(token.getRawType())
&& token.getType() instanceof ParameterizedType) {
Type[] typeArguments = ((ParameterizedType) token.getType()).getActualTypeArguments();
GenericType<?> elementType = GenericType.of(typeArguments[0]);
TypeCodec<?> elementCodec = codecFor(elementType);
TypeCodec<?> elementCodec = covariantCodecFor(elementType);
return TypeCodecs.listOf(elementCodec);
} else if (Set.class.isAssignableFrom(token.getRawType())
&& token.getType() instanceof ParameterizedType) {
Type[] typeArguments = ((ParameterizedType) token.getType()).getActualTypeArguments();
GenericType<?> elementType = GenericType.of(typeArguments[0]);
TypeCodec<?> elementCodec = codecFor(elementType);
TypeCodec<?> elementCodec = covariantCodecFor(elementType);
return TypeCodecs.setOf(elementCodec);
} else if (Map.class.isAssignableFrom(token.getRawType())
&& token.getType() instanceof ParameterizedType) {
Type[] typeArguments = ((ParameterizedType) token.getType()).getActualTypeArguments();
GenericType<?> keyType = GenericType.of(typeArguments[0]);
GenericType<?> valueType = GenericType.of(typeArguments[1]);
TypeCodec<?> keyCodec = codecFor(keyType);
TypeCodec<?> valueCodec = codecFor(valueType);
TypeCodec<?> keyCodec = covariantCodecFor(keyType);
TypeCodec<?> valueCodec = covariantCodecFor(valueType);
return TypeCodecs.mapOf(keyCodec, valueCodec);
}
throw new CodecNotFoundException(null, javaType);
Expand Down
Expand Up @@ -229,6 +229,29 @@ public void should_create_list_codec_for_java_value() throws UnknownHostExceptio
inOrder.verify(onCacheLookup).accept(null, GenericType.listOf(GenericType.INTEGER));
}

@Test
public void should_create_list_codec_for_java_value_when_first_element_is_a_subtype()
throws UnknownHostException {
ListType cqlType = DataTypes.listOf(DataTypes.INET);
GenericType<List<InetAddress>> javaType = new GenericType<List<InetAddress>>() {};
InetAddress address = InetAddress.getByAddress(new byte[] {127, 0, 0, 1});
// Because the actual implementation is a subclass, there is no exact match with the codec's
// declared type
assertThat(address).isInstanceOf(Inet4Address.class);
List<InetAddress> value = ImmutableList.of(address);

TestCachingCodecRegistry registry = new TestCachingCodecRegistry(onCacheLookup);
InOrder inOrder = Mockito.inOrder(onCacheLookup);

TypeCodec<List<InetAddress>> codec = registry.codecFor(value);
assertThat(codec).isNotNull();
assertThat(codec.canDecode(cqlType)).isTrue();
assertThat(codec.canEncode(javaType)).isTrue();
assertThat(codec.canEncode(value)).isTrue();

inOrder.verify(onCacheLookup).accept(null, GenericType.listOf(Inet4Address.class));
}

@Test
public void should_create_set_codec_for_cql_and_java_types() {
SetType cqlType = DataTypes.setOf(DataTypes.setOf(DataTypes.INT));
Expand Down Expand Up @@ -286,6 +309,29 @@ public void should_create_set_codec_for_java_value() {
inOrder.verify(onCacheLookup).accept(null, GenericType.setOf(GenericType.INTEGER));
}

@Test
public void should_create_set_codec_for_java_value_when_first_element_is_a_subtype()
throws UnknownHostException {
SetType cqlType = DataTypes.setOf(DataTypes.INET);
GenericType<Set<InetAddress>> javaType = new GenericType<Set<InetAddress>>() {};
InetAddress address = InetAddress.getByAddress(new byte[] {127, 0, 0, 1});
// Because the actual implementation is a subclass, there is no exact match with the codec's
// declared type
assertThat(address).isInstanceOf(Inet4Address.class);
Set<InetAddress> value = ImmutableSet.of(address);

TestCachingCodecRegistry registry = new TestCachingCodecRegistry(onCacheLookup);
InOrder inOrder = Mockito.inOrder(onCacheLookup);

TypeCodec<Set<InetAddress>> codec = registry.codecFor(value);
assertThat(codec).isNotNull();
assertThat(codec.canDecode(cqlType)).isTrue();
assertThat(codec.canEncode(javaType)).isTrue();
assertThat(codec.canEncode(value)).isTrue();

inOrder.verify(onCacheLookup).accept(null, GenericType.setOf(Inet4Address.class));
}

@Test
public void should_create_map_codec_for_cql_and_java_types() {
MapType cqlType = DataTypes.mapOf(DataTypes.INT, DataTypes.mapOf(DataTypes.INT, DataTypes.INT));
Expand Down Expand Up @@ -350,6 +396,32 @@ public void should_create_map_codec_for_java_value() {
.accept(null, GenericType.mapOf(GenericType.INTEGER, GenericType.INTEGER));
}

@Test
public void should_create_map_codec_for_java_value_when_first_element_is_a_subtype()
throws UnknownHostException {
MapType cqlType = DataTypes.mapOf(DataTypes.INET, DataTypes.INET);
GenericType<Map<InetAddress, InetAddress>> javaType =
new GenericType<Map<InetAddress, InetAddress>>() {};
InetAddress address = InetAddress.getByAddress(new byte[] {127, 0, 0, 1});
// Because the actual implementation is a subclass, there is no exact match with the codec's
// declared type
assertThat(address).isInstanceOf(Inet4Address.class);
Map<InetAddress, InetAddress> value = ImmutableMap.of(address, address);

TestCachingCodecRegistry registry = new TestCachingCodecRegistry(onCacheLookup);
InOrder inOrder = Mockito.inOrder(onCacheLookup);

TypeCodec<Map<InetAddress, InetAddress>> codec = registry.codecFor(value);
assertThat(codec).isNotNull();
assertThat(codec.canDecode(cqlType)).isTrue();
assertThat(codec.canEncode(javaType)).isTrue();
assertThat(codec.canEncode(value)).isTrue();

inOrder
.verify(onCacheLookup)
.accept(null, GenericType.mapOf(Inet4Address.class, Inet4Address.class));
}

@Test
public void should_create_tuple_codec_for_cql_and_java_types() {
TupleType cqlType = DataTypes.tupleOf(DataTypes.INT, DataTypes.listOf(DataTypes.TEXT));
Expand Down

0 comments on commit 279e79e

Please sign in to comment.