/*
* 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.reflect.TypeToken;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.InetAddress;
import java.nio.ByteBuffer;
import java.util.*;
import static com.datastax.driver.core.Assertions.assertThat;
import static com.datastax.driver.core.DataType.*;
import static com.datastax.driver.core.DataType.list;
import static com.datastax.driver.core.ProtocolVersion.V4;
import static com.datastax.driver.core.TypeTokens.*;
import static com.google.common.reflect.TypeToken.of;
import static java.util.Collections.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.testng.Assert.fail;
public class CodecRegistryTest {
@DataProvider
public static Object[][] cql() {
return new Object[][]{
{DataType.blob(), TypeCodec.blob()},
{DataType.cboolean(), TypeCodec.cboolean()},
{DataType.smallint(), TypeCodec.smallInt()},
{DataType.tinyint(), TypeCodec.tinyInt()},
{DataType.cint(), TypeCodec.cint()},
{DataType.bigint(), TypeCodec.bigint()},
{DataType.counter(), TypeCodec.counter()},
{DataType.cdouble(), TypeCodec.cdouble()},
{DataType.cfloat(), TypeCodec.cfloat()},
{DataType.varint(), TypeCodec.varint()},
{DataType.decimal(), TypeCodec.decimal()},
{DataType.varchar(), TypeCodec.varchar()},
{DataType.ascii(), TypeCodec.ascii()},
{DataType.timestamp(), TypeCodec.timestamp()},
{DataType.date(), TypeCodec.date()},
{DataType.time(), TypeCodec.time()},
{DataType.uuid(), TypeCodec.uuid()},
{DataType.timeuuid(), TypeCodec.timeUUID()},
{DataType.inet(), TypeCodec.inet()},
{DataType.duration(), TypeCodec.duration()}
};
}
@DataProvider
public static Object[][] cqlAndJava() {
return new Object[][]{
{DataType.blob(), ByteBuffer.class, TypeCodec.blob()},
{DataType.cboolean(), Boolean.class, TypeCodec.cboolean()},
{DataType.smallint(), Short.class, TypeCodec.smallInt()},
{DataType.tinyint(), Byte.class, TypeCodec.tinyInt()},
{DataType.cint(), Integer.class, TypeCodec.cint()},
{DataType.bigint(), Long.class, TypeCodec.bigint()},
{DataType.counter(), Long.class, TypeCodec.counter()},
{DataType.cdouble(), Double.class, TypeCodec.cdouble()},
{DataType.cfloat(), Float.class, TypeCodec.cfloat()},
{DataType.varint(), BigInteger.class, TypeCodec.varint()},
{DataType.decimal(), BigDecimal.class, TypeCodec.decimal()},
{DataType.varchar(), String.class, TypeCodec.varchar()},
{DataType.ascii(), String.class, TypeCodec.ascii()},
{DataType.timestamp(), Date.class, TypeCodec.timestamp()},
{DataType.date(), LocalDate.class, TypeCodec.date()},
{DataType.time(), Long.class, TypeCodec.time()},
{DataType.uuid(), UUID.class, TypeCodec.uuid()},
{DataType.timeuuid(), UUID.class, TypeCodec.timeUUID()},
{DataType.inet(), InetAddress.class, TypeCodec.inet()},
{DataType.duration(), Duration.class, TypeCodec.duration()}
};
}
@DataProvider
public static Object[][] value() {
return new Object[][]{
{ByteBuffer.allocate(0), TypeCodec.blob()},
{Boolean.TRUE, TypeCodec.cboolean()},
{(short) 42, TypeCodec.smallInt()},
{(byte) 42, TypeCodec.tinyInt()},
{42, TypeCodec.cint()},
{42L, TypeCodec.bigint()},
{42D, TypeCodec.cdouble()},
{42F, TypeCodec.cfloat()},
{new BigInteger("1234"), TypeCodec.varint()},
{new BigDecimal("123.45"), TypeCodec.decimal()},
{"foo", TypeCodec.varchar()},
{new Date(42), TypeCodec.timestamp()},
{LocalDate.fromDaysSinceEpoch(42), TypeCodec.date()},
{UUID.randomUUID(), TypeCodec.uuid()},
{mock(InetAddress.class), TypeCodec.inet()},
{Duration.from("1mo2d3h"), TypeCodec.duration()}
};
}
@DataProvider
public static Object[][] cqlAndValue() {
return new Object[][]{
{DataType.blob(), ByteBuffer.allocate(0), TypeCodec.blob()},
{DataType.cboolean(), true, TypeCodec.cboolean()},
{DataType.smallint(), (short) 42, TypeCodec.smallInt()},
{DataType.tinyint(), (byte) 42, TypeCodec.tinyInt()},
{DataType.cint(), 42, TypeCodec.cint()},
{DataType.bigint(), 42L, TypeCodec.bigint()},
{DataType.counter(), 42L, TypeCodec.counter()},
{DataType.cdouble(), 42D, TypeCodec.cdouble()},
{DataType.cfloat(), 42F, TypeCodec.cfloat()},
{DataType.varint(), new BigInteger("1234"), TypeCodec.varint()},
{DataType.decimal(), new BigDecimal("123.45"), TypeCodec.decimal()},
{DataType.varchar(), "foo", TypeCodec.varchar()},
{DataType.ascii(), "foo", TypeCodec.ascii()},
{DataType.timestamp(), new Date(42), TypeCodec.timestamp()},
{DataType.date(), LocalDate.fromDaysSinceEpoch(42), TypeCodec.date()},
{DataType.time(), 42L, TypeCodec.time()},
{DataType.uuid(), UUID.randomUUID(), TypeCodec.uuid()},
{DataType.timeuuid(), UUID.randomUUID(), TypeCodec.timeUUID()},
{DataType.inet(), mock(InetAddress.class), TypeCodec.inet()},
{DataType.duration(), Duration.from("1mo2d3h"), TypeCodec.duration()}
};
}
@Test(groups = "unit", dataProvider = "cql")
public void should_find_codec_by_cql_type(DataType cqlType, TypeCodec<?> expected) {
// given
CodecRegistry registry = new CodecRegistry();
// when
TypeCodec<?> actual = registry.codecFor(cqlType);
// then
assertThat(actual)
.isNotNull()
.accepts(cqlType)
.isSameAs(expected);
}
@Test(groups = "unit", dataProvider = "cqlAndJava")
public void should_find_codec_by_cql_type_java_type(DataType cqlType, Class<?> javaType, TypeCodec<?> expected) {
// given
CodecRegistry registry = new CodecRegistry();
// when
TypeCodec<?> actual = registry.codecFor(cqlType, javaType);
// then
assertThat(actual)
.isNotNull()
.accepts(cqlType)
.accepts(javaType)
.isSameAs(expected);
}
@Test(groups = "unit", dataProvider = "value")
public void should_find_codec_by_value(Object value, TypeCodec<?> expected) {
// given
CodecRegistry registry = new CodecRegistry();
// when
TypeCodec<?> actual = registry.codecFor(value);
// then
assertThat(actual)
.isNotNull()
.accepts(value)
.isSameAs(expected);
}
@Test(groups = "unit", dataProvider = "cqlAndValue")
public void should_find_codec_by_cql_type_and_value(DataType cqlType, Object value, TypeCodec<?> expected) {
// given
CodecRegistry registry = new CodecRegistry();
// when
TypeCodec<?> actual = registry.codecFor(cqlType, value);
// then
assertThat(actual)
.isNotNull()
.accepts(cqlType)
.accepts(value)
.isSameAs(expected);
}
@Test(groups = "unit")
public void should_find_newly_registered_codec_by_cql_type() {
// given
CodecRegistry registry = new CodecRegistry();
TypeCodec<?> expected = mockCodec(list(text()), listOf(String.class));
registry.register(expected);
// when
TypeCodec<?> actual = registry.codecFor(list(text()));
// then
assertThat(actual)
.isNotNull()
.isSameAs(expected);
}
@Test(groups = "unit")
public void should_find_default_codec_if_cql_type_already_registered() {
// given
CodecRegistry registry = new CodecRegistry();
TypeCodec<?> newCodec = mockCodec(text(), of(StringBuilder.class));
registry.register(newCodec);
// when
TypeCodec<?> actual = registry.codecFor(text());
// then
assertThat(actual)
.isNotNull()
.isNotSameAs(newCodec)
.accepts(text())
.accepts(String.class)
.doesNotAccept(StringBuilder.class);
}
@Test(groups = "unit")
public void should_find_newly_registered_codec_by_cql_type_and_java_type() {
// given
CodecRegistry registry = new CodecRegistry();
TypeCodec<?> expected = mockCodec(list(text()), listOf(String.class));
registry.register(expected);
// when
TypeCodec<?> actual = registry.codecFor(list(text()), listOf(String.class));
// then
assertThat(actual)
.isNotNull()
.isSameAs(expected);
}
@Test(groups = "unit")
public void should_create_list_codec() {
CollectionType cqlType = list(cint());
TypeToken<List<Integer>> javaType = listOf(Integer.class);
assertThat(new CodecRegistry().codecFor(cqlType))
.isNotNull()
.accepts(cqlType)
.accepts(javaType);
assertThat(new CodecRegistry().codecFor(cqlType, javaType))
.isNotNull()
.accepts(cqlType)
.accepts(javaType);
assertThat(new CodecRegistry().codecFor(singletonList(42)))
.isNotNull()
.accepts(cqlType)
.accepts(javaType);
assertThat(new CodecRegistry().codecFor(cqlType, singletonList(42)))
.isNotNull()
.accepts(cqlType)
.accepts(javaType);
assertThat(new CodecRegistry().codecFor(new ArrayList<Integer>()))
.isNotNull()
// empty collections are mapped to blob codec if no CQL type provided
.accepts(list(blob()))
.accepts(listOf(ByteBuffer.class));
assertThat(new CodecRegistry().codecFor(cqlType, new ArrayList<Integer>()))
.isNotNull()
.accepts(cqlType)
.accepts(javaType);
}
@Test(groups = "unit")
public void should_create_set_codec() {
CollectionType cqlType = set(cint());
TypeToken<Set<Integer>> javaType = setOf(Integer.class);
assertThat(new CodecRegistry().codecFor(cqlType))
.isNotNull()
.accepts(cqlType)
.accepts(javaType);
assertThat(new CodecRegistry().codecFor(cqlType, javaType))
.isNotNull()
.accepts(cqlType)
.accepts(javaType);
assertThat(new CodecRegistry().codecFor(singleton(42)))
.isNotNull()
.accepts(cqlType)
.accepts(javaType);
assertThat(new CodecRegistry().codecFor(cqlType, singleton(42)))
.isNotNull()
.accepts(cqlType)
.accepts(javaType);
assertThat(new CodecRegistry().codecFor(new HashSet<Integer>()))
.isNotNull()
// empty collections are mapped to blob codec if no CQL type provided
.accepts(set(blob()))
.accepts(setOf(ByteBuffer.class));
assertThat(new CodecRegistry().codecFor(cqlType, new HashSet<Integer>()))
.isNotNull()
.accepts(cqlType)
.accepts(javaType);
}
@Test(groups = "unit")
public void should_create_map_codec() {
CollectionType cqlType = map(cint(), list(varchar()));
TypeToken<Map<Integer, List<String>>> javaType = mapOf(of(Integer.class), listOf(String.class));
assertThat(new CodecRegistry().codecFor(cqlType))
.isNotNull()
.accepts(cqlType)
.accepts(javaType);
assertThat(new CodecRegistry().codecFor(cqlType, javaType))
.isNotNull()
.accepts(cqlType)
.accepts(javaType);
assertThat(new CodecRegistry().codecFor(singletonMap(42, singletonList("foo"))))
.isNotNull()
.accepts(cqlType)
.accepts(javaType);
assertThat(new CodecRegistry().codecFor(cqlType, singletonMap(42, singletonList("foo"))))
.isNotNull()
.accepts(cqlType)
.accepts(javaType);
assertThat(new CodecRegistry().codecFor(new HashMap<Integer, List<String>>()))
.isNotNull()
// empty collections are mapped to blob codec if no CQL type provided
.accepts(map(blob(), blob()))
.accepts(mapOf(ByteBuffer.class, ByteBuffer.class));
assertThat(new CodecRegistry().codecFor(cqlType, new HashMap<Integer, List<String>>()))
.isNotNull()
.accepts(cqlType)
.accepts(javaType);
}
@Test(groups = "unit")
public void should_create_tuple_codec() {
CodecRegistry registry = new CodecRegistry();
TupleType tupleType = TupleType.of(V4, registry, cint(), varchar());
assertThat(registry.codecFor(tupleType))
.isNotNull()
.accepts(tupleType)
.accepts(TupleValue.class);
registry = new CodecRegistry();
tupleType = TupleType.of(V4, registry, cint(), varchar());
assertThat(registry.codecFor(tupleType, TupleValue.class))
.isNotNull()
.accepts(tupleType)
.accepts(TupleValue.class);
registry = new CodecRegistry();
tupleType = TupleType.of(V4, registry, cint(), varchar());
assertThat(registry.codecFor(new TupleValue(tupleType)))
.isNotNull()
.accepts(tupleType)
.accepts(TupleValue.class);
assertThat(registry.codecFor(tupleType, new TupleValue(tupleType)))
.isNotNull()
.accepts(tupleType)
.accepts(TupleValue.class);
}
@Test(groups = "unit")
public void should_create_udt_codec() {
CodecRegistry registry = new CodecRegistry();
UserType udt = new UserType("ks", "test", false, Collections.<UserType.Field>emptyList(), V4, registry);
assertThat(registry.codecFor(udt))
.isNotNull()
.accepts(udt)
.accepts(UDTValue.class);
registry = new CodecRegistry();
udt = new UserType("ks", "test", false, Collections.<UserType.Field>emptyList(), V4, registry);
assertThat(registry.codecFor(udt, UDTValue.class))
.isNotNull()
.accepts(udt)
.accepts(UDTValue.class);
registry = new CodecRegistry();
udt = new UserType("ks", "test", false, Collections.<UserType.Field>emptyList(), V4, registry);
assertThat(registry.codecFor(new UDTValue(udt)))
.isNotNull()
.accepts(udt)
.accepts(UDTValue.class);
registry = new CodecRegistry();
udt = new UserType("ks", "test", false, Collections.<UserType.Field>emptyList(), V4, registry);
assertThat(registry.codecFor(udt, new UDTValue(udt)))
.isNotNull()
.accepts(udt)
.accepts(UDTValue.class);
}
@Test(groups = "unit")
public void should_create_codec_for_custom_cql_type() {
DataType custom = DataType.custom("foo");
assertThat(new CodecRegistry().codecFor(custom))
.isNotNull()
.accepts(custom)
.accepts(ByteBuffer.class);
assertThat(new CodecRegistry().codecFor(custom, ByteBuffer.class))
.isNotNull()
.accepts(custom)
.accepts(ByteBuffer.class);
assertThat(new CodecRegistry().codecFor(custom, ByteBuffer.allocate(0)))
.isNotNull()
.accepts(custom)
.accepts(ByteBuffer.class);
}
@Test(groups = "unit")
public void should_create_derived_codecs_for_java_type_handled_by_custom_codec() {
TypeCodec<?> newCodec = mockCodec(varchar(), of(StringBuilder.class));
CodecRegistry registry = new CodecRegistry().register(newCodec);
// lookup by CQL type only returns default codec
assertThat(registry.codecFor(list(varchar()))).doesNotAccept(listOf(StringBuilder.class));
assertThat(registry.codecFor(list(varchar()), listOf(StringBuilder.class))).isNotNull();
}
@Test(groups = "unit")
public void should_not_find_codec_if_java_type_unknown() {
try {
new CodecRegistry().codecFor(StringBuilder.class);
fail("Should not have found a codec for ANY <-> StringBuilder");
} catch (CodecNotFoundException e) {
// expected
}
try {
new CodecRegistry().codecFor(varchar(), StringBuilder.class);
fail("Should not have found a codec for varchar <-> StringBuilder");
} catch (CodecNotFoundException e) {
// expected
}
try {
new CodecRegistry().codecFor(new StringBuilder());
fail("Should not have found a codec for ANY <-> StringBuilder");
} catch (CodecNotFoundException e) {
// expected
}
try {
new CodecRegistry().codecFor(varchar(), new StringBuilder());
fail("Should not have found a codec for varchar <-> StringBuilder");
} catch (CodecNotFoundException e) {
// expected
}
}
@Test(groups = "unit")
public void should_ignore_codec_colliding_with_already_registered_codec() {
MemoryAppender logs = startCapturingLogs();
CodecRegistry registry = new CodecRegistry();
TypeCodec<?> newCodec = mockCodec(cint(), of(Integer.class));
registry.register(newCodec);
assertThat(logs.getNext()).contains("Ignoring codec MockCodec");
assertThat(
registry.codecFor(cint(), Integer.class)
).isNotSameAs(newCodec);
stopCapturingLogs(logs);
}
@Test(groups = "unit")
public void should_ignore_codec_colliding_with_already_generated_codec() {
MemoryAppender logs = startCapturingLogs();
CodecRegistry registry = new CodecRegistry();
// Force generation of a list token from the default token
registry.codecFor(list(cint()), listOf(Integer.class));
TypeCodec<?> newCodec = mockCodec(list(cint()), listOf(Integer.class));
registry.register(newCodec);
assertThat(logs.getNext()).contains("Ignoring codec MockCodec");
assertThat(
registry.codecFor(list(cint()), listOf(Integer.class))
).isNotSameAs(newCodec);
stopCapturingLogs(logs);
}
private MemoryAppender startCapturingLogs() {
Logger registryLogger = Logger.getLogger(CodecRegistry.class);
registryLogger.setLevel(Level.WARN);
MemoryAppender logs = new MemoryAppender();
registryLogger.addAppender(logs);
return logs;
}
private void stopCapturingLogs(MemoryAppender logs) {
Logger registryLogger = Logger.getLogger(CodecRegistry.class);
registryLogger.setLevel(null);
registryLogger.removeAppender(logs);
}
private <T> TypeCodec<T> mockCodec(DataType cqlType, TypeToken<T> javaType) {
@SuppressWarnings("unchecked")
TypeCodec<T> newCodec = mock(TypeCodec.class);
when(newCodec.getCqlType()).thenReturn(cqlType);
when(newCodec.getJavaType()).thenReturn(javaType);
when(newCodec.accepts(cqlType)).thenReturn(true);
when(newCodec.accepts(javaType)).thenReturn(true);
when(newCodec.toString()).thenReturn(String.format("MockCodec [%s <-> %s]", cqlType, javaType));
return newCodec;
}
}