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;
* *
* @author Oliver Gierke * @author Oliver Gierke
*/ */
class GenericArrayTypeInformation<S> extends ParameterizedTypeInformation<S> { class GenericArrayTypeInformation<S> extends ParentTypeAwareTypeInformation<S> {
private GenericArrayType type; private GenericArrayType type;
@ -36,7 +36,7 @@ class GenericArrayTypeInformation<S> extends ParameterizedTypeInformation<S> {
* @param parent * @param parent
*/ */
protected GenericArrayTypeInformation(GenericArrayType type, TypeDiscoverer<?> parent) { protected GenericArrayTypeInformation(GenericArrayType type, TypeDiscoverer<?> parent) {
super(type, parent); super(type, parent, parent.getTypeVariableMap());
this.type = type; this.type = type;
} }

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

@ -15,12 +15,12 @@
*/ */
package org.springframework.data.util; package org.springframework.data.util;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable; import java.util.Arrays;
import java.util.HashSet;
import java.util.Map; import java.util.Map;
import java.util.Set;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
/** /**
* Base class for all types that include parameterization of some kind. Crucial as we have to take note of the parent * 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;
* *
* @author Oliver Gierke * @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}. * Creates a new {@link ParameterizedTypeInformation} for the given {@link Type} and parent {@link TypeDiscoverer}.
@ -38,60 +38,40 @@ class ParameterizedTypeInformation<T> extends TypeDiscoverer<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(Type type, TypeDiscoverer<?> parent) { public ParameterizedTypeInformation(ParameterizedType type, TypeDiscoverer<?> parent) {
super(type, null); super(type, parent, null);
Assert.notNull(parent); this.type = type;
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);
} }
/* /*
* (non-Javadoc) * (non-Javadoc)
* @see org.springframework.data.util.TypeDiscoverer#equals(java.lang.Object) * @see org.springframework.data.util.TypeDiscoverer#getMapValueType()
*/ */
@Override @Override
public boolean equals(Object obj) { public TypeInformation<?> getMapValueType() {
if (!super.equals(obj)) { if (Map.class.equals(getType())) {
return false; Type[] arguments = type.getActualTypeArguments();
return createInfo(arguments[1]);
} }
if (!this.getClass().equals(obj.getClass())) { Class<?> rawType = getType();
return false;
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 super.getMapValueType();
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);
} }
} }

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

@ -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> {
if (fieldType instanceof Class) { if (fieldType instanceof Class) {
return new ClassTypeInformation((Class<?>) fieldType); return new ClassTypeInformation((Class<?>) fieldType);
} }
Map<TypeVariable, Type> variableMap = GenericTypeResolver.getTypeVariableMap(resolveType(fieldType));
if (fieldType instanceof ParameterizedType) { if (fieldType instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) fieldType; ParameterizedType parameterizedType = (ParameterizedType) fieldType;
@ -96,7 +98,7 @@ class TypeDiscoverer<S> implements TypeInformation<S> {
if (fieldType instanceof TypeVariable) { if (fieldType instanceof TypeVariable) {
TypeVariable<?> variable = (TypeVariable<?>) fieldType; TypeVariable<?> variable = (TypeVariable<?>) fieldType;
return new TypeVariableTypeInformation(variable, type, this); return new TypeVariableTypeInformation(variable, type, this, variableMap);
} }
if (fieldType instanceof GenericArrayType) { if (fieldType instanceof GenericArrayType) {
@ -263,12 +265,7 @@ class TypeDiscoverer<S> implements TypeInformation<S> {
if (!isMap()) { if (!isMap()) {
return null; return null;
} }
if (type instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) type;
return createInfo(parameterizedType.getActualTypeArguments()[1]);
}
return getTypeArgument(getType(), Map.class, 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.*;
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.util.Assert; import org.springframework.util.Assert;
@ -29,7 +30,7 @@ import org.springframework.util.Assert;
* *
* @author Oliver Gierke * @author Oliver Gierke
*/ */
class TypeVariableTypeInformation<T> extends ParameterizedTypeInformation<T> { class TypeVariableTypeInformation<T> extends ParentTypeAwareTypeInformation<T> {
private final TypeVariable<?> variable; private final TypeVariable<?> variable;
private final Type owningType; private final Type owningType;
@ -42,9 +43,9 @@ class TypeVariableTypeInformation<T> extends ParameterizedTypeInformation<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> map) {
super(variable, parent); super(variable, parent, map);
Assert.notNull(variable); Assert.notNull(variable);
this.variable = variable; this.variable = variable;
this.owningType = owningType; this.owningType = owningType;

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

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

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

@ -15,25 +15,36 @@
*/ */
package org.springframework.data.util; package org.springframework.data.util;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*; 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.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
/** /**
* Unit tests for {@link ParameterizedTypeInformation}. * Unit tests for {@link ParameterizedTypeInformation}.
* *
* @author Oliver Gierke * @author Oliver Gierke
*/ */
@RunWith(MockitoJUnitRunner.class)
public class ParameterizedTypeUnitTests { public class ParameterizedTypeUnitTests {
@Mock ParameterizedType one, two;
@Test @Test
public void considersTypeInformationsWithDifferingParentsNotEqual() { public void considersTypeInformationsWithDifferingParentsNotEqual() {
TypeDiscoverer<String> stringParent = new TypeDiscoverer<String>(String.class, null); TypeDiscoverer<String> stringParent = new TypeDiscoverer<String>(String.class, null);
TypeDiscoverer<Object> objectParent = new TypeDiscoverer<Object>(Object.class, null); TypeDiscoverer<Object> objectParent = new TypeDiscoverer<Object>(Object.class, null);
ParameterizedTypeInformation<Object> first = new ParameterizedTypeInformation<Object>(Object.class, stringParent); ParameterizedTypeInformation<Object> first = new ParameterizedTypeInformation<Object>(one, stringParent);
ParameterizedTypeInformation<Object> second = new ParameterizedTypeInformation<Object>(Object.class, objectParent); ParameterizedTypeInformation<Object> second = new ParameterizedTypeInformation<Object>(one, objectParent);
assertFalse(first.equals(second)); assertFalse(first.equals(second));
} }
@ -43,9 +54,40 @@ public class ParameterizedTypeUnitTests {
TypeDiscoverer<String> stringParent = new TypeDiscoverer<String>(String.class, null); TypeDiscoverer<String> stringParent = new TypeDiscoverer<String>(String.class, null);
ParameterizedTypeInformation<Object> first = new ParameterizedTypeInformation<Object>(Object.class, stringParent); ParameterizedTypeInformation<Object> first = new ParameterizedTypeInformation<Object>(one, stringParent);
ParameterizedTypeInformation<Object> second = new ParameterizedTypeInformation<Object>(Object.class, stringParent); ParameterizedTypeInformation<Object> second = new ParameterizedTypeInformation<Object>(one, stringParent);
assertTrue(first.equals(second)); 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