DATACMNS-1135 - Fixed generics lookup for recursively nested container references.
Revert the merging of a parent type's type variable map and only apply the locally available declared generics in case of parameterized types. Moved that augmentation into ParameterizedTypeInformation.
This commit is contained in:
@@ -18,8 +18,6 @@ package org.springframework.data.util;
|
|||||||
import java.lang.reflect.Array;
|
import java.lang.reflect.Array;
|
||||||
import java.lang.reflect.GenericArrayType;
|
import java.lang.reflect.GenericArrayType;
|
||||||
import java.lang.reflect.Type;
|
import java.lang.reflect.Type;
|
||||||
import java.lang.reflect.TypeVariable;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
@@ -38,12 +36,10 @@ class GenericArrayTypeInformation<S> extends ParentTypeAwareTypeInformation<S> {
|
|||||||
*
|
*
|
||||||
* @param type must not be {@literal null}.
|
* @param type must not be {@literal null}.
|
||||||
* @param parent must not be {@literal null}.
|
* @param parent must not be {@literal null}.
|
||||||
* @param typeVariableMap must not be {@literal null}.
|
|
||||||
*/
|
*/
|
||||||
protected GenericArrayTypeInformation(GenericArrayType type, TypeDiscoverer<?> parent,
|
protected GenericArrayTypeInformation(GenericArrayType type, TypeDiscoverer<?> parent) {
|
||||||
Map<TypeVariable<?>, Type> typeVariableMap) {
|
|
||||||
|
|
||||||
super(type, parent, typeVariableMap);
|
super(type, parent);
|
||||||
this.type = type;
|
this.type = type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,11 +21,13 @@ import java.lang.reflect.TypeVariable;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.stream.IntStream;
|
||||||
|
|
||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
@@ -49,15 +51,39 @@ class ParameterizedTypeInformation<T> extends ParentTypeAwareTypeInformation<T>
|
|||||||
* @param type must not be {@literal null}
|
* @param type must not be {@literal null}
|
||||||
* @param parent must not be {@literal null}
|
* @param parent must not be {@literal null}
|
||||||
*/
|
*/
|
||||||
public ParameterizedTypeInformation(ParameterizedType type, TypeDiscoverer<?> parent,
|
public ParameterizedTypeInformation(ParameterizedType type, Class<?> resolvedType, TypeDiscoverer<?> parent) {
|
||||||
Map<TypeVariable<?>, Type> typeVariableMap) {
|
|
||||||
|
|
||||||
super(type, parent, typeVariableMap);
|
super(type, parent, calculateTypeVariables(type, resolvedType, parent));
|
||||||
|
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.resolved = Lazy.of(() -> isResolvedCompletely());
|
this.resolved = Lazy.of(() -> isResolvedCompletely());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves the type variables to be used. Uses the parent's type variable map but overwrites variables locally
|
||||||
|
* declared.
|
||||||
|
*
|
||||||
|
* @param type must not be {@literal null}.
|
||||||
|
* @param resolvedType must not be {@literal null}.
|
||||||
|
* @param parent must not be {@literal null}.
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private static Map<TypeVariable<?>, Type> calculateTypeVariables(ParameterizedType type, Class<?> resolvedType,
|
||||||
|
TypeDiscoverer<?> parent) {
|
||||||
|
|
||||||
|
TypeVariable<?>[] typeParameters = resolvedType.getTypeParameters();
|
||||||
|
Type[] arguments = type.getActualTypeArguments();
|
||||||
|
|
||||||
|
Map<TypeVariable<?>, Type> localTypeVariables = new HashMap<>(parent.getTypeVariableMap());
|
||||||
|
|
||||||
|
IntStream.range(0, typeParameters.length) //
|
||||||
|
.mapToObj(it -> Pair.of(typeParameters[it], arguments[it])) //
|
||||||
|
.filter(it -> !(it.getSecond() instanceof TypeVariable)) //
|
||||||
|
.forEach(it -> localTypeVariables.put(it.getFirst(), it.getSecond()));
|
||||||
|
|
||||||
|
return localTypeVariables;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* (non-Javadoc)
|
* (non-Javadoc)
|
||||||
* @see org.springframework.data.util.TypeDiscoverer#doGetMapValueType()
|
* @see org.springframework.data.util.TypeDiscoverer#doGetMapValueType()
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ package org.springframework.data.util;
|
|||||||
|
|
||||||
import java.lang.reflect.Type;
|
import java.lang.reflect.Type;
|
||||||
import java.lang.reflect.TypeVariable;
|
import java.lang.reflect.TypeVariable;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
@@ -37,28 +36,15 @@ public abstract class ParentTypeAwareTypeInformation<S> extends TypeDiscoverer<S
|
|||||||
*
|
*
|
||||||
* @param type must not be {@literal null}.
|
* @param type must not be {@literal null}.
|
||||||
* @param parent must not be {@literal null}.
|
* @param parent must not be {@literal null}.
|
||||||
* @param map must not be {@literal null}.
|
|
||||||
*/
|
*/
|
||||||
protected ParentTypeAwareTypeInformation(Type type, TypeDiscoverer<?> parent, Map<TypeVariable<?>, Type> map) {
|
protected ParentTypeAwareTypeInformation(Type type, TypeDiscoverer<?> parent) {
|
||||||
|
this(type, parent, parent.getTypeVariableMap());
|
||||||
super(type, mergeMaps(parent, map));
|
|
||||||
this.parent = parent;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
protected ParentTypeAwareTypeInformation(Type type, TypeDiscoverer<?> parent, Map<TypeVariable<?>, Type> map) {
|
||||||
* Merges the type variable maps of the given parent with the new map.
|
|
||||||
*
|
|
||||||
* @param parent must not be {@literal null}.
|
|
||||||
* @param map must not be {@literal null}.
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
private static Map<TypeVariable<?>, Type> mergeMaps(TypeDiscoverer<?> parent, Map<TypeVariable<?>, Type> map) {
|
|
||||||
|
|
||||||
Map<TypeVariable<?>, Type> typeVariableMap = new HashMap<>();
|
super(type, map);
|
||||||
typeVariableMap.putAll(map);
|
this.parent = parent;
|
||||||
typeVariableMap.putAll(parent.getTypeVariableMap());
|
|
||||||
|
|
||||||
return typeVariableMap;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -129,31 +129,22 @@ class TypeDiscoverer<S> implements TypeInformation<S> {
|
|||||||
return ClassTypeInformation.from((Class<?>) fieldType);
|
return ClassTypeInformation.from((Class<?>) fieldType);
|
||||||
}
|
}
|
||||||
|
|
||||||
Class<S> resolveType = resolveType(fieldType);
|
Class<S> resolvedType = resolveType(fieldType);
|
||||||
Map<TypeVariable, Type> variableMap = new HashMap<>();
|
|
||||||
variableMap.putAll(GenericTypeResolver.getTypeVariableMap(resolveType));
|
|
||||||
|
|
||||||
if (fieldType instanceof ParameterizedType) {
|
if (fieldType instanceof ParameterizedType) {
|
||||||
|
|
||||||
ParameterizedType parameterizedType = (ParameterizedType) fieldType;
|
ParameterizedType parameterizedType = (ParameterizedType) fieldType;
|
||||||
|
return new ParameterizedTypeInformation(parameterizedType, resolvedType, this);
|
||||||
TypeVariable<Class<S>>[] typeParameters = resolveType.getTypeParameters();
|
|
||||||
Type[] arguments = parameterizedType.getActualTypeArguments();
|
|
||||||
|
|
||||||
for (int i = 0; i < typeParameters.length; i++) {
|
|
||||||
variableMap.put(typeParameters[i], arguments[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ParameterizedTypeInformation(parameterizedType, this, variableMap);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fieldType instanceof TypeVariable) {
|
if (fieldType instanceof TypeVariable) {
|
||||||
|
|
||||||
TypeVariable<?> variable = (TypeVariable<?>) fieldType;
|
TypeVariable<?> variable = (TypeVariable<?>) fieldType;
|
||||||
return new TypeVariableTypeInformation(variable, type, this, variableMap);
|
return new TypeVariableTypeInformation(variable, type, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fieldType instanceof GenericArrayType) {
|
if (fieldType instanceof GenericArrayType) {
|
||||||
return new GenericArrayTypeInformation((GenericArrayType) fieldType, this, variableMap);
|
return new GenericArrayTypeInformation((GenericArrayType) fieldType, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fieldType instanceof WildcardType) {
|
if (fieldType instanceof WildcardType) {
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ import static org.springframework.util.ObjectUtils.*;
|
|||||||
import java.lang.reflect.ParameterizedType;
|
import java.lang.reflect.ParameterizedType;
|
||||||
import java.lang.reflect.Type;
|
import java.lang.reflect.Type;
|
||||||
import java.lang.reflect.TypeVariable;
|
import java.lang.reflect.TypeVariable;
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
@@ -44,11 +43,12 @@ class TypeVariableTypeInformation<T> extends ParentTypeAwareTypeInformation<T> {
|
|||||||
* @param owningType must not be {@literal null}
|
* @param owningType must not be {@literal null}
|
||||||
* @param parent
|
* @param parent
|
||||||
*/
|
*/
|
||||||
public TypeVariableTypeInformation(TypeVariable<?> variable, Type owningType, TypeDiscoverer<?> parent,
|
public TypeVariableTypeInformation(TypeVariable<?> variable, Type owningType, TypeDiscoverer<?> parent) {
|
||||||
Map<TypeVariable<?>, Type> typeVariableMap) {
|
|
||||||
|
super(variable, parent);
|
||||||
|
|
||||||
super(variable, parent, typeVariableMap);
|
|
||||||
Assert.notNull(variable, "TypeVariable must not be null!");
|
Assert.notNull(variable, "TypeVariable must not be null!");
|
||||||
|
|
||||||
this.variable = variable;
|
this.variable = variable;
|
||||||
this.owningType = owningType;
|
this.owningType = owningType;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,24 +15,24 @@
|
|||||||
*/
|
*/
|
||||||
package org.springframework.data.util;
|
package org.springframework.data.util;
|
||||||
|
|
||||||
|
import static java.util.Collections.*;
|
||||||
import static org.assertj.core.api.Assertions.*;
|
import static org.assertj.core.api.Assertions.*;
|
||||||
import static org.mockito.Mockito.*;
|
import static org.mockito.Mockito.*;
|
||||||
import static org.springframework.data.util.ClassTypeInformation.*;
|
import static org.springframework.data.util.ClassTypeInformation.from;
|
||||||
|
|
||||||
import java.lang.reflect.ParameterizedType;
|
import java.lang.reflect.ParameterizedType;
|
||||||
import java.lang.reflect.Type;
|
import java.lang.reflect.Type;
|
||||||
import java.lang.reflect.TypeVariable;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.junit.MockitoJUnitRunner;
|
import org.mockito.junit.MockitoJUnitRunner;
|
||||||
|
import org.springframework.core.GenericTypeResolver;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unit tests for {@link ParameterizedTypeInformation}.
|
* Unit tests for {@link ParameterizedTypeInformation}.
|
||||||
@@ -43,23 +43,23 @@ import org.mockito.junit.MockitoJUnitRunner;
|
|||||||
@RunWith(MockitoJUnitRunner.class)
|
@RunWith(MockitoJUnitRunner.class)
|
||||||
public class ParameterizedTypeUnitTests {
|
public class ParameterizedTypeUnitTests {
|
||||||
|
|
||||||
static final Map<TypeVariable<?>, Type> EMPTY_MAP = Collections.emptyMap();
|
|
||||||
|
|
||||||
@Mock ParameterizedType one;
|
@Mock ParameterizedType one;
|
||||||
|
Class<?> resolvedOne;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
when(one.getActualTypeArguments()).thenReturn(new Type[0]);
|
when(one.getActualTypeArguments()).thenReturn(new Type[0]);
|
||||||
|
this.resolvedOne = GenericTypeResolver.resolveType(one, emptyMap());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void considersTypeInformationsWithDifferingParentsNotEqual() {
|
public void considersTypeInformationsWithDifferingParentsNotEqual() {
|
||||||
|
|
||||||
TypeDiscoverer<String> stringParent = new TypeDiscoverer<>(String.class, EMPTY_MAP);
|
TypeDiscoverer<String> stringParent = new TypeDiscoverer<>(String.class, emptyMap());
|
||||||
TypeDiscoverer<Object> objectParent = new TypeDiscoverer<>(Object.class, EMPTY_MAP);
|
TypeDiscoverer<Object> objectParent = new TypeDiscoverer<>(Object.class, emptyMap());
|
||||||
|
|
||||||
ParameterizedTypeInformation<Object> first = new ParameterizedTypeInformation<>(one, stringParent, EMPTY_MAP);
|
ParameterizedTypeInformation<Object> first = new ParameterizedTypeInformation<>(one, resolvedOne, stringParent);
|
||||||
ParameterizedTypeInformation<Object> second = new ParameterizedTypeInformation<>(one, objectParent, EMPTY_MAP);
|
ParameterizedTypeInformation<Object> second = new ParameterizedTypeInformation<>(one, resolvedOne, objectParent);
|
||||||
|
|
||||||
assertThat(first).isNotEqualTo(second);
|
assertThat(first).isNotEqualTo(second);
|
||||||
}
|
}
|
||||||
@@ -67,10 +67,10 @@ public class ParameterizedTypeUnitTests {
|
|||||||
@Test
|
@Test
|
||||||
public void considersTypeInformationsWithSameParentsNotEqual() {
|
public void considersTypeInformationsWithSameParentsNotEqual() {
|
||||||
|
|
||||||
TypeDiscoverer<String> stringParent = new TypeDiscoverer<>(String.class, EMPTY_MAP);
|
TypeDiscoverer<String> stringParent = new TypeDiscoverer<>(String.class, emptyMap());
|
||||||
|
|
||||||
ParameterizedTypeInformation<Object> first = new ParameterizedTypeInformation<>(one, stringParent, EMPTY_MAP);
|
ParameterizedTypeInformation<Object> first = new ParameterizedTypeInformation<>(one, resolvedOne, stringParent);
|
||||||
ParameterizedTypeInformation<Object> second = new ParameterizedTypeInformation<>(one, stringParent, EMPTY_MAP);
|
ParameterizedTypeInformation<Object> second = new ParameterizedTypeInformation<>(one, resolvedOne, stringParent);
|
||||||
|
|
||||||
assertThat(first.equals(second)).isTrue();
|
assertThat(first.equals(second)).isTrue();
|
||||||
}
|
}
|
||||||
@@ -136,6 +136,17 @@ public class ParameterizedTypeUnitTests {
|
|||||||
assertThat(typeInformation.getMapValueType()).isNull();
|
assertThat(typeInformation.getMapValueType()).isNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test // DATACMNS-1135
|
||||||
|
public void prefersLocalGenericsDeclarationOverParentBound() {
|
||||||
|
|
||||||
|
ClassTypeInformation<Candidate> candidate = ClassTypeInformation.from(Candidate.class);
|
||||||
|
|
||||||
|
TypeInformation<?> componentType = candidate.getRequiredProperty("experiences.values").getRequiredComponentType();
|
||||||
|
componentType = componentType.getRequiredProperty("responsibilities.values").getRequiredComponentType();
|
||||||
|
|
||||||
|
assertThat(componentType.getType()).isEqualTo(Responsibility.class);
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressWarnings("serial")
|
@SuppressWarnings("serial")
|
||||||
class Localized<S> extends HashMap<Locale, S> {
|
class Localized<S> extends HashMap<Locale, S> {
|
||||||
S value;
|
S value;
|
||||||
@@ -183,4 +194,22 @@ public class ParameterizedTypeUnitTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class Education {}
|
class Education {}
|
||||||
|
|
||||||
|
// DATACMNS-1135
|
||||||
|
|
||||||
|
abstract class CandidateInfo {}
|
||||||
|
|
||||||
|
class Responsibility extends CandidateInfo {}
|
||||||
|
|
||||||
|
class Experience extends CandidateInfo {
|
||||||
|
CandidateInfoContainer<Responsibility> responsibilities;
|
||||||
|
}
|
||||||
|
|
||||||
|
class CandidateInfoContainer<E extends CandidateInfo> {
|
||||||
|
List<E> values = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
class Candidate {
|
||||||
|
CandidateInfoContainer<Experience> experiences;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user