Browse Source

Extended basic mapping framework to support working with generics.

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
Oliver Gierke 15 years ago
parent
commit
ad56cae06e
  1. 17
      spring-data-commons-core/src/main/java/org/springframework/data/mapping/BasicMappingConfigurationBuilder.java
  2. 136
      spring-data-commons-core/src/main/java/org/springframework/data/mapping/BasicMappingContext.java
  3. 34
      spring-data-commons-core/src/main/java/org/springframework/data/mapping/BasicPersistentEntity.java
  4. 46
      spring-data-commons-core/src/main/java/org/springframework/data/mapping/BasicPersistentProperty.java
  5. 6
      spring-data-commons-core/src/main/java/org/springframework/data/mapping/MappingBeanHelper.java
  6. 6
      spring-data-commons-core/src/main/java/org/springframework/data/mapping/PropertyHandler.java
  7. 14
      spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/Association.java
  8. 9
      spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/MappingConfigurationBuilder.java
  9. 13
      spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/MappingContext.java
  10. 13
      spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/PersistentEntity.java
  11. 11
      spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/PersistentProperty.java
  12. 79
      spring-data-commons-core/src/main/java/org/springframework/data/util/ClassTypeInformation.java
  13. 355
      spring-data-commons-core/src/main/java/org/springframework/data/util/GenericTypeResolver.java
  14. 198
      spring-data-commons-core/src/main/java/org/springframework/data/util/TypeDiscoverer.java
  15. 38
      spring-data-commons-core/src/main/java/org/springframework/data/util/TypeInformation.java
  16. 102
      spring-data-commons-core/src/main/java/org/springframework/data/util/TypeVariableTypeInformation.java
  17. 146
      spring-data-commons-core/src/test/java/org/springframework/data/util/ClassTypeInformationUnitTests.java
  18. 16
      spring-data-commons-core/src/test/java/org/springframework/data/util/TypeDiscovererUnitTests.java

17
spring-data-commons-core/src/main/java/org/springframework/data/mapping/BasicMappingConfigurationBuilder.java

@ -22,6 +22,7 @@ import org.springframework.beans.factory.annotation.Value; @@ -22,6 +22,7 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.data.annotation.*;
import org.springframework.data.mapping.model.*;
import org.springframework.data.util.TypeInformation;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
@ -29,6 +30,9 @@ import java.beans.Introspector; @@ -29,6 +30,9 @@ import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
@ -37,6 +41,7 @@ import java.util.concurrent.ConcurrentMap; @@ -37,6 +41,7 @@ import java.util.concurrent.ConcurrentMap;
*/
public class BasicMappingConfigurationBuilder implements MappingConfigurationBuilder {
private static final Set<String> UNMAPPED_FIELDS = new HashSet<String>(Arrays.asList("class", "this$0"));
protected static ConcurrentMap<Class<?>, BeanInfo> beanInfo = new ConcurrentHashMap<Class<?>, BeanInfo>();
protected Logger log = LoggerFactory.getLogger(getClass());
@ -60,21 +65,21 @@ public class BasicMappingConfigurationBuilder implements MappingConfigurationBui @@ -60,21 +65,21 @@ public class BasicMappingConfigurationBuilder implements MappingConfigurationBui
}
@Override
public <T> PersistentEntity<T> createPersistentEntity(Class<T> type, MappingContext mappingContext) throws MappingConfigurationException {
return new BasicPersistentEntity<T>(mappingContext, type);
public <T> PersistentEntity<T> createPersistentEntity(TypeInformation typeInformation, MappingContext mappingContext) throws MappingConfigurationException {
return new BasicPersistentEntity<T>(mappingContext, typeInformation);
}
@Override
public boolean isPersistentProperty(Field field, PropertyDescriptor descriptor) throws MappingConfigurationException {
if ("class".equals(field.getName()) || isTransient(field)) {
if (UNMAPPED_FIELDS.contains(field.getName()) || isTransient(field)) {
return false;
}
return true;
}
@Override
public <T> PersistentProperty<T> createPersistentProperty(Field field, PropertyDescriptor descriptor, Class<T> type) throws MappingConfigurationException {
return new BasicPersistentProperty<T>(field.getName(), type, field, descriptor);
public PersistentProperty createPersistentProperty(Field field, PropertyDescriptor descriptor, TypeInformation information) throws MappingConfigurationException {
return new BasicPersistentProperty(field, descriptor, information);
}
@SuppressWarnings({"unchecked"})
@ -148,7 +153,7 @@ public class BasicMappingConfigurationBuilder implements MappingConfigurationBui @@ -148,7 +153,7 @@ public class BasicMappingConfigurationBuilder implements MappingConfigurationBui
}
@Override
public Association createAssociation(PersistentProperty<?> property) {
public Association createAssociation(PersistentProperty property) {
// Only support uni-directional associations in the Basic configuration
Association association = new Association(property, null);
property.setAssociation(association);

136
spring-data-commons-core/src/main/java/org/springframework/data/mapping/BasicMappingContext.java

@ -25,6 +25,8 @@ import org.springframework.core.convert.converter.ConverterRegistry; @@ -25,6 +25,8 @@ import org.springframework.core.convert.converter.ConverterRegistry;
import org.springframework.core.convert.support.ConversionServiceFactory;
import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.data.mapping.model.*;
import org.springframework.data.util.ClassTypeInformation;
import org.springframework.data.util.TypeInformation;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.ReflectionUtils.FieldCallback;
@ -47,7 +49,7 @@ public class BasicMappingContext implements MappingContext, InitializingBean { @@ -47,7 +49,7 @@ public class BasicMappingContext implements MappingContext, InitializingBean {
protected Logger log = LoggerFactory.getLogger(getClass());
protected MappingConfigurationBuilder builder;
protected ConcurrentMap<String, PersistentEntity<?>> persistentEntities = new ConcurrentHashMap<String, PersistentEntity<?>>();
protected ConcurrentMap<TypeInformation, PersistentEntity<?>> persistentEntities = new ConcurrentHashMap<TypeInformation, PersistentEntity<?>>();
protected ConcurrentMap<PersistentEntity<?>, List<Validator>> validators = new ConcurrentHashMap<PersistentEntity<?>, List<Validator>>();
protected ConcurrentSkipListSet<Listener> listeners = new ConcurrentSkipListSet<Listener>();
protected GenericConversionService conversionService = ConversionServiceFactory.createDefaultConversionService();
@ -70,80 +72,100 @@ public class BasicMappingContext implements MappingContext, InitializingBean { @@ -70,80 +72,100 @@ public class BasicMappingContext implements MappingContext, InitializingBean {
return persistentEntities.values();
}
@SuppressWarnings({"unchecked"})
/* (non-Javadoc)
* @see org.springframework.data.mapping.model.MappingContext#getPersistentEntity(java.lang.Class)
*/
@Override
public <T> PersistentEntity<T> getPersistentEntity(Class<T> type) {
return (PersistentEntity<T>) persistentEntities.get(type.getName());
return getPersistentEntity(new ClassTypeInformation(type));
}
@SuppressWarnings({"unchecked"})
@Override
public PersistentEntity<?> getPersistentEntity(String name) {
return persistentEntities.get(name);
}
public <T> PersistentEntity<T> getPersistentEntity(TypeInformation type) {
return (PersistentEntity<T>) persistentEntities.get(type);
}
@SuppressWarnings("unchecked")
public <T> PersistentEntity<T> addPersistentEntity(TypeInformation typeInformation) {
PersistentEntity<?> persistentEntity = persistentEntities.get(typeInformation);
if (persistentEntity != null) {
return (PersistentEntity<T>) persistentEntity;
}
Class<T> type = (Class<T>) typeInformation.getType();
try {
final PersistentEntity<T> entity = builder.createPersistentEntity(typeInformation, this);
BeanInfo info = Introspector.getBeanInfo(type);
final Map<String, PropertyDescriptor> descriptors = new HashMap<String, PropertyDescriptor>();
for (PropertyDescriptor descriptor : info.getPropertyDescriptors()) {
descriptors.put(descriptor.getName(), descriptor);
}
@Override
public <T> PersistentEntity<T> addPersistentEntity(Class<T> type) {
if (null == persistentEntities.get(type.getName())) {
try {
final PersistentEntity<T> entity = builder.createPersistentEntity(type, this);
BeanInfo info = Introspector.getBeanInfo(type);
final Map<String, PropertyDescriptor> descriptors = new HashMap<String, PropertyDescriptor>();
for (PropertyDescriptor descriptor : info.getPropertyDescriptors()) {
descriptors.put(descriptor.getName(), descriptor);
}
ReflectionUtils.doWithFields(type, new FieldCallback() {
@Override
public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
try {
PropertyDescriptor descriptor = descriptors.get(field.getName());
if (builder.isPersistentProperty(field, descriptor)) {
ReflectionUtils.makeAccessible(field);
PersistentProperty property = builder.createPersistentProperty(field, descriptor, entity.getPropertyInformation());
property.setOwner(entity);
entity.addPersistentProperty(property);
if (builder.isAssociation(field, descriptor)) {
Association association = builder.createAssociation(property);
entity.addAssociation(association);
}
ReflectionUtils.doWithFields(type, new FieldCallback() {
@Override
public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
try {
PropertyDescriptor descriptor = descriptors.get(field.getName());
if (builder.isPersistentProperty(field, descriptor)) {
ReflectionUtils.makeAccessible(field);
PersistentProperty<?> property = builder.createPersistentProperty(field, descriptor, field.getType());
property.setOwner(entity);
entity.addPersistentProperty(property);
if (builder.isAssociation(field, descriptor)) {
Association association = builder.createAssociation(property);
entity.addAssociation(association);
}
if (property.isIdProperty()) {
entity.setIdProperty(property);
}
if (property.isIdProperty()) {
entity.setIdProperty(property);
}
if (property.isComplexType() && !property.isTransient()) {
addPersistentEntity(property.getTypeInformation());
}
} catch (MappingConfigurationException e) {
log.error(e.getMessage(), e);
}
} catch (MappingConfigurationException e) {
log.error(e.getMessage(), e);
}
});
}
});
entity.setPreferredConstructor(builder.getPreferredConstructor(type));
entity.setPreferredConstructor(builder.getPreferredConstructor(type));
// Inform listeners
List<Listener> listenersToRemove = new ArrayList<Listener>();
for (Listener listener : listeners) {
if (!listener.persistentEntityAdded(entity)) {
listenersToRemove.add(listener);
}
}
for (Listener listener : listenersToRemove) {
listeners.remove(listener);
// Inform listeners
List<Listener> listenersToRemove = new ArrayList<Listener>();
for (Listener listener : listeners) {
if (!listener.persistentEntityAdded(entity)) {
listenersToRemove.add(listener);
}
}
for (Listener listener : listenersToRemove) {
listeners.remove(listener);
}
persistentEntities.put(type.getName(), entity);
persistentEntities.put(entity.getPropertyInformation(), entity);
return entity;
} catch (MappingConfigurationException e) {
log.error(e.getMessage(), e);
} catch (IntrospectionException e) {
throw new MappingException(e.getMessage(), e);
}
return entity;
} catch (MappingConfigurationException e) {
log.error(e.getMessage(), e);
} catch (IntrospectionException e) {
throw new MappingException(e.getMessage(), e);
}
return null;
}
@Override
public <T> PersistentEntity<T> addPersistentEntity(Class<T> type) {
return addPersistentEntity(new ClassTypeInformation(type));
}
@Override
public void addEntityValidator(PersistentEntity<?> entity, Validator validator) {
List<Validator> v = validators.get(entity);

34
spring-data-commons-core/src/main/java/org/springframework/data/mapping/BasicPersistentEntity.java

@ -17,6 +17,8 @@ @@ -17,6 +17,8 @@
package org.springframework.data.mapping;
import org.springframework.data.mapping.model.*;
import org.springframework.data.util.ClassTypeInformation;
import org.springframework.data.util.TypeInformation;
import java.util.Collection;
import java.util.HashMap;
@ -29,15 +31,18 @@ public class BasicPersistentEntity<T> implements PersistentEntity<T> { @@ -29,15 +31,18 @@ public class BasicPersistentEntity<T> implements PersistentEntity<T> {
protected final Class<T> type;
protected PreferredConstructor<T> preferredConstructor;
protected PersistentProperty<?> idProperty;
protected Map<String, PersistentProperty<?>> persistentProperties = new HashMap<String, PersistentProperty<?>>();
protected PersistentProperty idProperty;
protected Map<String, PersistentProperty> persistentProperties = new HashMap<String, PersistentProperty>();
protected Map<String, Association> associations = new HashMap<String, Association>();
protected final TypeInformation information;
protected MappingContext mappingContext;
public BasicPersistentEntity(MappingContext mappingContext, Class<T> type) {
public BasicPersistentEntity(MappingContext mappingContext, TypeInformation information) {
this.mappingContext = mappingContext;
this.type = type;
this.type = (Class<T>) information.getType();
this.information = information;
}
@Override
@ -56,22 +61,22 @@ public class BasicPersistentEntity<T> implements PersistentEntity<T> { @@ -56,22 +61,22 @@ public class BasicPersistentEntity<T> implements PersistentEntity<T> {
}
@Override
public PersistentProperty<?> getIdProperty() {
public PersistentProperty getIdProperty() {
return idProperty;
}
@Override
public void setIdProperty(PersistentProperty<?> property) {
public void setIdProperty(PersistentProperty property) {
idProperty = property;
}
@Override
public Collection<PersistentProperty<?>> getPersistentProperties() {
public Collection<PersistentProperty> getPersistentProperties() {
return persistentProperties.values();
}
@Override
public void addPersistentProperty(PersistentProperty<?> property) {
public void addPersistentProperty(PersistentProperty property) {
persistentProperties.put(property.getName(), property);
}
@ -85,7 +90,7 @@ public class BasicPersistentEntity<T> implements PersistentEntity<T> { @@ -85,7 +90,7 @@ public class BasicPersistentEntity<T> implements PersistentEntity<T> {
associations.put(association.getInverse().getName(), association);
}
public PersistentProperty<?> getPersistentProperty(String name) {
public PersistentProperty getPersistentProperty(String name) {
return persistentProperties.get(name);
}
@ -93,6 +98,11 @@ public class BasicPersistentEntity<T> implements PersistentEntity<T> { @@ -93,6 +98,11 @@ public class BasicPersistentEntity<T> implements PersistentEntity<T> {
public Class<T> getType() {
return type;
}
@Override
public TypeInformation getPropertyInformation() {
return information;
}
@Override
public Collection<String> getPersistentPropertyNames() {
@ -111,8 +121,8 @@ public class BasicPersistentEntity<T> implements PersistentEntity<T> { @@ -111,8 +121,8 @@ public class BasicPersistentEntity<T> implements PersistentEntity<T> {
@Override
public void doWithProperties(PropertyHandler handler) {
for (PersistentProperty<?> property : persistentProperties.values()) {
if (!property.isTransient() && !property.isAssociation()) {
for (PersistentProperty property : persistentProperties.values()) {
if (!property.isTransient() && !property.isAssociation() && !property.isIdProperty()) {
handler.doWithPersistentProperty(property);
}
}

46
spring-data-commons-core/src/main/java/org/springframework/data/mapping/BasicPersistentProperty.java

@ -23,6 +23,7 @@ import org.springframework.data.annotation.Transient; @@ -23,6 +23,7 @@ import org.springframework.data.annotation.Transient;
import org.springframework.data.mapping.model.Association;
import org.springframework.data.mapping.model.PersistentEntity;
import org.springframework.data.mapping.model.PersistentProperty;
import org.springframework.data.util.TypeInformation;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
@ -31,27 +32,25 @@ import java.lang.reflect.ParameterizedType; @@ -31,27 +32,25 @@ import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
* @author Jon Brisbin <jbrisbin@vmware.com>
*/
public class BasicPersistentProperty<T> implements PersistentProperty<T> {
public class BasicPersistentProperty implements PersistentProperty {
protected final String name;
protected final Class<T> type;
protected final PropertyDescriptor propertyDescriptor;
protected final TypeInformation information;
protected final Field field;
protected Association association;
protected Value value;
protected boolean isTransient = false;
protected PersistentEntity<?> owner;
public BasicPersistentProperty(String name,
Class<T> type,
Field field,
PropertyDescriptor propertyDescriptor) {
this.name = name;
this.type = type;
public BasicPersistentProperty(Field field, PropertyDescriptor propertyDescriptor, TypeInformation information) {
this.name = field.getName();
this.information = information.getProperty(this.name);
this.propertyDescriptor = propertyDescriptor;
this.field = field;
this.isTransient = Modifier.isTransient(field.getModifiers()) || field.isAnnotationPresent(Transient.class);
@ -83,8 +82,13 @@ public class BasicPersistentProperty<T> implements PersistentProperty<T> { @@ -83,8 +82,13 @@ public class BasicPersistentProperty<T> implements PersistentProperty<T> {
}
@Override
public Class<T> getType() {
return type;
public Class<?> getType() {
return information.getType();
}
@Override
public TypeInformation getTypeInformation() {
return information;
}
@Override
@ -124,15 +128,29 @@ public class BasicPersistentProperty<T> implements PersistentProperty<T> { @@ -124,15 +128,29 @@ public class BasicPersistentProperty<T> implements PersistentProperty<T> {
@Override
public boolean isCollection() {
return type.isAssignableFrom(Collection.class) || type.isAssignableFrom(List.class);
return getType().isAssignableFrom(Collection.class) || getType().isAssignableFrom(List.class) || isArray();
}
@Override
public boolean isMap() {
return getType().isAssignableFrom(Map.class);
}
/* (non-Javadoc)
* @see org.springframework.data.mapping.model.PersistentProperty#isArray()
*/
@Override
public boolean isArray() {
return getType().isArray();
}
@Override
public boolean isComplexType() {
if (isCollection() || type.isArray()) {
if (isCollection() || isArray()) {
return !MappingBeanHelper.isSimpleType(getComponentType());
} else {
return !MappingBeanHelper.isSimpleType(field.getType());
return !MappingBeanHelper.isSimpleType(getType());
}
}
@ -149,7 +167,7 @@ public class BasicPersistentProperty<T> implements PersistentProperty<T> { @@ -149,7 +167,7 @@ public class BasicPersistentProperty<T> implements PersistentProperty<T> {
}
}
}
return type.getComponentType();
return getType().getComponentType();
}
@Override

6
spring-data-commons-core/src/main/java/org/springframework/data/mapping/MappingBeanHelper.java

@ -137,14 +137,14 @@ public abstract class MappingBeanHelper { @@ -137,14 +137,14 @@ public abstract class MappingBeanHelper {
}
public static void setProperty(Object on,
PersistentProperty<?> property,
PersistentProperty property,
Object value)
throws IllegalAccessException, InvocationTargetException {
setProperty(on, property, value, false);
}
public static void setProperty(Object on,
PersistentProperty<?> property,
PersistentProperty property,
Object value,
boolean fieldAccessOnly)
throws IllegalAccessException, InvocationTargetException {
@ -170,7 +170,7 @@ public abstract class MappingBeanHelper { @@ -170,7 +170,7 @@ public abstract class MappingBeanHelper {
@SuppressWarnings({"unchecked"})
public static <T> T getProperty(Object from,
PersistentProperty<?> property,
PersistentProperty property,
Class<T> type,
boolean fieldAccessOnly)
throws IllegalAccessException, InvocationTargetException {

6
spring-data-commons-core/src/main/java/org/springframework/data/mapping/PropertyHandler.java

@ -19,8 +19,12 @@ package org.springframework.data.mapping; @@ -19,8 +19,12 @@ package org.springframework.data.mapping;
import org.springframework.data.mapping.model.PersistentProperty;
/**
* Callback interface to do something with all plain {@link PersistentProperty}
* instances <em>except</em> associations, transient properties and the id-property.
*
* @author Jon Brisbin <jbrisbin@vmware.com>
*/
public interface PropertyHandler {
void doWithPersistentProperty(PersistentProperty<?> persistentProperty);
void doWithPersistentProperty(PersistentProperty persistentProperty);
}

14
spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/Association.java

@ -21,27 +21,27 @@ package org.springframework.data.mapping.model; @@ -21,27 +21,27 @@ package org.springframework.data.mapping.model;
*/
public class Association {
protected PersistentProperty<?> inverse;
protected PersistentProperty<?> obverse;
protected PersistentProperty inverse;
protected PersistentProperty obverse;
public Association(PersistentProperty<?> inverse, PersistentProperty<?> obverse) {
public Association(PersistentProperty inverse, PersistentProperty obverse) {
this.inverse = inverse;
this.obverse = obverse;
}
public PersistentProperty<?> getInverse() {
public PersistentProperty getInverse() {
return inverse;
}
public void setInverse(PersistentProperty<?> inverse) {
public void setInverse(PersistentProperty inverse) {
this.inverse = inverse;
}
public PersistentProperty<?> getObverse() {
public PersistentProperty getObverse() {
return obverse;
}
public void setObverse(PersistentProperty<?> obverse) {
public void setObverse(PersistentProperty obverse) {
this.obverse = obverse;
}
}

9
spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/MappingConfigurationBuilder.java

@ -19,6 +19,9 @@ package org.springframework.data.mapping.model; @@ -19,6 +19,9 @@ package org.springframework.data.mapping.model;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import org.springframework.data.mapping.BasicPersistentEntity;
import org.springframework.data.util.TypeInformation;
/**
* @author Jon Brisbin <jbrisbin@vmware.com>
*/
@ -26,16 +29,16 @@ public interface MappingConfigurationBuilder { @@ -26,16 +29,16 @@ public interface MappingConfigurationBuilder {
<T> boolean isPersistentEntity(Class<T> clazz);
<T> PersistentEntity<T> createPersistentEntity(Class<T> clazz, MappingContext mappingContext) throws MappingConfigurationException;
<T> PersistentEntity<T> createPersistentEntity(TypeInformation typeInformation, MappingContext mappingContext) throws MappingConfigurationException;
boolean isPersistentProperty(Field field, PropertyDescriptor descriptor) throws MappingConfigurationException;
<T> PersistentProperty<T> createPersistentProperty(Field field, PropertyDescriptor descriptor, Class<T> type) throws MappingConfigurationException;
PersistentProperty createPersistentProperty(Field field, PropertyDescriptor descriptor, TypeInformation owningTypeInformation) throws MappingConfigurationException;
<T> PreferredConstructor<T> getPreferredConstructor(Class<T> clazz) throws MappingConfigurationException;
boolean isAssociation(Field field, PropertyDescriptor descriptor) throws MappingConfigurationException;
Association createAssociation(PersistentProperty<?> property);
Association createAssociation(PersistentProperty property);
}

13
spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/MappingContext.java

@ -18,6 +18,7 @@ import org.springframework.beans.factory.InitializingBean; @@ -18,6 +18,7 @@ import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.converter.ConverterRegistry;
import org.springframework.data.util.TypeInformation;
import org.springframework.validation.Validator;
import java.util.Collection;
@ -46,17 +47,11 @@ public interface MappingContext extends InitializingBean { @@ -46,17 +47,11 @@ public interface MappingContext extends InitializingBean {
* @return A list of PersistentEntity instances
*/
Collection<PersistentEntity<?>> getPersistentEntities();
/**
* Obtains a PersistentEntity by name
*
* @param name The name of the entity
* @return The entity or null
*/
PersistentEntity<?> getPersistentEntity(String name);
<T> PersistentEntity<T> getPersistentEntity(Class<T> type);
<T> PersistentEntity<T> getPersistentEntity(TypeInformation type);
/**
* Adds a PersistentEntity instance
*

13
spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/PersistentEntity.java

@ -3,6 +3,7 @@ package org.springframework.data.mapping.model; @@ -3,6 +3,7 @@ package org.springframework.data.mapping.model;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.data.mapping.AssociationHandler;
import org.springframework.data.mapping.PropertyHandler;
import org.springframework.data.util.TypeInformation;
import java.util.Collection;
@ -31,18 +32,18 @@ public interface PersistentEntity<T> extends InitializingBean { @@ -31,18 +32,18 @@ public interface PersistentEntity<T> extends InitializingBean {
*
* @return The identity
*/
PersistentProperty<?> getIdProperty();
PersistentProperty getIdProperty();
void setIdProperty(PersistentProperty<?> property);
void setIdProperty(PersistentProperty property);
/**
* A list of properties to be persisted
*
* @return A list of PersistentProperty instances
*/
Collection<PersistentProperty<?>> getPersistentProperties();
Collection<PersistentProperty> getPersistentProperties();
void addPersistentProperty(PersistentProperty<?> property);
void addPersistentProperty(PersistentProperty property);
/**
* A list of the associations for this entity. This is typically a subset of the list returned by {@link #getPersistentProperties()}
@ -59,12 +60,14 @@ public interface PersistentEntity<T> extends InitializingBean { @@ -59,12 +60,14 @@ public interface PersistentEntity<T> extends InitializingBean {
* @param name The name of the property
* @return The PersistentProperty or null if it doesn't exist
*/
PersistentProperty<?> getPersistentProperty(String name);
PersistentProperty getPersistentProperty(String name);
/**
* @return The underlying Java class for this entity
*/
Class<T> getType();
TypeInformation getPropertyInformation();
/**
* A list of property names

11
spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/PersistentProperty.java

@ -1,6 +1,7 @@ @@ -1,6 +1,7 @@
package org.springframework.data.mapping.model;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.util.TypeInformation;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
@ -9,7 +10,7 @@ import java.lang.reflect.Field; @@ -9,7 +10,7 @@ import java.lang.reflect.Field;
* @author Graeme Rocher
* @since 1.0
*/
public interface PersistentProperty<T> {
public interface PersistentProperty {
Object getOwner();
@ -27,7 +28,9 @@ public interface PersistentProperty<T> { @@ -27,7 +28,9 @@ public interface PersistentProperty<T> {
*
* @return The property type
*/
Class<T> getType();
Class<?> getType();
TypeInformation getTypeInformation();
PropertyDescriptor getPropertyDescriptor();
@ -44,6 +47,10 @@ public interface PersistentProperty<T> { @@ -44,6 +47,10 @@ public interface PersistentProperty<T> {
void setAssociation(Association association);
boolean isCollection();
boolean isMap();
boolean isArray();
boolean isComplexType();

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

@ -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();
}
}

355
spring-data-commons-core/src/main/java/org/springframework/data/util/GenericTypeResolver.java

@ -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);
}
}
}
}
}

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

@ -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;
}
}

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

@ -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();
}

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

@ -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;
}
}

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

@ -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> {
}
}

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

@ -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…
Cancel
Save