DATACMNS-1126 - Add class-generating entity instantiation support for Kotlin.

We now discover Kotlin constructors and apply parameter defaulting to allow construction of immutable value objects. Constructor discovery uses primary constructors by default and considers PersistenceConstructor annotations.

KotlinClassGeneratingEntityInstantiator can instantiate Kotlin classes with default parameters by resolving the synthetic constructor. Null values translate to parameter defaults.

class Person(val firstname: String = "Walter") {
}

class Address(val street: String, val city: String) {

	@PersistenceConstructor
	constructor(street: String = "Unknown", city: String = "Unknown", country: String) : this(street, city)
}

Original pull request: #233.
This commit is contained in:
Mark Paluch
2017-07-26 12:07:20 +02:00
committed by Oliver Gierke
parent e4e677d6b1
commit 22b37800ec
15 changed files with 894 additions and 216 deletions

View File

@@ -20,34 +20,12 @@ import lombok.RequiredArgsConstructor;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Collections;
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.*;
import java.util.stream.Collectors;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.data.annotation.TypeAlias;
import org.springframework.data.mapping.Alias;
import org.springframework.data.mapping.Association;
import org.springframework.data.mapping.AssociationHandler;
import org.springframework.data.mapping.IdentifierAccessor;
import org.springframework.data.mapping.MappingException;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.PersistentPropertyAccessor;
import org.springframework.data.mapping.PreferredConstructor;
import org.springframework.data.mapping.PropertyHandler;
import org.springframework.data.mapping.SimpleAssociationHandler;
import org.springframework.data.mapping.SimplePropertyHandler;
import org.springframework.data.mapping.TargetAwareIdentifierAccessor;
import org.springframework.data.mapping.*;
import org.springframework.data.util.Lazy;
import org.springframework.data.util.TypeInformation;
import org.springframework.lang.Nullable;
@@ -112,7 +90,7 @@ public class BasicPersistentEntity<T, P extends PersistentProperty<P>> implement
this.properties = new ArrayList<>();
this.persistentPropertiesCache = new ArrayList<>();
this.comparator = comparator;
this.constructor = new PreferredConstructorDiscoverer<>(this).getConstructor();
this.constructor = PreferredConstructorDiscoverer.discover(this);
this.associations = comparator == null ? new HashSet<>() : new TreeSet<>(new AssociationComparator<>(comparator));
this.propertyCache = new HashMap<>();

View File

@@ -15,20 +15,14 @@
*/
package org.springframework.data.mapping.model;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.util.List;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.PreferredConstructor;
import org.springframework.data.mapping.PreferredConstructor.Parameter;
import org.springframework.data.util.ClassTypeInformation;
import org.springframework.data.util.TypeInformation;
import org.springframework.lang.Nullable;
import com.mysema.commons.lang.Assert;
/**
* Helper class to find a {@link PreferredConstructor}.
*
@@ -37,108 +31,34 @@ import org.springframework.lang.Nullable;
* @author Roman Rodov
* @author Mark Paluch
*/
public class PreferredConstructorDiscoverer<T, P extends PersistentProperty<P>> {
private final ParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer();
private @Nullable PreferredConstructor<T, P> constructor;
public interface PreferredConstructorDiscoverer<T, P extends PersistentProperty<P>> {
/**
* Creates a new {@link PreferredConstructorDiscoverer} for the given type.
* Discovers the {@link PreferredConstructor} for the given type.
*
* @param type must not be {@literal null}.
*/
public PreferredConstructorDiscoverer(Class<T> type) {
this(ClassTypeInformation.from(type), null);
}
/**
* Creates a new {@link PreferredConstructorDiscoverer} for the given {@link PersistentEntity}.
*
* @param entity must not be {@literal null}.
*/
public PreferredConstructorDiscoverer(PersistentEntity<T, P> entity) {
this(entity.getTypeInformation(), entity);
}
/**
* Creates a new {@link PreferredConstructorDiscoverer} for the given type.
*
* @param type must not be {@literal null}.
* @param entity
*/
protected PreferredConstructorDiscoverer(TypeInformation<T> type, @Nullable PersistentEntity<T, P> entity) {
boolean noArgConstructorFound = false;
int numberOfArgConstructors = 0;
Class<?> rawOwningType = type.getType();
for (Constructor<?> candidate : rawOwningType.getDeclaredConstructors()) {
PreferredConstructor<T, P> preferredConstructor = buildPreferredConstructor(candidate, type, entity);
// Synthetic constructors should not be considered
if (preferredConstructor.getConstructor().isSynthetic()) {
continue;
}
// Explicitly defined constructor trumps all
if (preferredConstructor.isExplicitlyAnnotated()) {
this.constructor = preferredConstructor;
return;
}
// No-arg constructor trumps custom ones
if (this.constructor == null || preferredConstructor.isNoArgConstructor()) {
this.constructor = preferredConstructor;
}
if (preferredConstructor.isNoArgConstructor()) {
noArgConstructorFound = true;
} else {
numberOfArgConstructors++;
}
}
if (!noArgConstructorFound && numberOfArgConstructors > 1) {
this.constructor = null;
}
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private PreferredConstructor<T, P> buildPreferredConstructor(Constructor<?> constructor,
TypeInformation<T> typeInformation, @Nullable PersistentEntity<T, P> entity) {
List<TypeInformation<?>> parameterTypes = typeInformation.getParameterTypes(constructor);
if (parameterTypes.isEmpty()) {
return new PreferredConstructor<>((Constructor<T>) constructor);
}
String[] parameterNames = discoverer.getParameterNames(constructor);
Parameter<Object, P>[] parameters = new Parameter[parameterTypes.size()];
Annotation[][] parameterAnnotations = constructor.getParameterAnnotations();
for (int i = 0; i < parameterTypes.size(); i++) {
String name = parameterNames == null ? null : parameterNames[i];
TypeInformation<?> type = parameterTypes.get(i);
Annotation[] annotations = parameterAnnotations[i];
parameters[i] = new Parameter(name, type, annotations, entity);
}
return new PreferredConstructor<>((Constructor<T>) constructor, parameters);
}
/**
* Returns the discovered {@link PreferredConstructor}.
*
* @return
* @return the {@link PreferredConstructor} if found or {@literal null}.
*/
@Nullable
public PreferredConstructor<T, P> getConstructor() {
return constructor;
static <T, P extends PersistentProperty<P>> PreferredConstructor<T, P> discover(Class<T> type) {
Assert.notNull(type, "Type must not be null!");
return PreferredConstructorDiscoverers.findDiscoverer(type).discover(ClassTypeInformation.from(type), null);
}
/**
* Discovers the {@link PreferredConstructorDiscoverer} for the given {@link PersistentEntity}.
*
* @param entity must not be {@literal null}.
* @return the {@link PreferredConstructor} if found or {@literal null}.
*/
@Nullable
static <T, P extends PersistentProperty<P>> PreferredConstructor<T, P> discover(PersistentEntity<T, P> entity) {
Assert.notNull(entity, "PersistentEntity must not be null!");
return PreferredConstructorDiscoverers.findDiscoverer(entity.getType()).discover(entity.getTypeInformation(),
entity);
}
}

View File

@@ -0,0 +1,183 @@
/*
* Copyright 2011-2017 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 kotlin.jvm.JvmClassMappingKt;
import kotlin.reflect.KFunction;
import kotlin.reflect.full.KClasses;
import kotlin.reflect.jvm.ReflectJvmMapping;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.util.Arrays;
import java.util.List;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.PreferredConstructor;
import org.springframework.data.mapping.PreferredConstructor.Parameter;
import org.springframework.data.util.ReflectionUtils;
import org.springframework.data.util.TypeInformation;
import org.springframework.lang.Nullable;
/**
* Helper class to find a {@link PreferredConstructor}.
*
* @author Oliver Gierke
* @author Christoph Strobl
* @author Roman Rodov
* @author Mark Paluch
* @since 2.0
*/
enum PreferredConstructorDiscoverers {
/**
* Discovers a {@link PreferredConstructor} for Java types.
*/
DEFAULT {
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.model.PreferredConstructorDiscoverers#discover(org.springframework.data.util.TypeInformation, org.springframework.data.mapping.PersistentEntity)
*/
@Nullable
@Override
public <T, P extends PersistentProperty<P>> PreferredConstructor<T, P> discover(TypeInformation<T> type,
@Nullable PersistentEntity<T, P> entity) {
boolean noArgConstructorFound = false;
int numberOfArgConstructors = 0;
Class<?> rawOwningType = type.getType();
PreferredConstructor<T, P> constructor = null;
for (Constructor<?> candidate : rawOwningType.getDeclaredConstructors()) {
PreferredConstructor<T, P> preferredConstructor = buildPreferredConstructor(candidate, type, entity);
// Synthetic constructors should not be considered
if (preferredConstructor.getConstructor().isSynthetic()) {
continue;
}
// Explicitly defined constructor trumps all
if (preferredConstructor.isExplicitlyAnnotated()) {
return preferredConstructor;
}
// No-arg constructor trumps custom ones
if (constructor == null || preferredConstructor.isNoArgConstructor()) {
constructor = preferredConstructor;
}
if (preferredConstructor.isNoArgConstructor()) {
noArgConstructorFound = true;
} else {
numberOfArgConstructors++;
}
}
if (!noArgConstructorFound && numberOfArgConstructors > 1) {
constructor = null;
}
return constructor;
}
},
/**
* Discovers a {@link PreferredConstructor} for Kotlin types.
*/
KOTLIN {
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.model.PreferredConstructorDiscoverers#discover(org.springframework.data.util.TypeInformation, org.springframework.data.mapping.PersistentEntity)
*/
@Nullable
@Override
public <T, P extends PersistentProperty<P>> PreferredConstructor<T, P> discover(TypeInformation<T> type,
@Nullable PersistentEntity<T, P> entity) {
Class<?> rawOwningType = type.getType();
return Arrays.stream(rawOwningType.getDeclaredConstructors()) //
.map(it -> buildPreferredConstructor(it, type, entity)) //
.filter(it -> !it.getConstructor().isSynthetic()) // Synthetic constructors should not be considered
.filter(PreferredConstructor::isExplicitlyAnnotated) // Explicitly defined constructor trumps all
.findFirst() //
.orElseGet(() -> {
KFunction<T> primaryConstructor = KClasses
.getPrimaryConstructor(JvmClassMappingKt.getKotlinClass(type.getType()));
Constructor<T> javaConstructor = ReflectJvmMapping.getJavaConstructor(primaryConstructor);
return javaConstructor != null ? buildPreferredConstructor(javaConstructor, type, entity) : null;
});
}
};
private static final ParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer();
/**
* Find the appropriate discoverer for {@code type}.
*
* @param type must not be {@literal null}.
* @return the appropriate discoverer for {@code type}.
*/
public static PreferredConstructorDiscoverers findDiscoverer(Class<?> type) {
return ReflectionUtils.isKotlinClass(type) ? KOTLIN : DEFAULT;
}
/**
* Discovers a constructor for the given type.
*
* @param type must not be {@literal null}.
* @param entity may be {@literal null}.
* @return the {@link PreferredConstructor} if found or {@literal null}.
*/
@Nullable
public abstract <T, P extends PersistentProperty<P>> PreferredConstructor<T, P> discover(TypeInformation<T> type,
@Nullable PersistentEntity<T, P> entity);
@SuppressWarnings({ "unchecked", "rawtypes" })
private static <T, P extends PersistentProperty<P>> PreferredConstructor<T, P> buildPreferredConstructor(
Constructor<?> constructor, TypeInformation<T> typeInformation, @Nullable PersistentEntity<T, P> entity) {
List<TypeInformation<?>> parameterTypes = typeInformation.getParameterTypes(constructor);
if (parameterTypes.isEmpty()) {
return new PreferredConstructor<>((Constructor<T>) constructor);
}
String[] parameterNames = discoverer.getParameterNames(constructor);
Parameter<Object, P>[] parameters = new Parameter[parameterTypes.size()];
Annotation[][] parameterAnnotations = constructor.getParameterAnnotations();
for (int i = 0; i < parameterTypes.size(); i++) {
String name = parameterNames == null ? null : parameterNames[i];
TypeInformation<?> type = parameterTypes.get(i);
Annotation[] annotations = parameterAnnotations[i];
parameters[i] = new Parameter(name, type, annotations, entity);
}
return new PreferredConstructor<>((Constructor<T>) constructor, parameters);
}
}