Move and modify

This commit is contained in:
Christoph Strobl
2020-10-12 12:24:18 +02:00
parent 0b507c342f
commit 755f65299d
16 changed files with 3555 additions and 0 deletions

View File

@@ -0,0 +1,690 @@
/*
* Copyright 2011-2020 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
*
* https://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.mapping.context;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.core.KotlinDetector;
import org.springframework.data.mapping.MappingException;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.PersistentPropertyPath;
import org.springframework.data.mapping.PersistentPropertyPaths;
import org.springframework.data.mapping.PropertyPath;
import org.springframework.data.mapping.model.BeanWrapperPropertyAccessorFactory;
import org.springframework.data.mapping.model.ClassGeneratingPropertyAccessorFactory;
import org.springframework.data.mapping.model.EntityInstantiators;
import org.springframework.data.mapping.model.InstantiationAwarePropertyAccessorFactory;
import org.springframework.data.mapping.model.MutablePersistentEntity;
import org.springframework.data.mapping.model.PersistentPropertyAccessorFactory;
import org.springframework.data.mapping.model.Property;
import org.springframework.data.mapping.model.SimpleTypeHolder;
import org.springframework.data.mapping.model.StaticPropertyAccessorFactory;
import org.springframework.data.spel.EvaluationContextProvider;
import org.springframework.data.spel.ExtensionAwareEvaluationContextProvider;
import org.springframework.data.util.ClassTypeInformation;
import org.springframework.data.util.KotlinReflectionUtils;
import org.springframework.data.util.Optionals;
import org.springframework.data.util.StaticTypeInformation;
import org.springframework.data.util.Streamable;
import org.springframework.data.util.TypeInformation;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.ReflectionUtils.FieldCallback;
import org.springframework.util.ReflectionUtils.FieldFilter;
/**
* Base class to build mapping metadata and thus create instances of {@link PersistentEntity} and
* {@link PersistentProperty}.
* <p>
* The implementation uses a {@link ReentrantReadWriteLock} to make sure {@link PersistentEntity} are completely
* populated before accessing them from outside.
*
* @param <E> the concrete {@link PersistentEntity} type the {@link MappingContext} implementation creates
* @param <P> the concrete {@link PersistentProperty} type the {@link MappingContext} implementation creates
* @author Jon Brisbin
* @author Oliver Gierke
* @author Michael Hunger
* @author Thomas Darimont
* @author Tomasz Wysocki
* @author Mark Paluch
* @author Mikael Klamra
* @author Christoph Strobl
*/
public abstract class AbstractMappingContext<E extends MutablePersistentEntity<?, P>, P extends PersistentProperty<P>>
implements MappingContext<E, P>, ApplicationEventPublisherAware, ApplicationContextAware, InitializingBean {
private static final boolean IN_NATIVE_IMAGE = System.getProperty("org.graalvm.nativeimage.imagecode") != null;
private final Optional<E> NONE = Optional.empty();
private final Map<TypeInformation<?>, Optional<E>> persistentEntities = new HashMap<>();
private final PersistentPropertyAccessorFactory persistentPropertyAccessorFactory;
private final PersistentPropertyPathFactory<E, P> persistentPropertyPathFactory;
private @Nullable ApplicationEventPublisher applicationEventPublisher;
private EvaluationContextProvider evaluationContextProvider = EvaluationContextProvider.DEFAULT;
private Set<? extends Class<?>> initialEntitySet = new HashSet<>();
private boolean strict = false;
private SimpleTypeHolder simpleTypeHolder = SimpleTypeHolder.DEFAULT;
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
private final Lock read = lock.readLock();
private final Lock write = lock.writeLock();
protected AbstractMappingContext() {
this.persistentPropertyPathFactory = new PersistentPropertyPathFactory<>(this);
EntityInstantiators instantiators = new EntityInstantiators();
PersistentPropertyAccessorFactory accessorFactory = IN_NATIVE_IMAGE ? BeanWrapperPropertyAccessorFactory.INSTANCE
: new ClassGeneratingPropertyAccessorFactory();
this.persistentPropertyAccessorFactory = new InstantiationAwarePropertyAccessorFactory(accessorFactory,
instantiators);
}
/*
* (non-Javadoc)
* @see org.springframework.context.ApplicationEventPublisherAware#setApplicationEventPublisher(org.springframework.context.ApplicationEventPublisher)
*/
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
}
/*
* (non-Javadoc)
* @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext)
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.evaluationContextProvider = new ExtensionAwareEvaluationContextProvider(applicationContext);
if (applicationEventPublisher == null) {
this.applicationEventPublisher = applicationContext;
}
}
/**
* Sets the {@link Set} of types to populate the context initially.
*
* @param initialEntitySet
*/
public void setInitialEntitySet(Set<? extends Class<?>> initialEntitySet) {
this.initialEntitySet = initialEntitySet;
}
/**
* Configures whether the {@link MappingContext} is in strict mode which means, that it will throw
* {@link MappingException}s in case one tries to lookup a {@link PersistentEntity} not already in the context. This
* defaults to {@literal false} so that unknown types will be transparently added to the MappingContext if not known
* in advance.
*
* @param strict
*/
public void setStrict(boolean strict) {
this.strict = strict;
}
/**
* Configures the {@link SimpleTypeHolder} to be used by the {@link MappingContext}. Allows customization of what
* types will be regarded as simple types and thus not recursively analyzed.
*
* @param simpleTypes must not be {@literal null}.
*/
public void setSimpleTypeHolder(SimpleTypeHolder simpleTypes) {
Assert.notNull(simpleTypes, "SimpleTypeHolder must not be null!");
this.simpleTypeHolder = simpleTypes;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.model.MappingContext#getPersistentEntities()
*/
@Override
public Collection<E> getPersistentEntities() {
try {
read.lock();
return persistentEntities.values().stream()//
.flatMap(Optionals::toStream)//
.collect(Collectors.toSet());
} finally {
read.unlock();
}
}
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.model.MappingContext#getPersistentEntity(java.lang.Class)
*/
@Nullable
public E getPersistentEntity(Class<?> type) {
return getPersistentEntity(ClassTypeInformation.from(type));
}
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.context.MappingContext#hasPersistentEntityFor(java.lang.Class)
*/
@Override
public boolean hasPersistentEntityFor(Class<?> type) {
Assert.notNull(type, "Type must not be null!");
Optional<E> entity = persistentEntities.get(ClassTypeInformation.from(type));
return entity == null ? false : entity.isPresent();
}
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.model.MappingContext#getPersistentEntity(org.springframework.data.util.TypeInformation)
*/
@Nullable
@Override
public E getPersistentEntity(TypeInformation<?> type) {
Assert.notNull(type, "Type must not be null!");
try {
read.lock();
Optional<E> entity = persistentEntities.get(type);
if (entity != null) {
return entity.orElse(null);
}
} finally {
read.unlock();
}
if (!shouldCreatePersistentEntityFor(type)) {
try {
write.lock();
persistentEntities.put(type, NONE);
} finally {
write.unlock();
}
return null;
}
if (strict) {
throw new MappingException("Unknown persistent entity " + type);
}
return addPersistentEntity(type).orElse(null);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.context.MappingContext#getPersistentEntity(org.springframework.data.mapping.PersistentProperty)
*/
@Nullable
@Override
public E getPersistentEntity(P persistentProperty) {
Assert.notNull(persistentProperty, "PersistentProperty must not be null!");
if (!persistentProperty.isEntity()) {
return null;
}
TypeInformation<?> typeInfo = persistentProperty.getTypeInformation();
return getPersistentEntity(typeInfo.getRequiredActualType());
}
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.context.MappingContext#getPersistentPropertyPath(java.lang.Class, java.lang.String)
*/
@Override
public PersistentPropertyPath<P> getPersistentPropertyPath(PropertyPath propertyPath) {
return persistentPropertyPathFactory.from(propertyPath);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.context.MappingContext#getPersistentPropertyPath(java.lang.String, java.lang.Class)
*/
@Override
public PersistentPropertyPath<P> getPersistentPropertyPath(String propertyPath, Class<?> type) {
return persistentPropertyPathFactory.from(type, propertyPath);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.context.MappingContext#findPersistentPropertyPath(java.lang.Class, java.util.function.Predicate)
*/
@Override
public <T> PersistentPropertyPaths<T, P> findPersistentPropertyPaths(Class<T> type, Predicate<? super P> predicate) {
Assert.notNull(type, "Type must not be null!");
Assert.notNull(predicate, "Selection predicate must not be null!");
return doFindPersistentPropertyPaths(type, predicate, it -> !it.isAssociation());
}
/**
* Actually looks up the {@link PersistentPropertyPaths} for the given type, selection predicate and traversal guard.
* Primary purpose is to allow sub-types to alter the default traversal guard, e.g. used by
* {@link #findPersistentPropertyPaths(Class, Predicate)}.
*
* @param type will never be {@literal null}.
* @param predicate will never be {@literal null}.
* @param traversalGuard will never be {@literal null}.
* @return will never be {@literal null}.
* @see #findPersistentPropertyPaths(Class, Predicate)
*/
protected final <T> PersistentPropertyPaths<T, P> doFindPersistentPropertyPaths(Class<T> type,
Predicate<? super P> predicate, Predicate<P> traversalGuard) {
return persistentPropertyPathFactory.from(ClassTypeInformation.from(type), predicate, traversalGuard);
}
/**
* Adds the given type to the {@link MappingContext}.
*
* @param type must not be {@literal null}.
* @return
*/
protected Optional<E> addPersistentEntity(Class<?> type) {
return addPersistentEntity(ClassTypeInformation.from(type));
}
/**
* Adds the given {@link TypeInformation} to the {@link MappingContext}.
*
* @param typeInformation must not be {@literal null}.
* @return
*/
protected Optional<E> addPersistentEntity(TypeInformation<?> typeInformation) {
Assert.notNull(typeInformation, "TypeInformation must not be null!");
try {
read.lock();
Optional<E> persistentEntity = persistentEntities.get(typeInformation);
if (persistentEntity != null) {
return persistentEntity;
}
} finally {
read.unlock();
}
Class<?> type = typeInformation.getType();
E entity = null;
try {
write.lock();
entity = createPersistentEntity(typeInformation);
entity.setEvaluationContextProvider(evaluationContextProvider);
// Eagerly cache the entity as we might have to find it during recursive lookups.
persistentEntities.put(typeInformation, Optional.of(entity));
if (typeInformation instanceof StaticTypeInformation<?>) {
// ((StaticTypeInformation<?>)typeInformation).doWithProperties()
Map<String, TypeInformation<?>> properties = ((StaticTypeInformation<?>) typeInformation).getProperties();
for (Entry<String, TypeInformation<?>> entry : properties.entrySet()) {
P target = createPersistentProperty(Property.of(typeInformation, entry.getKey()), entity, simpleTypeHolder);
entity.addPersistentProperty(target);
}
entity.setPersistentPropertyAccessorFactory(new StaticPropertyAccessorFactory());
return Optional.of(entity);
}
PropertyDescriptor[] pds = BeanUtils.getPropertyDescriptors(type);
final Map<String, PropertyDescriptor> descriptors = new HashMap<>();
for (PropertyDescriptor descriptor : pds) {
descriptors.put(descriptor.getName(), descriptor);
}
try {
PersistentPropertyCreator persistentPropertyCreator = new PersistentPropertyCreator(entity, descriptors);
ReflectionUtils.doWithFields(type, persistentPropertyCreator, PersistentPropertyFilter.INSTANCE);
persistentPropertyCreator.addPropertiesForRemainingDescriptors();
entity.verify();
if (persistentPropertyAccessorFactory.isSupported(entity)) {
entity.setPersistentPropertyAccessorFactory(persistentPropertyAccessorFactory);
}
} catch (RuntimeException e) {
persistentEntities.remove(typeInformation);
throw e;
}
} catch (BeansException e) {
throw new MappingException(e.getMessage(), e);
} finally {
write.unlock();
}
// Inform listeners
if (applicationEventPublisher != null && entity != null) {
applicationEventPublisher.publishEvent(new MappingContextEvent<>(this, entity));
}
return Optional.of(entity);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.context.PersistentEntityAware#getManagedTypes()
*/
@Override
public Collection<TypeInformation<?>> getManagedTypes() {
try {
read.lock();
return Collections.unmodifiableSet(new HashSet<>(persistentEntities.keySet()));
} finally {
read.unlock();
}
}
/**
* Creates the concrete {@link PersistentEntity} instance.
*
* @param <T>
* @param typeInformation
* @return
*/
protected abstract <T> E createPersistentEntity(TypeInformation<T> typeInformation);
/**
* Creates the concrete instance of {@link PersistentProperty}.
*
* @param property
* @param owner
* @param simpleTypeHolder
* @return
*/
protected abstract P createPersistentProperty(Property property, E owner, SimpleTypeHolder simpleTypeHolder);
/*
* (non-Javadoc)
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
*/
@Override
public void afterPropertiesSet() {
initialize();
}
/**
* Initializes the mapping context. Will add the types configured through {@link #setInitialEntitySet(Set)} to the
* context.
*/
public void initialize() {
initialEntitySet.forEach(this::addPersistentEntity);
}
/**
* Returns whether a {@link PersistentEntity} instance should be created for the given {@link TypeInformation}. By
* default this will reject all types considered simple and non-supported Kotlin classes, but it might be necessary to
* tweak that in case you have registered custom converters for top level types (which renders them to be considered
* simple) but still need meta-information about them.
* <p/>
*
* @param type will never be {@literal null}.
* @return
*/
protected boolean shouldCreatePersistentEntityFor(TypeInformation<?> type) {
if (simpleTypeHolder.isSimpleType(type.getType())) {
return false;
}
return !KotlinDetector.isKotlinType(type.getType()) || KotlinReflectionUtils.isSupportedKotlinClass(type.getType());
}
/**
* {@link FieldCallback} to create {@link PersistentProperty} instances.
*
* @author Oliver Gierke
*/
private final class PersistentPropertyCreator implements FieldCallback {
private final E entity;
private final Map<String, PropertyDescriptor> descriptors;
private final Map<String, PropertyDescriptor> remainingDescriptors;
public PersistentPropertyCreator(E entity, Map<String, PropertyDescriptor> descriptors) {
this(entity, descriptors, descriptors);
}
private PersistentPropertyCreator(E entity, Map<String, PropertyDescriptor> descriptors,
Map<String, PropertyDescriptor> remainingDescriptors) {
this.entity = entity;
this.descriptors = descriptors;
this.remainingDescriptors = remainingDescriptors;
}
/*
* (non-Javadoc)
* @see org.springframework.util.ReflectionUtils.FieldCallback#doWith(java.lang.reflect.Field)
*/
public void doWith(Field field) {
String fieldName = field.getName();
TypeInformation<?> type = entity.getTypeInformation();
ReflectionUtils.makeAccessible(field);
Property property = Optional.ofNullable(descriptors.get(fieldName))//
.map(it -> Property.of(type, field, it))//
.orElseGet(() -> Property.of(type, field));
createAndRegisterProperty(property);
this.remainingDescriptors.remove(fieldName);
}
/**
* Adds {@link PersistentProperty} instances for all suitable {@link PropertyDescriptor}s without a backing
* {@link Field}.
*
* @see PersistentPropertyFilter
*/
public void addPropertiesForRemainingDescriptors() {
remainingDescriptors.values().stream() //
.filter(Property::supportsStandalone) //
.map(it -> Property.of(entity.getTypeInformation(), it)) //
.filter(PersistentPropertyFilter.INSTANCE::matches) //
.forEach(this::createAndRegisterProperty);
}
private void createAndRegisterProperty(Property input) {
P property = createPersistentProperty(input, entity, simpleTypeHolder);
if (property.isTransient()) {
return;
}
if (!input.isFieldBacked() && !property.usePropertyAccess()) {
return;
}
entity.addPersistentProperty(property);
if (property.isAssociation()) {
entity.addAssociation(property.getRequiredAssociation());
}
if (entity.getType().equals(property.getRawType())) {
return;
}
property.getPersistentEntityTypes().forEach(AbstractMappingContext.this::addPersistentEntity);
}
}
/**
* Filter rejecting static fields as well as artificially introduced ones. See
* {@link PersistentPropertyFilter#UNMAPPED_PROPERTIES} for details.
*
* @author Oliver Gierke
*/
static enum PersistentPropertyFilter implements FieldFilter {
INSTANCE;
private static final Streamable<PropertyMatch> UNMAPPED_PROPERTIES;
static {
Set<PropertyMatch> matches = new HashSet<>();
matches.add(new PropertyMatch("class", null));
matches.add(new PropertyMatch("this\\$.*", null));
matches.add(new PropertyMatch("metaClass", "groovy.lang.MetaClass"));
UNMAPPED_PROPERTIES = Streamable.of(matches);
}
/*
* (non-Javadoc)
* @see org.springframework.util.ReflectionUtils.FieldFilter#matches(java.lang.reflect.Field)
*/
public boolean matches(Field field) {
if (Modifier.isStatic(field.getModifiers())) {
return false;
}
return !UNMAPPED_PROPERTIES.stream()//
.anyMatch(it -> it.matches(field.getName(), field.getType()));
}
/**
* Returns whether the given {@link PropertyDescriptor} is one to create a {@link PersistentProperty} for.
*
* @param property must not be {@literal null}.
* @return
*/
public boolean matches(Property property) {
Assert.notNull(property, "Property must not be null!");
if (!property.hasAccessor()) {
return false;
}
return !UNMAPPED_PROPERTIES.stream()//
.anyMatch(it -> it.matches(property.getName(), property.getType()));
}
/**
* Value object to help defining property exclusion based on name patterns and types.
*
* @since 1.4
* @author Oliver Gierke
*/
static class PropertyMatch {
private final @Nullable String namePattern;
private final @Nullable String typeName;
/**
* Creates a new {@link PropertyMatch} for the given name pattern and type name. At least one of the parameters
* must not be {@literal null}.
*
* @param namePattern a regex pattern to match field names, can be {@literal null}.
* @param typeName the name of the type to exclude, can be {@literal null}.
*/
public PropertyMatch(@Nullable String namePattern, @Nullable String typeName) {
Assert.isTrue(!(namePattern == null && typeName == null), "Either name pattern or type name must be given!");
this.namePattern = namePattern;
this.typeName = typeName;
}
/**
* Returns whether the given {@link Field} matches the defined {@link PropertyMatch}.
*
* @param name must not be {@literal null}.
* @param type must not be {@literal null}.
* @return
*/
public boolean matches(String name, Class<?> type) {
Assert.notNull(name, "Name must not be null!");
Assert.notNull(type, "Type must not be null!");
if (namePattern != null && !name.matches(namePattern)) {
return false;
}
if (typeName != null && !type.getName().equals(typeName)) {
return false;
}
return true;
}
}
}
}

View File

@@ -0,0 +1,640 @@
/*
* Copyright 2011-2020 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
*
* https://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.mapping.model;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.TreeSet;
import java.util.stream.Collectors;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.data.annotation.Immutable;
import org.springframework.data.annotation.TypeAlias;
import org.springframework.data.domain.Persistable;
import org.springframework.data.mapping.*;
import org.springframework.data.spel.EvaluationContextProvider;
import org.springframework.data.support.IsNewStrategy;
import org.springframework.data.support.PersistableIsNewStrategy;
import org.springframework.data.util.Lazy;
import org.springframework.data.util.StaticTypeInformation;
import org.springframework.data.util.TypeInformation;
import org.springframework.expression.EvaluationContext;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ConcurrentReferenceHashMap;
import org.springframework.util.ConcurrentReferenceHashMap.ReferenceType;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
/**
* Simple value object to capture information of {@link PersistentEntity}s.
*
* @author Oliver Gierke
* @author Jon Brisbin
* @author Patryk Wasik
* @author Thomas Darimont
* @author Christoph Strobl
* @author Mark Paluch
*/
public class BasicPersistentEntity<T, P extends PersistentProperty<P>> implements MutablePersistentEntity<T, P> {
private static final String TYPE_MISMATCH = "Target bean of type %s is not of type of the persistent entity (%s)!";
private final @Nullable PreferredConstructor<T, P> constructor;
private final TypeInformation<T> information;
private final List<P> properties;
private final List<P> persistentPropertiesCache;
private final @Nullable Comparator<P> comparator;
private final Set<Association<P>> associations;
private final Map<String, P> propertyCache;
private final Map<Class<? extends Annotation>, Optional<Annotation>> annotationCache;
private final MultiValueMap<Class<? extends Annotation>, P> propertyAnnotationCache;
private @Nullable P idProperty;
private @Nullable P versionProperty;
private PersistentPropertyAccessorFactory propertyAccessorFactory;
private EvaluationContextProvider evaluationContextProvider = EvaluationContextProvider.DEFAULT;
private final Lazy<Alias> typeAlias;
private final Lazy<IsNewStrategy> isNewStrategy;
private final Lazy<Boolean> isImmutable;
private final Lazy<Boolean> requiresPropertyPopulation;
/**
* Creates a new {@link BasicPersistentEntity} from the given {@link TypeInformation}.
*
* @param information must not be {@literal null}.
*/
public BasicPersistentEntity(TypeInformation<T> information) {
this(information, null);
}
/**
* Creates a new {@link BasicPersistentEntity} for the given {@link TypeInformation} and {@link Comparator}. The given
* {@link Comparator} will be used to define the order of the {@link PersistentProperty} instances added to the
* entity.
*
* @param information must not be {@literal null}.
* @param comparator can be {@literal null}.
*/
public BasicPersistentEntity(TypeInformation<T> information, @Nullable Comparator<P> comparator) {
Assert.notNull(information, "Information must not be null!");
this.information = information;
this.properties = new ArrayList<>();
this.persistentPropertiesCache = new ArrayList<>();
this.comparator = comparator;
this.constructor = information instanceof StaticTypeInformation ? null : PreferredConstructorDiscoverer.discover(this);
this.associations = comparator == null ? new HashSet<>() : new TreeSet<>(new AssociationComparator<>(comparator));
this.propertyCache = new HashMap<>(16, 1f);
this.annotationCache = new ConcurrentReferenceHashMap<>(16, ReferenceType.WEAK);
this.propertyAnnotationCache = CollectionUtils
.toMultiValueMap(new ConcurrentReferenceHashMap<>(16, ReferenceType.WEAK));
this.propertyAccessorFactory = BeanWrapperPropertyAccessorFactory.INSTANCE;
this.typeAlias = Lazy.of(() -> getAliasFromAnnotation(getType()));
this.isNewStrategy = Lazy.of(() -> Persistable.class.isAssignableFrom(information.getType()) //
? PersistableIsNewStrategy.INSTANCE
: getFallbackIsNewStrategy());
this.isImmutable = Lazy.of(() -> isAnnotationPresent(Immutable.class));
this.requiresPropertyPopulation = Lazy.of(() -> !isImmutable() && properties.stream() //
.anyMatch(it -> !(isConstructorArgument(it) || it.isTransient())));
}
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.PersistentEntity#getPersistenceConstructor()
*/
@Nullable
public PreferredConstructor<T, P> getPersistenceConstructor() {
return constructor;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.PersistentEntity#isConstructorArgument(org.springframework.data.mapping.PersistentProperty)
*/
public boolean isConstructorArgument(PersistentProperty<?> property) {
return constructor != null && constructor.isConstructorParameter(property);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.PersistentEntity#isIdProperty(org.springframework.data.mapping.PersistentProperty)
*/
public boolean isIdProperty(PersistentProperty<?> property) {
return idProperty != null && idProperty.equals(property);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.PersistentEntity#isVersionProperty(org.springframework.data.mapping.PersistentProperty)
*/
public boolean isVersionProperty(PersistentProperty<?> property) {
return versionProperty != null && versionProperty.equals(property);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.PersistentEntity#getName()
*/
public String getName() {
return getType().getName();
}
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.PersistentEntity#getIdProperty()
*/
@Nullable
public P getIdProperty() {
return idProperty;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.PersistentEntity#getVersionProperty()
*/
@Nullable
public P getVersionProperty() {
return versionProperty;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.PersistentEntity#hasIdProperty()
*/
public boolean hasIdProperty() {
return idProperty != null;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.PersistentEntity#hasVersionProperty()
*/
public boolean hasVersionProperty() {
return versionProperty != null;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.model.MutablePersistentEntity#addPersistentProperty(P)
*/
public void addPersistentProperty(P property) {
Assert.notNull(property, "Property must not be null!");
if (properties.contains(property)) {
return;
}
properties.add(property);
if (!property.isTransient() && !property.isAssociation()) {
persistentPropertiesCache.add(property);
}
propertyCache.computeIfAbsent(property.getName(), key -> property);
P candidate = returnPropertyIfBetterIdPropertyCandidateOrNull(property);
if (candidate != null) {
this.idProperty = candidate;
}
if (property.isVersionProperty()) {
P versionProperty = this.versionProperty;
if (versionProperty != null) {
throw new MappingException(
String.format(
"Attempt to add version property %s but already have property %s registered "
+ "as version. Check your mapping configuration!",
property.getField(), versionProperty.getField()));
}
this.versionProperty = property;
}
}
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.model.MutablePersistentEntity#setEvaluationContextProvider(org.springframework.data.spel.EvaluationContextProvider)
*/
@Override
public void setEvaluationContextProvider(EvaluationContextProvider provider) {
this.evaluationContextProvider = provider;
}
/**
* Returns the given property if it is a better candidate for the id property than the current id property.
*
* @param property the new id property candidate, will never be {@literal null}.
* @return the given id property or {@literal null} if the given property is not an id property.
*/
@Nullable
protected P returnPropertyIfBetterIdPropertyCandidateOrNull(P property) {
if (!property.isIdProperty()) {
return null;
}
P idProperty = this.idProperty;
if (idProperty != null) {
throw new MappingException(String.format("Attempt to add id property %s but already have property %s registered "
+ "as id. Check your mapping configuration!", property.getField(), idProperty.getField()));
}
return property;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.model.MutablePersistentEntity#addAssociation(org.springframework.data.mapping.model.Association)
*/
public void addAssociation(Association<P> association) {
Assert.notNull(association, "Association must not be null!");
associations.add(association);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.PersistentEntity#getPersistentProperty(java.lang.String)
*/
@Override
@Nullable
public P getPersistentProperty(String name) {
return propertyCache.get(name);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.PersistentEntity#getPersistentProperties(java.lang.String)
*/
@Override
public Iterable<P> getPersistentProperties(Class<? extends Annotation> annotationType) {
Assert.notNull(annotationType, "Annotation type must not be null!");
return propertyAnnotationCache.computeIfAbsent(annotationType, this::doFindPersistentProperty);
}
private List<P> doFindPersistentProperty(Class<? extends Annotation> annotationType) {
List<P> annotatedProperties = properties.stream() //
.filter(it -> it.isAnnotationPresent(annotationType)) //
.collect(Collectors.toList());
if (!annotatedProperties.isEmpty()) {
return annotatedProperties;
}
return associations.stream() //
.map(Association::getInverse) //
.filter(it -> it.isAnnotationPresent(annotationType)).collect(Collectors.toList());
}
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.PersistentEntity#getType()
*/
public Class<T> getType() {
return information.getType();
}
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.PersistentEntity#getTypeAlias()
*/
public Alias getTypeAlias() {
return typeAlias.get();
}
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.PersistentEntity#getTypeInformation()
*/
public TypeInformation<T> getTypeInformation() {
return information;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.PersistentEntity#doWithProperties(org.springframework.data.mapping.PropertyHandler)
*/
public void doWithProperties(PropertyHandler<P> handler) {
Assert.notNull(handler, "PropertyHandler must not be null!");
for (P property : persistentPropertiesCache) {
handler.doWithPersistentProperty(property);
}
}
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.PersistentEntity#doWithProperties(org.springframework.data.mapping.PropertyHandler.Simple)
*/
@Override
public void doWithProperties(SimplePropertyHandler handler) {
Assert.notNull(handler, "Handler must not be null!");
for (PersistentProperty<?> property : persistentPropertiesCache) {
handler.doWithPersistentProperty(property);
}
}
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.PersistentEntity#doWithAssociations(org.springframework.data.mapping.AssociationHandler)
*/
public void doWithAssociations(AssociationHandler<P> handler) {
Assert.notNull(handler, "Handler must not be null!");
for (Association<P> association : associations) {
handler.doWithAssociation(association);
}
}
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.PersistentEntity#doWithAssociations(org.springframework.data.mapping.SimpleAssociationHandler)
*/
public void doWithAssociations(SimpleAssociationHandler handler) {
Assert.notNull(handler, "Handler must not be null!");
for (Association<? extends PersistentProperty<?>> association : associations) {
handler.doWithAssociation(association);
}
}
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.PersistentEntity#findAnnotation(java.lang.Class)
*/
@Nullable
@Override
public <A extends Annotation> A findAnnotation(Class<A> annotationType) {
return doFindAnnotation(annotationType).orElse(null);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.PersistentEntity#isAnnotationPresent(java.lang.Class)
*/
@Override
public <A extends Annotation> boolean isAnnotationPresent(Class<A> annotationType) {
return doFindAnnotation(annotationType).isPresent();
}
@SuppressWarnings("unchecked")
private <A extends Annotation> Optional<A> doFindAnnotation(Class<A> annotationType) {
return (Optional<A>) annotationCache.computeIfAbsent(annotationType,
it -> Optional.ofNullable(AnnotatedElementUtils.findMergedAnnotation(getType(), it)));
}
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.model.MutablePersistentEntity#verify()
*/
public void verify() {
if (comparator != null) {
properties.sort(comparator);
persistentPropertiesCache.sort(comparator);
}
}
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.model.MutablePersistentEntity#setPersistentPropertyAccessorFactory(org.springframework.data.mapping.model.PersistentPropertyAccessorFactory)
*/
@Override
public void setPersistentPropertyAccessorFactory(PersistentPropertyAccessorFactory factory) {
this.propertyAccessorFactory = factory;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.PersistentEntity#getPropertyAccessor(java.lang.Object)
*/
@Override
public <B> PersistentPropertyAccessor<B> getPropertyAccessor(B bean) {
verifyBeanType(bean);
return propertyAccessorFactory.getPropertyAccessor(this, bean);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.PersistentEntity#getPropertyPathAccessor(java.lang.Object)
*/
@Override
public <B> PersistentPropertyPathAccessor<B> getPropertyPathAccessor(B bean) {
return new SimplePersistentPropertyPathAccessor<>(getPropertyAccessor(bean));
}
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.PersistentEntity#getIdentifierAccessor(java.lang.Object)
*/
@Override
public IdentifierAccessor getIdentifierAccessor(Object bean) {
verifyBeanType(bean);
if (Persistable.class.isAssignableFrom(getType())) {
return new PersistableIdentifierAccessor((Persistable<?>) bean);
}
return hasIdProperty() ? new IdPropertyIdentifierAccessor(this, bean) : new AbsentIdentifierAccessor(bean);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.PersistentEntity#isNew(java.lang.Object)
*/
@Override
public boolean isNew(Object bean) {
verifyBeanType(bean);
return isNewStrategy.get().isNew(bean);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.PersistentEntity#isImmutable()
*/
@Override
public boolean isImmutable() {
return isImmutable.get();
}
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.PersistentEntity#requiresPropertyPopulation()
*/
@Override
public boolean requiresPropertyPopulation() {
return requiresPropertyPopulation.get();
}
/*
* (non-Javadoc)
* @see java.lang.Iterable#iterator()
*/
@Override
public Iterator<P> iterator() {
Iterator<P> iterator = properties.iterator();
return new Iterator<P>() {
@Override
public boolean hasNext() {
return iterator.hasNext();
}
@Override
public P next() {
return iterator.next();
}
};
}
protected EvaluationContext getEvaluationContext(Object rootObject) {
return evaluationContextProvider.getEvaluationContext(rootObject);
}
/**
* Returns the default {@link IsNewStrategy} to be used. Will be a {@link PersistentEntityIsNewStrategy} by default.
* Note, that this strategy only gets used if the entity doesn't implement {@link Persistable} as this indicates the
* user wants to be in control over whether an entity is new or not.
*
* @return
* @since 2.1
*/
protected IsNewStrategy getFallbackIsNewStrategy() {
return PersistentEntityIsNewStrategy.of(this);
}
/**
* Verifies the given bean type to no be {@literal null} and of the type of the current {@link PersistentEntity}.
*
* @param bean must not be {@literal null}.
*/
private void verifyBeanType(Object bean) {
Assert.notNull(bean, "Target bean must not be null!");
Assert.isInstanceOf(getType(), bean,
() -> String.format(TYPE_MISMATCH, bean.getClass().getName(), getType().getName()));
}
/**
* Calculates the {@link Alias} to be used for the given type.
*
* @param type must not be {@literal null}.
* @return
*/
private static Alias getAliasFromAnnotation(Class<?> type) {
Optional<String> typeAliasValue = Optional
.ofNullable(AnnotatedElementUtils.findMergedAnnotation(type, TypeAlias.class))//
.map(TypeAlias::value)//
.filter(StringUtils::hasText);
return Alias.ofNullable(typeAliasValue.orElse(null));
}
/**
* A null-object implementation of {@link IdentifierAccessor} to be able to return an accessor for entities that do
* not have an identifier property.
*
* @author Oliver Gierke
*/
private static class AbsentIdentifierAccessor extends TargetAwareIdentifierAccessor {
public AbsentIdentifierAccessor(Object target) {
super(target);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.IdentifierAccessor#getIdentifier()
*/
@Override
@Nullable
public Object getIdentifier() {
return null;
}
}
/**
* Simple {@link Comparator} adaptor to delegate ordering to the inverse properties of the association.
*
* @author Oliver Gierke
*/
private static final class AssociationComparator<P extends PersistentProperty<P>>
implements Comparator<Association<P>>, Serializable {
private static final long serialVersionUID = 4508054194886854513L;
private final Comparator<P> delegate;
AssociationComparator(Comparator<P> delegate) {
this.delegate = delegate;
}
/*
* (non-Javadoc)
* @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
*/
public int compare(@Nullable Association<P> left, @Nullable Association<P> right) {
if (left == null) {
throw new IllegalArgumentException("Left argument must not be null!");
}
if (right == null) {
throw new IllegalArgumentException("Right argument must not be null!");
}
return delegate.compare(left.getInverse(), right.getInverse());
}
}
}

View File

@@ -0,0 +1,105 @@
/*
* Copyright 2012-2020 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
*
* https://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.mapping.model;
import java.util.Collections;
import java.util.Map;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.util.StaticTypeInformation;
import org.springframework.util.Assert;
/**
* Simple value object allowing access to {@link EntityInstantiator} instances for a given type falling back to a
* default one.
*
* @author Oliver Drotbohm
* @author Thomas Darimont
* @author Christoph Strobl
* @author Mark Paluch
* @since 2.3
*/
public class EntityInstantiators {
private final EntityInstantiator fallback;
private final Map<Class<?>, EntityInstantiator> customInstantiators;
/**
* Creates a new {@link EntityInstantiators} using the default fallback instantiator and no custom ones.
*/
public EntityInstantiators() {
this(Collections.emptyMap());
}
/**
* Creates a new {@link EntityInstantiators} using the given {@link EntityInstantiator} as fallback.
*
* @param fallback must not be {@literal null}.
*/
public EntityInstantiators(EntityInstantiator fallback) {
this(fallback, Collections.emptyMap());
}
/**
* Creates a new {@link EntityInstantiators} using the default fallback instantiator and the given custom ones.
*
* @param customInstantiators must not be {@literal null}.
*/
public EntityInstantiators(Map<Class<?>, EntityInstantiator> customInstantiators) {
this(new KotlinClassGeneratingEntityInstantiator(), customInstantiators);
}
/**
* Creates a new {@link EntityInstantiator} using the given fallback {@link EntityInstantiator} and the given custom
* ones.
*
* @param defaultInstantiator must not be {@literal null}.
* @param customInstantiators must not be {@literal null}.
*/
public EntityInstantiators(EntityInstantiator defaultInstantiator,
Map<Class<?>, EntityInstantiator> customInstantiators) {
Assert.notNull(defaultInstantiator, "DefaultInstantiator must not be null!");
Assert.notNull(customInstantiators, "CustomInstantiators must not be null!");
this.fallback = defaultInstantiator;
this.customInstantiators = customInstantiators;
}
/**
* Returns the {@link EntityInstantiator} to be used to create the given {@link PersistentEntity}.
*
* @param entity must not be {@literal null}.
* @return will never be {@literal null}.
*/
public EntityInstantiator getInstantiatorFor(PersistentEntity<?, ?> entity) {
Assert.notNull(entity, "Entity must not be null!");
Class<?> type = entity.getType();
if (!customInstantiators.containsKey(type)) {
if(entity.getTypeInformation() instanceof StaticTypeInformation) {
EntityInstantiator instantiator = ((StaticTypeInformation)entity.getTypeInformation()).getInstantiator();
return instantiator != null ? instantiator : fallback;
}
return fallback;
}
EntityInstantiator instantiator = customInstantiators.get(entity.getType());
return instantiator == null ? fallback : instantiator;
}
}

View File

@@ -0,0 +1,337 @@
/*
* Copyright 2016-2020 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
*
* https://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.mapping.model;
import java.beans.FeatureDescriptor;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
import org.springframework.data.util.Lazy;
import org.springframework.data.util.Optionals;
import org.springframework.data.util.TypeInformation;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
/**
* Value object to abstract the concept of a property backed by a {@link Field} and / or a {@link PropertyDescriptor}.
*
* @author Oliver Gierke
* @author Christoph Strobl
* @author Mark Paluch
*/
public class Property {
private @Nullable TypeInformation<?> typeInformation;
private final Optional<Field> field;
private final Optional<PropertyDescriptor> descriptor;
private final Class<?> rawType;
private final Lazy<Integer> hashCode;
private final Optional<Method> getter;
private final Optional<Method> setter;
private final Lazy<String> name;
private final Lazy<String> toString;
private final Lazy<Optional<Method>> wither;
private Property(String name, TypeInformation<?> typeInformation) {
this.typeInformation = typeInformation;
this.field = Optional.empty();
this.descriptor = Optional.empty();
this.rawType = typeInformation.getType();
this.hashCode = Lazy.of( () -> typeInformation.hashCode() + name.hashCode());
this.getter = Optional.empty();
this.setter = Optional.empty();
this.name = Lazy.of(name);
this.toString = Lazy.of(() -> typeInformation.toString() + name);
this.wither = Lazy.of(() -> findWither(typeInformation, getName(), getType()));
}
private Property(TypeInformation<?> type, Optional<Field> field, Optional<PropertyDescriptor> descriptor) {
Assert.notNull(type, "Type must not be null!");
Assert.isTrue(Optionals.isAnyPresent(field, descriptor), "Either field or descriptor has to be given!");
this.field = field;
this.descriptor = descriptor;
this.rawType = withFieldOrDescriptor( //
it -> type.getRequiredProperty(it.getName()).getType(), //
it -> type.getRequiredProperty(it.getName()).getType() //
);
this.hashCode = Lazy.of(() -> withFieldOrDescriptor(Object::hashCode));
this.name = Lazy.of(() -> withFieldOrDescriptor(Field::getName, FeatureDescriptor::getName));
this.toString = Lazy.of(() -> withFieldOrDescriptor(Object::toString,
it -> String.format("%s.%s", type.getType().getName(), it.getDisplayName())));
this.getter = descriptor.map(PropertyDescriptor::getReadMethod)//
.filter(it -> getType() != null)//
.filter(it -> getType().isAssignableFrom(type.getReturnType(it).getType()));
this.setter = descriptor.map(PropertyDescriptor::getWriteMethod)//
.filter(it -> getType() != null)//
.filter(it -> type.getParameterTypes(it).get(0).getType().isAssignableFrom(getType()));
this.wither = Lazy.of(() -> findWither(type, getName(), getType()));
}
/**
* Creates a new {@link Property} backed by the given field.
*
* @param type the owning type, must not be {@literal null}.
* @param field must not be {@literal null}.
* @return
*/
public static Property of(TypeInformation<?> type, Field field) {
Assert.notNull(field, "Field must not be null!");
return new Property(type, Optional.of(field), Optional.empty());
}
/**
* Creates a new {@link Property} backed by the given {@link Field} and {@link PropertyDescriptor}.
*
* @param type the owning type, must not be {@literal null}.
* @param field must not be {@literal null}.
* @param descriptor must not be {@literal null}.
* @return
*/
public static Property of(TypeInformation<?> type, Field field, PropertyDescriptor descriptor) {
Assert.notNull(field, "Field must not be null!");
Assert.notNull(descriptor, "PropertyDescriptor must not be null!");
return new Property(type, Optional.of(field), Optional.of(descriptor));
}
/**
* Creates a new {@link Property} backed by the given {@link Field} and {@link PropertyDescriptor}.
*
* @param type the owning type, must not be {@literal null}.
* @param field must not be {@literal null}.
* @param descriptor must not be {@literal null}.
* @return
*/
public static Property of(TypeInformation<?> type, String name) {
return new Property(name, type);
}
/**
* Creates a new {@link Property} for the given {@link PropertyDescriptor}. The creation might fail if the given
* property is not representing a proper property.
*
* @param type the owning type, must not be {@literal null}.
* @param descriptor must not be {@literal null}.
* @return
* @see #supportsStandalone(PropertyDescriptor)
*/
public static Property of(TypeInformation<?> type, PropertyDescriptor descriptor) {
Assert.notNull(descriptor, "PropertyDescriptor must not be null!");
return new Property(type, Optional.empty(), Optional.of(descriptor));
}
/**
* Returns whether the given {@link PropertyDescriptor} is supported in for standalone creation of a {@link Property}
* instance.
*
* @param descriptor
* @return
*/
public static boolean supportsStandalone(PropertyDescriptor descriptor) {
Assert.notNull(descriptor, "PropertyDescriptor must not be null!");
return descriptor.getPropertyType() != null;
}
/**
* Returns whether the property is backed by a field.
*
* @return
*/
public boolean isFieldBacked() {
return field.isPresent();
}
/**
* Returns the getter of the property if available and if it matches the type of the property.
*
* @return will never be {@literal null}.
*/
public Optional<Method> getGetter() {
return getter;
}
/**
* Returns the setter of the property if available and if its first (only) parameter matches the type of the property.
*
* @return will never be {@literal null}.
*/
public Optional<Method> getSetter() {
return setter;
}
/**
* Returns the wither of the property if available and if its first (only) parameter matches the type of the property.
*
* @return will never be {@literal null}.
*/
public Optional<Method> getWither() {
return wither.get();
}
/**
* Returns the field of the property if available and if its first (only) parameter matches the type of the property.
*
* @return will never be {@literal null}.
*/
public Optional<Field> getField() {
return this.field;
}
/**
* Returns whether the property exposes a getter or a setter.
*
* @return
*/
public boolean hasAccessor() {
return getGetter().isPresent() || getSetter().isPresent();
}
/**
* Returns the name of the property.
*
* @return will never be {@literal null}.
*/
public String getName() {
return this.name.get();
}
/**
* Returns the type of the property.
*
* @return will never be {@literal null}.
*/
public Class<?> getType() {
return rawType;
}
/*
* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(@Nullable Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof Property)) {
return false;
}
Property that = (Property) obj;
if(this.typeInformation != null && that.typeInformation != null) {
if(this.typeInformation != that.typeInformation) {
return false;
}
if(!this.name.get().equals(that.name.get())) {
return false;
}
}
return this.field.isPresent() ? this.field.equals(that.field) : this.descriptor.equals(that.descriptor);
}
/*
* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
return hashCode.get();
}
/*
* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return toString.get();
}
/**
* Maps the backing {@link Field} or {@link PropertyDescriptor} using the given {@link Function}.
*
* @param function must not be {@literal null}.
* @return
*/
private <T> T withFieldOrDescriptor(Function<Object, T> function) {
return withFieldOrDescriptor(function, function);
}
/**
* Maps the backing {@link Field} or {@link PropertyDescriptor} using the given functions.
*
* @param field must not be {@literal null}.
* @param descriptor must not be {@literal null}.
* @return
*/
private <T> T withFieldOrDescriptor(Function<? super Field, T> field,
Function<? super PropertyDescriptor, T> descriptor) {
return Optionals.firstNonEmpty(//
() -> this.field.map(field), //
() -> this.descriptor.map(descriptor))//
.orElseThrow(() -> new IllegalStateException("Should not occur! Either field or descriptor has to be given"));
}
private static Optional<Method> findWither(TypeInformation<?> owner, String propertyName, Class<?> rawType) {
AtomicReference<Method> resultHolder = new AtomicReference<>();
String methodName = String.format("with%s", StringUtils.capitalize(propertyName));
ReflectionUtils.doWithMethods(owner.getType(), it -> {
if (owner.isAssignableFrom(owner.getReturnType(it))) {
resultHolder.set(it);
}
}, it -> isMethodWithSingleParameterOfType(it, methodName, rawType));
Method method = resultHolder.get();
return method != null ? Optional.of(method) : Optional.empty();
}
private static boolean isMethodWithSingleParameterOfType(Method method, String name, Class<?> type) {
return method.getParameterCount() == 1 //
&& method.getName().equals(name) //
&& method.getParameterTypes()[0].equals(type);
}
}

View File

@@ -0,0 +1,106 @@
/*
* Copyright 2020. 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.
*/
/*
* Copyright 2020 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.mapping.model;
import java.util.function.BiFunction;
import java.util.function.Function;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.PersistentPropertyAccessor;
import org.springframework.data.util.StaticTypeInformation;
import org.springframework.lang.Nullable;
/**
* @author Christoph Strobl
* @since 2020/10
*/
public class StaticPropertyAccessorFactory implements PersistentPropertyAccessorFactory {
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.model.PersistentPropertyAccessorFactory#getPropertyAccessor(org.springframework.data.mapping.PersistentEntity, java.lang.Object)
*/
@Override
public <T> PersistentPropertyAccessor<T> getPropertyAccessor(PersistentEntity<?, ?> entity, T bean) {
return new StaticPropertyAccessor<>((StaticTypeInformation<T>) entity.getTypeInformation(), bean);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.model.PersistentPropertyAccessorFactory#isSupported(org.springframework.data.mapping.PersistentEntity)
*/
@Override
public boolean isSupported(PersistentEntity<?, ?> entity) {
boolean isStaticTypedEntity = entity.getTypeInformation() instanceof StaticTypeInformation;
System.out.println(entity.getName() + " isStaticTypedEntity: " + isStaticTypedEntity);
return isStaticTypedEntity;
}
static class StaticPropertyAccessor<T> implements PersistentPropertyAccessor<T> {
T bean;
StaticTypeInformation<T> typeInformation;
public StaticPropertyAccessor(StaticTypeInformation<T> typeInformation, T bean) {
this.bean = bean;
this.typeInformation = typeInformation;
}
@Override
public void setProperty(PersistentProperty<?> property, @Nullable Object value) {
BiFunction<T, Object, T> setFunction = typeInformation.getSetter().get(property.getName());
if (setFunction == null) {
return;
}
this.bean = setFunction.apply(bean, value);
}
@Nullable
@Override
public Object getProperty(PersistentProperty<?> property) {
Function<T, Object> getFunction = typeInformation.getGetter().get(property.getName());
if (getFunction == null) {
return null;
}
return getFunction.apply(bean);
}
@Override
public T getBean() {
return this.bean;
}
}
}

View File

@@ -0,0 +1,71 @@
/*
* Copyright 2020. 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.
*/
/*
* Copyright 2020 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;
/**
* @author Christoph Strobl
* @since 2020/10
*/
public class Address {
String city;
String street;
public Address(String city, String street) {
this.city = city;
this.street = street;
}
public String getCity() {
return city;
}
public String getStreet() {
return street;
}
public void setCity(String city) {
this.city = city;
}
public void setStreet(String street) {
this.street = street;
}
@Override
public String toString() {
return "Address{" +
"city='" + city + '\'' +
", street='" + street + '\'' +
'}';
}
}

View File

@@ -0,0 +1,100 @@
/*
* Copyright 2020. 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.
*/
/*
* Copyright 2020 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.annotation.Annotation;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.BiFunction;
import java.util.function.Function;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.PreferredConstructor.Parameter;
import org.springframework.data.mapping.model.EntityInstantiator;
import org.springframework.data.mapping.model.ParameterValueProvider;
/**
* @author Christoph Strobl
* @since 2020/10
*/
public class AddressTypeInformation extends StaticTypeInformation<Address> {
public AddressTypeInformation() {
super(Address.class);
}
@Override
protected Map<String, TypeInformation<?>> computePropertiesMap() {
Map<String, TypeInformation<?>> properties = new LinkedHashMap<>();
properties.put("city", new StringTypeInformation());
properties.put("street", new StringTypeInformation());
return properties;
}
@Override
protected Map<String, Function<Address, Object>> computeGetter() {
Map<String, Function<Address, Object>> getters = new LinkedHashMap<>();
getters.put("city", Address::getCity);
getters.put("street", Address::getStreet);
return getters;
}
@Override
protected Map<String, BiFunction<Address, Object, Address>> computeSetter() {
Map<String, BiFunction<Address, Object, Address>> setter = new LinkedHashMap<>();
setter.put("city", (bean, id) -> {bean.setCity((String)id); return bean;});
setter.put("street", (bean, id) -> {bean.setStreet((String)id); return bean;});
return setter;
}
@Override
protected EntityInstantiator computeEntityInstantiator() {
return new EntityInstantiator() {
@Override
public <T, E extends PersistentEntity<? extends T, P>, P extends PersistentProperty<P>> T createInstance(E entity,
ParameterValueProvider<P> provider) {
String city = (String) provider
.getParameterValue(new Parameter("city", new StringTypeInformation(), new Annotation[]{}, entity));
String street = (String) provider
.getParameterValue(new Parameter("street", new StringTypeInformation(), new Annotation[]{}, entity));
return (T) new Address(city, street);
}
};
}
}

View File

@@ -0,0 +1,191 @@
/*
* Copyright 2011-2020 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
*
* https://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.reflect.Method;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.springframework.core.GenericTypeResolver;
import org.springframework.util.Assert;
import org.springframework.util.ConcurrentReferenceHashMap;
import org.springframework.util.ConcurrentReferenceHashMap.ReferenceType;
/**
* {@link TypeInformation} for a plain {@link Class}.
*
* @author Oliver Gierke
* @author Christoph Strobl
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public class ClassTypeInformation<S> extends TypeDiscoverer<S> {
public static final ClassTypeInformation<Collection> COLLECTION = new ClassTypeInformation(Collection.class);
public static final ClassTypeInformation<List> LIST = new ClassTypeInformation(List.class);
public static final ClassTypeInformation<Set> SET = new ClassTypeInformation(Set.class);
public static final ClassTypeInformation<Map> MAP = new ClassTypeInformation(Map.class);
public static final ClassTypeInformation<Object> OBJECT = new ClassTypeInformation(Object.class);
private static final Map<Class<?>, ClassTypeInformation<?>> cache = new ConcurrentReferenceHashMap<>(64,
ReferenceType.WEAK);
static {
Arrays.asList(COLLECTION, LIST, SET, MAP, OBJECT).forEach(it -> cache.put(it.getType(), it));
}
private final Class<S> type;
public static void warmCache(ClassTypeInformation<?>... typeInformations) {
for(ClassTypeInformation<?> information : typeInformations) {
cache.put(information.getType(), information);
}
}
/**
* Simple factory method to easily create new instances of {@link ClassTypeInformation}.
*
* @param <S>
* @param type must not be {@literal null}.
* @return
*/
public static <S> ClassTypeInformation<S> from(Class<S> type) {
Assert.notNull(type, "Type must not be null!");
return (ClassTypeInformation<S>) cache.computeIfAbsent(type, ClassTypeInformation::new);
}
/**
* Creates a {@link TypeInformation} from the given method's return type.
*
* @param method must not be {@literal null}.
* @return
*/
public static <S> TypeInformation<S> fromReturnTypeOf(Method method) {
Assert.notNull(method, "Method must not be null!");
return (TypeInformation<S>) ClassTypeInformation.from(method.getDeclaringClass())
.createInfo(method.getGenericReturnType());
}
/**
* Creates {@link ClassTypeInformation} for the given type.
*
* @param type
*/
ClassTypeInformation(Class<S> type) {
super(ProxyUtils.getUserClass(type), getTypeVariableMap(type));
this.type = type;
}
ClassTypeInformation(Class<S> type, TypeInformation<?> componentType, TypeInformation<?> keyType) {
super(type, componentType, keyType);
this.type = type;
}
/**
* Little helper to allow us to create a generified map, actually just to satisfy the compiler.
*
* @param type must not be {@literal null}.
* @return
*/
private static Map<TypeVariable<?>, Type> getTypeVariableMap(Class<?> type) {
return getTypeVariableMap(type, new HashSet<>());
}
private static Map<TypeVariable<?>, Type> getTypeVariableMap(Class<?> type, Collection<Type> visited) {
if (visited.contains(type)) {
return Collections.emptyMap();
} else {
visited.add(type);
}
Map<TypeVariable, Type> source = GenericTypeResolver.getTypeVariableMap(type);
Map<TypeVariable<?>, Type> map = new HashMap<>(source.size());
for (Entry<TypeVariable, Type> entry : source.entrySet()) {
Type value = entry.getValue();
map.put(entry.getKey(), entry.getValue());
if (value instanceof Class) {
for (Entry<TypeVariable<?>, Type> nestedEntry : getTypeVariableMap((Class<?>) value, visited).entrySet()) {
if (!map.containsKey(nestedEntry.getKey())) {
map.put(nestedEntry.getKey(), nestedEntry.getValue());
}
}
}
}
return map;
}
/*
* (non-Javadoc)
* @see org.springframework.data.util.TypeDiscoverer#getType()
*/
@Override
public Class<S> getType() {
return type;
}
/*
* (non-Javadoc)
* @see org.springframework.data.util.TypeDiscoverer#getRawTypeInformation()
*/
@Override
public ClassTypeInformation<?> getRawTypeInformation() {
return this;
}
/*
* (non-Javadoc)
* @see org.springframework.data.util.TypeDiscoverer#isAssignableFrom(org.springframework.data.util.TypeInformation)
*/
@Override
public boolean isAssignableFrom(TypeInformation<?> target) {
return getType().isAssignableFrom(target.getType());
}
/*
* (non-Javadoc)
* @see org.springframework.data.util.TypeDiscoverer#specialize(org.springframework.data.util.ClassTypeInformation)
*/
@Override
public TypeInformation<? extends S> specialize(ClassTypeInformation<?> type) {
return (TypeInformation<? extends S>) type;
}
/*
* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return type.getName();
}
}

View File

@@ -0,0 +1,50 @@
/*
* Copyright 2020. 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.
*/
/*
* Copyright 2020 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.util.List;
/**
* @author Christoph Strobl
* @since 2020/10
*/
public class ListTypeInformation extends StaticTypeInformation<List> {
public ListTypeInformation(TypeInformation<?> componentType) {
super(List.class, componentType, null);
}
@Override
public boolean isCollectionLike() {
return true;
}
}

View File

@@ -0,0 +1,112 @@
/*
* Copyright 2020. 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.
*/
/*
* Copyright 2020 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.util.List;
/**
* @author Christoph Strobl
* @since 2020/10
*/
public class Person {
private long id;
private String firstname, lastname; // TODO: we need a persistence constructor to resolve this here.
private int age;
private Address address;
private List<String> nicknames;
public Person(String firstname, String lastname) {
this.firstname = firstname;
this.lastname = lastname;
}
public String getFirstname() {
return firstname;
}
public String getLastname() {
return lastname;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public long getId() {
return id;
}
public void setId(long id) {
id = id;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
public List<String> getNicknames() {
return nicknames;
}
public void setNicknames(List<String> nicknames) {
this.nicknames = nicknames;
}
public void setFirstname(String firstname) {
this.firstname = firstname;
}
public void setLastname(String lastname) {
this.lastname = lastname;
}
@Override
public String toString() {
return "Person{" +
"id=" + id +
", firstname='" + firstname + '\'' +
", lastname='" + lastname + '\'' +
", age=" + age +
", address=" + address +
", nicknames=" + nicknames +
'}';
}
}

View File

@@ -0,0 +1,119 @@
/*
* Copyright 2020. 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.
*/
/*
* Copyright 2020 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.annotation.Annotation;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.BiFunction;
import java.util.function.Function;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.PreferredConstructor.Parameter;
import org.springframework.data.mapping.model.EntityInstantiator;
import org.springframework.data.mapping.model.ParameterValueProvider;
/**
* @author Christoph Strobl
* @since 2020/10
*/
public class PersonTypeInformation extends StaticTypeInformation<Person> {
public PersonTypeInformation() {
super(Person.class);
}
@Override
protected Map<String, TypeInformation<?>> computePropertiesMap() {
LinkedHashMap<String, TypeInformation<?>> properties = new LinkedHashMap<>();
properties.put("firstname", new StringTypeInformation());
properties.put("lastname", new StringTypeInformation());
properties.put("id", new StaticTypeInformation<>(Long.class));
properties.put("age", new StaticTypeInformation<>(int.class));
properties.put("address", new AddressTypeInformation());
properties.put("nicknames", new ListTypeInformation(new StringTypeInformation()));
return properties;
}
@Override
protected Map<String, BiFunction<Person, Object, Person>> computeSetter() {
Map<String, BiFunction<Person, Object, Person>> setter = new LinkedHashMap<>();
setter.put("id", (bean, id) -> {bean.setId((Long)id); return bean;});
setter.put("age", (bean, id) -> {bean.setAge((int)id); return bean;});
setter.put("firstname", (bean, id) -> {bean.setFirstname((String)id); return bean;});
setter.put("lastname", (bean, id) -> {bean.setLastname((String)id); return bean;});
setter.put("address", (bean, id) -> {bean.setAddress((Address) id); return bean;});
setter.put("nicknames", (bean, id) -> {bean.setNicknames((List<String>) id); return bean;});
return setter;
}
@Override
protected Map<String, Function<Person, Object>> computeGetter() {
Map<String, Function<Person, Object>> getter = new LinkedHashMap<>();
getter.put("firstname", Person::getFirstname);
getter.put("lastname", Person::getLastname);
getter.put("id", Person::getId);
getter.put("address", Person::getAddress);
getter.put("nicknames", Person::getNicknames);
getter.put("age", Person::getAge);
return getter;
}
@Override
protected EntityInstantiator computeEntityInstantiator() {
return new EntityInstantiator() {
@Override
public <T, E extends PersistentEntity<? extends T, P>, P extends PersistentProperty<P>> T createInstance(E entity,
ParameterValueProvider<P> provider) {
String firstname = (String) provider
.getParameterValue(new Parameter("firstanme", new StringTypeInformation(), new Annotation[] {}, entity));
String lastname = (String) provider
.getParameterValue(new Parameter("firstanme", new StringTypeInformation(), new Annotation[] {}, entity));
return (T) new Person(firstname, lastname);
}
};
}
}

View File

@@ -0,0 +1,245 @@
/*
* Copyright 2020. 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.
*/
/*
* Copyright 2020 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.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.function.BiFunction;
import java.util.function.Function;
import org.springframework.data.mapping.PreferredConstructor;
import org.springframework.data.mapping.model.EntityInstantiator;
import org.springframework.lang.Nullable;
import org.springframework.util.ObjectUtils;
/**
* @author Christoph Strobl
* @since 2020/10
*/
public class StaticTypeInformation<S> extends ClassTypeInformation<S> {
private final Class<S> type;
@Nullable private final TypeInformation<?> componentType;
@Nullable private final TypeInformation<?> keyType;
private StaticTypeInformation<?> superTypeInformation;
private List<TypeInformation<?>> typeArguments;
private final Map<String, TypeInformation<?>> properties;
private final Map<String, BiFunction<S,Object,S>> setter;
private final Map<String, Function<S,Object>> getter;
private EntityInstantiator instantiator;
public StaticTypeInformation(Class<S> type) {
this(type, null, null);
}
public StaticTypeInformation(Class<S> type, @Nullable TypeInformation<?> componentType,
@Nullable TypeInformation<?> keyType) {
super(type, componentType, keyType);
this.type = type;
this.componentType = componentType;
this.keyType = keyType;
this.properties = computePropertiesMap();
this.typeArguments = computeTypeArguments();
this.instantiator = computeEntityInstantiator();
this.setter = computeSetter();
this.getter = computeGetter();
}
protected Map<String, TypeInformation<?>> computePropertiesMap() {
return Collections.emptyMap();
};
protected List<TypeInformation<?>> computeTypeArguments() {
return Collections.emptyList();
}
protected EntityInstantiator computeEntityInstantiator() {
return null;
}
protected Map<String, BiFunction<S,Object,S>> computeSetter() {
return Collections.emptyMap();
}
protected Map<String, Function<S,Object>> computeGetter() {
return Collections.emptyMap();
}
public Map<String, TypeInformation<?>> getProperties() {
return properties;
}
public Map<String, BiFunction<S, Object, S>> getSetter() {
return setter;
}
public Map<String, Function<S, Object>> getGetter() {
return getter;
}
public EntityInstantiator getInstantiator() {
return instantiator;
}
@Override
public List<TypeInformation<?>> getParameterTypes(Constructor<?> constructor) {
return null;
}
@Nullable
@Override
public TypeInformation<?> getProperty(String property) {
return properties.get(property);
}
@Override
public boolean isCollectionLike() {
return false;
}
@Override
public boolean isMap() {
return false;
}
@Nullable
@Override
public TypeInformation<?> getMapValueType() {
return componentType;
}
@Override
public Class<S> getType() {
return type;
}
@Override
public ClassTypeInformation<?> getRawTypeInformation() {
return this;
}
@Nullable
@Override
public TypeInformation<?> getActualType() {
return componentType != null ? componentType : this;
}
@Override
public TypeInformation<?> getReturnType(Method method) {
return null;
}
@Override
public List<TypeInformation<?>> getParameterTypes(Method method) {
return Collections.emptyList();
}
@Nullable
@Override
public TypeInformation<?> getSuperTypeInformation(Class<?> superType) {
return superTypeInformation;
}
@Override
public boolean isAssignableFrom(TypeInformation<?> target) {
return this.type.isAssignableFrom(target.getType());
}
@Override
public List<TypeInformation<?>> getTypeArguments() {
return typeArguments;
}
@Override
public TypeInformation<? extends S> specialize(ClassTypeInformation<?> type) {
return null;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (!super.equals(o)) return false;
StaticTypeInformation<?> that = (StaticTypeInformation<?>) o;
if (!ObjectUtils.nullSafeEquals(type, that.type)) {
return false;
}
if (!ObjectUtils.nullSafeEquals(componentType, that.componentType)) {
return false;
}
if (!ObjectUtils.nullSafeEquals(keyType, that.keyType)) {
return false;
}
if (!ObjectUtils.nullSafeEquals(superTypeInformation, that.superTypeInformation)) {
return false;
}
if (!ObjectUtils.nullSafeEquals(typeArguments, that.typeArguments)) {
return false;
}
if (!ObjectUtils.nullSafeEquals(properties, that.properties)) {
return false;
}
if (!ObjectUtils.nullSafeEquals(setter, that.setter)) {
return false;
}
if (!ObjectUtils.nullSafeEquals(getter, that.getter)) {
return false;
}
return ObjectUtils.nullSafeEquals(instantiator, that.instantiator);
}
@Override
public int hashCode() {
int result = super.hashCode();
result = 31 * result + ObjectUtils.nullSafeHashCode(type);
result = 31 * result + ObjectUtils.nullSafeHashCode(componentType);
result = 31 * result + ObjectUtils.nullSafeHashCode(keyType);
result = 31 * result + ObjectUtils.nullSafeHashCode(superTypeInformation);
result = 31 * result + ObjectUtils.nullSafeHashCode(typeArguments);
result = 31 * result + ObjectUtils.nullSafeHashCode(properties);
result = 31 * result + ObjectUtils.nullSafeHashCode(setter);
result = 31 * result + ObjectUtils.nullSafeHashCode(getter);
result = 31 * result + ObjectUtils.nullSafeHashCode(instantiator);
return result;
}
PreferredConstructor
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2020. 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.
*/
/*
* Copyright 2020 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;
/**
* @author Christoph Strobl
* @since 2020/10
*/
public class StaticTypeInformationProvider {
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright 2020. 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.
*/
/*
* Copyright 2020 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;
/**
* @author Christoph Strobl
* @since 2020/10
*/
public class StringTypeInformation extends StaticTypeInformation<String> {
public StringTypeInformation() {
super(String.class);
}
}

View File

@@ -0,0 +1,672 @@
/*
* Copyright 2011-2020 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
*
* https://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.beans.PropertyDescriptor;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import org.springframework.beans.BeanUtils;
import org.springframework.core.GenericTypeResolver;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils;
/**
* Basic {@link TypeDiscoverer} that contains basic functionality to discover property types.
*
* @author Oliver Gierke
* @author Christoph Strobl
* @author Mark Paluch
*/
class TypeDiscoverer<S> implements TypeInformation<S> {
private static final Class<?>[] MAP_TYPES;
static {
ClassLoader classLoader = TypeDiscoverer.class.getClassLoader();
Set<Class<?>> mapTypes = new HashSet<>();
mapTypes.add(Map.class);
try {
mapTypes.add(ClassUtils.forName("io.vavr.collection.Map", classLoader));
} catch (ClassNotFoundException o_O) {}
MAP_TYPES = mapTypes.toArray(new Class[0]);
}
private final Type type;
private final Map<TypeVariable<?>, Type> typeVariableMap;
private final Map<String, Optional<TypeInformation<?>>> fieldTypes = new ConcurrentHashMap<>();
private final int hashCode;
private final Lazy<Class<S>> resolvedType;
private final Lazy<TypeInformation<?>> componentType;
private final Lazy<TypeInformation<?>> valueType;
/**
* Creates a new {@link TypeDiscoverer} for the given type, type variable map and parent.
*
* @param type must not be {@literal null}.
* @param typeVariableMap must not be {@literal null}.
*/
protected TypeDiscoverer(Type type, Map<TypeVariable<?>, Type> typeVariableMap) {
Assert.notNull(type, "Type must not be null!");
Assert.notNull(typeVariableMap, "TypeVariableMap must not be null!");
this.type = type;
this.resolvedType = Lazy.of(() -> resolveType(type));
this.componentType = Lazy.of(this::doGetComponentType);
this.valueType = Lazy.of(this::doGetMapValueType);
this.typeVariableMap = typeVariableMap;
this.hashCode = 17 + 31 * type.hashCode() + 31 * typeVariableMap.hashCode();
}
protected TypeDiscoverer(Class<?> type, TypeInformation<?> componentType, TypeInformation<?> keyType) {
this.type = null;
this.typeVariableMap = Collections.emptyMap();
this.hashCode = 17 + 31 * type.hashCode();
this.resolvedType = Lazy.of((Class<S>) type);
this.componentType = componentType == null ? Lazy.empty() : Lazy.of(componentType);
this.valueType = keyType == null ? Lazy.empty() : Lazy.of(keyType);
}
/**
* Returns the type variable map.
*
* @return
*/
protected Map<TypeVariable<?>, Type> getTypeVariableMap() {
return typeVariableMap;
}
/**
* Creates {@link TypeInformation} for the given {@link Type}.
*
* @param fieldType must not be {@literal null}.
* @return
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
protected TypeInformation<?> createInfo(Type fieldType) {
Assert.notNull(fieldType, "Field type must not be null!");
if (fieldType.equals(this.type)) {
return this;
}
if (fieldType instanceof Class) {
return ClassTypeInformation.from((Class<?>) fieldType);
}
if (fieldType instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) fieldType;
return new ParameterizedTypeInformation(parameterizedType, this);
}
if (fieldType instanceof TypeVariable) {
TypeVariable<?> variable = (TypeVariable<?>) fieldType;
return new TypeVariableTypeInformation(variable, this);
}
if (fieldType instanceof GenericArrayType) {
return new GenericArrayTypeInformation((GenericArrayType) fieldType, this);
}
if (fieldType instanceof WildcardType) {
WildcardType wildcardType = (WildcardType) fieldType;
Type[] bounds = wildcardType.getLowerBounds();
if (bounds.length > 0) {
return createInfo(bounds[0]);
}
bounds = wildcardType.getUpperBounds();
if (bounds.length > 0) {
return createInfo(bounds[0]);
}
}
throw new IllegalArgumentException();
}
/**
* Resolves the given type into a plain {@link Class}.
*
* @param type
* @return
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
protected Class<S> resolveType(Type type) {
Map<TypeVariable, Type> map = new HashMap<>();
map.putAll(getTypeVariableMap());
return (Class<S>) GenericTypeResolver.resolveType(type, map);
}
/*
* (non-Javadoc)
* @see org.springframework.data.util.TypeInformation#getParameterTypes(java.lang.reflect.Constructor)
*/
public List<TypeInformation<?>> getParameterTypes(Constructor<?> constructor) {
Assert.notNull(constructor, "Constructor must not be null!");
Type[] types = constructor.getGenericParameterTypes();
List<TypeInformation<?>> result = new ArrayList<>(types.length);
for (Type parameterType : types) {
result.add(createInfo(parameterType));
}
return result;
}
/*
* (non-Javadoc)
* @see org.springframework.data.util.TypeInformation#getProperty(java.lang.String)
*/
@Nullable
public TypeInformation<?> getProperty(String fieldname) {
int separatorIndex = fieldname.indexOf('.');
if (separatorIndex == -1) {
return fieldTypes.computeIfAbsent(fieldname, this::getPropertyInformation).orElse(null);
}
String head = fieldname.substring(0, separatorIndex);
TypeInformation<?> info = getProperty(head);
if (info == null) {
return null;
}
return info.getProperty(fieldname.substring(separatorIndex + 1));
}
/**
* Returns the {@link TypeInformation} for the given atomic field. Will inspect fields first and return the type of a
* field if available. Otherwise it will fall back to a {@link PropertyDescriptor}.
*
* @see #getGenericType(PropertyDescriptor)
* @param fieldname
* @return
*/
@SuppressWarnings("null")
private Optional<TypeInformation<?>> getPropertyInformation(String fieldname) {
Class<?> rawType = getType();
Field field = ReflectionUtils.findField(rawType, fieldname);
if (field != null) {
return Optional.of(createInfo(field.getGenericType()));
}
return findPropertyDescriptor(rawType, fieldname).map(it -> createInfo(getGenericType(it)));
}
/**
* Finds the {@link PropertyDescriptor} for the property with the given name on the given type.
*
* @param type must not be {@literal null}.
* @param fieldname must not be {@literal null} or empty.
* @return
*/
private static Optional<PropertyDescriptor> findPropertyDescriptor(Class<?> type, String fieldname) {
PropertyDescriptor descriptor = BeanUtils.getPropertyDescriptor(type, fieldname);
if (descriptor != null) {
return Optional.of(descriptor);
}
List<Class<?>> superTypes = new ArrayList<>();
superTypes.addAll(Arrays.asList(type.getInterfaces()));
superTypes.add(type.getSuperclass());
return Streamable.of(type.getInterfaces()).stream()//
.flatMap(it -> Optionals.toStream(findPropertyDescriptor(it, fieldname)))//
.findFirst();
}
/**
* Returns the generic type for the given {@link PropertyDescriptor}. Will inspect its read method followed by the
* first parameter of the write method.
*
* @param descriptor must not be {@literal null}
* @return
*/
@Nullable
private static Type getGenericType(PropertyDescriptor descriptor) {
Method method = descriptor.getReadMethod();
if (method != null) {
return method.getGenericReturnType();
}
method = descriptor.getWriteMethod();
if (method == null) {
return null;
}
Type[] parameterTypes = method.getGenericParameterTypes();
return parameterTypes.length == 0 ? null : parameterTypes[0];
}
/*
* (non-Javadoc)
* @see org.springframework.data.util.TypeInformation#getType()
*/
public Class<S> getType() {
return resolvedType.get();
}
/*
* (non-Javadoc)
* @see org.springframework.data.util.TypeInformation#getRawTypeInformation()
*/
@Override
public ClassTypeInformation<?> getRawTypeInformation() {
return ClassTypeInformation.from(getType()).getRawTypeInformation();
}
/*
* (non-Javadoc)
* @see org.springframework.data.util.TypeInformation#getActualType()
*/
@Nullable
public TypeInformation<?> getActualType() {
if (isMap()) {
return getMapValueType();
}
if (isCollectionLike()) {
return getComponentType();
}
return this;
}
/*
* (non-Javadoc)
* @see org.springframework.data.util.TypeInformation#isMap()
*/
public boolean isMap() {
Class<S> type = getType();
for (Class<?> mapType : MAP_TYPES) {
if (mapType.isAssignableFrom(type)) {
return true;
}
}
return false;
}
/*
* (non-Javadoc)
* @see org.springframework.data.util.TypeInformation#getMapValueType()
*/
@Nullable
public TypeInformation<?> getMapValueType() {
return valueType.orElse(null);
}
@Nullable
protected TypeInformation<?> doGetMapValueType() {
return isMap() ? getTypeArgument(getBaseType(MAP_TYPES), 1)
: getTypeArguments().stream().skip(1).findFirst().orElse(null);
}
/*
* (non-Javadoc)
* @see org.springframework.data.util.TypeInformation#isCollectionLike()
*/
public boolean isCollectionLike() {
Class<?> rawType = getType();
return rawType.isArray() //
|| Iterable.class.equals(rawType) //
|| Collection.class.isAssignableFrom(rawType) //
|| Streamable.class.isAssignableFrom(rawType);
}
/*
* (non-Javadoc)
* @see org.springframework.data.util.TypeInformation#getComponentType()
*/
@Nullable
public final TypeInformation<?> getComponentType() {
return componentType.orElse(null);
}
@Nullable
protected TypeInformation<?> doGetComponentType() {
Class<S> rawType = getType();
if (rawType.isArray()) {
return createInfo(rawType.getComponentType());
}
if (isMap()) {
return getTypeArgument(getBaseType(MAP_TYPES), 0);
}
if (Iterable.class.isAssignableFrom(rawType)) {
return getTypeArgument(Iterable.class, 0);
}
List<TypeInformation<?>> arguments = getTypeArguments();
return arguments.size() > 0 ? arguments.get(0) : null;
}
/*
* (non-Javadoc)
* @see org.springframework.data.util.TypeInformation#getReturnType(java.lang.reflect.Method)
*/
public TypeInformation<?> getReturnType(Method method) {
Assert.notNull(method, "Method must not be null!");
return createInfo(method.getGenericReturnType());
}
/*
* (non-Javadoc)
* @see org.springframework.data.util.TypeInformation#getMethodParameterTypes(java.lang.reflect.Method)
*/
public List<TypeInformation<?>> getParameterTypes(Method method) {
Assert.notNull(method, "Method most not be null!");
return Streamable.of(method.getGenericParameterTypes()).stream()//
.map(this::createInfo)//
.collect(Collectors.toList());
}
/*
* (non-Javadoc)
* @see org.springframework.data.util.TypeInformation#getSuperTypeInformation(java.lang.Class)
*/
@Nullable
public TypeInformation<?> getSuperTypeInformation(Class<?> superType) {
Class<?> rawType = getType();
if (!superType.isAssignableFrom(rawType)) {
return null;
}
if (getType().equals(superType)) {
return this;
}
List<Type> candidates = new ArrayList<>();
Type genericSuperclass = rawType.getGenericSuperclass();
if (genericSuperclass != null) {
candidates.add(genericSuperclass);
}
candidates.addAll(Arrays.asList(rawType.getGenericInterfaces()));
for (Type candidate : candidates) {
TypeInformation<?> candidateInfo = createInfo(candidate);
if (superType.equals(candidateInfo.getType())) {
return candidateInfo;
} else {
TypeInformation<?> nestedSuperType = candidateInfo.getSuperTypeInformation(superType);
if (nestedSuperType != null) {
return nestedSuperType;
}
}
}
return null;
}
/*
* (non-Javadoc)
* @see org.springframework.data.util.TypeInformation#getTypeParameters()
*/
public List<TypeInformation<?>> getTypeArguments() {
return Collections.emptyList();
}
/* (non-Javadoc)
* @see org.springframework.data.util.TypeInformation#isAssignableFrom(org.springframework.data.util.TypeInformation)
*/
public boolean isAssignableFrom(TypeInformation<?> target) {
TypeInformation<?> superTypeInformation = target.getSuperTypeInformation(getType());
return superTypeInformation == null ? false : superTypeInformation.equals(this);
}
/*
* (non-Javadoc)
* @see org.springframework.data.util.TypeInformation#specialize(org.springframework.data.util.ClassTypeInformation)
*/
@Override
@SuppressWarnings("unchecked")
public TypeInformation<? extends S> specialize(ClassTypeInformation<?> type) {
Assert.notNull(type, "Type must not be null!");
Assert.isTrue(getType().isAssignableFrom(type.getType()),
() -> String.format("%s must be assignable from %s", getType(), type.getType()));
List<TypeInformation<?>> typeArguments = getTypeArguments();
return (TypeInformation<? extends S>) (typeArguments.isEmpty() //
? type //
: type.createInfo(new SyntheticParamterizedType(type, getTypeArguments())));
}
@Nullable
private TypeInformation<?> getTypeArgument(Class<?> bound, int index) {
Class<?>[] arguments = GenericTypeResolver.resolveTypeArguments(getType(), bound);
if (arguments != null) {
return createInfo(arguments[index]);
}
return getSuperTypeInformation(bound) instanceof ParameterizedTypeInformation //
? ClassTypeInformation.OBJECT //
: null;
}
private Class<?> getBaseType(Class<?>[] candidates) {
Class<S> type = getType();
for (Class<?> candidate : candidates) {
if (candidate.isAssignableFrom(type)) {
return candidate;
}
}
throw new IllegalArgumentException(String.format("Type %s not contained in candidates %s!", type, candidates));
}
/*
* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(@Nullable Object obj) {
if (obj == this) {
return true;
}
if (obj == null) {
return false;
}
if (!this.getClass().equals(obj.getClass())) {
return false;
}
TypeDiscoverer<?> that = (TypeDiscoverer<?>) obj;
if (!this.type.equals(that.type)) {
return false;
}
if (this.typeVariableMap.isEmpty() && that.typeVariableMap.isEmpty()) {
return true;
}
return this.typeVariableMap.equals(that.typeVariableMap);
}
/*
* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
return hashCode;
}
/**
* A synthetic {@link ParameterizedType}.
*
* @author Oliver Gierke
* @since 1.11
*/
private static class SyntheticParamterizedType implements ParameterizedType {
private final ClassTypeInformation<?> typeInformation;
private final List<TypeInformation<?>> typeParameters;
public SyntheticParamterizedType(ClassTypeInformation<?> typeInformation, List<TypeInformation<?>> typeParameters) {
this.typeInformation = typeInformation;
this.typeParameters = typeParameters;
}
/*
* (non-Javadoc)
* @see java.lang.reflect.ParameterizedType#getRawType()
*/
@Override
public Type getRawType() {
return typeInformation.getType();
}
/*
* (non-Javadoc)
* @see java.lang.reflect.ParameterizedType#getOwnerType()
*/
@Override
@Nullable
public Type getOwnerType() {
return null;
}
/*
* (non-Javadoc)
* @see java.lang.reflect.ParameterizedType#getActualTypeArguments()
*/
@Override
public Type[] getActualTypeArguments() {
Type[] result = new Type[typeParameters.size()];
for (int i = 0; i < typeParameters.size(); i++) {
result[i] = typeParameters.get(i).getType();
}
return result;
}
/*
* (non-Javadoc)
* @see org.springframework.data.util.ParentTypeAwareTypeInformation#equals(java.lang.Object)
*/
@Override
public boolean equals(@Nullable Object o) {
if (this == o) {
return true;
}
if (!(o instanceof SyntheticParamterizedType)) {
return false;
}
SyntheticParamterizedType that = (SyntheticParamterizedType) o;
if (!ObjectUtils.nullSafeEquals(typeInformation, that.typeInformation)) {
return false;
}
return ObjectUtils.nullSafeEquals(typeParameters, that.typeParameters);
}
/*
* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
int result = ObjectUtils.nullSafeHashCode(typeInformation);
result = 31 * result + ObjectUtils.nullSafeHashCode(typeParameters);
return result;
}
}
}

View File

@@ -78,7 +78,11 @@ import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
import org.springframework.data.mongodb.core.mapping.PersonPojoStringId;
import org.springframework.data.mongodb.core.mapping.TextScore;
import org.springframework.data.mongodb.core.mapping.event.AfterConvertCallback;
import org.springframework.data.util.Address;
import org.springframework.data.util.AddressTypeInformation;
import org.springframework.data.util.ClassTypeInformation;
import org.springframework.data.util.Person;
import org.springframework.data.util.PersonTypeInformation;
import org.springframework.test.util.ReflectionTestUtils;
import com.mongodb.BasicDBList;
@@ -2178,6 +2182,34 @@ public class MappingMongoConverterUnitTests {
assertThat(((LinkedHashMap) result.get("cluster")).get("_id")).isEqualTo(100L);
}
@Test
public void xxx() {
ClassTypeInformation.warmCache(new PersonTypeInformation(), new AddressTypeInformation());
MongoMappingContext mappingContext = new MongoMappingContext();
mappingContext.setInitialEntitySet(new LinkedHashSet<>(Arrays.asList(org.springframework.data.util.Person.class, org.springframework.data.util.Address.class)));
mappingContext.initialize();
org.springframework.data.util.Person source = new org.springframework.data.util.Person("spring", "data");
source.setAddress(new org.springframework.data.util.Address("the city", "never sleeps"));
source.setAge(10);
source.setId(9876);
MappingMongoConverter converter = new MappingMongoConverter(NoOpDbRefResolver.INSTANCE, mappingContext);
org.bson.Document targetDocument = new org.bson.Document();
converter.write(source, targetDocument);
System.out.println("target: " + targetDocument);
org.springframework.data.util.Person targetEntity = converter.read(org.springframework.data.util.Person.class, targetDocument);
System.out.println("targetEntity: " + targetEntity);
}
static class GenericType<T> {
T content;