From ad56cae06e327fa2bd83afa3322c234db2ddcc34 Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Tue, 15 Mar 2011 15:26:50 +0100 Subject: [PATCH] Extended basic mapping framework to support working with generics. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) might be used inside different entities that type T to something different. Created isMap() and isArray() on PersistentProperty to align to isCollection(). --- .../BasicMappingConfigurationBuilder.java | 17 +- .../data/mapping/BasicMappingContext.java | 136 ++++--- .../data/mapping/BasicPersistentEntity.java | 34 +- .../data/mapping/BasicPersistentProperty.java | 46 ++- .../data/mapping/MappingBeanHelper.java | 6 +- .../data/mapping/PropertyHandler.java | 6 +- .../data/mapping/model/Association.java | 14 +- .../model/MappingConfigurationBuilder.java | 9 +- .../data/mapping/model/MappingContext.java | 13 +- .../data/mapping/model/PersistentEntity.java | 13 +- .../mapping/model/PersistentProperty.java | 11 +- .../data/util/ClassTypeInformation.java | 79 ++++ .../data/util/GenericTypeResolver.java | 355 ++++++++++++++++++ .../data/util/TypeDiscoverer.java | 198 ++++++++++ .../data/util/TypeInformation.java | 38 ++ .../util/TypeVariableTypeInformation.java | 102 +++++ .../util/ClassTypeInformationUnitTests.java | 146 +++++++ .../data/util/TypeDiscovererUnitTests.java | 16 + 18 files changed, 1120 insertions(+), 119 deletions(-) create mode 100644 spring-data-commons-core/src/main/java/org/springframework/data/util/ClassTypeInformation.java create mode 100644 spring-data-commons-core/src/main/java/org/springframework/data/util/GenericTypeResolver.java create mode 100644 spring-data-commons-core/src/main/java/org/springframework/data/util/TypeDiscoverer.java create mode 100644 spring-data-commons-core/src/main/java/org/springframework/data/util/TypeInformation.java create mode 100644 spring-data-commons-core/src/main/java/org/springframework/data/util/TypeVariableTypeInformation.java create mode 100644 spring-data-commons-core/src/test/java/org/springframework/data/util/ClassTypeInformationUnitTests.java create mode 100644 spring-data-commons-core/src/test/java/org/springframework/data/util/TypeDiscovererUnitTests.java diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/mapping/BasicMappingConfigurationBuilder.java b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/BasicMappingConfigurationBuilder.java index 538f816ca..6fe9dbe64 100644 --- a/spring-data-commons-core/src/main/java/org/springframework/data/mapping/BasicMappingConfigurationBuilder.java +++ b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/BasicMappingConfigurationBuilder.java @@ -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; 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; */ public class BasicMappingConfigurationBuilder implements MappingConfigurationBuilder { + private static final Set UNMAPPED_FIELDS = new HashSet(Arrays.asList("class", "this$0")); protected static ConcurrentMap, BeanInfo> beanInfo = new ConcurrentHashMap, BeanInfo>(); protected Logger log = LoggerFactory.getLogger(getClass()); @@ -60,21 +65,21 @@ public class BasicMappingConfigurationBuilder implements MappingConfigurationBui } @Override - public PersistentEntity createPersistentEntity(Class type, MappingContext mappingContext) throws MappingConfigurationException { - return new BasicPersistentEntity(mappingContext, type); + public PersistentEntity createPersistentEntity(TypeInformation typeInformation, MappingContext mappingContext) throws MappingConfigurationException { + return new BasicPersistentEntity(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 PersistentProperty createPersistentProperty(Field field, PropertyDescriptor descriptor, Class type) throws MappingConfigurationException { - return new BasicPersistentProperty(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 } @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); diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/mapping/BasicMappingContext.java b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/BasicMappingContext.java index 16aa94009..b52158830 100644 --- a/spring-data-commons-core/src/main/java/org/springframework/data/mapping/BasicMappingContext.java +++ b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/BasicMappingContext.java @@ -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 { protected Logger log = LoggerFactory.getLogger(getClass()); protected MappingConfigurationBuilder builder; - protected ConcurrentMap> persistentEntities = new ConcurrentHashMap>(); + protected ConcurrentMap> persistentEntities = new ConcurrentHashMap>(); protected ConcurrentMap, List> validators = new ConcurrentHashMap, List>(); protected ConcurrentSkipListSet listeners = new ConcurrentSkipListSet(); protected GenericConversionService conversionService = ConversionServiceFactory.createDefaultConversionService(); @@ -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 PersistentEntity getPersistentEntity(Class type) { - return (PersistentEntity) persistentEntities.get(type.getName()); + return getPersistentEntity(new ClassTypeInformation(type)); } - + + @SuppressWarnings({"unchecked"}) @Override - public PersistentEntity getPersistentEntity(String name) { - return persistentEntities.get(name); - } + public PersistentEntity getPersistentEntity(TypeInformation type) { + return (PersistentEntity) persistentEntities.get(type); + } + + @SuppressWarnings("unchecked") + public PersistentEntity addPersistentEntity(TypeInformation typeInformation) { + + PersistentEntity persistentEntity = persistentEntities.get(typeInformation); + + if (persistentEntity != null) { + return (PersistentEntity) persistentEntity; + } + + Class type = (Class) typeInformation.getType(); + + try { + final PersistentEntity entity = builder.createPersistentEntity(typeInformation, this); + BeanInfo info = Introspector.getBeanInfo(type); + + final Map descriptors = new HashMap(); + for (PropertyDescriptor descriptor : info.getPropertyDescriptors()) { + descriptors.put(descriptor.getName(), descriptor); + } - @Override - public PersistentEntity addPersistentEntity(Class type) { - if (null == persistentEntities.get(type.getName())) { - try { - final PersistentEntity entity = builder.createPersistentEntity(type, this); - BeanInfo info = Introspector.getBeanInfo(type); - - final Map descriptors = new HashMap(); - 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 listenersToRemove = new ArrayList(); - for (Listener listener : listeners) { - if (!listener.persistentEntityAdded(entity)) { - listenersToRemove.add(listener); - } - } - for (Listener listener : listenersToRemove) { - listeners.remove(listener); + // Inform listeners + List listenersToRemove = new ArrayList(); + 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 PersistentEntity addPersistentEntity(Class type) { + return addPersistentEntity(new ClassTypeInformation(type)); + } + @Override public void addEntityValidator(PersistentEntity entity, Validator validator) { List v = validators.get(entity); diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/mapping/BasicPersistentEntity.java b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/BasicPersistentEntity.java index f66e12047..80a4bed52 100644 --- a/spring-data-commons-core/src/main/java/org/springframework/data/mapping/BasicPersistentEntity.java +++ b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/BasicPersistentEntity.java @@ -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 implements PersistentEntity { protected final Class type; protected PreferredConstructor preferredConstructor; - protected PersistentProperty idProperty; - protected Map> persistentProperties = new HashMap>(); + protected PersistentProperty idProperty; + protected Map persistentProperties = new HashMap(); protected Map associations = new HashMap(); + protected final TypeInformation information; protected MappingContext mappingContext; - - public BasicPersistentEntity(MappingContext mappingContext, Class type) { + + + public BasicPersistentEntity(MappingContext mappingContext, TypeInformation information) { this.mappingContext = mappingContext; - this.type = type; + this.type = (Class) information.getType(); + this.information = information; } @Override @@ -56,22 +61,22 @@ public class BasicPersistentEntity implements PersistentEntity { } @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> getPersistentProperties() { + public Collection 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 implements PersistentEntity { 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 implements PersistentEntity { public Class getType() { return type; } + + @Override + public TypeInformation getPropertyInformation() { + return information; + } @Override public Collection getPersistentPropertyNames() { @@ -111,8 +121,8 @@ public class BasicPersistentEntity implements PersistentEntity { @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); } } diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/mapping/BasicPersistentProperty.java b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/BasicPersistentProperty.java index 621fbaa72..6ce822ae5 100644 --- a/spring-data-commons-core/src/main/java/org/springframework/data/mapping/BasicPersistentProperty.java +++ b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/BasicPersistentProperty.java @@ -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; import java.lang.reflect.Type; import java.util.Collection; import java.util.List; +import java.util.Map; /** * @author Jon Brisbin */ -public class BasicPersistentProperty implements PersistentProperty { +public class BasicPersistentProperty implements PersistentProperty { protected final String name; - protected final Class 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 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 implements PersistentProperty { } @Override - public Class getType() { - return type; + public Class getType() { + return information.getType(); + } + + @Override + public TypeInformation getTypeInformation() { + return information; } @Override @@ -124,15 +128,29 @@ public class BasicPersistentProperty implements PersistentProperty { @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 implements PersistentProperty { } } } - return type.getComponentType(); + return getType().getComponentType(); } @Override diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/mapping/MappingBeanHelper.java b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/MappingBeanHelper.java index 966121bc5..fcc77a3ec 100644 --- a/spring-data-commons-core/src/main/java/org/springframework/data/mapping/MappingBeanHelper.java +++ b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/MappingBeanHelper.java @@ -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 { @SuppressWarnings({"unchecked"}) public static T getProperty(Object from, - PersistentProperty property, + PersistentProperty property, Class type, boolean fieldAccessOnly) throws IllegalAccessException, InvocationTargetException { diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/mapping/PropertyHandler.java b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/PropertyHandler.java index 1ba044dfd..51e0bb332 100644 --- a/spring-data-commons-core/src/main/java/org/springframework/data/mapping/PropertyHandler.java +++ b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/PropertyHandler.java @@ -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 except associations, transient properties and the id-property. + * * @author Jon Brisbin */ public interface PropertyHandler { - void doWithPersistentProperty(PersistentProperty persistentProperty); + + void doWithPersistentProperty(PersistentProperty persistentProperty); } diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/Association.java b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/Association.java index 185924c1a..3fba58799 100644 --- a/spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/Association.java +++ b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/Association.java @@ -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; } } diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/MappingConfigurationBuilder.java b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/MappingConfigurationBuilder.java index da20bad6e..76c1ba7d2 100644 --- a/spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/MappingConfigurationBuilder.java +++ b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/MappingConfigurationBuilder.java @@ -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 */ @@ -26,16 +29,16 @@ public interface MappingConfigurationBuilder { boolean isPersistentEntity(Class clazz); - PersistentEntity createPersistentEntity(Class clazz, MappingContext mappingContext) throws MappingConfigurationException; + PersistentEntity createPersistentEntity(TypeInformation typeInformation, MappingContext mappingContext) throws MappingConfigurationException; boolean isPersistentProperty(Field field, PropertyDescriptor descriptor) throws MappingConfigurationException; - PersistentProperty createPersistentProperty(Field field, PropertyDescriptor descriptor, Class type) throws MappingConfigurationException; + PersistentProperty createPersistentProperty(Field field, PropertyDescriptor descriptor, TypeInformation owningTypeInformation) throws MappingConfigurationException; PreferredConstructor getPreferredConstructor(Class clazz) throws MappingConfigurationException; boolean isAssociation(Field field, PropertyDescriptor descriptor) throws MappingConfigurationException; - Association createAssociation(PersistentProperty property); + Association createAssociation(PersistentProperty property); } diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/MappingContext.java b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/MappingContext.java index 0d49d5a89..ab627ee91 100644 --- a/spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/MappingContext.java +++ b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/MappingContext.java @@ -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 { * @return A list of PersistentEntity instances */ Collection> getPersistentEntities(); - - /** - * Obtains a PersistentEntity by name - * - * @param name The name of the entity - * @return The entity or null - */ - PersistentEntity getPersistentEntity(String name); - + PersistentEntity getPersistentEntity(Class type); + PersistentEntity getPersistentEntity(TypeInformation type); + /** * Adds a PersistentEntity instance * diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/PersistentEntity.java b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/PersistentEntity.java index c9c6e7077..ed90132e0 100644 --- a/spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/PersistentEntity.java +++ b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/PersistentEntity.java @@ -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 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> getPersistentProperties(); + Collection 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 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 getType(); + + TypeInformation getPropertyInformation(); /** * A list of property names diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/PersistentProperty.java b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/PersistentProperty.java index 234422d1a..e6276c43a 100644 --- a/spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/PersistentProperty.java +++ b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/PersistentProperty.java @@ -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; * @author Graeme Rocher * @since 1.0 */ -public interface PersistentProperty { +public interface PersistentProperty { Object getOwner(); @@ -27,7 +28,9 @@ public interface PersistentProperty { * * @return The property type */ - Class getType(); + Class getType(); + + TypeInformation getTypeInformation(); PropertyDescriptor getPropertyDescriptor(); @@ -44,6 +47,10 @@ public interface PersistentProperty { void setAssociation(Association association); boolean isCollection(); + + boolean isMap(); + + boolean isArray(); boolean isComplexType(); diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/util/ClassTypeInformation.java b/spring-data-commons-core/src/main/java/org/springframework/data/util/ClassTypeInformation.java new file mode 100644 index 000000000..d4cd4835d --- /dev/null +++ b/spring-data-commons-core/src/main/java/org/springframework/data/util/ClassTypeInformation.java @@ -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> basicTypes) { + this(type, GenericTypeResolver.getTypeVariableMap(type), basicTypes, null); + } + + ClassTypeInformation(Class type, TypeDiscoverer parent) { + this(type, null, null, parent); + } + + @SuppressWarnings("rawtypes") + ClassTypeInformation(Class type, Map typeVariableMap, Set> 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(); + } +} \ No newline at end of file diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/util/GenericTypeResolver.java b/spring-data-commons-core/src/main/java/org/springframework/data/util/GenericTypeResolver.java new file mode 100644 index 000000000..afa417ee3 --- /dev/null +++ b/spring-data-commons-core/src/main/java/org/springframework/data/util/GenericTypeResolver.java @@ -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>> typeVariableCache = Collections + .synchronizedMap(new WeakHashMap>>()); + + /** + * 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 null 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 null 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 Object.class + * otherwise + */ + static Class resolveType(Type genericType, + Map 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 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 getTypeVariableMap(Class clazz) { + Reference> ref = typeVariableCache.get(clazz); + Map typeVariableMap = (ref != null ? ref.get() : null); + + if (typeVariableMap == null) { + typeVariableMap = new HashMap(); + + // 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>( + typeVariableMap)); + } + + return typeVariableMap; + } + + /** + * Extracts the bound Type 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 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}. + *

+ * Consider this case: + * + *