Browse Source
Added TypeInformation interface and infrastructure to discover generic types given a basic type that binds generic types. Starting from that you can then traverse TypeInformations for fields by calling getProperty(…). A subsequent call to getType() will then return the resolved type. Adapted the Basic mapping infrastructure to accomodate the TypeInformation interfaces where necessary. In most cases it was just about using TypeInformation instead of class as we have to keep track of the parent types that (in case of a generic field) determine the actual type of sub fields. BasicMappingContext is now using the TypeInformation instance created for PersistentEntity as key for the cache. This is necessary as generic types (e.g. Foo<T>) might be used inside different entities that type T to something different. Created isMap() and isArray() on PersistentProperty to align to isCollection().pull/2/head
18 changed files with 1120 additions and 119 deletions
@ -0,0 +1,79 @@
@@ -0,0 +1,79 @@
|
||||
package org.springframework.data.util; |
||||
|
||||
import java.lang.reflect.Type; |
||||
import java.lang.reflect.TypeVariable; |
||||
import java.util.Map; |
||||
import java.util.Set; |
||||
|
||||
/** |
||||
* Property information for a plain {@link Class}. |
||||
* |
||||
* @author Oliver Gierke |
||||
*/ |
||||
public class ClassTypeInformation extends TypeDiscoverer { |
||||
|
||||
private final Class<?> type; |
||||
|
||||
/** |
||||
* Creates {@link ClassTypeInformation} for the given type. |
||||
* @param type |
||||
*/ |
||||
public ClassTypeInformation(Class<?> type) { |
||||
this(type, GenericTypeResolver.getTypeVariableMap(type), null, null); |
||||
} |
||||
|
||||
/** |
||||
* Creates {@link ClassTypeInformation} for the given type and the given basic types. Handing over a basic type |
||||
* will prevent it's nested fields to be traversed for further {@link TypeInformation}. |
||||
* |
||||
* @param type |
||||
* @param basicTypes |
||||
*/ |
||||
public ClassTypeInformation(Class<?> type, Set<Class<?>> basicTypes) { |
||||
this(type, GenericTypeResolver.getTypeVariableMap(type), basicTypes, null); |
||||
} |
||||
|
||||
ClassTypeInformation(Class<?> type, TypeDiscoverer parent) { |
||||
this(type, null, null, parent); |
||||
} |
||||
|
||||
@SuppressWarnings("rawtypes") |
||||
ClassTypeInformation(Class<?> type, Map<TypeVariable, Type> typeVariableMap, Set<Class<?>> basicTypes, |
||||
TypeDiscoverer parent) { |
||||
super(type, typeVariableMap, parent); |
||||
this.type = type; |
||||
} |
||||
|
||||
/* |
||||
* (non-Javadoc) |
||||
* |
||||
* @see org.springframework.data.document.mongodb.TypeDiscovererTest.FieldInformation#getType() |
||||
*/ |
||||
@Override |
||||
public Class<?> getType() { |
||||
return type; |
||||
} |
||||
|
||||
/* (non-Javadoc) |
||||
* @see org.springframework.data.util.TypeDiscoverer#equals(java.lang.Object) |
||||
*/ |
||||
@Override |
||||
public boolean equals(Object obj) { |
||||
|
||||
if (!super.equals(obj)) { |
||||
return false; |
||||
} |
||||
|
||||
ClassTypeInformation that = (ClassTypeInformation) obj; |
||||
return this.type.equals(that.type); |
||||
} |
||||
|
||||
/* (non-Javadoc) |
||||
* @see org.springframework.data.util.TypeDiscoverer#hashCode() |
||||
*/ |
||||
@Override |
||||
public int hashCode() { |
||||
int result = super.hashCode(); |
||||
return result += 31 * type.hashCode(); |
||||
} |
||||
} |
||||
@ -0,0 +1,355 @@
@@ -0,0 +1,355 @@
|
||||
/* |
||||
* Copyright 2002-2010 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. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.data.util; |
||||
|
||||
import java.lang.ref.Reference; |
||||
import java.lang.ref.WeakReference; |
||||
import java.lang.reflect.Array; |
||||
import java.lang.reflect.GenericArrayType; |
||||
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.Map; |
||||
import java.util.WeakHashMap; |
||||
|
||||
import org.springframework.core.GenericCollectionTypeResolver; |
||||
import org.springframework.core.JdkVersion; |
||||
import org.springframework.core.MethodParameter; |
||||
import org.springframework.util.Assert; |
||||
|
||||
/** |
||||
* Copy of Spring's {@link org.springframework.core.GenericTypeResolver}. Needed |
||||
* until {@link #getTypeVariableMap(Class)} gets public. |
||||
* |
||||
* TODO: remove that class, as soon as Spring 3.0.6 gets released. |
||||
* @see SPR-8005 |
||||
*/ |
||||
abstract class GenericTypeResolver { |
||||
|
||||
/** Cache from Class to TypeVariable Map */ |
||||
private static final Map<Class, Reference<Map<TypeVariable, Type>>> typeVariableCache = Collections |
||||
.synchronizedMap(new WeakHashMap<Class, Reference<Map<TypeVariable, Type>>>()); |
||||
|
||||
/** |
||||
* Determine the target type for the given parameter specification. |
||||
* |
||||
* @param methodParam |
||||
* the method parameter specification |
||||
* @return the corresponding generic parameter type |
||||
*/ |
||||
public static Type getTargetType(MethodParameter methodParam) { |
||||
Assert.notNull(methodParam, "MethodParameter must not be null"); |
||||
if (methodParam.getConstructor() != null) { |
||||
return methodParam.getConstructor().getGenericParameterTypes()[methodParam |
||||
.getParameterIndex()]; |
||||
} else { |
||||
if (methodParam.getParameterIndex() >= 0) { |
||||
return methodParam.getMethod().getGenericParameterTypes()[methodParam |
||||
.getParameterIndex()]; |
||||
} else { |
||||
return methodParam.getMethod().getGenericReturnType(); |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Resolve the single type argument of the given generic interface against the |
||||
* given target class which is assumed to implement the generic interface and |
||||
* possibly declare a concrete type for its type variable. |
||||
* |
||||
* @param clazz |
||||
* the target class to check against |
||||
* @param genericIfc |
||||
* the generic interface or superclass to resolve the type argument |
||||
* from |
||||
* @return the resolved type of the argument, or <code>null</code> if not |
||||
* resolvable |
||||
*/ |
||||
public static Class<?> resolveTypeArgument(Class clazz, Class genericIfc) { |
||||
Class[] typeArgs = resolveTypeArguments(clazz, genericIfc); |
||||
if (typeArgs == null) { |
||||
return null; |
||||
} |
||||
if (typeArgs.length != 1) { |
||||
throw new IllegalArgumentException( |
||||
"Expected 1 type argument on generic interface [" |
||||
+ genericIfc.getName() + "] but found " + typeArgs.length); |
||||
} |
||||
return typeArgs[0]; |
||||
} |
||||
|
||||
/** |
||||
* Resolve the type arguments of the given generic interface against the given |
||||
* target class which is assumed to implement the generic interface and |
||||
* possibly declare concrete types for its type variables. |
||||
* |
||||
* @param clazz |
||||
* the target class to check against |
||||
* @param genericIfc |
||||
* the generic interface or superclass to resolve the type argument |
||||
* from |
||||
* @return the resolved type of each argument, with the array size matching |
||||
* the number of actual type arguments, or <code>null</code> if not |
||||
* resolvable |
||||
*/ |
||||
public static Class[] resolveTypeArguments(Class clazz, Class genericIfc) { |
||||
return doResolveTypeArguments(clazz, clazz, genericIfc); |
||||
} |
||||
|
||||
private static Class[] doResolveTypeArguments(Class ownerClass, |
||||
Class classToIntrospect, Class genericIfc) { |
||||
while (classToIntrospect != null) { |
||||
if (genericIfc.isInterface()) { |
||||
Type[] ifcs = classToIntrospect.getGenericInterfaces(); |
||||
for (Type ifc : ifcs) { |
||||
Class[] result = doResolveTypeArguments(ownerClass, ifc, genericIfc); |
||||
if (result != null) { |
||||
return result; |
||||
} |
||||
} |
||||
} else { |
||||
Class[] result = doResolveTypeArguments(ownerClass, |
||||
classToIntrospect.getGenericSuperclass(), genericIfc); |
||||
if (result != null) { |
||||
return result; |
||||
} |
||||
} |
||||
classToIntrospect = classToIntrospect.getSuperclass(); |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
private static Class[] doResolveTypeArguments(Class ownerClass, Type ifc, |
||||
Class genericIfc) { |
||||
if (ifc instanceof ParameterizedType) { |
||||
ParameterizedType paramIfc = (ParameterizedType) ifc; |
||||
Type rawType = paramIfc.getRawType(); |
||||
if (genericIfc.equals(rawType)) { |
||||
Type[] typeArgs = paramIfc.getActualTypeArguments(); |
||||
Class[] result = new Class[typeArgs.length]; |
||||
for (int i = 0; i < typeArgs.length; i++) { |
||||
Type arg = typeArgs[i]; |
||||
result[i] = extractClass(ownerClass, arg); |
||||
} |
||||
return result; |
||||
} else if (genericIfc.isAssignableFrom((Class) rawType)) { |
||||
return doResolveTypeArguments(ownerClass, (Class) rawType, genericIfc); |
||||
} |
||||
} else if (genericIfc.isAssignableFrom((Class) ifc)) { |
||||
return doResolveTypeArguments(ownerClass, (Class) ifc, genericIfc); |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
/** |
||||
* Extract a class instance from given Type. |
||||
*/ |
||||
private static Class extractClass(Class ownerClass, Type arg) { |
||||
if (arg instanceof ParameterizedType) { |
||||
return extractClass(ownerClass, ((ParameterizedType) arg).getRawType()); |
||||
} else if (arg instanceof GenericArrayType) { |
||||
GenericArrayType gat = (GenericArrayType) arg; |
||||
Type gt = gat.getGenericComponentType(); |
||||
Class<?> componentClass = extractClass(ownerClass, gt); |
||||
return Array.newInstance(componentClass, 0).getClass(); |
||||
} else if (arg instanceof TypeVariable) { |
||||
TypeVariable tv = (TypeVariable) arg; |
||||
arg = getTypeVariableMap(ownerClass).get(tv); |
||||
if (arg == null) { |
||||
arg = extractBoundForTypeVariable(tv); |
||||
} else { |
||||
arg = extractClass(ownerClass, arg); |
||||
} |
||||
} |
||||
return (arg instanceof Class ? (Class) arg : Object.class); |
||||
} |
||||
|
||||
/** |
||||
* Resolve the specified generic type against the given TypeVariable map. |
||||
* |
||||
* @param genericType |
||||
* the generic type to resolve |
||||
* @param typeVariableMap |
||||
* the TypeVariable Map to resolved against |
||||
* @return the type if it resolves to a Class, or <code>Object.class</code> |
||||
* otherwise |
||||
*/ |
||||
static Class resolveType(Type genericType, |
||||
Map<TypeVariable, Type> typeVariableMap) { |
||||
Type rawType = getRawType(genericType, typeVariableMap); |
||||
return (rawType instanceof Class ? (Class) rawType : Object.class); |
||||
} |
||||
|
||||
/** |
||||
* Determine the raw type for the given generic parameter type. |
||||
* |
||||
* @param genericType |
||||
* the generic type to resolve |
||||
* @param typeVariableMap |
||||
* the TypeVariable Map to resolved against |
||||
* @return the resolved raw type |
||||
*/ |
||||
static Type getRawType(Type genericType, |
||||
Map<TypeVariable, Type> typeVariableMap) { |
||||
Type resolvedType = genericType; |
||||
if (genericType instanceof TypeVariable) { |
||||
TypeVariable tv = (TypeVariable) genericType; |
||||
resolvedType = typeVariableMap.get(tv); |
||||
if (resolvedType == null) { |
||||
resolvedType = extractBoundForTypeVariable(tv); |
||||
} |
||||
} |
||||
if (resolvedType instanceof ParameterizedType) { |
||||
return ((ParameterizedType) resolvedType).getRawType(); |
||||
} else { |
||||
return resolvedType; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Build a mapping of {@link TypeVariable#getName TypeVariable names} to |
||||
* concrete {@link Class} for the specified {@link Class}. Searches all super |
||||
* types, enclosing types and interfaces. |
||||
*/ |
||||
static Map<TypeVariable, Type> getTypeVariableMap(Class clazz) { |
||||
Reference<Map<TypeVariable, Type>> ref = typeVariableCache.get(clazz); |
||||
Map<TypeVariable, Type> typeVariableMap = (ref != null ? ref.get() : null); |
||||
|
||||
if (typeVariableMap == null) { |
||||
typeVariableMap = new HashMap<TypeVariable, Type>(); |
||||
|
||||
// interfaces
|
||||
extractTypeVariablesFromGenericInterfaces(clazz.getGenericInterfaces(), |
||||
typeVariableMap); |
||||
|
||||
// super class
|
||||
Type genericType = clazz.getGenericSuperclass(); |
||||
Class type = clazz.getSuperclass(); |
||||
while (type != null && !Object.class.equals(type)) { |
||||
if (genericType instanceof ParameterizedType) { |
||||
ParameterizedType pt = (ParameterizedType) genericType; |
||||
populateTypeMapFromParameterizedType(pt, typeVariableMap); |
||||
} |
||||
extractTypeVariablesFromGenericInterfaces(type.getGenericInterfaces(), |
||||
typeVariableMap); |
||||
genericType = type.getGenericSuperclass(); |
||||
type = type.getSuperclass(); |
||||
} |
||||
|
||||
// enclosing class
|
||||
type = clazz; |
||||
while (type.isMemberClass()) { |
||||
genericType = type.getGenericSuperclass(); |
||||
if (genericType instanceof ParameterizedType) { |
||||
ParameterizedType pt = (ParameterizedType) genericType; |
||||
populateTypeMapFromParameterizedType(pt, typeVariableMap); |
||||
} |
||||
type = type.getEnclosingClass(); |
||||
} |
||||
|
||||
typeVariableCache.put(clazz, new WeakReference<Map<TypeVariable, Type>>( |
||||
typeVariableMap)); |
||||
} |
||||
|
||||
return typeVariableMap; |
||||
} |
||||
|
||||
/** |
||||
* Extracts the bound <code>Type</code> for a given {@link TypeVariable}. |
||||
*/ |
||||
static Type extractBoundForTypeVariable(TypeVariable typeVariable) { |
||||
Type[] bounds = typeVariable.getBounds(); |
||||
if (bounds.length == 0) { |
||||
return Object.class; |
||||
} |
||||
Type bound = bounds[0]; |
||||
if (bound instanceof TypeVariable) { |
||||
bound = extractBoundForTypeVariable((TypeVariable) bound); |
||||
} |
||||
return bound; |
||||
} |
||||
|
||||
private static void extractTypeVariablesFromGenericInterfaces( |
||||
Type[] genericInterfaces, Map<TypeVariable, Type> typeVariableMap) { |
||||
for (Type genericInterface : genericInterfaces) { |
||||
if (genericInterface instanceof ParameterizedType) { |
||||
ParameterizedType pt = (ParameterizedType) genericInterface; |
||||
populateTypeMapFromParameterizedType(pt, typeVariableMap); |
||||
if (pt.getRawType() instanceof Class) { |
||||
extractTypeVariablesFromGenericInterfaces( |
||||
((Class) pt.getRawType()).getGenericInterfaces(), typeVariableMap); |
||||
} |
||||
} else if (genericInterface instanceof Class) { |
||||
extractTypeVariablesFromGenericInterfaces( |
||||
((Class) genericInterface).getGenericInterfaces(), typeVariableMap); |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Read the {@link TypeVariable TypeVariables} from the supplied |
||||
* {@link ParameterizedType} and add mappings corresponding to the |
||||
* {@link TypeVariable#getName TypeVariable name} -> concrete type to the |
||||
* supplied {@link Map}. |
||||
* <p> |
||||
* Consider this case: |
||||
* |
||||
* <pre class="code> |
||||
* public interface Foo<S, T> { |
||||
* .. |
||||
* } |
||||
* |
||||
* public class FooImpl implements Foo<String, Integer> { |
||||
* .. |
||||
* } |
||||
* </pre> |
||||
* |
||||
* For '<code>FooImpl</code>' the following mappings would be added to the |
||||
* {@link Map}: {S=java.lang.String, T=java.lang.Integer}. |
||||
*/ |
||||
private static void populateTypeMapFromParameterizedType( |
||||
ParameterizedType type, Map<TypeVariable, Type> typeVariableMap) { |
||||
if (type.getRawType() instanceof Class) { |
||||
Type[] actualTypeArguments = type.getActualTypeArguments(); |
||||
TypeVariable[] typeVariables = ((Class) type.getRawType()) |
||||
.getTypeParameters(); |
||||
for (int i = 0; i < actualTypeArguments.length; i++) { |
||||
Type actualTypeArgument = actualTypeArguments[i]; |
||||
TypeVariable variable = typeVariables[i]; |
||||
if (actualTypeArgument instanceof Class) { |
||||
typeVariableMap.put(variable, actualTypeArgument); |
||||
} else if (actualTypeArgument instanceof GenericArrayType) { |
||||
typeVariableMap.put(variable, actualTypeArgument); |
||||
} else if (actualTypeArgument instanceof ParameterizedType) { |
||||
typeVariableMap.put(variable, actualTypeArgument); |
||||
} else if (actualTypeArgument instanceof TypeVariable) { |
||||
// We have a type that is parameterized at instantiation time
|
||||
// the nearest match on the bridge method will be the bounded type.
|
||||
TypeVariable typeVariableArgument = (TypeVariable) actualTypeArgument; |
||||
Type resolvedType = typeVariableMap.get(typeVariableArgument); |
||||
if (resolvedType == null) { |
||||
resolvedType = extractBoundForTypeVariable(typeVariableArgument); |
||||
} |
||||
typeVariableMap.put(variable, resolvedType); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,198 @@
@@ -0,0 +1,198 @@
|
||||
package org.springframework.data.util; |
||||
|
||||
import static org.springframework.util.ObjectUtils.*; |
||||
import java.lang.reflect.Field; |
||||
import java.lang.reflect.ParameterizedType; |
||||
import java.lang.reflect.Type; |
||||
import java.lang.reflect.TypeVariable; |
||||
import java.util.Map; |
||||
import java.util.concurrent.ConcurrentHashMap; |
||||
|
||||
import org.springframework.util.Assert; |
||||
import org.springframework.util.ReflectionUtils; |
||||
|
||||
/** |
||||
* Basic {@link TypeDiscoverer} that contains basic functionality to discover |
||||
* property types. |
||||
* |
||||
* @author Oliver Gierke |
||||
*/ |
||||
class TypeDiscoverer implements TypeInformation { |
||||
|
||||
private final Type type; |
||||
@SuppressWarnings("rawtypes") |
||||
private final Map<TypeVariable, Type> typeVariableMap; |
||||
private final Map<String, TypeInformation> fieldTypes = new ConcurrentHashMap<String, TypeInformation>(); |
||||
private final TypeDiscoverer parent; |
||||
|
||||
/** |
||||
* Creates a ne {@link TypeDiscoverer} for the given type, type variable map and parent. |
||||
* |
||||
* @param type must not be null. |
||||
* @param typeVariableMap |
||||
* @param parent |
||||
*/ |
||||
@SuppressWarnings("rawtypes") |
||||
protected TypeDiscoverer(Type type, Map<TypeVariable, Type> typeVariableMap, |
||||
TypeDiscoverer parent) { |
||||
|
||||
Assert.notNull(type); |
||||
this.type = type; |
||||
this.typeVariableMap = typeVariableMap; |
||||
this.parent = parent; |
||||
} |
||||
|
||||
/** |
||||
* Returns the type variable map. Will traverse the parents up to the root on |
||||
* and use it's map. |
||||
* |
||||
* @return |
||||
*/ |
||||
@SuppressWarnings("rawtypes") |
||||
private Map<TypeVariable, Type> getTypeVariableMap() { |
||||
|
||||
return parent != null ? parent.getTypeVariableMap() : typeVariableMap; |
||||
} |
||||
|
||||
/** |
||||
* Creates {@link TypeInformation} for the given {@link Type}. |
||||
* |
||||
* @param fieldType |
||||
* @return |
||||
*/ |
||||
private TypeInformation createInfo(Type fieldType) { |
||||
|
||||
if (fieldType instanceof ParameterizedType) { |
||||
ParameterizedType parameterizedType = (ParameterizedType) fieldType; |
||||
return new TypeDiscoverer(parameterizedType, null, this); |
||||
} |
||||
|
||||
if (fieldType instanceof TypeVariable) { |
||||
TypeVariable<?> variable = (TypeVariable<?>) fieldType; |
||||
return new TypeVariableTypeInformation(variable, type, this); |
||||
} |
||||
|
||||
if (fieldType instanceof Class) { |
||||
return new ClassTypeInformation((Class<?>) fieldType, this); |
||||
} |
||||
|
||||
throw new IllegalArgumentException(); |
||||
} |
||||
|
||||
/** |
||||
* Resolves the given type into a plain {@link Class}. |
||||
* |
||||
* @param type |
||||
* @return |
||||
*/ |
||||
protected Class<?> resolveType(Type type) { |
||||
|
||||
return GenericTypeResolver.resolveType(type, getTypeVariableMap()); |
||||
} |
||||
|
||||
/* |
||||
* (non-Javadoc) |
||||
* |
||||
* @see |
||||
* org.springframework.data.document.mongodb.TypeDiscovererTest.FieldInformation |
||||
* #getField(java.lang.String) |
||||
*/ |
||||
public TypeInformation getProperty(String fieldname) { |
||||
|
||||
int separatorIndex = fieldname.indexOf("."); |
||||
|
||||
if (separatorIndex == -1) { |
||||
if (fieldTypes.containsKey(fieldname)) { |
||||
return fieldTypes.get(fieldname); |
||||
} |
||||
|
||||
TypeInformation propertyInformation = getPropertyInformation(fieldname); |
||||
fieldTypes.put(fieldname, propertyInformation); |
||||
return propertyInformation; |
||||
} |
||||
|
||||
String head = fieldname.substring(0, separatorIndex); |
||||
TypeInformation info = fieldTypes.get(head); |
||||
return info.getProperty(fieldname.substring(separatorIndex + 1)); |
||||
} |
||||
|
||||
private TypeInformation getPropertyInformation(String fieldname) { |
||||
|
||||
Field field = ReflectionUtils.findField(getType(), fieldname); |
||||
|
||||
if (field == null) { |
||||
return null; |
||||
} |
||||
|
||||
return createInfo(field.getGenericType()); |
||||
} |
||||
|
||||
/* |
||||
* (non-Javadoc) |
||||
* |
||||
* @see |
||||
* org.springframework.data.document.mongodb.TypeDiscovererTest.FieldInformation |
||||
* #getType() |
||||
*/ |
||||
public Class<?> getType() { |
||||
return resolveType(type); |
||||
} |
||||
|
||||
/* (non-Javadoc) |
||||
* @see org.springframework.data.util.TypeInformation#getMapValueType() |
||||
*/ |
||||
@Override |
||||
public Class<?> getMapValueType() { |
||||
|
||||
if (!Map.class.isAssignableFrom(getType())) { |
||||
return null; |
||||
} |
||||
|
||||
ParameterizedType parameterizedType = (ParameterizedType) type; |
||||
return createInfo(parameterizedType.getActualTypeArguments()[1]).getType(); |
||||
} |
||||
|
||||
/* |
||||
* (non-Javadoc) |
||||
* |
||||
* @see java.lang.Object#equals(java.lang.Object) |
||||
*/ |
||||
@Override |
||||
public boolean equals(Object obj) { |
||||
if (obj == this) { |
||||
return true; |
||||
} |
||||
|
||||
if (obj == null) { |
||||
return false; |
||||
} |
||||
|
||||
if (!this.getClass().equals(obj.getClass())) { |
||||
return false; |
||||
} |
||||
|
||||
TypeDiscoverer that = (TypeDiscoverer) obj; |
||||
|
||||
boolean typeEqual = nullSafeEquals(this.type, that.type); |
||||
boolean typeVariableMapEqual = nullSafeEquals(this.typeVariableMap, |
||||
that.typeVariableMap); |
||||
boolean parentEqual = nullSafeEquals(this.parent, that.parent); |
||||
|
||||
return typeEqual && typeVariableMapEqual && parentEqual; |
||||
} |
||||
|
||||
/* |
||||
* (non-Javadoc) |
||||
* |
||||
* @see java.lang.Object#hashCode() |
||||
*/ |
||||
@Override |
||||
public int hashCode() { |
||||
|
||||
int result = 17; |
||||
result += nullSafeHashCode(type); |
||||
result += nullSafeHashCode(typeVariableMap); |
||||
result += nullSafeHashCode(parent); |
||||
return result; |
||||
} |
||||
} |
||||
@ -0,0 +1,38 @@
@@ -0,0 +1,38 @@
|
||||
package org.springframework.data.util; |
||||
|
||||
import java.util.Map; |
||||
|
||||
/** |
||||
* Interface to access property types and resolving generics on the way. |
||||
* Starting with a {@link ClassTypeInformation} you can travers properties using |
||||
* {@link #getProperty(String)} to access type information. |
||||
* |
||||
* @author Oliver Gierke |
||||
*/ |
||||
public interface TypeInformation { |
||||
|
||||
/** |
||||
* Returns the property information for the property with the given name. |
||||
* Supports proeprty traversal through dot notation. |
||||
* |
||||
* @param fieldname |
||||
* @return |
||||
*/ |
||||
TypeInformation getProperty(String fieldname); |
||||
|
||||
|
||||
/** |
||||
* Will return the type of the value in case the underlying type is a {@link Map}. |
||||
* |
||||
* @return |
||||
*/ |
||||
Class<?> getMapValueType(); |
||||
|
||||
/** |
||||
* Returns the type of the property. Will resolve generics and the generic |
||||
* context of |
||||
* |
||||
* @return |
||||
*/ |
||||
Class<?> getType(); |
||||
} |
||||
@ -0,0 +1,102 @@
@@ -0,0 +1,102 @@
|
||||
package org.springframework.data.util; |
||||
|
||||
import static org.springframework.util.ObjectUtils.*; |
||||
import java.lang.reflect.ParameterizedType; |
||||
import java.lang.reflect.Type; |
||||
import java.lang.reflect.TypeVariable; |
||||
|
||||
import org.springframework.util.Assert; |
||||
|
||||
/** |
||||
* Special {@link TypeDiscoverer} to determine the actual type for a {@link TypeVariable}. Will consider the |
||||
* context the {@link TypeVariable} is being used in. |
||||
* |
||||
* @author Oliver Gierke |
||||
*/ |
||||
class TypeVariableTypeInformation extends TypeDiscoverer { |
||||
|
||||
private final TypeVariable<?> variable; |
||||
private final Type owningType; |
||||
|
||||
/** |
||||
* Creates a bew {@link TypeVariableTypeInformation} for the given {@link TypeVariable} owning {@link Type} and |
||||
* parent {@link TypeDiscoverer}. |
||||
* |
||||
* @param variable must not be {@literal null} |
||||
* @param owningType must not be {@literal null} |
||||
* @param parent |
||||
*/ |
||||
public TypeVariableTypeInformation(TypeVariable<?> variable, Type owningType, TypeDiscoverer parent) { |
||||
|
||||
super(variable, null, parent); |
||||
Assert.notNull(variable); |
||||
this.variable = variable; |
||||
this.owningType = owningType; |
||||
} |
||||
|
||||
/* |
||||
* (non-Javadoc) |
||||
* |
||||
* @see org.springframework.data.document.mongodb.TypeDiscovererTest.TypeDiscoverer#getType() |
||||
*/ |
||||
@Override |
||||
public Class<?> getType() { |
||||
|
||||
int index = getIndex(variable); |
||||
|
||||
if (owningType instanceof ParameterizedType && index != -1) { |
||||
Type fieldType = ((ParameterizedType) owningType).getActualTypeArguments()[index]; |
||||
return resolveType(fieldType); |
||||
} |
||||
|
||||
return resolveType(variable); |
||||
} |
||||
|
||||
/** |
||||
* Returns the index of the type parameter binding the given {@link TypeVariable}. |
||||
* @param variable |
||||
* @return |
||||
*/ |
||||
private int getIndex(TypeVariable<?> variable) { |
||||
|
||||
Class<?> rawType = resolveType(owningType); |
||||
TypeVariable<?>[] typeParameters = rawType.getTypeParameters(); |
||||
|
||||
for (int i = 0; i < typeParameters.length; i++) { |
||||
if (variable.equals(typeParameters[i])) { |
||||
return i; |
||||
} |
||||
} |
||||
|
||||
return -1; |
||||
} |
||||
|
||||
/* |
||||
* (non-Javadoc) |
||||
* |
||||
* @see org.springframework.data.util.TypeDiscoverer#equals(java.lang.Object) |
||||
*/ |
||||
@Override |
||||
public boolean equals(Object obj) { |
||||
if (!super.equals(obj)) { |
||||
return false; |
||||
} |
||||
|
||||
TypeVariableTypeInformation that = (TypeVariableTypeInformation) obj; |
||||
return nullSafeEquals(this.owningType, that.owningType) |
||||
&& nullSafeEquals(this.variable, that.variable); |
||||
} |
||||
|
||||
/* |
||||
* (non-Javadoc) |
||||
* |
||||
* @see org.springframework.data.util.TypeDiscoverer#hashCode() |
||||
*/ |
||||
@Override |
||||
public int hashCode() { |
||||
int result = super.hashCode(); |
||||
result += 31 * nullSafeHashCode(this.owningType); |
||||
result += 31 * nullSafeHashCode(this.variable); |
||||
return result; |
||||
} |
||||
} |
||||
@ -0,0 +1,146 @@
@@ -0,0 +1,146 @@
|
||||
package org.springframework.data.util; |
||||
|
||||
import static org.hamcrest.CoreMatchers.*; |
||||
import static org.junit.Assert.*; |
||||
|
||||
import java.util.Calendar; |
||||
import java.util.Map; |
||||
|
||||
import org.junit.Test; |
||||
import org.springframework.data.mapping.Person; |
||||
|
||||
/** |
||||
* @author Oliver Gierke |
||||
*/ |
||||
public class ClassTypeInformationUnitTests { |
||||
|
||||
@Test |
||||
public void discoversTypeForSimpleGenericField() { |
||||
|
||||
TypeInformation discoverer = new ClassTypeInformation( |
||||
ConcreteType.class); |
||||
assertEquals(ConcreteType.class, discoverer.getType()); |
||||
assertEquals(String.class, discoverer.getProperty("content").getType()); |
||||
} |
||||
|
||||
@Test |
||||
public void discoversTypeForNestedGenericField() { |
||||
|
||||
TypeInformation discoverer = new ClassTypeInformation( |
||||
ConcreteWrapper.class); |
||||
assertEquals(ConcreteWrapper.class, discoverer.getType()); |
||||
TypeInformation wrapper = discoverer.getProperty("wrapped"); |
||||
assertEquals(GenericType.class, wrapper.getType()); |
||||
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()); |
||||
} |
||||
|
||||
@Test |
||||
public void discoversBoundType() { |
||||
|
||||
ClassTypeInformation information = new ClassTypeInformation( |
||||
GenericTypeWithBound.class); |
||||
assertEquals(Person.class, information.getProperty("person").getType()); |
||||
} |
||||
|
||||
@Test |
||||
public void discoversBoundTypeForSpecialization() { |
||||
|
||||
ClassTypeInformation information = new ClassTypeInformation( |
||||
SpecialGenericTypeWithBound.class); |
||||
assertEquals(SpecialPerson.class, information.getProperty("person") |
||||
.getType()); |
||||
} |
||||
|
||||
@Test |
||||
public void discoversBoundTypeForNested() { |
||||
|
||||
ClassTypeInformation information = new ClassTypeInformation( |
||||
AnotherGenericType.class); |
||||
assertEquals(GenericTypeWithBound.class, information.getProperty("nested") |
||||
.getType()); |
||||
assertEquals(Person.class, information.getProperty("nested.person") |
||||
.getType()); |
||||
} |
||||
|
||||
@Test |
||||
public void discoversArrays() { |
||||
ClassTypeInformation information = new ClassTypeInformation(CollectionContainer.class); |
||||
Class<?> type = information.getProperty("array").getType(); |
||||
assertEquals(String[].class, type); |
||||
assertThat(type.isArray(), is(true)); |
||||
} |
||||
|
||||
@Test |
||||
public void discoversMapValueType() { |
||||
|
||||
ClassTypeInformation information = new ClassTypeInformation(StringMapContainer.class); |
||||
TypeInformation genericMap = information.getProperty("genericMap"); |
||||
assertEquals(Map.class, genericMap.getType()); |
||||
assertEquals(String.class, genericMap.getMapValueType()); |
||||
|
||||
TypeInformation map = information.getProperty("map"); |
||||
assertEquals(Map.class, map.getType()); |
||||
assertEquals(Calendar.class, map.getMapValueType()); |
||||
} |
||||
|
||||
private class StringMapContainer extends MapContainer<String> { |
||||
|
||||
} |
||||
|
||||
private class MapContainer<T> { |
||||
Map<String, T> genericMap; |
||||
Map<String, Calendar> map; |
||||
} |
||||
|
||||
private class CollectionContainer { |
||||
|
||||
String[] array; |
||||
} |
||||
|
||||
private class GenericTypeWithBound<T extends Person> { |
||||
|
||||
T person; |
||||
} |
||||
|
||||
private class AnotherGenericType<T extends Person, S extends GenericTypeWithBound<T>> { |
||||
|
||||
S nested; |
||||
} |
||||
|
||||
private class SpecialGenericTypeWithBound extends |
||||
GenericTypeWithBound<SpecialPerson> { |
||||
|
||||
} |
||||
|
||||
private abstract class SpecialPerson extends Person { |
||||
|
||||
protected SpecialPerson(Integer ssn, String firstName, String lastName) { |
||||
super(ssn, firstName, lastName); |
||||
} |
||||
} |
||||
|
||||
private class GenericType<T, S> { |
||||
|
||||
Long index; |
||||
T content; |
||||
} |
||||
|
||||
private class ConcreteType extends GenericType<String, Object> { |
||||
|
||||
} |
||||
|
||||
private class GenericWrapper<S> { |
||||
|
||||
GenericType<S, Object> wrapped; |
||||
} |
||||
|
||||
private class ConcreteWrapper extends GenericWrapper<String> { |
||||
|
||||
} |
||||
} |
||||
@ -0,0 +1,16 @@
@@ -0,0 +1,16 @@
|
||||
package org.springframework.data.util; |
||||
|
||||
import org.junit.Test; |
||||
|
||||
/** |
||||
* Unit tests for {@link TypeDiscoverer}. |
||||
* |
||||
* @author Oliver Gierke |
||||
*/ |
||||
public class TypeDiscovererUnitTests { |
||||
|
||||
@Test(expected = IllegalArgumentException.class) |
||||
public void rejectsNullType() { |
||||
new TypeDiscoverer(null, null, null); |
||||
} |
||||
} |
||||
Loading…
Reference in new issue