DATACMNS-590 - Fixed calculation of nested generics in ParentTypeAwareTypeInformation.
So far, the lookup of the type variable map was preferring the type variable maps detected on parent types in nested structures. This caused the concrete type variables in nested types not being considered correctly which caused the type resolution to fall back to the generic bounds. We now accumulate the type variable maps to avoid having to lookup a certain map in the nesting hierarchy. The core fix is in ParentTypeAwareTypeInformation's constructor and mergeMaps(…) respectively. Simplified the handling of type variable maps and made proper use of generics throughout the class hierarchy.
This commit is contained in:
@@ -93,7 +93,7 @@ public class ClassTypeInformationUnitTests {
|
||||
TypeInformation<StringCollectionContainer> information = ClassTypeInformation.from(StringCollectionContainer.class);
|
||||
|
||||
TypeInformation<?> property = information.getProperty("array");
|
||||
assertEquals(property.getComponentType().getType(), String.class);
|
||||
assertThat(property.getComponentType().getType(), is((Object) String.class));
|
||||
|
||||
Class<?> type = property.getType();
|
||||
assertEquals(String[].class, type);
|
||||
@@ -327,6 +327,20 @@ public class ClassTypeInformationUnitTests {
|
||||
is("org.springframework.data.util.ClassTypeInformationUnitTests$SpecialPerson"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATACMNS-590
|
||||
*/
|
||||
@Test
|
||||
public void resolvesNestedGenericsToConcreteType() {
|
||||
|
||||
ClassTypeInformation<ConcreteRoot> rootType = from(ConcreteRoot.class);
|
||||
TypeInformation<?> subsPropertyType = rootType.getProperty("subs");
|
||||
TypeInformation<?> subsElementType = subsPropertyType.getActualType();
|
||||
TypeInformation<?> subSubType = subsElementType.getProperty("subSub");
|
||||
|
||||
assertThat(subSubType.getType(), is((Object) ConcreteSubSub.class));
|
||||
}
|
||||
|
||||
static class StringMapContainer extends MapContainer<String> {
|
||||
|
||||
}
|
||||
@@ -461,4 +475,24 @@ public class ClassTypeInformationUnitTests {
|
||||
|
||||
SortedMap<String, ? extends SortedMap<String, List<Person>>> seriously;
|
||||
}
|
||||
|
||||
// DATACMNS-590
|
||||
|
||||
static abstract class GenericRoot<T extends GenericSub<?>> {
|
||||
List<T> subs;
|
||||
}
|
||||
|
||||
static abstract class GenericSub<T extends GenericSubSub> {
|
||||
T subSub;
|
||||
}
|
||||
|
||||
static abstract class GenericSubSub {}
|
||||
|
||||
static class ConcreteRoot extends GenericRoot<ConcreteSub> {}
|
||||
|
||||
static class ConcreteSub extends GenericSub<ConcreteSubSub> {}
|
||||
|
||||
static class ConcreteSubSub extends GenericSubSub {
|
||||
String content;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2011 the original author or authors.
|
||||
* Copyright 2011-2014 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -22,8 +22,11 @@ import static org.springframework.data.util.ClassTypeInformation.*;
|
||||
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.lang.reflect.TypeVariable;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
@@ -39,6 +42,8 @@ import org.mockito.runners.MockitoJUnitRunner;
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class ParameterizedTypeUnitTests {
|
||||
|
||||
static final Map<TypeVariable<?>, Type> EMPTY_MAP = Collections.emptyMap();
|
||||
|
||||
@Mock ParameterizedType one;
|
||||
|
||||
@Before
|
||||
@@ -49,22 +54,22 @@ public class ParameterizedTypeUnitTests {
|
||||
@Test
|
||||
public void considersTypeInformationsWithDifferingParentsNotEqual() {
|
||||
|
||||
TypeDiscoverer<String> stringParent = new TypeDiscoverer<String>(String.class, null);
|
||||
TypeDiscoverer<Object> objectParent = new TypeDiscoverer<Object>(Object.class, null);
|
||||
TypeDiscoverer<String> stringParent = new TypeDiscoverer<String>(String.class, EMPTY_MAP);
|
||||
TypeDiscoverer<Object> objectParent = new TypeDiscoverer<Object>(Object.class, EMPTY_MAP);
|
||||
|
||||
ParameterizedTypeInformation<Object> first = new ParameterizedTypeInformation<Object>(one, stringParent);
|
||||
ParameterizedTypeInformation<Object> second = new ParameterizedTypeInformation<Object>(one, objectParent);
|
||||
ParameterizedTypeInformation<Object> first = new ParameterizedTypeInformation<Object>(one, stringParent, EMPTY_MAP);
|
||||
ParameterizedTypeInformation<Object> second = new ParameterizedTypeInformation<Object>(one, objectParent, EMPTY_MAP);
|
||||
|
||||
assertFalse(first.equals(second));
|
||||
assertThat(first, is(not(second)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void considersTypeInformationsWithSameParentsNotEqual() {
|
||||
|
||||
TypeDiscoverer<String> stringParent = new TypeDiscoverer<String>(String.class, null);
|
||||
TypeDiscoverer<String> stringParent = new TypeDiscoverer<String>(String.class, EMPTY_MAP);
|
||||
|
||||
ParameterizedTypeInformation<Object> first = new ParameterizedTypeInformation<Object>(one, stringParent);
|
||||
ParameterizedTypeInformation<Object> second = new ParameterizedTypeInformation<Object>(one, stringParent);
|
||||
ParameterizedTypeInformation<Object> first = new ParameterizedTypeInformation<Object>(one, stringParent, EMPTY_MAP);
|
||||
ParameterizedTypeInformation<Object> second = new ParameterizedTypeInformation<Object>(one, stringParent, EMPTY_MAP);
|
||||
|
||||
assertTrue(first.equals(second));
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2011-2012 the original author or authors.
|
||||
* Copyright 2011-2014 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -17,11 +17,13 @@ package org.springframework.data.util;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.springframework.data.util.ClassTypeInformation.*;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Type;
|
||||
import java.lang.reflect.TypeVariable;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
@@ -39,13 +41,10 @@ import org.mockito.runners.MockitoJUnitRunner;
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class TypeDiscovererUnitTests {
|
||||
|
||||
@Mock
|
||||
@SuppressWarnings("rawtypes")
|
||||
Map<TypeVariable, Type> firstMap;
|
||||
static final Map<TypeVariable<?>, Type> EMPTY_MAP = Collections.emptyMap();
|
||||
|
||||
@Mock
|
||||
@SuppressWarnings("rawtypes")
|
||||
Map<TypeVariable, Type> secondMap;
|
||||
@Mock Map<TypeVariable<?>, Type> firstMap;
|
||||
@Mock Map<TypeVariable<?>, Type> secondMap;
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void rejectsNullType() {
|
||||
@@ -55,8 +54,8 @@ public class TypeDiscovererUnitTests {
|
||||
@Test
|
||||
public void isNotEqualIfTypesDiffer() {
|
||||
|
||||
TypeDiscoverer<Object> objectTypeInfo = new TypeDiscoverer<Object>(Object.class, null);
|
||||
TypeDiscoverer<String> stringTypeInfo = new TypeDiscoverer<String>(String.class, null);
|
||||
TypeDiscoverer<Object> objectTypeInfo = new TypeDiscoverer<Object>(Object.class, EMPTY_MAP);
|
||||
TypeDiscoverer<String> stringTypeInfo = new TypeDiscoverer<String>(String.class, EMPTY_MAP);
|
||||
|
||||
assertFalse(objectTypeInfo.equals(stringTypeInfo));
|
||||
}
|
||||
@@ -75,37 +74,44 @@ public class TypeDiscovererUnitTests {
|
||||
@Test
|
||||
public void dealsWithTypesReferencingThemselves() {
|
||||
|
||||
TypeInformation<SelfReferencing> information = new ClassTypeInformation<SelfReferencing>(SelfReferencing.class);
|
||||
TypeInformation<SelfReferencing> information = from(SelfReferencing.class);
|
||||
TypeInformation<?> first = information.getProperty("parent").getMapValueType();
|
||||
TypeInformation<?> second = first.getProperty("map").getMapValueType();
|
||||
|
||||
assertEquals(first, second);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dealsWithTypesReferencingThemselvesInAMap() {
|
||||
|
||||
TypeInformation<SelfReferencingMap> information = new ClassTypeInformation<SelfReferencingMap>(
|
||||
SelfReferencingMap.class);
|
||||
TypeInformation<SelfReferencingMap> information = from(SelfReferencingMap.class);
|
||||
TypeInformation<?> mapValueType = information.getProperty("map").getMapValueType();
|
||||
|
||||
assertEquals(mapValueType, information);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void returnsComponentAndValueTypesForMapExtensions() {
|
||||
TypeInformation<?> discoverer = new TypeDiscoverer<Object>(CustomMap.class, null);
|
||||
|
||||
TypeInformation<?> discoverer = new TypeDiscoverer<Object>(CustomMap.class, EMPTY_MAP);
|
||||
|
||||
assertEquals(Locale.class, discoverer.getMapValueType().getType());
|
||||
assertEquals(String.class, discoverer.getComponentType().getType());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void returnsComponentTypeForCollectionExtension() {
|
||||
TypeDiscoverer<CustomCollection> discoverer = new TypeDiscoverer<CustomCollection>(CustomCollection.class, null);
|
||||
|
||||
TypeDiscoverer<CustomCollection> discoverer = new TypeDiscoverer<CustomCollection>(CustomCollection.class, firstMap);
|
||||
|
||||
assertEquals(String.class, discoverer.getComponentType().getType());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void returnsComponentTypeForArrays() {
|
||||
TypeDiscoverer<String[]> discoverer = new TypeDiscoverer<String[]>(String[].class, null);
|
||||
|
||||
TypeDiscoverer<String[]> discoverer = new TypeDiscoverer<String[]>(String[].class, EMPTY_MAP);
|
||||
|
||||
assertEquals(String.class, discoverer.getComponentType().getType());
|
||||
}
|
||||
|
||||
@@ -117,9 +123,10 @@ public class TypeDiscovererUnitTests {
|
||||
public void discoveresConstructorParameterTypesCorrectly() throws NoSuchMethodException, SecurityException {
|
||||
|
||||
TypeDiscoverer<GenericConstructors> discoverer = new TypeDiscoverer<GenericConstructors>(GenericConstructors.class,
|
||||
null);
|
||||
firstMap);
|
||||
Constructor<GenericConstructors> constructor = GenericConstructors.class.getConstructor(List.class, Locale.class);
|
||||
List<TypeInformation<?>> types = discoverer.getParameterTypes(constructor);
|
||||
|
||||
assertThat(types.size(), is(2));
|
||||
assertThat(types.get(0).getType(), equalTo((Class) List.class));
|
||||
assertThat(types.get(0).getComponentType().getType(), is(equalTo((Class) String.class)));
|
||||
@@ -128,7 +135,9 @@ public class TypeDiscovererUnitTests {
|
||||
@Test
|
||||
@SuppressWarnings("rawtypes")
|
||||
public void returnsNullForComponentAndValueTypesForRawMaps() {
|
||||
TypeDiscoverer<Map> discoverer = new TypeDiscoverer<Map>(Map.class, null);
|
||||
|
||||
TypeDiscoverer<Map> discoverer = new TypeDiscoverer<Map>(Map.class, EMPTY_MAP);
|
||||
|
||||
assertThat(discoverer.getComponentType(), is(nullValue()));
|
||||
assertThat(discoverer.getMapValueType(), is(nullValue()));
|
||||
}
|
||||
@@ -140,14 +149,16 @@ public class TypeDiscovererUnitTests {
|
||||
@SuppressWarnings("rawtypes")
|
||||
public void doesNotConsiderTypeImplementingIterableACollection() {
|
||||
|
||||
TypeDiscoverer<Person> discoverer = new TypeDiscoverer<Person>(Person.class, null);
|
||||
TypeInformation reference = ClassTypeInformation.from(Address.class);
|
||||
TypeDiscoverer<Person> discoverer = new TypeDiscoverer<Person>(Person.class, EMPTY_MAP);
|
||||
TypeInformation reference = from(Address.class);
|
||||
|
||||
TypeInformation<?> addresses = discoverer.getProperty("addresses");
|
||||
|
||||
assertThat(addresses.isCollectionLike(), is(false));
|
||||
assertThat(addresses.getComponentType(), is(reference));
|
||||
|
||||
TypeInformation<?> adressIterable = discoverer.getProperty("addressIterable");
|
||||
|
||||
assertThat(adressIterable.isCollectionLike(), is(true));
|
||||
assertThat(adressIterable.getComponentType(), is(reference));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user