DATACMNS-783 - DefaultTypeMapper now specializes raw generic types.

If the type lookup from the store source returns a raw generic type (e.g. resolving the value of a generic property against a value of that generic type - i.e. not a more concrete type binding the generic information) in the context of a generic property, we previously did not apply the generics information of the contextual instance to that very raw type.

We now expose a TypeInformation.specialize(ClassTypeInformation) which applies the current generics context to the given raw type and basically creates a synthetic parameterized TypeInformation instance.

DefaultTypeMapper applies this specialization by default now with ClassTypeInformation simply returning the given type as is so that we don't create any resolution overhead in case no generics are involved in the first place.
This commit is contained in:
Oliver Gierke
2015-11-13 16:52:09 +01:00
parent 0d6476c642
commit d2737983c3
6 changed files with 184 additions and 14 deletions

View File

@@ -15,7 +15,7 @@
*/
package org.springframework.data.convert;
import static org.hamcrest.CoreMatchers.*;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
@@ -50,14 +50,13 @@ public class DefaultTypeMapperUnitTests {
Map<String, String> source;
@Before
@SuppressWarnings({ "rawtypes", "unchecked" })
public void setUp() {
this.typeMapper = new DefaultTypeMapper<Map<String, String>>(accessor, Arrays.asList(mapper));
this.source = Collections.singletonMap("key", STRING);
when(accessor.readAliasFrom(source)).thenReturn(STRING);
when(mapper.resolveTypeFrom(STRING)).thenReturn((TypeInformation) STRING_TYPE_INFO);
doReturn(STRING).when(accessor).readAliasFrom(source);
doReturn(STRING_TYPE_INFO).when(mapper).resolveTypeFrom(STRING);
}
@Test
@@ -83,4 +82,35 @@ public class DefaultTypeMapperUnitTests {
assertThat(this.typeMapper.getAliasFor(STRING_TYPE_INFO), is(alias));
}
/**
* @see DATACMNS-783
*/
@Test
public void specializesRawSourceTypeUsingGenericContext() {
ClassTypeInformation<Foo> root = ClassTypeInformation.from(Foo.class);
TypeInformation<?> propertyType = root.getProperty("abstractBar");
TypeInformation<?> barType = ClassTypeInformation.from(Bar.class);
doReturn(barType).when(accessor).readAliasFrom(source);
doReturn(barType).when(mapper).resolveTypeFrom(barType);
TypeInformation<?> result = typeMapper.readType(source, propertyType);
assertThat(result.getType(), is((Object) Bar.class));
assertThat(result.getProperty("field").getType(), is((Object) Character.class));
}
static class TypeWithAbstractGenericType<T> {
AbstractBar<T> abstractBar;
}
static class Foo extends TypeWithAbstractGenericType<Character> {}
static abstract class AbstractBar<T> {}
static class Bar<T> extends AbstractBar<T> {
T field;
}
}

View File

@@ -224,8 +224,8 @@ public class ClassTypeInformationUnitTests {
assertThat(parameterType.isAssignableFrom(stringInfo), is(true));
assertThat(stringInfo.getSuperTypeInformation(GenericInterface.class), is((Object) parameterType));
assertThat(parameterType.isAssignableFrom(from(LongImplementation.class)), is(false));
assertThat(parameterType.isAssignableFrom(from(StringImplementation.class).getSuperTypeInformation(
GenericInterface.class)), is(true));
assertThat(parameterType
.isAssignableFrom(from(StringImplementation.class).getSuperTypeInformation(GenericInterface.class)), is(true));
}
@Test
@@ -238,8 +238,8 @@ public class ClassTypeInformationUnitTests {
assertThat(parameterType.isAssignableFrom(from(StringImplementation.class)), is(false));
assertThat(parameterType.isAssignableFrom(from(LongImplementation.class)), is(true));
assertThat(parameterType.isAssignableFrom(from(StringImplementation.class).getSuperTypeInformation(
GenericInterface.class)), is(false));
assertThat(parameterType
.isAssignableFrom(from(StringImplementation.class).getSuperTypeInformation(GenericInterface.class)), is(false));
}
@Test
@@ -252,8 +252,8 @@ public class ClassTypeInformationUnitTests {
assertThat(parameterType.isAssignableFrom(from(StringImplementation.class)), is(false));
assertThat(parameterType.isAssignableFrom(from(LongImplementation.class)), is(true));
assertThat(parameterType.isAssignableFrom(from(StringImplementation.class).getSuperTypeInformation(
GenericInterface.class)), is(false));
assertThat(parameterType
.isAssignableFrom(from(StringImplementation.class).getSuperTypeInformation(GenericInterface.class)), is(false));
}
@Test
@@ -353,6 +353,35 @@ public class ClassTypeInformationUnitTests {
assertThat(leafType.getType(), is((Object) Leaf.class));
}
/**
* @see DATACMNS-783
*/
@Test
public void specializesTypeUsingTypeVariableContext() {
ClassTypeInformation<Foo> root = ClassTypeInformation.from(Foo.class);
TypeInformation<?> property = root.getProperty("abstractBar");
TypeInformation<?> specialized = property.specialize(ClassTypeInformation.from(Bar.class));
assertThat(specialized.getType(), is((Object) Bar.class));
assertThat(specialized.getProperty("field").getType(), is((Object) Character.class));
}
/**
* @see DATACMNS-783
*/
@Test
@SuppressWarnings("rawtypes")
public void usesTargetTypeDirectlyIfNoGenericsAreInvolved() {
ClassTypeInformation<Foo> root = ClassTypeInformation.from(Foo.class);
TypeInformation<?> property = root.getProperty("object");
ClassTypeInformation<?> from = ClassTypeInformation.from(Bar.class);
assertThat(property.specialize(from), is((TypeInformation) from));
}
static class StringMapContainer extends MapContainer<String> {
}
@@ -527,4 +556,17 @@ public class ClassTypeInformationUnitTests {
static class ConcreteInnerIntermediate extends GenericInnerIntermediate<Leaf> {}
static class Leaf {}
static class TypeWithAbstractGenericType<T> {
AbstractBar<T> abstractBar;
Object object;
}
static class Foo extends TypeWithAbstractGenericType<Character> {}
static abstract class AbstractBar<T> {}
static class Bar<T> extends AbstractBar<T> {
T field;
}
}