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: + * + *