Browse Source

Polishing.

Refine KotlinInstantiationDelegate design, improve encapsulation to avoid handing in and out values from lookups.

Replace stream usage with loops, remove unused code, avoid duplicate parameter lookups.

See #3389
Original pull request: #3390
pull/3394/head
Mark Paluch 2 months ago
parent
commit
bd0d7298df
No known key found for this signature in database
GPG Key ID: 55BC6374BAA9D973
  1. 17
      src/main/java/org/springframework/data/mapping/model/KotlinClassGeneratingEntityInstantiator.java
  2. 205
      src/main/java/org/springframework/data/mapping/model/KotlinInstantiationDelegate.java
  3. 14
      src/main/java/org/springframework/data/mapping/model/ReflectionEntityInstantiator.java

17
src/main/java/org/springframework/data/mapping/model/KotlinClassGeneratingEntityInstantiator.java

@ -15,7 +15,6 @@ @@ -15,7 +15,6 @@
*/
package org.springframework.data.mapping.model;
import java.lang.reflect.Constructor;
import java.util.Arrays;
import org.springframework.data.mapping.InstanceCreatorMetadata;
@ -43,15 +42,11 @@ class KotlinClassGeneratingEntityInstantiator extends ClassGeneratingEntityInsta @@ -43,15 +42,11 @@ class KotlinClassGeneratingEntityInstantiator extends ClassGeneratingEntityInsta
if (KotlinReflectionUtils.isSupportedKotlinClass(entity.getType())
&& creator instanceof PreferredConstructor<?, ?> constructor) {
PreferredConstructor<?, ? extends PersistentProperty<?>> kotlinJvmConstructor = KotlinInstantiationDelegate
.resolveKotlinJvmConstructor(constructor);
KotlinInstantiationDelegate delegate = KotlinInstantiationDelegate.resolve(constructor);
if (kotlinJvmConstructor != null) {
ObjectInstantiator instantiator = createObjectInstantiator(entity, kotlinJvmConstructor);
return new DefaultingKotlinClassInstantiatorAdapter(instantiator, constructor,
kotlinJvmConstructor.getConstructor());
if (delegate != null) {
ObjectInstantiator instantiator = createObjectInstantiator(entity, delegate.getInstanceCreator());
return new DefaultingKotlinClassInstantiatorAdapter(instantiator, delegate);
}
}
@ -82,10 +77,10 @@ class KotlinClassGeneratingEntityInstantiator extends ClassGeneratingEntityInsta @@ -82,10 +77,10 @@ class KotlinClassGeneratingEntityInstantiator extends ClassGeneratingEntityInsta
private final KotlinInstantiationDelegate delegate;
DefaultingKotlinClassInstantiatorAdapter(ObjectInstantiator instantiator,
PreferredConstructor<?, ?> defaultConstructor, Constructor<?> constructorToInvoke) {
KotlinInstantiationDelegate delegate) {
this.instantiator = instantiator;
this.delegate = new KotlinInstantiationDelegate(defaultConstructor, constructorToInvoke);
this.delegate = delegate;
}
@Override

205
src/main/java/org/springframework/data/mapping/model/KotlinInstantiationDelegate.java

@ -26,7 +26,6 @@ import java.util.IdentityHashMap; @@ -26,7 +26,6 @@ import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.IntStream;
import org.jspecify.annotations.Nullable;
@ -49,31 +48,27 @@ import org.springframework.data.util.ReflectionUtils; @@ -49,31 +48,27 @@ import org.springframework.data.util.ReflectionUtils;
*/
class KotlinInstantiationDelegate {
private final KFunction<?> constructor;
private final PreferredConstructor<?, ?> constructor;
private final KFunction<?> constructorFunction;
private final List<KParameter> kParameters;
private final Map<KParameter, Integer> indexByKParameter;
private final List<Function<@Nullable Object, @Nullable Object>> wrappers = new ArrayList<>();
private final Constructor<?> constructorToInvoke;
private final List<Function<@Nullable Object, @Nullable Object>> wrappers;
private final boolean hasDefaultConstructorMarker;
public KotlinInstantiationDelegate(PreferredConstructor<?, ?> preferredConstructor,
Constructor<?> constructorToInvoke) {
private KotlinInstantiationDelegate(PreferredConstructor<?, ?> constructor, KFunction<?> constructorFunction) {
KFunction<?> kotlinConstructor = ReflectJvmMapping.getKotlinFunction(preferredConstructor.getConstructor());
this.constructor = constructor;
this.hasDefaultConstructorMarker = hasDefaultConstructorMarker(getConstructor().getParameters());
if (kotlinConstructor == null) {
throw new IllegalArgumentException(
"No corresponding Kotlin constructor found for " + preferredConstructor.getConstructor());
}
this.constructor = kotlinConstructor;
this.kParameters = kotlinConstructor.getParameters();
this.indexByKParameter = new IdentityHashMap<>();
this.constructorFunction = constructorFunction;
this.kParameters = constructorFunction.getParameters();
this.indexByKParameter = new IdentityHashMap<>(kParameters.size());
for (int i = 0; i < kParameters.size(); i++) {
indexByKParameter.put(kParameters.get(i), i);
}
this.constructorToInvoke = constructorToInvoke;
this.wrappers = new ArrayList<>(kParameters.size());
for (KParameter kParameter : kParameters) {
@ -82,17 +77,32 @@ class KotlinInstantiationDelegate { @@ -82,17 +77,32 @@ class KotlinInstantiationDelegate {
}
}
static boolean hasDefaultConstructorMarker(java.lang.reflect.Parameter[] parameters) {
/**
* @return the constructor to invoke. {@link PreferredConstructor#getParameters() Constructor parameters} describe the
* detected (i.e. user-facing) constructor parameters and not {@link PreferredConstructor#getConstructor()}
* parameters and therefore do not contain any synthetic parameters.
* @since 4.0
*/
public InstanceCreatorMetadata<?> getInstanceCreator() {
return constructor;
}
return parameters.length > 0
&& parameters[parameters.length - 1].getType().getName().equals("kotlin.jvm.internal.DefaultConstructorMarker");
/**
* @return the constructor to invoke. {@link PreferredConstructor#getParameters() Constructor parameters} describe the
* detected (i.e. user-facing) constructor parameters and not {@link PreferredConstructor#getConstructor()}
* parameters and therefore do not contain any synthetic parameters.
* @since 4.0
*/
public Constructor<?> getConstructor() {
return constructor.getConstructor();
}
/**
* @return number of constructor arguments.
* @return number of actual constructor arguments.
* @see #getConstructor()
*/
public int getRequiredParameterCount() {
return constructorToInvoke.getParameterCount();
return getConstructor().getParameterCount();
}
/**
@ -107,7 +117,6 @@ class KotlinInstantiationDelegate { @@ -107,7 +117,6 @@ class KotlinInstantiationDelegate {
}
int userParameterCount = kParameters.size();
List<Parameter<Object, P>> parameters = entityCreator.getParameters();
// Prepare user-space arguments
@ -117,35 +126,59 @@ class KotlinInstantiationDelegate { @@ -117,35 +126,59 @@ class KotlinInstantiationDelegate {
params[i] = provider.getParameterValue(parameter);
}
KotlinDefaultMask defaultMask = KotlinDefaultMask.forConstructor(constructor, it -> {
// late rewrapping to indicate potential absence of parameters for defaulting
for (int i = 0; i < userParameterCount; i++) {
params[i] = wrappers.get(i).apply(params[i]);
}
int index = indexByKParameter.get(it);
if (hasDefaultConstructorMarker) {
Parameter<Object, P> parameter = parameters.get(index);
Class<Object> type = parameter.getType().getType();
KotlinDefaultMask defaultMask = KotlinDefaultMask.forConstructor(constructorFunction, it -> {
if (it.isOptional() && (params[index] == null)) {
if (type.isPrimitive()) {
int index = indexByKParameter.get(it);
// apply primitive defaulting to prevent NPE on primitive downcast
params[index] = ReflectionUtils.getPrimitiveDefault(type);
Parameter<Object, P> parameter = parameters.get(index);
Class<Object> type = parameter.getType().getType();
if (it.isOptional() && (params[index] == null)) {
if (type.isPrimitive()) {
// apply primitive defaulting to prevent NPE on primitive downcast
params[index] = ReflectionUtils.getPrimitiveDefault(type);
}
return false;
}
return false;
}
return true;
});
return true;
});
// late rewrapping to indicate potential absence of parameters for defaulting
for (int i = 0; i < userParameterCount; i++) {
params[i] = wrappers.get(i).apply(params[i]);
int[] defaulting = defaultMask.getDefaulting();
// append nullability masks to creation arguments
for (int i = 0; i < defaulting.length; i++) {
params[userParameterCount + i] = defaulting[i];
}
}
}
/**
* Try to resolve {@code KotlinInstantiationDelegate} from a {@link PreferredConstructor}. Resolution attempts to find
* a JVM constructor equivalent considering value class mangling, Kotlin defaulting and potentially synthetic
* constructors generated by the Kotlin compile including the lookup of a {@link KFunction} from the given
* {@link PreferredConstructor}.
*
* @return the {@code KotlinInstantiationDelegate} if resolution was successful; {@literal null} otherwise.
* @since 4.0
*/
public static @Nullable KotlinInstantiationDelegate resolve(PreferredConstructor<?, ?> preferredConstructor) {
int[] defaulting = defaultMask.getDefaulting();
// append nullability masks to creation arguments
for (int i = 0; i < defaulting.length; i++) {
params[userParameterCount + i] = defaulting[i];
KFunction<?> constructorFunction = ReflectJvmMapping.getKotlinFunction(preferredConstructor.getConstructor());
if (constructorFunction == null) {
return null;
}
PreferredConstructor<?, ?> resolved = resolveKotlinJvmConstructor(preferredConstructor, constructorFunction);
return resolved != null ? new KotlinInstantiationDelegate(resolved, constructorFunction) : null;
}
/**
@ -153,18 +186,23 @@ class KotlinInstantiationDelegate { @@ -153,18 +186,23 @@ class KotlinInstantiationDelegate {
* constructor accepting the same user-space parameters suffixed by Kotlin-specifics required for defaulting and the
* {@code kotlin.jvm.internal.DefaultConstructorMarker} or an actual non-synthetic constructor (i.e. private
* constructor).
* <p>
* Constructor resolution may return {@literal null} indicating that no matching constructor could be found.
* <p>
* The resulting constructor {@link PreferredConstructor#getParameters()} (and parameter count) reflect user-facing
* parameters and do not contain any synthetic parameters.
*
* @return the resolved constructor or {@literal null} if the constructor could not be resolved.
* @since 2.0
* @author Mark Paluch
*/
@SuppressWarnings("unchecked")
@Nullable
public static PreferredConstructor<?, ?> resolveKotlinJvmConstructor(
PreferredConstructor<?, ?> preferredConstructor) {
private static PreferredConstructor<?, ?> resolveKotlinJvmConstructor(PreferredConstructor<?, ?> preferredConstructor,
KFunction<?> constructorFunction) {
Constructor<?> hit = doResolveKotlinConstructor(preferredConstructor.getConstructor());
Constructor<?> hit = findKotlinConstructor(preferredConstructor.getConstructor(), constructorFunction);
if (hit == preferredConstructor.getConstructor()) {
if (preferredConstructor.getConstructor().equals(hit)) {
return preferredConstructor;
}
@ -176,17 +214,23 @@ class KotlinInstantiationDelegate { @@ -176,17 +214,23 @@ class KotlinInstantiationDelegate {
}
@Nullable
private static Constructor<?> doResolveKotlinConstructor(Constructor<?> detectedConstructor) {
private static Constructor<?> findKotlinConstructor(Constructor<?> preferredConstructor,
KFunction<?> constructorFunction) {
Class<?> entityType = detectedConstructor.getDeclaringClass();
Class<?> entityType = preferredConstructor.getDeclaringClass();
Constructor<?> hit = null;
Constructor<?> privateFallback = null;
KFunction<?> kotlinFunction = ReflectJvmMapping.getKotlinFunction(detectedConstructor);
java.lang.reflect.Parameter[] detectedParameters = preferredConstructor.getParameters();
boolean hasDefaultConstructorMarker = KotlinInstantiationDelegate.hasDefaultConstructorMarker(detectedParameters);
for (Constructor<?> candidate : entityType.getDeclaredConstructors()) {
java.lang.reflect.Parameter[] candidateParameters = preferredConstructor.equals(candidate)
? detectedParameters
: candidate.getParameters();
if (Modifier.isPrivate(candidate.getModifiers())) {
if (detectedConstructor.equals(candidate)) {
if (preferredConstructor.equals(candidate)) {
privateFallback = candidate;
}
}
@ -196,26 +240,22 @@ class KotlinInstantiationDelegate { @@ -196,26 +240,22 @@ class KotlinInstantiationDelegate {
continue;
}
java.lang.reflect.Parameter[] detectedConstructorParameters = detectedConstructor.getParameters();
java.lang.reflect.Parameter[] candidateParameters = candidate.getParameters();
if (!KotlinInstantiationDelegate.hasDefaultConstructorMarker(detectedConstructorParameters)) {
if (!hasDefaultConstructorMarker) {
// candidates must contain at least two additional parameters (int, DefaultConstructorMarker).
// Number of defaulting masks derives from the original constructor arg count
int syntheticParameters = KotlinDefaultMask.getMaskCount(detectedConstructor.getParameterCount())
int syntheticParameters = KotlinDefaultMask.getMaskCount(detectedParameters.length)
+ /* DefaultConstructorMarker */ 1;
if ((detectedConstructor.getParameterCount() + syntheticParameters) != candidate.getParameterCount()) {
if ((detectedParameters.length + syntheticParameters) != candidate.getParameterCount()) {
continue;
}
} else if (kotlinFunction != null) {
} else {
int optionalParameterCount = (int) kotlinFunction.getParameters().stream().filter(KParameter::isOptional)
.count();
int optionalParameterCount = getOptionalParameterCount(constructorFunction);
int syntheticParameters = KotlinDefaultMask.getExactMaskCount(optionalParameterCount);
if ((detectedConstructor.getParameterCount() + syntheticParameters) != candidate.getParameterCount()) {
if ((detectedParameters.length + syntheticParameters) != candidate.getParameterCount()) {
continue;
}
}
@ -224,9 +264,8 @@ class KotlinInstantiationDelegate { @@ -224,9 +264,8 @@ class KotlinInstantiationDelegate {
continue;
}
int userParameterCount = kotlinFunction != null ? kotlinFunction.getParameters().size()
: detectedConstructor.getParameterCount();
if (parametersMatch(detectedConstructorParameters, candidateParameters, userParameterCount)) {
int userParameterCount = constructorFunction.getParameters().size();
if (parametersMatch(detectedParameters, candidateParameters, userParameterCount)) {
hit = candidate;
}
}
@ -238,24 +277,48 @@ class KotlinInstantiationDelegate { @@ -238,24 +277,48 @@ class KotlinInstantiationDelegate {
return hit;
}
private static int getOptionalParameterCount(KFunction<?> function) {
int count = 0;
for (KParameter parameter : function.getParameters()) {
if (parameter.isOptional()) {
count++;
}
}
return count;
}
private static boolean parametersMatch(java.lang.reflect.Parameter[] constructorParameters,
java.lang.reflect.Parameter[] candidateParameters, int userParameterCount) {
return IntStream.range(0, userParameterCount)
.allMatch(i -> parametersMatch(constructorParameters[i], candidateParameters[i]));
for (int i = 0; i < userParameterCount; i++) {
if (!parametersMatch(constructorParameters[i].getType(), candidateParameters[i].getType())) {
return false;
}
}
return true;
}
static boolean parametersMatch(java.lang.reflect.Parameter constructorParameter,
java.lang.reflect.Parameter candidateParameter) {
private static boolean parametersMatch(Class<?> constructorParameter, Class<?> candidateParameter) {
if (constructorParameter.getType().equals(candidateParameter.getType())) {
if (constructorParameter.equals(candidateParameter)) {
return true;
}
// candidate can be also a wrapper
Class<?> componentOrWrapperType = KotlinValueUtils.getConstructorValueHierarchy(candidateParameter.getType())
.getActualType();
Class<?> componentOrWrapperType = KotlinValueUtils.getConstructorValueHierarchy(candidateParameter).getActualType();
return constructorParameter.equals(componentOrWrapperType);
}
private static boolean hasDefaultConstructorMarker(java.lang.reflect.Parameter[] parameters) {
return parameters.length > 0 && isDefaultConstructorMarker(parameters[parameters.length - 1].getType());
}
return constructorParameter.getType().equals(componentOrWrapperType);
private static boolean isDefaultConstructorMarker(Class<?> cls) {
return cls.getName().equals("kotlin.jvm.internal.DefaultConstructorMarker");
}
}

14
src/main/java/org/springframework/data/mapping/model/ReflectionEntityInstantiator.java

@ -62,11 +62,10 @@ enum ReflectionEntityInstantiator implements EntityInstantiator { @@ -62,11 +62,10 @@ enum ReflectionEntityInstantiator implements EntityInstantiator {
if (KotlinDetector.isKotlinReflectPresent() && KotlinReflectionUtils.isSupportedKotlinClass(entity.getType())
&& creator instanceof PreferredConstructor<?, ?> constructor) {
PreferredConstructor<?, ? extends PersistentProperty<?>> kotlinJvmConstructor = KotlinInstantiationDelegate
.resolveKotlinJvmConstructor(constructor);
KotlinInstantiationDelegate delegate = KotlinInstantiationDelegate.resolve(constructor);
if (kotlinJvmConstructor != null) {
return instantiateKotlinClass(entity, provider, constructor, kotlinJvmConstructor);
if (delegate != null) {
return instantiateKotlinClass(entity, provider, delegate);
}
}
@ -98,14 +97,11 @@ enum ReflectionEntityInstantiator implements EntityInstantiator { @@ -98,14 +97,11 @@ enum ReflectionEntityInstantiator implements EntityInstantiator {
throw new MappingInstantiationException(entity, new ArrayList<>(Arrays.asList(params)), e);
}
}
@SuppressWarnings("unchecked")
private static <T, E extends PersistentEntity<? extends T, P>, P extends PersistentProperty<P>> T instantiateKotlinClass(
E entity, ParameterValueProvider<P> provider, PreferredConstructor<?, ?> preferredConstructor,
PreferredConstructor<?, ? extends PersistentProperty<?>> kotlinJvmConstructor) {
E entity, ParameterValueProvider<P> provider, KotlinInstantiationDelegate delegate) {
Constructor<?> ctor = kotlinJvmConstructor.getConstructor();
KotlinInstantiationDelegate delegate = new KotlinInstantiationDelegate(preferredConstructor, ctor);
Constructor<?> ctor = delegate.getConstructor();
Object[] params = new Object[delegate.getRequiredParameterCount()];
delegate.extractInvocationArguments(params, entity.getInstanceCreatorMetadata(), provider);

Loading…
Cancel
Save