DATACMNS-211 - Improved TypeInformation abstraction.
This commit is contained in:
@@ -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> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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> {
|
||||
*/
|
||||
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> {
|
||||
* @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> {
|
||||
*/
|
||||
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> {
|
||||
|
||||
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> {
|
||||
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> {
|
||||
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> {
|
||||
result += nullSafeHashCode(typeVariableMap);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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> {
|
||||
* @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();
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
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 {
|
||||
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> {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
public class ParameterizedTypeUnitTests {
|
||||
|
||||
@Mock
|
||||
ParameterizedType one, two;
|
||||
ParameterizedType one;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
when(one.getActualTypeArguments()).thenReturn(new Type[0]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void considersTypeInformationsWithDifferingParentsNotEqual() {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@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());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user