Browse Source

DATACMNS-211 - Improved TypeInformation abstraction.

pull/15/head
Oliver Gierke 14 years ago
parent
commit
f1aff4ed10
  1. 18
      spring-data-commons-core/src/main/java/org/springframework/data/util/ClassTypeInformation.java
  2. 114
      spring-data-commons-core/src/main/java/org/springframework/data/util/ParameterizedTypeInformation.java
  3. 127
      spring-data-commons-core/src/main/java/org/springframework/data/util/TypeDiscoverer.java
  4. 42
      spring-data-commons-core/src/main/java/org/springframework/data/util/TypeInformation.java
  5. 118
      spring-data-commons-core/src/test/java/org/springframework/data/util/ClassTypeInformationUnitTests.java
  6. 10
      spring-data-commons-core/src/test/java/org/springframework/data/util/ParameterizedTypeUnitTests.java
  7. 3
      spring-data-commons-core/src/test/java/org/springframework/data/util/TypeDiscovererUnitTests.java

18
spring-data-commons-core/src/main/java/org/springframework/data/util/ClassTypeInformation.java

@ -102,10 +102,9 @@ public class ClassTypeInformation<S> extends TypeDiscoverer<S> { @@ -102,10 +102,9 @@ public class ClassTypeInformation<S> extends TypeDiscoverer<S> {
}
/*
* (non-Javadoc)
*
* @see org.springframework.data.document.mongodb.TypeDiscovererTest.FieldInformation#getType()
*/
* (non-Javadoc)
* @see org.springframework.data.util.TypeDiscoverer#getType()
*/
@Override
public Class<S> getType() {
return type;
@ -125,9 +124,18 @@ public class ClassTypeInformation<S> extends TypeDiscoverer<S> { @@ -125,9 +124,18 @@ public class ClassTypeInformation<S> extends TypeDiscoverer<S> {
return super.getComponentType();
}
/*
* (non-Javadoc)
* @see org.springframework.data.util.TypeDiscoverer#isAssignableFrom(org.springframework.data.util.TypeInformation)
*/
@Override
public boolean isAssignableFrom(TypeInformation<?> target) {
return getType().isAssignableFrom(target.getType());
}
private static Type resolveArrayType(Class<?> type) {
Assert.isTrue(type.isArray());
Class<?> componentType = type.getComponentType();
return componentType.isArray() ? resolveArrayType(componentType) : componentType;
}
}
}

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

@ -17,8 +17,10 @@ package org.springframework.data.util; @@ -17,8 +17,10 @@ package org.springframework.data.util;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@ -74,4 +76,116 @@ class ParameterizedTypeInformation<T> extends ParentTypeAwareTypeInformation<T> @@ -74,4 +76,116 @@ class ParameterizedTypeInformation<T> extends ParentTypeAwareTypeInformation<T>
return super.getMapValueType();
}
/*
* (non-Javadoc)
* @see org.springframework.data.util.TypeDiscoverer#getTypeParameters()
*/
@Override
public List<TypeInformation<?>> getTypeArguments() {
List<TypeInformation<?>> result = new ArrayList<TypeInformation<?>>();
for (Type argument : type.getActualTypeArguments()) {
result.add(createInfo(argument));
}
return result;
}
/*
* (non-Javadoc)
* @see org.springframework.data.util.TypeDiscoverer#isAssignableFrom(org.springframework.data.util.TypeInformation)
*/
@Override
public boolean isAssignableFrom(TypeInformation<?> target) {
if (this.equals(target)) {
return true;
}
Class<T> rawType = getType();
Class<?> rawTargetType = target.getType();
if (!rawType.isAssignableFrom(rawTargetType)) {
return false;
}
TypeInformation<?> otherTypeInformation = rawType.equals(rawTargetType) ? target : target
.getSuperTypeInformation(rawType);
List<TypeInformation<?>> myParameters = getTypeArguments();
List<TypeInformation<?>> typeParameters = otherTypeInformation.getTypeArguments();
if (myParameters.size() != typeParameters.size()) {
return false;
}
for (int i = 0; i < myParameters.size(); i++) {
if (!myParameters.get(i).isAssignableFrom(typeParameters.get(i))) {
return false;
}
}
return true;
}
/*
* (non-Javadoc)
* @see org.springframework.data.util.TypeDiscoverer#getComponentType()
*/
@Override
public TypeInformation<?> getComponentType() {
return createInfo(type.getActualTypeArguments()[0]);
}
/*
* (non-Javadoc)
* @see org.springframework.data.util.ParentTypeAwareTypeInformation#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof ParameterizedTypeInformation)) {
return false;
}
ParameterizedTypeInformation<?> that = (ParameterizedTypeInformation<?>) obj;
if (this.isResolvedCompletely() && that.isResolvedCompletely()) {
return this.type.equals(that.type);
}
return super.equals(obj);
}
private boolean isResolvedCompletely() {
Type[] types = type.getActualTypeArguments();
if (types.length == 0) {
return false;
}
for (Type type : types) {
TypeInformation<?> info = createInfo(type);
if (info instanceof ParameterizedTypeInformation) {
if (!((ParameterizedTypeInformation<?>) info).isResolvedCompletely()) {
return false;
}
}
if (!(info instanceof ClassTypeInformation)) {
return false;
}
}
return true;
}
}

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

@ -27,7 +27,9 @@ import java.lang.reflect.Type; @@ -27,7 +27,9 @@ import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@ -144,6 +146,7 @@ class TypeDiscoverer<S> implements TypeInformation<S> { @@ -144,6 +146,7 @@ class TypeDiscoverer<S> implements TypeInformation<S> {
*/
public List<TypeInformation<?>> getParameterTypes(Constructor<?> constructor) {
Assert.notNull(constructor);
List<TypeInformation<?>> result = new ArrayList<TypeInformation<?>>();
for (Type type : constructor.getGenericParameterTypes()) {
@ -238,13 +241,22 @@ class TypeDiscoverer<S> implements TypeInformation<S> { @@ -238,13 +241,22 @@ class TypeDiscoverer<S> implements TypeInformation<S> {
* @see org.springframework.data.util.TypeInformation#getActualType()
*/
public TypeInformation<?> getActualType() {
if (isMap()) {
return getMapValueType();
} else if (isCollectionLike()) {
}
if (isCollectionLike()) {
return getComponentType();
} else {
}
List<TypeInformation<?>> arguments = getTypeArguments();
if (arguments.isEmpty()) {
return this;
}
return arguments.get(arguments.size() - 1);
}
/*
@ -261,16 +273,17 @@ class TypeDiscoverer<S> implements TypeInformation<S> { @@ -261,16 +273,17 @@ class TypeDiscoverer<S> implements TypeInformation<S> {
*/
public TypeInformation<?> getMapValueType() {
if (!isMap()) {
return null;
if (isMap()) {
return getTypeArgument(getType(), Map.class, 1);
}
return getTypeArgument(getType(), Map.class, 1);
}
List<TypeInformation<?>> arguments = getTypeArguments();
private TypeInformation<?> getTypeArgument(Class<?> type, Class<?> bound, int index) {
Class<?>[] arguments = GenericTypeResolver.resolveTypeArguments(type, bound);
return arguments == null ? null : createInfo(arguments[index]);
if (arguments.size() > 1) {
return arguments.get(1);
}
return null;
}
/*
@ -296,13 +309,8 @@ class TypeDiscoverer<S> implements TypeInformation<S> { @@ -296,13 +309,8 @@ class TypeDiscoverer<S> implements TypeInformation<S> {
Class<S> rawType = getType();
if (!(isMap() || isCollectionLike() || Iterable.class.isAssignableFrom(rawType))) {
return null;
}
if (type instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) type;
return createInfo(parameterizedType.getActualTypeArguments()[0]);
if (rawType.isArray()) {
return createInfo(rawType.getComponentType());
}
if (isMap()) {
@ -313,8 +321,10 @@ class TypeDiscoverer<S> implements TypeInformation<S> { @@ -313,8 +321,10 @@ class TypeDiscoverer<S> implements TypeInformation<S> {
return getTypeArgument(rawType, Iterable.class, 0);
}
if (rawType.isArray()) {
return createInfo(rawType.getComponentType());
List<TypeInformation<?>> arguments = getTypeArguments();
if (arguments.size() > 0) {
return arguments.get(0);
}
return null;
@ -330,6 +340,85 @@ class TypeDiscoverer<S> implements TypeInformation<S> { @@ -330,6 +340,85 @@ class TypeDiscoverer<S> implements TypeInformation<S> {
return createInfo(method.getGenericReturnType());
}
/*
* (non-Javadoc)
* @see org.springframework.data.util.TypeInformation#getMethodParameterTypes(java.lang.reflect.Method)
*/
public List<TypeInformation<?>> getParameterTypes(Method method) {
Assert.notNull(method);
Type[] parameterTypes = method.getGenericParameterTypes();
List<TypeInformation<?>> result = new ArrayList<TypeInformation<?>>(parameterTypes.length);
for (Type type : parameterTypes) {
result.add(createInfo(type));
}
return result;
}
/*
* (non-Javadoc)
* @see org.springframework.data.util.TypeInformation#getSuperTypeInformation(java.lang.Class)
*/
public TypeInformation<?> getSuperTypeInformation(Class<?> superType) {
Class<?> type = getType();
if (!superType.isAssignableFrom(type)) {
return null;
}
if (getType().equals(superType)) {
return this;
}
List<Type> candidates = new ArrayList<Type>();
Type genericSuperclass = type.getGenericSuperclass();
if (genericSuperclass != null) {
candidates.add(genericSuperclass);
}
candidates.addAll(Arrays.asList(type.getGenericInterfaces()));
for (Type candidate : candidates) {
TypeInformation<?> candidateInfo = createInfo(candidate);
if (superType.equals(candidateInfo.getType())) {
return candidateInfo;
} else {
TypeInformation<?> nestedSuperType = candidateInfo.getSuperTypeInformation(superType);
if (nestedSuperType != null) {
return nestedSuperType;
}
}
}
return null;
}
/*
* (non-Javadoc)
* @see org.springframework.data.util.TypeInformation#getTypeParameters()
*/
public List<TypeInformation<?>> getTypeArguments() {
return Collections.emptyList();
}
/* (non-Javadoc)
* @see org.springframework.data.util.TypeInformation#isAssignableFrom(org.springframework.data.util.TypeInformation)
*/
public boolean isAssignableFrom(TypeInformation<?> target) {
return target.getSuperTypeInformation(getType()).equals(this);
}
private TypeInformation<?> getTypeArgument(Class<?> type, Class<?> bound, int index) {
Class<?>[] arguments = GenericTypeResolver.resolveTypeArguments(type, bound);
return arguments == null ? null : createInfo(arguments[index]);
}
/*
* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
@ -368,4 +457,4 @@ class TypeDiscoverer<S> implements TypeInformation<S> { @@ -368,4 +457,4 @@ class TypeDiscoverer<S> implements TypeInformation<S> {
result += nullSafeHashCode(typeVariableMap);
return result;
}
}
}

42
spring-data-commons-core/src/main/java/org/springframework/data/util/TypeInformation.java

@ -27,6 +27,12 @@ import java.util.List; @@ -27,6 +27,12 @@ import java.util.List;
*/
public interface TypeInformation<S> {
/**
* Returns the {@link TypeInformation}s for the parameters of the given {@link Constructor}.
*
* @param constructor must not be {@literal null}.
* @return
*/
List<TypeInformation<?>> getParameterTypes(Constructor<?> constructor);
/**
@ -92,4 +98,38 @@ public interface TypeInformation<S> { @@ -92,4 +98,38 @@ public interface TypeInformation<S> {
* @return
*/
TypeInformation<?> getReturnType(Method method);
}
/**
* Returns the {@link TypeInformation}s for the parameters of the given {@link Method}.
*
* @param method must not be {@literal null}.
* @return
*/
List<TypeInformation<?>> getParameterTypes(Method method);
/**
* Returns the {@link TypeInformation} for the given raw super type.
*
* @param superType must not be {@literal null}.
* @return the {@link TypeInformation} for the given raw super type or {@literal null} in case the current
* {@link TypeInformation} does not implement the given type.
*/
TypeInformation<?> getSuperTypeInformation(Class<?> superType);
/**
* Returns if the current {@link TypeInformation} can be safely assigned to the given one. Mimics semantics of
* {@link Class#isAssignableFrom(Class)} but takes generics into account. Thus it will allow to detect that a
* {@code List<Long>} is assignable to {@code List<? extends Number>}.
*
* @param target
* @return
*/
boolean isAssignableFrom(TypeInformation<?> target);
/**
* Returns the {@link TypeInformation} for the type arguments of the current {@link TypeInformation}.
*
* @return
*/
List<TypeInformation<?>> getTypeArguments();
}

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

@ -17,7 +17,9 @@ package org.springframework.data.util; @@ -17,7 +17,9 @@ package org.springframework.data.util;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.springframework.data.util.ClassTypeInformation.*;
import java.lang.reflect.Method;
import java.util.Calendar;
import java.util.Collection;
import java.util.List;
@ -172,6 +174,86 @@ public class ClassTypeInformationUnitTests { @@ -172,6 +174,86 @@ public class ClassTypeInformationUnitTests {
assertThat(component.getComponentType().getType(), is(typeCompatibleWith(String.class)));
}
@Test
public void resolvesTypeParametersCorrectly() {
TypeInformation<ConcreteType> information = ClassTypeInformation.from(ConcreteType.class);
TypeInformation<?> superTypeInformation = information.getSuperTypeInformation(GenericType.class);
List<TypeInformation<?>> parameters = superTypeInformation.getTypeArguments();
assertThat(parameters, hasSize(2));
assertThat(parameters.get(0).getType(), is((Object) String.class));
assertThat(parameters.get(1).getType(), is((Object) Object.class));
}
@Test
public void resolvesNestedInheritedTypeParameters() {
TypeInformation<SecondExtension> information = ClassTypeInformation.from(SecondExtension.class);
TypeInformation<?> superTypeInformation = information.getSuperTypeInformation(Base.class);
List<TypeInformation<?>> parameters = superTypeInformation.getTypeArguments();
assertThat(parameters, hasSize(1));
assertThat(parameters.get(0).getType(), is((Object) String.class));
}
@Test
public void discoveresMethodParameterTypesCorrectly() throws Exception {
TypeInformation<SecondExtension> information = ClassTypeInformation.from(SecondExtension.class);
Method method = SecondExtension.class.getMethod("foo", Base.class);
List<TypeInformation<?>> informations = information.getParameterTypes(method);
TypeInformation<?> returnTypeInformation = information.getReturnType(method);
assertThat(informations, hasSize(1));
assertThat(informations.get(0).getType(), is((Object) Base.class));
assertThat(informations.get(0), is((Object) returnTypeInformation));
}
@Test
public void discoversImplementationBindingCorrectlyForString() throws Exception {
TypeInformation<TypedClient> information = from(TypedClient.class);
Method method = TypedClient.class.getMethod("stringMethod", GenericInterface.class);
TypeInformation<?> parameterType = information.getParameterTypes(method).get(0);
TypeInformation<StringImplementation> stringInfo = from(StringImplementation.class);
assertThat(parameterType.isAssignableFrom(stringInfo), is(true));
assertThat(stringInfo.getSuperTypeInformation(GenericInterface.class), is((Object) parameterType));
assertThat(parameterType.isAssignableFrom(from(LongImplementation.class)), is(false));
assertThat(parameterType.isAssignableFrom(from(StringImplementation.class).getSuperTypeInformation(
GenericInterface.class)), is(true));
}
@Test
public void discoversImplementationBindingCorrectlyForLong() throws Exception {
TypeInformation<TypedClient> information = from(TypedClient.class);
Method method = TypedClient.class.getMethod("longMethod", GenericInterface.class);
TypeInformation<?> parameterType = information.getParameterTypes(method).get(0);
assertThat(parameterType.isAssignableFrom(from(StringImplementation.class)), is(false));
assertThat(parameterType.isAssignableFrom(from(LongImplementation.class)), is(true));
assertThat(parameterType.isAssignableFrom(from(StringImplementation.class).getSuperTypeInformation(
GenericInterface.class)), is(false));
}
@Test
public void discoversImplementationBindingCorrectlyForNumber() throws Exception {
TypeInformation<TypedClient> information = from(TypedClient.class);
Method method = TypedClient.class.getMethod("boundToNumberMethod", GenericInterface.class);
TypeInformation<?> parameterType = information.getParameterTypes(method).get(0);
assertThat(parameterType.isAssignableFrom(from(StringImplementation.class)), is(false));
assertThat(parameterType.isAssignableFrom(from(LongImplementation.class)), is(true));
assertThat(parameterType.isAssignableFrom(from(StringImplementation.class).getSuperTypeInformation(
GenericInterface.class)), is(false));
}
static class StringMapContainer extends MapContainer<String> {
}
@ -248,4 +330,40 @@ public class ClassTypeInformationUnitTests { @@ -248,4 +330,40 @@ public class ClassTypeInformationUnitTests {
List<? extends String> wildcard;
List<? extends Collection<? extends String>> complexWildcard;
}
static class Base<T> {
}
static class FirstExtension<T> extends Base<String> {
public Base<GenericWrapper<T>> foo(Base<GenericWrapper<T>> param) {
return null;
}
}
static class SecondExtension extends FirstExtension<Long> {
}
interface GenericInterface<T> {
}
interface TypedClient {
void stringMethod(GenericInterface<String> param);
void longMethod(GenericInterface<Long> param);
void boundToNumberMethod(GenericInterface<? extends Number> param);
}
class StringImplementation implements GenericInterface<String> {
}
class LongImplementation implements GenericInterface<Long> {
}
}

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

@ -17,11 +17,14 @@ package org.springframework.data.util; @@ -17,11 +17,14 @@ package org.springframework.data.util;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Locale;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@ -36,7 +39,12 @@ import org.mockito.runners.MockitoJUnitRunner; @@ -36,7 +39,12 @@ import org.mockito.runners.MockitoJUnitRunner;
public class ParameterizedTypeUnitTests {
@Mock
ParameterizedType one, two;
ParameterizedType one;
@Before
public void setUp() {
when(one.getActualTypeArguments()).thenReturn(new Type[0]);
}
@Test
public void considersTypeInformationsWithDifferingParentsNotEqual() {

3
spring-data-commons-core/src/test/java/org/springframework/data/util/TypeDiscovererUnitTests.java

@ -25,7 +25,6 @@ import java.util.Collection; @@ -25,7 +25,6 @@ import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import org.junit.Test;
import org.junit.runner.RunWith;
@ -93,7 +92,7 @@ public class TypeDiscovererUnitTests { @@ -93,7 +92,7 @@ public class TypeDiscovererUnitTests {
@Test
public void returnsComponentAndValueTypesForMapExtensions() {
TypeDiscoverer<Properties> discoverer = new TypeDiscoverer<Properties>(CustomMap.class, null);
TypeInformation<?> discoverer = new TypeDiscoverer<Object>(CustomMap.class, null);
assertEquals(Locale.class, discoverer.getMapValueType().getType());
assertEquals(String.class, discoverer.getComponentType().getType());
}

Loading…
Cancel
Save