Browse Source

DATACMNS-88 - Map value types are now resolve correctly for generic types extending Map.

Extracted base ParentTypeAwareTypeInformation class as base class for TypeInformations that need to consider the parent's type variable map.
pull/13/merge
Oliver Gierke 15 years ago
parent
commit
b3c8cb5620
  1. 4
      spring-data-commons-core/src/main/java/org/springframework/data/util/GenericArrayTypeInformation.java
  2. 86
      spring-data-commons-core/src/main/java/org/springframework/data/util/ParameterizedTypeInformation.java
  3. 81
      spring-data-commons-core/src/main/java/org/springframework/data/util/ParentTypeAwareTypeInformation.java
  4. 11
      spring-data-commons-core/src/main/java/org/springframework/data/util/TypeDiscoverer.java
  5. 7
      spring-data-commons-core/src/main/java/org/springframework/data/util/TypeVariableTypeInformation.java
  6. 46
      spring-data-commons-core/src/test/java/org/springframework/data/util/ClassTypeInformationUnitTests.java
  7. 50
      spring-data-commons-core/src/test/java/org/springframework/data/util/ParameterizedTypeUnitTests.java

4
spring-data-commons-core/src/main/java/org/springframework/data/util/GenericArrayTypeInformation.java

@ -24,7 +24,7 @@ import java.lang.reflect.Type; @@ -24,7 +24,7 @@ import java.lang.reflect.Type;
*
* @author Oliver Gierke
*/
class GenericArrayTypeInformation<S> extends ParameterizedTypeInformation<S> {
class GenericArrayTypeInformation<S> extends ParentTypeAwareTypeInformation<S> {
private GenericArrayType type;
@ -36,7 +36,7 @@ class GenericArrayTypeInformation<S> extends ParameterizedTypeInformation<S> { @@ -36,7 +36,7 @@ class GenericArrayTypeInformation<S> extends ParameterizedTypeInformation<S> {
* @param parent
*/
protected GenericArrayTypeInformation(GenericArrayType type, TypeDiscoverer<?> parent) {
super(type, parent);
super(type, parent, parent.getTypeVariableMap());
this.type = type;
}

86
spring-data-commons-core/src/main/java/org/springframework/data/util/ParameterizedTypeInformation.java

@ -15,12 +15,12 @@ @@ -15,12 +15,12 @@
*/
package org.springframework.data.util;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import java.util.Set;
/**
* Base class for all types that include parameterization of some kind. Crucial as we have to take note of the parent
@ -28,9 +28,9 @@ import org.springframework.util.ObjectUtils; @@ -28,9 +28,9 @@ import org.springframework.util.ObjectUtils;
*
* @author Oliver Gierke
*/
class ParameterizedTypeInformation<T> extends TypeDiscoverer<T> {
class ParameterizedTypeInformation<T> extends ParentTypeAwareTypeInformation<T> {
private final TypeDiscoverer<?> parent;
private final ParameterizedType type;
/**
* Creates a new {@link ParameterizedTypeInformation} for the given {@link Type} and parent {@link TypeDiscoverer}.
@ -38,60 +38,40 @@ class ParameterizedTypeInformation<T> extends TypeDiscoverer<T> { @@ -38,60 +38,40 @@ class ParameterizedTypeInformation<T> extends TypeDiscoverer<T> {
* @param type must not be {@literal null}
* @param parent must not be {@literal null}
*/
public ParameterizedTypeInformation(Type type, TypeDiscoverer<?> parent) {
super(type, null);
Assert.notNull(parent);
this.parent = parent;
}
/**
* Considers the parent's type variable map before invoking the super class method.
*
* @return
*/
@SuppressWarnings("rawtypes")
protected Map<TypeVariable, Type> getTypeVariableMap() {
return parent != null ? parent.getTypeVariableMap() : super.getTypeVariableMap();
}
/* (non-Javadoc)
* @see org.springframework.data.util.TypeDiscoverer#createInfo(java.lang.reflect.Type)
*/
@Override
protected TypeInformation<?> createInfo(Type fieldType) {
if (parent.getType().equals(fieldType)) {
return parent;
}
return super.createInfo(fieldType);
public ParameterizedTypeInformation(ParameterizedType type, TypeDiscoverer<?> parent) {
super(type, parent, null);
this.type = type;
}
/*
* (non-Javadoc)
* @see org.springframework.data.util.TypeDiscoverer#equals(java.lang.Object)
* @see org.springframework.data.util.TypeDiscoverer#getMapValueType()
*/
@Override
public boolean equals(Object obj) {
if (!super.equals(obj)) {
return false;
public TypeInformation<?> getMapValueType() {
if (Map.class.equals(getType())) {
Type[] arguments = type.getActualTypeArguments();
return createInfo(arguments[1]);
}
if (!this.getClass().equals(obj.getClass())) {
return false;
Class<?> rawType = getType();
Set<Type> supertypes = new HashSet<Type>();
supertypes.add(rawType.getGenericSuperclass());
supertypes.addAll(Arrays.asList(rawType.getGenericInterfaces()));
for (Type supertype : supertypes) {
Class<?> rawSuperType = GenericTypeResolver.resolveType(supertype, getTypeVariableMap());
if (Map.class.isAssignableFrom(rawSuperType)) {
ParameterizedType parameterizedSupertype = (ParameterizedType) supertype;
Type[] arguments = parameterizedSupertype.getActualTypeArguments();
return createInfo(arguments[1]);
}
}
ParameterizedTypeInformation<?> that = (ParameterizedTypeInformation<?>) obj;
return this.parent == null ? that.parent == null : this.parent.equals(that.parent);
}
/*
* (non-Javadoc)
* @see org.springframework.data.util.TypeDiscoverer#hashCode()
*/
@Override
public int hashCode() {
return super.hashCode() + 31 * ObjectUtils.nullSafeHashCode(parent);
return super.getMapValueType();
}
}

81
spring-data-commons-core/src/main/java/org/springframework/data/util/ParentTypeAwareTypeInformation.java

@ -0,0 +1,81 @@ @@ -0,0 +1,81 @@
package org.springframework.data.util;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.Map;
import org.springframework.util.ObjectUtils;
/**
* Base class for {@link TypeInformation} implementations that need parent type awareness.
*
* @author Oliver Gierke
*/
public abstract class ParentTypeAwareTypeInformation<S> extends TypeDiscoverer<S> {
private final TypeDiscoverer<?> parent;
/**
* Creates a new {@link ParentTypeAwareTypeInformation}.
*
* @param type
* @param typeVariableMap
*/
@SuppressWarnings("rawtypes")
protected ParentTypeAwareTypeInformation(Type type, TypeDiscoverer<?> parent, Map<TypeVariable, Type> map) {
super(type, map);
this.parent = parent;
}
/**
* Considers the parent's type variable map before invoking the super class method.
*
* @return
*/
@SuppressWarnings("rawtypes")
protected Map<TypeVariable, Type> getTypeVariableMap() {
return parent == null ? super.getTypeVariableMap() : parent.getTypeVariableMap();
}
/*
* (non-Javadoc)
* @see org.springframework.data.util.TypeDiscoverer#createInfo(java.lang.reflect.Type)
*/
@Override
protected TypeInformation<?> createInfo(Type fieldType) {
if (parent.getType().equals(fieldType)) {
return parent;
}
return super.createInfo(fieldType);
}
/*
* (non-Javadoc)
* @see org.springframework.data.util.TypeDiscoverer#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (!super.equals(obj)) {
return false;
}
if (!this.getClass().equals(obj.getClass())) {
return false;
}
ParentTypeAwareTypeInformation<?> that = (ParentTypeAwareTypeInformation<?>) obj;
return this.parent == null ? that.parent == null : this.parent.equals(that.parent);
}
/*
* (non-Javadoc)
* @see org.springframework.data.util.TypeDiscoverer#hashCode()
*/
@Override
public int hashCode() {
return super.hashCode() + 31 * ObjectUtils.nullSafeHashCode(parent);
}
}

11
spring-data-commons-core/src/main/java/org/springframework/data/util/TypeDiscoverer.java

@ -88,6 +88,8 @@ class TypeDiscoverer<S> implements TypeInformation<S> { @@ -88,6 +88,8 @@ class TypeDiscoverer<S> implements TypeInformation<S> {
if (fieldType instanceof Class) {
return new ClassTypeInformation((Class<?>) fieldType);
}
Map<TypeVariable, Type> variableMap = GenericTypeResolver.getTypeVariableMap(resolveType(fieldType));
if (fieldType instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) fieldType;
@ -96,7 +98,7 @@ class TypeDiscoverer<S> implements TypeInformation<S> { @@ -96,7 +98,7 @@ class TypeDiscoverer<S> implements TypeInformation<S> {
if (fieldType instanceof TypeVariable) {
TypeVariable<?> variable = (TypeVariable<?>) fieldType;
return new TypeVariableTypeInformation(variable, type, this);
return new TypeVariableTypeInformation(variable, type, this, variableMap);
}
if (fieldType instanceof GenericArrayType) {
@ -263,12 +265,7 @@ class TypeDiscoverer<S> implements TypeInformation<S> { @@ -263,12 +265,7 @@ class TypeDiscoverer<S> implements TypeInformation<S> {
if (!isMap()) {
return null;
}
if (type instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) type;
return createInfo(parameterizedType.getActualTypeArguments()[1]);
}
return getTypeArgument(getType(), Map.class, 1);
}

7
spring-data-commons-core/src/main/java/org/springframework/data/util/TypeVariableTypeInformation.java

@ -20,6 +20,7 @@ import static org.springframework.util.ObjectUtils.*; @@ -20,6 +20,7 @@ import static org.springframework.util.ObjectUtils.*;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.Map;
import org.springframework.util.Assert;
@ -29,7 +30,7 @@ import org.springframework.util.Assert; @@ -29,7 +30,7 @@ import org.springframework.util.Assert;
*
* @author Oliver Gierke
*/
class TypeVariableTypeInformation<T> extends ParameterizedTypeInformation<T> {
class TypeVariableTypeInformation<T> extends ParentTypeAwareTypeInformation<T> {
private final TypeVariable<?> variable;
private final Type owningType;
@ -42,9 +43,9 @@ class TypeVariableTypeInformation<T> extends ParameterizedTypeInformation<T> { @@ -42,9 +43,9 @@ class TypeVariableTypeInformation<T> extends ParameterizedTypeInformation<T> {
* @param owningType must not be {@literal null}
* @param parent
*/
public TypeVariableTypeInformation(TypeVariable<?> variable, Type owningType, TypeDiscoverer<?> parent) {
public TypeVariableTypeInformation(TypeVariable<?> variable, Type owningType, TypeDiscoverer<?> parent, Map<TypeVariable, Type> map) {
super(variable, parent);
super(variable, parent, map);
Assert.notNull(variable);
this.variable = variable;
this.owningType = owningType;

46
spring-data-commons-core/src/test/java/org/springframework/data/util/ClassTypeInformationUnitTests.java

@ -55,33 +55,40 @@ public class ClassTypeInformationUnitTests { @@ -55,33 +55,40 @@ public class ClassTypeInformationUnitTests {
TypeInformation<?> content = wrapper.getProperty("content");
assertEquals(String.class, content.getType());
assertEquals(String.class, discoverer.getProperty("wrapped").getProperty("content").getType());
assertEquals(String.class, discoverer.getProperty("wrapped.content").getType());
assertEquals(String.class,
discoverer.getProperty("wrapped").getProperty("content").getType());
assertEquals(String.class, discoverer.getProperty("wrapped.content")
.getType());
}
@Test
@SuppressWarnings("rawtypes")
public void discoversBoundType() {
TypeInformation<GenericTypeWithBound> information = ClassTypeInformation.from(GenericTypeWithBound.class);
TypeInformation<GenericTypeWithBound> information = ClassTypeInformation.from(
GenericTypeWithBound.class);
assertEquals(Person.class, information.getProperty("person").getType());
}
@Test
public void discoversBoundTypeForSpecialization() {
TypeInformation<SpecialGenericTypeWithBound> information = ClassTypeInformation
.from(SpecialGenericTypeWithBound.class);
assertEquals(SpecialPerson.class, information.getProperty("person").getType());
TypeInformation<SpecialGenericTypeWithBound> information = ClassTypeInformation.from(
SpecialGenericTypeWithBound.class);
assertEquals(SpecialPerson.class, information.getProperty("person")
.getType());
}
@Test
@SuppressWarnings("rawtypes")
public void discoversBoundTypeForNested() {
TypeInformation<AnotherGenericType> information = ClassTypeInformation.from(AnotherGenericType.class);
assertEquals(GenericTypeWithBound.class, information.getProperty("nested").getType());
assertEquals(Person.class, information.getProperty("nested.person").getType());
TypeInformation<AnotherGenericType> information = ClassTypeInformation.from(
AnotherGenericType.class);
assertEquals(GenericTypeWithBound.class, information.getProperty("nested")
.getType());
assertEquals(Person.class, information.getProperty("nested.person")
.getType());
}
@Test
@ -118,25 +125,25 @@ public class ClassTypeInformationUnitTests { @@ -118,25 +125,25 @@ public class ClassTypeInformationUnitTests {
assertEquals(Map.class, map.getType());
assertEquals(Calendar.class, map.getMapValueType().getType());
}
@Test
public void typeInfoDoesNotEqualForGenericTypesWithDifferentParent() {
TypeInformation<ConcreteWrapper> first = ClassTypeInformation.from(ConcreteWrapper.class);
TypeInformation<AnotherConcreteWrapper> second = ClassTypeInformation.from(AnotherConcreteWrapper.class);
assertFalse(first.getProperty("wrapped").equals(second.getProperty("wrapped")));
}
@Test
public void handlesPropertyFieldMismatchCorrectly() {
TypeInformation<PropertyGetter> from = ClassTypeInformation.from(PropertyGetter.class);
TypeInformation<?> property = from.getProperty("_name");
assertThat(property, is(notNullValue()));
assertThat(property.getType(), is(typeCompatibleWith(String.class)));
property = from.getProperty("name");
assertThat(property, is(notNullValue()));
assertThat(property.getType(), is(typeCompatibleWith(byte[].class)));
@ -203,7 +210,8 @@ public class ClassTypeInformationUnitTests { @@ -203,7 +210,8 @@ public class ClassTypeInformationUnitTests {
S nested;
}
static class SpecialGenericTypeWithBound extends GenericTypeWithBound<SpecialPerson> {
static class SpecialGenericTypeWithBound extends
GenericTypeWithBound<SpecialPerson> {
}
@ -231,14 +239,14 @@ public class ClassTypeInformationUnitTests { @@ -231,14 +239,14 @@ public class ClassTypeInformationUnitTests {
static class ConcreteWrapper extends GenericWrapper<String> {
}
static class AnotherConcreteWrapper extends GenericWrapper<Long> {
}
static class PropertyGetter {
private String _name;
public byte[] getName() {
return _name.getBytes();
}

50
spring-data-commons-core/src/test/java/org/springframework/data/util/ParameterizedTypeUnitTests.java

@ -15,25 +15,36 @@ @@ -15,25 +15,36 @@
*/
package org.springframework.data.util;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import java.lang.reflect.ParameterizedType;
import java.util.HashMap;
import java.util.Locale;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
/**
* Unit tests for {@link ParameterizedTypeInformation}.
*
* @author Oliver Gierke
*/
@RunWith(MockitoJUnitRunner.class)
public class ParameterizedTypeUnitTests {
@Mock ParameterizedType one, two;
@Test
public void considersTypeInformationsWithDifferingParentsNotEqual() {
TypeDiscoverer<String> stringParent = new TypeDiscoverer<String>(String.class, null);
TypeDiscoverer<Object> objectParent = new TypeDiscoverer<Object>(Object.class, null);
ParameterizedTypeInformation<Object> first = new ParameterizedTypeInformation<Object>(Object.class, stringParent);
ParameterizedTypeInformation<Object> second = new ParameterizedTypeInformation<Object>(Object.class, objectParent);
ParameterizedTypeInformation<Object> first = new ParameterizedTypeInformation<Object>(one, stringParent);
ParameterizedTypeInformation<Object> second = new ParameterizedTypeInformation<Object>(one, objectParent);
assertFalse(first.equals(second));
}
@ -43,9 +54,40 @@ public class ParameterizedTypeUnitTests { @@ -43,9 +54,40 @@ public class ParameterizedTypeUnitTests {
TypeDiscoverer<String> stringParent = new TypeDiscoverer<String>(String.class, null);
ParameterizedTypeInformation<Object> first = new ParameterizedTypeInformation<Object>(Object.class, stringParent);
ParameterizedTypeInformation<Object> second = new ParameterizedTypeInformation<Object>(Object.class, stringParent);
ParameterizedTypeInformation<Object> first = new ParameterizedTypeInformation<Object>(one, stringParent);
ParameterizedTypeInformation<Object> second = new ParameterizedTypeInformation<Object>(one, stringParent);
assertTrue(first.equals(second));
}
/**
* @see DATACMNS-88
*/
@Test
public void resolvesMapValueTypeCorrectly() {
TypeInformation<Foo> type = ClassTypeInformation.from(Foo.class);
TypeInformation<?> propertyType = type.getProperty("param");
assertThat(propertyType.getProperty("value").getType(), is(typeCompatibleWith(String.class)));
assertThat(propertyType.getMapValueType().getType(), is(typeCompatibleWith(String.class)));
propertyType = type.getProperty("param2");
assertThat(propertyType.getProperty("value").getType(), is(typeCompatibleWith(String.class)));
assertThat(propertyType.getMapValueType().getType(), is(typeCompatibleWith(Locale.class)));
}
@SuppressWarnings("serial")
class Localized<S> extends HashMap<Locale, S> {
S value;
}
@SuppressWarnings("serial")
class Localized2<S> extends HashMap<S, Locale> {
S value;
}
class Foo {
Localized<String> param;
Localized2<String> param2;
}
}

Loading…
Cancel
Save