DATACMNS-1402 - Fix invocation of default Kotlin constructor.

We now correctly calculate the number of defaulting masks used to represent constructor arguments. Previously, we've been one off which caused that Kotlin classes with 32/33 parameters weren't able to be instantiated.

We also now reuse KotlinDefaultMask to apply defaulting calculation and removed code duplicates.
This commit is contained in:
Mark Paluch
2018-10-04 14:19:55 +02:00
committed by Oliver Gierke
parent 42ee9448d9
commit fc4b2d1deb
5 changed files with 240 additions and 24 deletions

View File

@@ -28,6 +28,7 @@ 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.KotlinDefaultMask;
import org.springframework.data.mapping.model.MappingInstantiationException;
import org.springframework.data.mapping.model.ParameterValueProvider;
import org.springframework.data.util.ReflectionUtils;
@@ -114,7 +115,7 @@ public class KotlinClassGeneratingEntityInstantiator extends ClassGeneratingEnti
// candidates must contain at least two additional parameters (int, DefaultConstructorMarker).
// Number of defaulting masks derives from the original constructor arg count
int syntheticParameters = (constructor.getParameterCount() / Integer.SIZE) + 1
int syntheticParameters = KotlinDefaultMask.getMaskCount(constructor.getParameterCount())
+ /* DefaultConstructorMarker */ 1;
if (constructor.getParameterCount() + syntheticParameters != candidate.getParameterCount()) {
@@ -172,6 +173,7 @@ public class KotlinClassGeneratingEntityInstantiator extends ClassGeneratingEnti
static class DefaultingKotlinClassInstantiatorAdapter implements EntityInstantiator {
private final ObjectInstantiator instantiator;
private final KFunction<?> constructor;
private final List<KParameter> kParameters;
private final Constructor<?> synthetic;
@@ -185,6 +187,7 @@ public class KotlinClassGeneratingEntityInstantiator extends ClassGeneratingEnti
}
this.instantiator = instantiator;
this.constructor = kotlinConstructor;
this.kParameters = kotlinConstructor.getParameters();
this.synthetic = constructor.getConstructor();
}
@@ -214,10 +217,8 @@ public class KotlinClassGeneratingEntityInstantiator extends ClassGeneratingEnti
throw new IllegalArgumentException("PreferredConstructor must not be null!");
}
int[] defaulting = new int[(synthetic.getParameterCount() / Integer.SIZE) + 1];
Object[] params = allocateArguments(
synthetic.getParameterCount() + defaulting.length + /* DefaultConstructorMarker */1);
synthetic.getParameterCount() + KotlinDefaultMask.getMaskCount(synthetic.getParameterCount()) + /* DefaultConstructorMarker */1);
int userParameterCount = kParameters.size();
List<Parameter<Object, P>> parameters = preferredConstructor.getParameters();
@@ -225,28 +226,30 @@ public class KotlinClassGeneratingEntityInstantiator extends ClassGeneratingEnti
// Prepare user-space arguments
for (int i = 0; i < userParameterCount; i++) {
int slot = i / Integer.SIZE;
int offset = slot * Integer.SIZE;
Parameter<Object, P> parameter = parameters.get(i);
Class<Object> type = parameter.getType().getType();
Object param = provider.getParameterValue(parameter);
KParameter kParameter = kParameters.get(i);
// what about null and parameter is mandatory? What if parameter is non-null?
if (kParameter.isOptional() && param == null) {
defaulting[slot] = defaulting[slot] | (1 << (i - offset));
if (type.isPrimitive()) {
param = ReflectionUtils.getPrimitiveDefault(type);
}
}
params[i] = param;
params[i] = provider.getParameterValue(parameter);
}
KotlinDefaultMask defaultMask = KotlinDefaultMask.from(constructor, it -> {
int index = kParameters.indexOf(it);
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 true;
});
int[] defaulting = defaultMask.getDefaulting();
// append nullability masks to creation arguments
for (int i = 0; i < defaulting.length; i++) {
params[userParameterCount + i] = defaulting[i];

View File

@@ -24,14 +24,19 @@ import kotlin.reflect.KFunction;
import kotlin.reflect.KParameter;
import kotlin.reflect.KParameter.Kind;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
/**
* Value object representing defaulting masks used for Kotlin methods applying parameter defaulting.
*
* @author Mark Paluch
* @since 2.1
*/
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
class KotlinDefaultMask {
public class KotlinDefaultMask {
@Getter
private final int[] defaulting;
/**
@@ -46,6 +51,16 @@ class KotlinDefaultMask {
}
}
/**
* Return the number of defaulting masks required to represent the number of {@code arguments}.
*
* @param arguments number of method arguments.
* @return the number of defaulting masks required.
*/
public static int getMaskCount(int arguments) {
return ((arguments - 1) / Integer.SIZE) + 1;
}
/**
* Creates defaulting mask(s) used to invoke Kotlin {@literal default} methods that conditionally apply parameter
* values.