Skip to content

Commit 497e85e

Browse files
committed
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.
1 parent 7f7d216 commit 497e85e

File tree

6 files changed

+184
-14
lines changed

6 files changed

+184
-14
lines changed

src/main/java/org/springframework/data/convert/DefaultTypeMapper.java

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -147,10 +147,16 @@ public <T> TypeInformation<? extends T> readType(S source, TypeInformation<T> ba
147147

148148
Class<T> rawType = basicType == null ? null : basicType.getType();
149149

150-
boolean isMoreConcreteCustomType = rawType == null ? true : rawType.isAssignableFrom(documentsTargetType)
151-
&& !rawType.equals(documentsTargetType);
152-
return isMoreConcreteCustomType ? (TypeInformation<? extends T>) ClassTypeInformation.from(documentsTargetType)
153-
: basicType;
150+
boolean isMoreConcreteCustomType = rawType == null ? true
151+
: rawType.isAssignableFrom(documentsTargetType) && !rawType.equals(documentsTargetType);
152+
153+
if (!isMoreConcreteCustomType) {
154+
return basicType;
155+
}
156+
157+
ClassTypeInformation<?> targetType = ClassTypeInformation.from(documentsTargetType);
158+
159+
return (TypeInformation<? extends T>) (basicType != null ? basicType.specialize(targetType) : targetType);
154160
}
155161

156162
/**

src/main/java/org/springframework/data/util/ClassTypeInformation.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,15 @@ public boolean isAssignableFrom(TypeInformation<?> target) {
167167
return getType().isAssignableFrom(target.getType());
168168
}
169169

170+
/*
171+
* (non-Javadoc)
172+
* @see org.springframework.data.util.TypeDiscoverer#specialize(org.springframework.data.util.ClassTypeInformation)
173+
*/
174+
@Override
175+
public TypeInformation<?> specialize(ClassTypeInformation<?> type) {
176+
return type;
177+
}
178+
170179
/*
171180
* (non-Javadoc)
172181
* @see java.lang.Object#toString()

src/main/java/org/springframework/data/util/TypeDiscoverer.java

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -493,6 +493,20 @@ public boolean isAssignableFrom(TypeInformation<?> target) {
493493
return target.getSuperTypeInformation(getType()).equals(this);
494494
}
495495

496+
/*
497+
* (non-Javadoc)
498+
* @see org.springframework.data.util.TypeInformation#specialize(org.springframework.data.util.ClassTypeInformation)
499+
*/
500+
@Override
501+
public TypeInformation<?> specialize(ClassTypeInformation<?> type) {
502+
503+
Assert.isTrue(getType().isAssignableFrom(type.getType()));
504+
505+
List<TypeInformation<?>> arguments = getTypeArguments();
506+
507+
return arguments.isEmpty() ? type : createInfo(new SyntheticParamterizedType(type, arguments));
508+
}
509+
496510
private TypeInformation<?> getTypeArgument(Class<?> bound, int index) {
497511

498512
Class<?>[] arguments = GenericTypeResolver.resolveTypeArguments(getType(), bound);
@@ -537,4 +551,63 @@ public boolean equals(Object obj) {
537551
public int hashCode() {
538552
return hashCode;
539553
}
554+
555+
/**
556+
* A synthetic {@link ParameterizedType}.
557+
*
558+
* @author Oliver Gierke
559+
* @since 1.11
560+
*/
561+
private static class SyntheticParamterizedType implements ParameterizedType {
562+
563+
private final ClassTypeInformation<?> typeInformation;
564+
private final List<TypeInformation<?>> typeParameters;
565+
566+
/**
567+
* @param typeInformation must not be {@literal null}.
568+
* @param typeParameters must not be {@literal null}.
569+
*/
570+
public SyntheticParamterizedType(ClassTypeInformation<?> typeInformation, List<TypeInformation<?>> typeParameters) {
571+
572+
Assert.notNull(typeInformation, "Type must not be null!");
573+
Assert.notNull(typeParameters, "Type parameters must not be null!");
574+
575+
this.typeInformation = typeInformation;
576+
this.typeParameters = typeParameters;
577+
}
578+
579+
/*
580+
* (non-Javadoc)
581+
* @see java.lang.reflect.ParameterizedType#getRawType()
582+
*/
583+
@Override
584+
public Type getRawType() {
585+
return typeInformation.getType();
586+
}
587+
588+
/*
589+
* (non-Javadoc)
590+
* @see java.lang.reflect.ParameterizedType#getOwnerType()
591+
*/
592+
@Override
593+
public Type getOwnerType() {
594+
return null;
595+
}
596+
597+
/*
598+
* (non-Javadoc)
599+
* @see java.lang.reflect.ParameterizedType#getActualTypeArguments()
600+
*/
601+
@Override
602+
public Type[] getActualTypeArguments() {
603+
604+
Type[] result = new Type[typeParameters.size()];
605+
606+
for (int i = 0; i < typeParameters.size(); i++) {
607+
result[i] = typeParameters.get(0).getType();
608+
}
609+
610+
return result;
611+
}
612+
}
540613
}

src/main/java/org/springframework/data/util/TypeInformation.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,4 +140,14 @@ public interface TypeInformation<S> {
140140
* @return
141141
*/
142142
List<TypeInformation<?>> getTypeArguments();
143+
144+
/**
145+
* Specializes the given (raw) {@link ClassTypeInformation} using the context of the current potentially parameterized
146+
* type, basically turning the given raw type into a parameterized one. Will return the given type as is if no
147+
* generics are involved.
148+
*
149+
* @param type must not be {@literal null}.
150+
* @return will never be {@literal null}.
151+
*/
152+
TypeInformation<?> specialize(ClassTypeInformation<?> type);
143153
}

src/test/java/org/springframework/data/convert/DefaultTypeMapperUnitTests.java

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
*/
1616
package org.springframework.data.convert;
1717

18-
import static org.hamcrest.CoreMatchers.*;
18+
import static org.hamcrest.Matchers.*;
1919
import static org.junit.Assert.*;
2020
import static org.mockito.Mockito.*;
2121

@@ -50,14 +50,13 @@ public class DefaultTypeMapperUnitTests {
5050
Map<String, String> source;
5151

5252
@Before
53-
@SuppressWarnings({ "rawtypes", "unchecked" })
5453
public void setUp() {
5554

5655
this.typeMapper = new DefaultTypeMapper<Map<String, String>>(accessor, Arrays.asList(mapper));
5756
this.source = Collections.singletonMap("key", STRING);
5857

59-
when(accessor.readAliasFrom(source)).thenReturn(STRING);
60-
when(mapper.resolveTypeFrom(STRING)).thenReturn((TypeInformation) STRING_TYPE_INFO);
58+
doReturn(STRING).when(accessor).readAliasFrom(source);
59+
doReturn(STRING_TYPE_INFO).when(mapper).resolveTypeFrom(STRING);
6160
}
6261

6362
@Test
@@ -83,4 +82,35 @@ public void returnsTypeAliasForInformation() {
8382

8483
assertThat(this.typeMapper.getAliasFor(STRING_TYPE_INFO), is(alias));
8584
}
85+
86+
/**
87+
* @see DATACMNS-783
88+
*/
89+
@Test
90+
public void specializesRawSourceTypeUsingGenericContext() {
91+
92+
ClassTypeInformation<Foo> root = ClassTypeInformation.from(Foo.class);
93+
TypeInformation<?> propertyType = root.getProperty("abstractBar");
94+
TypeInformation<?> barType = ClassTypeInformation.from(Bar.class);
95+
96+
doReturn(barType).when(accessor).readAliasFrom(source);
97+
doReturn(barType).when(mapper).resolveTypeFrom(barType);
98+
99+
TypeInformation<?> result = typeMapper.readType(source, propertyType);
100+
101+
assertThat(result.getType(), is((Object) Bar.class));
102+
assertThat(result.getProperty("field").getType(), is((Object) Character.class));
103+
}
104+
105+
static class TypeWithAbstractGenericType<T> {
106+
AbstractBar<T> abstractBar;
107+
}
108+
109+
static class Foo extends TypeWithAbstractGenericType<Character> {}
110+
111+
static abstract class AbstractBar<T> {}
112+
113+
static class Bar<T> extends AbstractBar<T> {
114+
T field;
115+
}
86116
}

src/test/java/org/springframework/data/util/ClassTypeInformationUnitTests.java

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -224,8 +224,8 @@ public void discoversImplementationBindingCorrectlyForString() throws Exception
224224
assertThat(parameterType.isAssignableFrom(stringInfo), is(true));
225225
assertThat(stringInfo.getSuperTypeInformation(GenericInterface.class), is((Object) parameterType));
226226
assertThat(parameterType.isAssignableFrom(from(LongImplementation.class)), is(false));
227-
assertThat(parameterType.isAssignableFrom(from(StringImplementation.class).getSuperTypeInformation(
228-
GenericInterface.class)), is(true));
227+
assertThat(parameterType
228+
.isAssignableFrom(from(StringImplementation.class).getSuperTypeInformation(GenericInterface.class)), is(true));
229229
}
230230

231231
@Test
@@ -238,8 +238,8 @@ public void discoversImplementationBindingCorrectlyForLong() throws Exception {
238238

239239
assertThat(parameterType.isAssignableFrom(from(StringImplementation.class)), is(false));
240240
assertThat(parameterType.isAssignableFrom(from(LongImplementation.class)), is(true));
241-
assertThat(parameterType.isAssignableFrom(from(StringImplementation.class).getSuperTypeInformation(
242-
GenericInterface.class)), is(false));
241+
assertThat(parameterType
242+
.isAssignableFrom(from(StringImplementation.class).getSuperTypeInformation(GenericInterface.class)), is(false));
243243
}
244244

245245
@Test
@@ -252,8 +252,8 @@ public void discoversImplementationBindingCorrectlyForNumber() throws Exception
252252

253253
assertThat(parameterType.isAssignableFrom(from(StringImplementation.class)), is(false));
254254
assertThat(parameterType.isAssignableFrom(from(LongImplementation.class)), is(true));
255-
assertThat(parameterType.isAssignableFrom(from(StringImplementation.class).getSuperTypeInformation(
256-
GenericInterface.class)), is(false));
255+
assertThat(parameterType
256+
.isAssignableFrom(from(StringImplementation.class).getSuperTypeInformation(GenericInterface.class)), is(false));
257257
}
258258

259259
@Test
@@ -353,6 +353,35 @@ public void considersGenericsOfTypeBounds() {
353353
assertThat(leafType.getType(), is((Object) Leaf.class));
354354
}
355355

356+
/**
357+
* @see DATACMNS-783
358+
*/
359+
@Test
360+
public void specializesTypeUsingTypeVariableContext() {
361+
362+
ClassTypeInformation<Foo> root = ClassTypeInformation.from(Foo.class);
363+
TypeInformation<?> property = root.getProperty("abstractBar");
364+
365+
TypeInformation<?> specialized = property.specialize(ClassTypeInformation.from(Bar.class));
366+
367+
assertThat(specialized.getType(), is((Object) Bar.class));
368+
assertThat(specialized.getProperty("field").getType(), is((Object) Character.class));
369+
}
370+
371+
/**
372+
* @see DATACMNS-783
373+
*/
374+
@Test
375+
@SuppressWarnings("rawtypes")
376+
public void usesTargetTypeDirectlyIfNoGenericsAreInvolved() {
377+
378+
ClassTypeInformation<Foo> root = ClassTypeInformation.from(Foo.class);
379+
TypeInformation<?> property = root.getProperty("object");
380+
381+
ClassTypeInformation<?> from = ClassTypeInformation.from(Bar.class);
382+
assertThat(property.specialize(from), is((TypeInformation) from));
383+
}
384+
356385
static class StringMapContainer extends MapContainer<String> {
357386

358387
}
@@ -527,4 +556,17 @@ static class ConcreteRootIntermediate extends GenericRootIntermediate<ConcreteIn
527556
static class ConcreteInnerIntermediate extends GenericInnerIntermediate<Leaf> {}
528557

529558
static class Leaf {}
559+
560+
static class TypeWithAbstractGenericType<T> {
561+
AbstractBar<T> abstractBar;
562+
Object object;
563+
}
564+
565+
static class Foo extends TypeWithAbstractGenericType<Character> {}
566+
567+
static abstract class AbstractBar<T> {}
568+
569+
static class Bar<T> extends AbstractBar<T> {
570+
T field;
571+
}
530572
}

0 commit comments

Comments
 (0)