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

@@ -21,10 +21,8 @@ import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.asm.ClassWriter;
@@ -57,6 +55,19 @@ import org.springframework.util.ClassUtils;
*/
public class ClassGeneratingEntityInstantiator implements EntityInstantiator {
private static final int ARG_CACHE_SIZE = 100;
private static final ThreadLocal<Object[][]> OBJECT_POOL = ThreadLocal.withInitial(() -> {
Object[][] cached = new Object[ARG_CACHE_SIZE][];
for (int i = 0; i < ARG_CACHE_SIZE; i++) {
cached[i] = new Object[i];
}
return cached;
});
private final ObjectInstantiatorClassGenerator generator;
private volatile Map<TypeInformation<?>, EntityInstantiator> entityInstantiators = new HashMap<>(32);
@@ -120,12 +131,20 @@ public class ClassGeneratingEntityInstantiator implements EntityInstantiator {
}
try {
return new EntityInstantiatorAdapter(createObjectInstantiator(entity));
return doCreateEntityInstantiator(entity);
} catch (Throwable ex) {
return ReflectionEntityInstantiator.INSTANCE;
}
}
/**
* @param entity
* @return
*/
protected EntityInstantiator doCreateEntityInstantiator(PersistentEntity<?, ?> entity) {
return new EntityInstantiatorAdapter(createObjectInstantiator(entity, entity.getPersistenceConstructor()));
}
/**
* @param entity
* @return
@@ -151,17 +170,46 @@ public class ClassGeneratingEntityInstantiator implements EntityInstantiator {
}
/**
* Creates a dynamically generated {@link ObjectInstantiator} for the given {@link PersistentEntity}. There will
* always be exactly one {@link ObjectInstantiator} instance per {@link PersistentEntity}.
* <p>
* Allocates an object array for instance creation. This method uses the argument array cache if possible.
*
* @param argumentCount
* @return
* @since 2.0
* @see #ARG_CACHE_SIZE
*/
static Object[] allocateArguments(int argumentCount) {
return argumentCount < ARG_CACHE_SIZE ? OBJECT_POOL.get()[argumentCount] : new Object[argumentCount];
}
/**
* Deallocates an object array used for instance creation. Parameters are cleared if the array was cached.
*
* @param argumentCount
* @return
* @since 2.0
* @see #ARG_CACHE_SIZE
*/
static void deallocateArguments(Object[] params) {
if (params.length != 0 && params.length < ARG_CACHE_SIZE) {
Arrays.fill(params, null);
}
}
/**
* Creates a dynamically generated {@link ObjectInstantiator} for the given {@link PersistentEntity} and
* {@link PreferredConstructor}. There will always be exactly one {@link ObjectInstantiator} instance per
* {@link PersistentEntity}.
*
* @param entity
* @param constructor
* @return
*/
private ObjectInstantiator createObjectInstantiator(PersistentEntity<?, ?> entity) {
ObjectInstantiator createObjectInstantiator(PersistentEntity<?, ?> entity,
@Nullable PreferredConstructor<?, ?> constructor) {
try {
return (ObjectInstantiator) this.generator.generateCustomInstantiatorClass(entity).newInstance();
return (ObjectInstantiator) this.generator.generateCustomInstantiatorClass(entity, constructor).newInstance();
} catch (Exception e) {
throw new RuntimeException(e);
}
@@ -172,11 +220,10 @@ public class ClassGeneratingEntityInstantiator implements EntityInstantiator {
*
* @author Thomas Darimont
* @author Oliver Gierke
* @author Mark Paluch
*/
private static class EntityInstantiatorAdapter implements EntityInstantiator {
private static final Object[] EMPTY_ARRAY = new Object[0];
private final ObjectInstantiator instantiator;
/**
@@ -203,6 +250,8 @@ public class ClassGeneratingEntityInstantiator implements EntityInstantiator {
return (T) instantiator.newInstance(params);
} catch (Exception e) {
throw new MappingInstantiationException(entity, Arrays.asList(params), e);
} finally {
deallocateArguments(params);
}
}
@@ -216,17 +265,18 @@ public class ClassGeneratingEntityInstantiator implements EntityInstantiator {
private <P extends PersistentProperty<P>, T> Object[] extractInvocationArguments(
@Nullable PreferredConstructor<? extends T, P> constructor, ParameterValueProvider<P> provider) {
if (provider == null || constructor == null || !constructor.hasParameters()) {
return EMPTY_ARRAY;
if (constructor == null || !constructor.hasParameters()) {
return allocateArguments(0);
}
List<Object> params = new ArrayList<>(constructor.getConstructor().getParameterCount());
Object[] params = allocateArguments(constructor.getConstructor().getParameterCount());
int index = 0;
for (Parameter<?, P> parameter : constructor.getParameters()) {
params.add(provider.getParameterValue(parameter));
params[index++] = provider.getParameterValue(parameter);
}
return params.toArray();
return params;
}
}
@@ -290,7 +340,7 @@ public class ClassGeneratingEntityInstantiator implements EntityInstantiator {
private final ByteArrayClassLoader classLoader;
private ObjectInstantiatorClassGenerator() {
ObjectInstantiatorClassGenerator() {
this.classLoader = AccessController.doPrivileged(
(PrivilegedAction<ByteArrayClassLoader>) () -> new ByteArrayClassLoader(ClassUtils.getDefaultClassLoader()));
@@ -300,12 +350,14 @@ public class ClassGeneratingEntityInstantiator implements EntityInstantiator {
* Generate a new class for the given {@link PersistentEntity}.
*
* @param entity
* @param constructor
* @return
*/
public Class<?> generateCustomInstantiatorClass(PersistentEntity<?, ?> entity) {
public Class<?> generateCustomInstantiatorClass(PersistentEntity<?, ?> entity,
@Nullable PreferredConstructor<?, ?> constructor) {
String className = generateClassName(entity);
byte[] bytecode = generateBytecode(className, entity);
byte[] bytecode = generateBytecode(className, entity, constructor);
return classLoader.loadClass(className, bytecode);
}
@@ -323,9 +375,11 @@ public class ClassGeneratingEntityInstantiator implements EntityInstantiator {
*
* @param internalClassName
* @param entity
* @param constructor
* @return
*/
public byte[] generateBytecode(String internalClassName, PersistentEntity<?, ?> entity) {
public byte[] generateBytecode(String internalClassName, PersistentEntity<?, ?> entity,
@Nullable PreferredConstructor<?, ?> constructor) {
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
@@ -334,7 +388,7 @@ public class ClassGeneratingEntityInstantiator implements EntityInstantiator {
visitDefaultConstructor(cw);
visitCreateMethod(cw, entity);
visitCreateMethod(cw, entity, constructor);
cw.visitEnd();
@@ -357,8 +411,10 @@ public class ClassGeneratingEntityInstantiator implements EntityInstantiator {
*
* @param cw
* @param entity
* @param constructor
*/
private void visitCreateMethod(ClassWriter cw, PersistentEntity<?, ?> entity) {
private void visitCreateMethod(ClassWriter cw, PersistentEntity<?, ?> entity,
@Nullable PreferredConstructor<?, ?> constructor) {
String entityTypeResourcePath = Type.getInternalName(entity.getType());
@@ -368,8 +424,6 @@ public class ClassGeneratingEntityInstantiator implements EntityInstantiator {
mv.visitTypeInsn(NEW, entityTypeResourcePath);
mv.visitInsn(DUP);
PreferredConstructor<?, ?> constructor = entity.getPersistenceConstructor();
if (constructor != null) {
Constructor<?> ctor = constructor.getConstructor();

View File

@@ -24,10 +24,11 @@ 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 Gierke
* @author Thomas Darimont
* @author Christoph Strobl
* @author Mark Paluch
*/
public class EntityInstantiators {
@@ -43,7 +44,7 @@ public class EntityInstantiators {
/**
* Creates a new {@link EntityInstantiators} using the given {@link EntityInstantiator} as fallback.
*
*
* @param fallback must not be {@literal null}.
*/
public EntityInstantiators(EntityInstantiator fallback) {
@@ -52,17 +53,17 @@ public class EntityInstantiators {
/**
* 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 ClassGeneratingEntityInstantiator(), customInstantiators);
this(new ClassGeneratingKotlinEntityInstantiator(), 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}.
*/
@@ -78,7 +79,7 @@ public class EntityInstantiators {
/**
* 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}.
*/

View File

@@ -0,0 +1,238 @@
/*
* Copyright 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.convert;
import kotlin.reflect.KFunction;
import kotlin.reflect.KParameter;
import kotlin.reflect.jvm.ReflectJvmMapping;
import java.lang.reflect.Constructor;
import java.util.List;
import java.util.stream.IntStream;
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.mapping.model.ParameterValueProvider;
import org.springframework.data.util.ReflectionUtils;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* Kotlin-specific extension to {@link ClassGeneratingEntityInstantiator} that adapts Kotlin constructors with
* defaulting.
*
* @author Mark Paluch
* @since 2.0
*/
public class ClassGeneratingKotlinEntityInstantiator extends ClassGeneratingEntityInstantiator {
/*
* (non-Javadoc)
* @see org.springframework.data.convert.ClassGeneratingEntityInstantiator#doCreateEntityInstantiator(org.springframework.data.mapping.PersistentEntity)
*/
@Override
protected EntityInstantiator doCreateEntityInstantiator(PersistentEntity<?, ?> entity) {
if (ReflectionUtils.isKotlinClass(entity.getType()) && entity.getPersistenceConstructor() != null) {
PreferredConstructor<?, ?> defaultConstructor = new DefaultingKotlinConstructorResolver(entity)
.getDefaultConstructor();
if (defaultConstructor != null) {
return new DefaultingKotlinClassInstantiatorAdapter(createObjectInstantiator(entity, defaultConstructor),
entity.getPersistenceConstructor());
}
}
return super.doCreateEntityInstantiator(entity);
}
/**
* Resolves a {@link PreferredConstructor} to a synthetic Kotlin constructor accepting the same user-space parameters
* suffixed by Kotlin-specifics required for defaulting and the {@code kotlin.jvm.internal.DefaultConstructorMarker}.
*
* @since 2.0
* @author Mark Paluch
*/
static class DefaultingKotlinConstructorResolver {
@Nullable private final PreferredConstructor<?, ?> defaultConstructor;
@SuppressWarnings("unchecked")
DefaultingKotlinConstructorResolver(PersistentEntity<?, ?> entity) {
Constructor<?> hit = resolveDefaultConstructor(entity);
PreferredConstructor<?, ?> persistenceConstructor = entity.getPersistenceConstructor();
if (hit != null && persistenceConstructor != null) {
this.defaultConstructor = new PreferredConstructor<>(hit,
persistenceConstructor.getParameters().toArray(new Parameter[0]));
} else {
this.defaultConstructor = null;
}
}
@Nullable
private static Constructor<?> resolveDefaultConstructor(PersistentEntity<?, ?> entity) {
if (entity.getPersistenceConstructor() == null) {
return null;
}
Constructor<?> hit = null;
Constructor<?> constructor = entity.getPersistenceConstructor().getConstructor();
for (Constructor<?> candidate : entity.getType().getDeclaredConstructors()) {
// use only synthetic constructors
if (!candidate.isSynthetic()) {
continue;
}
// with a parameter count greater zero
if (constructor.getParameterCount() == 0) {
continue;
}
// candidates must contain at least two additional parameters (int, DefaultConstructorMarker)
if (constructor.getParameterCount() + 2 > candidate.getParameterCount()) {
continue;
}
java.lang.reflect.Parameter[] constructorParameters = constructor.getParameters();
java.lang.reflect.Parameter[] candidateParameters = candidate.getParameters();
if (!candidateParameters[candidateParameters.length - 1].getType().getName()
.equals("kotlin.jvm.internal.DefaultConstructorMarker")) {
continue;
}
if (parametersMatch(constructorParameters, candidateParameters)) {
hit = candidate;
break;
}
}
return hit;
}
private static boolean parametersMatch(java.lang.reflect.Parameter[] constructorParameters,
java.lang.reflect.Parameter[] candidateParameters) {
return IntStream.range(0, constructorParameters.length)
.allMatch(i -> constructorParameters[i].getType().equals(candidateParameters[i].getType()));
}
@Nullable
PreferredConstructor<?, ?> getDefaultConstructor() {
return defaultConstructor;
}
}
/**
* Entity instantiator for Kotlin constructors that apply parameter defaulting. Kotlin constructors that apply
* argument defaulting are marked with {@link kotlin.jvm.internal.DefaultConstructorMarker} and accept additional
* parameters besides the regular (user-space) parameters. Additional parameters are:
* <ul>
* <li>defaulting bitmask ({@code int}), a bit mask slot for each 32 parameters</li>
* <li>{@code DefaultConstructorMarker} (usually null)</li>
* </ul>
* <strong>Defaulting bitmask</strong>
* <p/>
* The defaulting bitmask is a 32 bit integer representing which positional argument should be defaulted. Defaulted
* arguments are passed as {@literal null} and require the appropriate positional bit set ( {@code 1 << 2} for the 2.
* argument)). Since the bitmask represents only 32 bit states, it requires additional masks (slots) if more than 32
* arguments are represented.
*
* @author Mark Paluch
* @since 2.0
*/
static class DefaultingKotlinClassInstantiatorAdapter implements EntityInstantiator {
private final ObjectInstantiator instantiator;
private final List<KParameter> kParameters;
private final Constructor<?> synthetic;
DefaultingKotlinClassInstantiatorAdapter(ObjectInstantiator instantiator, PreferredConstructor<?, ?> constructor) {
KFunction<?> kotlinConstructor = ReflectJvmMapping.getKotlinFunction(constructor.getConstructor());
if (kotlinConstructor == null) {
throw new IllegalArgumentException(
"No corresponding Kotlin constructor found for " + constructor.getConstructor());
}
this.instantiator = instantiator;
this.kParameters = kotlinConstructor.getParameters();
this.synthetic = constructor.getConstructor();
}
/*
* (non-Javadoc)
* @see org.springframework.data.convert.EntityInstantiator#createInstance(org.springframework.data.mapping.PersistentEntity, org.springframework.data.mapping.model.ParameterValueProvider)
*/
@Override
@SuppressWarnings("unchecked")
public <T, E extends PersistentEntity<? extends T, P>, P extends PersistentProperty<P>> T createInstance(E entity,
ParameterValueProvider<P> provider) {
PreferredConstructor<? extends T, P> preferredConstructor = entity.getPersistenceConstructor();
Assert.notNull(preferredConstructor, "PreferredConstructor must not be null!");
int[] defaulting = new int[(synthetic.getParameterCount() / 32) + 1];
Object[] params = allocateArguments(
synthetic.getParameterCount() + defaulting.length + /* DefaultConstructorMarker */1);
int userParameterCount = kParameters.size();
List<Parameter<Object, P>> parameters = preferredConstructor.getParameters();
// Prepare user-space arguments
for (int i = 0; i < userParameterCount; i++) {
int slot = i / 32;
int offset = slot * 32;
Object param = provider.getParameterValue(parameters.get(i));
KParameter kParameter = kParameters.get(i);
// what about null and parameter is mandatory? What if parameter is non-null?
if (kParameter.isOptional()) {
if (param == null) {
defaulting[slot] = defaulting[slot] | (1 << (i - offset));
}
}
params[i] = param;
}
// append nullability masks to creation arguments
for (int i = 0; i < defaulting.length; i++) {
params[userParameterCount + i] = defaulting[i];
}
try {
return (T) instantiator.newInstance(params);
} finally {
deallocateArguments(params);
}
}
}
}

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

View File

@@ -45,6 +45,7 @@ import org.springframework.util.ConcurrentReferenceHashMap;
*
* @author Oliver Gierke
* @author Christoph Strobl
* @author Mark Paluch
* @since 1.12
*/
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
@@ -296,8 +297,7 @@ public abstract class ReturnedType {
return Collections.emptyList();
}
PreferredConstructorDiscoverer<?, ?> discoverer = new PreferredConstructorDiscoverer(type);
PreferredConstructor<?, ?> constructor = discoverer.getConstructor();
PreferredConstructor<?, ?> constructor = PreferredConstructorDiscoverer.discover(type);
if (constructor == null) {
return Collections.emptyList();

View File

@@ -44,11 +44,15 @@ import org.springframework.util.ReflectionUtils.FieldFilter;
* @author Oliver Gierke
* @author Thomas Darimont
* @author Christoph Strobl
* @author Mark Paluch
* @since 1.5
*/
@UtilityClass
public class ReflectionUtils {
private static final boolean KOTLIN_IS_PRESENT = ClassUtils.isPresent("kotlin.Unit",
BeanUtils.class.getClassLoader());
/**
* Creates an instance of the class with the given fully qualified name or returns the given default instance if the
* class cannot be loaded or instantiated.
@@ -338,4 +342,17 @@ public class ReflectionUtils {
return true;
}
/**
* Return true if the specified class is a Kotlin one.
*
* @return {@literal true} if {@code type} is a Kotlin class.
* @since 2.0
*/
public static boolean isKotlinClass(Class<?> type) {
return KOTLIN_IS_PRESENT && Arrays.stream(type.getDeclaredAnnotations()) //
.map(Annotation::annotationType) //
.anyMatch(annotation -> annotation.getName().equals("kotlin.Metadata"));
}
}