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.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.data.annotation.PersistenceConstructor;
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.KotlinReflectionUtils;
import org.springframework.data.util.TypeInformation;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
public interface PreferredConstructorDiscoverer<T, P extends PersistentProperty<P>> {
@Nullable
static <T, P extends PersistentProperty<P>> PreferredConstructor<T, P> discover(Class<T> type) {
Assert.notNull(type, "Type must not be null!");
return Discoverers.findDiscoverer(type)
.discover(ClassTypeInformation.from(type), 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 Discoverers.findDiscoverer(entity.getType())
.discover(entity.getTypeInformation(), entity);
}
enum Discoverers {
DEFAULT {
@Nullable
@Override
<T, P extends PersistentProperty<P>> PreferredConstructor<T, P> discover(TypeInformation<T> type,
@Nullable PersistentEntity<T, P> entity) {
Class<?> rawOwningType = type.getType();
List<Constructor<?>> candidates = new ArrayList<>();
Constructor<?> noArg = null;
for (Constructor<?> candidate : rawOwningType.getDeclaredConstructors()) {
if (candidate.isSynthetic()) {
continue;
}
if (candidate.isAnnotationPresent(PersistenceConstructor.class)) {
return buildPreferredConstructor(candidate, type, entity);
}
if (candidate.getParameterCount() == 0) {
noArg = candidate;
} else {
candidates.add(candidate);
}
}
if (noArg != null) {
return buildPreferredConstructor(noArg, type, entity);
}
return candidates.size() > 1 || candidates.isEmpty() ? null
: buildPreferredConstructor(candidates.iterator().next(), type, entity);
}
},
KOTLIN {
@Nullable
@Override
<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())
.filter(it -> !it.isSynthetic())
.filter(it -> it.isAnnotationPresent(PersistenceConstructor.class))
.map(it -> buildPreferredConstructor(it, type, entity))
.findFirst()
.orElseGet(() -> {
KFunction<T> primaryConstructor = KClasses
.getPrimaryConstructor(JvmClassMappingKt.getKotlinClass(type.getType()));
if (primaryConstructor == null) {
return DEFAULT.discover(type, entity);
}
Constructor<T> javaConstructor = ReflectJvmMapping.getJavaConstructor(primaryConstructor);
return javaConstructor != null ? buildPreferredConstructor(javaConstructor, type, entity) : null;
});
}
};
private static final ParameterNameDiscoverer PARAMETER_NAME_DISCOVERER = new DefaultParameterNameDiscoverer();
private static Discoverers findDiscoverer(Class<?> type) {
return KotlinReflectionUtils.isSupportedKotlinClass(type) ? KOTLIN : DEFAULT;
}
@Nullable
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) {
if (constructor.getParameterCount() == 0) {
return new PreferredConstructor<>((Constructor<T>) constructor);
}
List<TypeInformation<?>> parameterTypes = typeInformation.getParameterTypes(constructor);
String[] parameterNames = PARAMETER_NAME_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);
}
}
}