diff --git a/src/main/java/org/springframework/data/convert/DefaultTypeMapper.java b/src/main/java/org/springframework/data/convert/DefaultTypeMapper.java index cae8a12b6..9bc99009b 100644 --- a/src/main/java/org/springframework/data/convert/DefaultTypeMapper.java +++ b/src/main/java/org/springframework/data/convert/DefaultTypeMapper.java @@ -147,10 +147,16 @@ public class DefaultTypeMapper implements TypeMapper { Class rawType = basicType == null ? null : basicType.getType(); - boolean isMoreConcreteCustomType = rawType == null ? true : rawType.isAssignableFrom(documentsTargetType) - && !rawType.equals(documentsTargetType); - return isMoreConcreteCustomType ? (TypeInformation) ClassTypeInformation.from(documentsTargetType) - : basicType; + boolean isMoreConcreteCustomType = rawType == null ? true + : rawType.isAssignableFrom(documentsTargetType) && !rawType.equals(documentsTargetType); + + if (!isMoreConcreteCustomType) { + return basicType; + } + + ClassTypeInformation targetType = ClassTypeInformation.from(documentsTargetType); + + return (TypeInformation) (basicType != null ? basicType.specialize(targetType) : targetType); } /** diff --git a/src/main/java/org/springframework/data/util/ClassTypeInformation.java b/src/main/java/org/springframework/data/util/ClassTypeInformation.java index aecfc1a8f..ee288dd10 100644 --- a/src/main/java/org/springframework/data/util/ClassTypeInformation.java +++ b/src/main/java/org/springframework/data/util/ClassTypeInformation.java @@ -167,6 +167,15 @@ public class ClassTypeInformation extends TypeDiscoverer { return getType().isAssignableFrom(target.getType()); } + /* + * (non-Javadoc) + * @see org.springframework.data.util.TypeDiscoverer#specialize(org.springframework.data.util.ClassTypeInformation) + */ + @Override + public TypeInformation specialize(ClassTypeInformation type) { + return type; + } + /* * (non-Javadoc) * @see java.lang.Object#toString() diff --git a/src/main/java/org/springframework/data/util/TypeDiscoverer.java b/src/main/java/org/springframework/data/util/TypeDiscoverer.java index cbe4e209e..9f587e498 100644 --- a/src/main/java/org/springframework/data/util/TypeDiscoverer.java +++ b/src/main/java/org/springframework/data/util/TypeDiscoverer.java @@ -493,6 +493,20 @@ class TypeDiscoverer implements TypeInformation { return target.getSuperTypeInformation(getType()).equals(this); } + /* + * (non-Javadoc) + * @see org.springframework.data.util.TypeInformation#specialize(org.springframework.data.util.ClassTypeInformation) + */ + @Override + public TypeInformation specialize(ClassTypeInformation type) { + + Assert.isTrue(getType().isAssignableFrom(type.getType())); + + List> arguments = getTypeArguments(); + + return arguments.isEmpty() ? type : createInfo(new SyntheticParamterizedType(type, arguments)); + } + private TypeInformation getTypeArgument(Class bound, int index) { Class[] arguments = GenericTypeResolver.resolveTypeArguments(getType(), bound); @@ -537,4 +551,63 @@ class TypeDiscoverer implements TypeInformation { public int hashCode() { return hashCode; } + + /** + * A synthetic {@link ParameterizedType}. + * + * @author Oliver Gierke + * @since 1.11 + */ + private static class SyntheticParamterizedType implements ParameterizedType { + + private final ClassTypeInformation typeInformation; + private final List> typeParameters; + + /** + * @param typeInformation must not be {@literal null}. + * @param typeParameters must not be {@literal null}. + */ + public SyntheticParamterizedType(ClassTypeInformation typeInformation, List> typeParameters) { + + Assert.notNull(typeInformation, "Type must not be null!"); + Assert.notNull(typeParameters, "Type parameters must not be null!"); + + this.typeInformation = typeInformation; + this.typeParameters = typeParameters; + } + + /* + * (non-Javadoc) + * @see java.lang.reflect.ParameterizedType#getRawType() + */ + @Override + public Type getRawType() { + return typeInformation.getType(); + } + + /* + * (non-Javadoc) + * @see java.lang.reflect.ParameterizedType#getOwnerType() + */ + @Override + public Type getOwnerType() { + return null; + } + + /* + * (non-Javadoc) + * @see java.lang.reflect.ParameterizedType#getActualTypeArguments() + */ + @Override + public Type[] getActualTypeArguments() { + + Type[] result = new Type[typeParameters.size()]; + + for (int i = 0; i < typeParameters.size(); i++) { + result[i] = typeParameters.get(0).getType(); + } + + return result; + } + } } diff --git a/src/main/java/org/springframework/data/util/TypeInformation.java b/src/main/java/org/springframework/data/util/TypeInformation.java index a71275904..8d875abbf 100644 --- a/src/main/java/org/springframework/data/util/TypeInformation.java +++ b/src/main/java/org/springframework/data/util/TypeInformation.java @@ -140,4 +140,14 @@ public interface TypeInformation { * @return */ List> getTypeArguments(); + + /** + * Specializes the given (raw) {@link ClassTypeInformation} using the context of the current potentially parameterized + * type, basically turning the given raw type into a parameterized one. Will return the given type as is if no + * generics are involved. + * + * @param type must not be {@literal null}. + * @return will never be {@literal null}. + */ + TypeInformation specialize(ClassTypeInformation type); } diff --git a/src/test/java/org/springframework/data/convert/DefaultTypeMapperUnitTests.java b/src/test/java/org/springframework/data/convert/DefaultTypeMapperUnitTests.java index d07a3251a..e25b38dde 100644 --- a/src/test/java/org/springframework/data/convert/DefaultTypeMapperUnitTests.java +++ b/src/test/java/org/springframework/data/convert/DefaultTypeMapperUnitTests.java @@ -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 source; @Before - @SuppressWarnings({ "rawtypes", "unchecked" }) public void setUp() { this.typeMapper = new DefaultTypeMapper>(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 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 { + AbstractBar abstractBar; + } + + static class Foo extends TypeWithAbstractGenericType {} + + static abstract class AbstractBar {} + + static class Bar extends AbstractBar { + T field; + } } diff --git a/src/test/java/org/springframework/data/util/ClassTypeInformationUnitTests.java b/src/test/java/org/springframework/data/util/ClassTypeInformationUnitTests.java index 844bea797..ef1ef5355 100644 --- a/src/test/java/org/springframework/data/util/ClassTypeInformationUnitTests.java +++ b/src/test/java/org/springframework/data/util/ClassTypeInformationUnitTests.java @@ -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 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 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 { } @@ -527,4 +556,17 @@ public class ClassTypeInformationUnitTests { static class ConcreteInnerIntermediate extends GenericInnerIntermediate {} static class Leaf {} + + static class TypeWithAbstractGenericType { + AbstractBar abstractBar; + Object object; + } + + static class Foo extends TypeWithAbstractGenericType {} + + static abstract class AbstractBar {} + + static class Bar extends AbstractBar { + T field; + } }