Skip to content

Commit f38c216

Browse files
committed
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
1 parent b935b29 commit f38c216

File tree

3 files changed

+145
-97
lines changed

3 files changed

+145
-97
lines changed

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

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
*/
1616
package org.springframework.data.mapping.model;
1717

18-
import java.lang.reflect.Constructor;
1918
import java.util.Arrays;
2019

2120
import org.springframework.data.mapping.InstanceCreatorMetadata;
@@ -43,15 +42,11 @@ protected EntityInstantiator doCreateEntityInstantiator(PersistentEntity<?, ?> e
4342
if (KotlinReflectionUtils.isSupportedKotlinClass(entity.getType())
4443
&& creator instanceof PreferredConstructor<?, ?> constructor) {
4544

46-
PreferredConstructor<?, ? extends PersistentProperty<?>> kotlinJvmConstructor = KotlinInstantiationDelegate
47-
.resolveKotlinJvmConstructor(constructor);
45+
KotlinInstantiationDelegate delegate = KotlinInstantiationDelegate.resolve(constructor);
4846

49-
if (kotlinJvmConstructor != null) {
50-
51-
ObjectInstantiator instantiator = createObjectInstantiator(entity, kotlinJvmConstructor);
52-
53-
return new DefaultingKotlinClassInstantiatorAdapter(instantiator, constructor,
54-
kotlinJvmConstructor.getConstructor());
47+
if (delegate != null) {
48+
ObjectInstantiator instantiator = createObjectInstantiator(entity, delegate.getInstanceCreator());
49+
return new DefaultingKotlinClassInstantiatorAdapter(instantiator, delegate);
5550
}
5651
}
5752

@@ -82,10 +77,10 @@ static class DefaultingKotlinClassInstantiatorAdapter implements EntityInstantia
8277
private final KotlinInstantiationDelegate delegate;
8378

8479
DefaultingKotlinClassInstantiatorAdapter(ObjectInstantiator instantiator,
85-
PreferredConstructor<?, ?> defaultConstructor, Constructor<?> constructorToInvoke) {
80+
KotlinInstantiationDelegate delegate) {
8681

8782
this.instantiator = instantiator;
88-
this.delegate = new KotlinInstantiationDelegate(defaultConstructor, constructorToInvoke);
83+
this.delegate = delegate;
8984
}
9085

9186
@Override

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

Lines changed: 134 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
import java.util.List;
2727
import java.util.Map;
2828
import java.util.function.Function;
29-
import java.util.stream.IntStream;
3029

3130
import org.springframework.data.mapping.InstanceCreatorMetadata;
3231
import org.springframework.data.mapping.Parameter;
@@ -48,31 +47,27 @@
4847
*/
4948
class KotlinInstantiationDelegate {
5049

51-
private final KFunction<?> constructor;
50+
private final PreferredConstructor<?, ?> constructor;
51+
private final KFunction<?> constructorFunction;
5252
private final List<KParameter> kParameters;
5353
private final Map<KParameter, Integer> indexByKParameter;
54-
private final List<Function<Object, Object>> wrappers = new ArrayList<>();
55-
private final Constructor<?> constructorToInvoke;
54+
private final List<Function<Object, Object>> wrappers;
55+
private final boolean hasDefaultConstructorMarker;
5656

57-
public KotlinInstantiationDelegate(PreferredConstructor<?, ?> preferredConstructor,
58-
Constructor<?> constructorToInvoke) {
57+
private KotlinInstantiationDelegate(PreferredConstructor<?, ?> constructor, KFunction<?> constructorFunction) {
5958

60-
KFunction<?> kotlinConstructor = ReflectJvmMapping.getKotlinFunction(preferredConstructor.getConstructor());
59+
this.constructor = constructor;
60+
this.hasDefaultConstructorMarker = hasDefaultConstructorMarker(getConstructor().getParameters());
6161

62-
if (kotlinConstructor == null) {
63-
throw new IllegalArgumentException(
64-
"No corresponding Kotlin constructor found for " + preferredConstructor.getConstructor());
65-
}
66-
67-
this.constructor = kotlinConstructor;
68-
this.kParameters = kotlinConstructor.getParameters();
69-
this.indexByKParameter = new IdentityHashMap<>();
62+
this.constructorFunction = constructorFunction;
63+
this.kParameters = constructorFunction.getParameters();
64+
this.indexByKParameter = new IdentityHashMap<>(kParameters.size());
7065

7166
for (int i = 0; i < kParameters.size(); i++) {
7267
indexByKParameter.put(kParameters.get(i), i);
7368
}
7469

75-
this.constructorToInvoke = constructorToInvoke;
70+
this.wrappers = new ArrayList<>(kParameters.size());
7671

7772
for (KParameter kParameter : kParameters) {
7873

@@ -81,27 +76,36 @@ public KotlinInstantiationDelegate(PreferredConstructor<?, ?> preferredConstruct
8176
}
8277
}
8378

84-
static boolean hasDefaultConstructorMarker(java.lang.reflect.Parameter[] parameters) {
79+
/**
80+
* @return the constructor to invoke. {@link PreferredConstructor#getParameters() Constructor parameters} describe the
81+
* detected (i.e. user-facing) constructor parameters and not {@link PreferredConstructor#getConstructor()}
82+
* parameters and therefore do not contain any synthetic parameters.
83+
* @since 3.5.6
84+
*/
85+
public InstanceCreatorMetadata<?> getInstanceCreator() {
86+
return constructor;
87+
}
8588

86-
return parameters.length > 0
87-
&& parameters[parameters.length - 1].getType().getName().equals("kotlin.jvm.internal.DefaultConstructorMarker");
89+
/**
90+
* @return the constructor to invoke. {@link PreferredConstructor#getParameters() Constructor parameters} describe the
91+
* detected (i.e. user-facing) constructor parameters and not {@link PreferredConstructor#getConstructor()}
92+
* parameters and therefore do not contain any synthetic parameters.
93+
* @since 3.5.6
94+
*/
95+
public Constructor<?> getConstructor() {
96+
return constructor.getConstructor();
8897
}
8998

9099
/**
91-
* @return number of constructor arguments.
100+
* @return number of actual constructor arguments.
101+
* @see #getConstructor()
92102
*/
93103
public int getRequiredParameterCount() {
94-
return constructorToInvoke.getParameterCount();
104+
return getConstructor().getParameterCount();
95105
}
96106

97107
/**
98108
* Extract the actual construction arguments for a direct constructor call.
99-
*
100-
* @param params
101-
* @param entityCreator
102-
* @param provider
103-
* @return
104-
* @param <P>
105109
*/
106110
public <P extends PersistentProperty<P>> Object[] extractInvocationArguments(Object[] params,
107111
@Nullable InstanceCreatorMetadata<P> entityCreator, ParameterValueProvider<P> provider) {
@@ -111,7 +115,6 @@ public <P extends PersistentProperty<P>> Object[] extractInvocationArguments(Obj
111115
}
112116

113117
int userParameterCount = kParameters.size();
114-
115118
List<Parameter<Object, P>> parameters = entityCreator.getParameters();
116119

117120
// Prepare user-space arguments
@@ -121,56 +124,85 @@ public <P extends PersistentProperty<P>> Object[] extractInvocationArguments(Obj
121124
params[i] = provider.getParameterValue(parameter);
122125
}
123126

124-
KotlinDefaultMask defaultMask = KotlinDefaultMask.forConstructor(constructor, it -> {
127+
// late rewrapping to indicate potential absence of parameters for defaulting
128+
for (int i = 0; i < userParameterCount; i++) {
129+
params[i] = wrappers.get(i).apply(params[i]);
130+
}
125131

126-
int index = indexByKParameter.get(it);
132+
if (hasDefaultConstructorMarker) {
127133

128-
Parameter<Object, P> parameter = parameters.get(index);
129-
Class<Object> type = parameter.getType().getType();
134+
KotlinDefaultMask defaultMask = KotlinDefaultMask.forConstructor(constructorFunction, it -> {
130135

131-
if (it.isOptional() && (params[index] == null)) {
132-
if (type.isPrimitive()) {
136+
int index = indexByKParameter.get(it);
133137

134-
// apply primitive defaulting to prevent NPE on primitive downcast
135-
params[index] = ReflectionUtils.getPrimitiveDefault(type);
138+
Parameter<Object, P> parameter = parameters.get(index);
139+
Class<Object> type = parameter.getType().getType();
140+
141+
if (it.isOptional() && (params[index] == null)) {
142+
if (type.isPrimitive()) {
143+
144+
// apply primitive defaulting to prevent NPE on primitive downcast
145+
params[index] = ReflectionUtils.getPrimitiveDefault(type);
146+
}
147+
return false;
136148
}
137-
return false;
138-
}
139149

140-
return true;
141-
});
150+
return true;
151+
});
142152

143-
// late rewrapping to indicate potential absence of parameters for defaulting
144-
for (int i = 0; i < userParameterCount; i++) {
145-
params[i] = wrappers.get(i).apply(params[i]);
153+
int[] defaulting = defaultMask.getDefaulting();
154+
// append nullability masks to creation arguments
155+
for (int i = 0; i < defaulting.length; i++) {
156+
params[userParameterCount + i] = defaulting[i];
157+
}
146158
}
147159

148-
int[] defaulting = defaultMask.getDefaulting();
149-
// append nullability masks to creation arguments
150-
for (int i = 0; i < defaulting.length; i++) {
151-
params[userParameterCount + i] = defaulting[i];
160+
return params;
161+
}
162+
163+
/**
164+
* Try to resolve {@code KotlinInstantiationDelegate} from a {@link PreferredConstructor}. Resolution attempts to find
165+
* a JVM constructor equivalent considering value class mangling, Kotlin defaulting and potentially synthetic
166+
* constructors generated by the Kotlin compile including the lookup of a {@link KFunction} from the given
167+
* {@link PreferredConstructor}.
168+
*
169+
* @return the {@code KotlinInstantiationDelegate} if resolution was successful; {@literal null} otherwise.
170+
* @since 3.5.6
171+
*/
172+
public static @Nullable KotlinInstantiationDelegate resolve(PreferredConstructor<?, ?> preferredConstructor) {
173+
174+
KFunction<?> constructorFunction = ReflectJvmMapping.getKotlinFunction(preferredConstructor.getConstructor());
175+
176+
if (constructorFunction == null) {
177+
return null;
152178
}
153179

154-
return params;
180+
PreferredConstructor<?, ?> resolved = resolveKotlinJvmConstructor(preferredConstructor, constructorFunction);
181+
return resolved != null ? new KotlinInstantiationDelegate(resolved, constructorFunction) : null;
155182
}
156183

157184
/**
158185
* Resolves a {@link PreferredConstructor} to the constructor to be invoked. This can be a synthetic Kotlin
159186
* constructor accepting the same user-space parameters suffixed by Kotlin-specifics required for defaulting and the
160187
* {@code kotlin.jvm.internal.DefaultConstructorMarker} or an actual non-synthetic constructor (i.e. private
161188
* constructor).
189+
* <p>
190+
* Constructor resolution may return {@literal null} indicating that no matching constructor could be found.
191+
* <p>
192+
* The resulting constructor {@link PreferredConstructor#getParameters()} (and parameter count) reflect user-facing
193+
* parameters and do not contain any synthetic parameters.
162194
*
195+
* @return the resolved constructor or {@literal null} if the constructor could not be resolved.
163196
* @since 2.0
164-
* @author Mark Paluch
165197
*/
166198
@SuppressWarnings("unchecked")
167199
@Nullable
168-
public static PreferredConstructor<?, ?> resolveKotlinJvmConstructor(
169-
PreferredConstructor<?, ?> preferredConstructor) {
200+
private static PreferredConstructor<?, ?> resolveKotlinJvmConstructor(PreferredConstructor<?, ?> preferredConstructor,
201+
KFunction<?> constructorFunction) {
170202

171-
Constructor<?> hit = doResolveKotlinConstructor(preferredConstructor.getConstructor());
203+
Constructor<?> hit = findKotlinConstructor(preferredConstructor.getConstructor(), constructorFunction);
172204

173-
if (hit == preferredConstructor.getConstructor()) {
205+
if (preferredConstructor.getConstructor().equals(hit)) {
174206
return preferredConstructor;
175207
}
176208

@@ -182,17 +214,23 @@ public <P extends PersistentProperty<P>> Object[] extractInvocationArguments(Obj
182214
}
183215

184216
@Nullable
185-
private static Constructor<?> doResolveKotlinConstructor(Constructor<?> detectedConstructor) {
217+
private static Constructor<?> findKotlinConstructor(Constructor<?> preferredConstructor,
218+
KFunction<?> constructorFunction) {
186219

187-
Class<?> entityType = detectedConstructor.getDeclaringClass();
220+
Class<?> entityType = preferredConstructor.getDeclaringClass();
188221
Constructor<?> hit = null;
189222
Constructor<?> privateFallback = null;
190-
KFunction<?> kotlinFunction = ReflectJvmMapping.getKotlinFunction(detectedConstructor);
223+
java.lang.reflect.Parameter[] detectedParameters = preferredConstructor.getParameters();
224+
boolean hasDefaultConstructorMarker = KotlinInstantiationDelegate.hasDefaultConstructorMarker(detectedParameters);
191225

192226
for (Constructor<?> candidate : entityType.getDeclaredConstructors()) {
193227

228+
java.lang.reflect.Parameter[] candidateParameters = preferredConstructor.equals(candidate)
229+
? detectedParameters
230+
: candidate.getParameters();
231+
194232
if (Modifier.isPrivate(candidate.getModifiers())) {
195-
if (detectedConstructor.equals(candidate)) {
233+
if (preferredConstructor.equals(candidate)) {
196234
privateFallback = candidate;
197235
}
198236
}
@@ -202,26 +240,22 @@ private static Constructor<?> doResolveKotlinConstructor(Constructor<?> detected
202240
continue;
203241
}
204242

205-
java.lang.reflect.Parameter[] detectedConstructorParameters = detectedConstructor.getParameters();
206-
java.lang.reflect.Parameter[] candidateParameters = candidate.getParameters();
207-
208-
if (!KotlinInstantiationDelegate.hasDefaultConstructorMarker(detectedConstructorParameters)) {
243+
if (!hasDefaultConstructorMarker) {
209244

210245
// candidates must contain at least two additional parameters (int, DefaultConstructorMarker).
211246
// Number of defaulting masks derives from the original constructor arg count
212-
int syntheticParameters = KotlinDefaultMask.getMaskCount(detectedConstructor.getParameterCount())
247+
int syntheticParameters = KotlinDefaultMask.getMaskCount(detectedParameters.length)
213248
+ /* DefaultConstructorMarker */ 1;
214249

215-
if ((detectedConstructor.getParameterCount() + syntheticParameters) != candidate.getParameterCount()) {
250+
if ((detectedParameters.length + syntheticParameters) != candidate.getParameterCount()) {
216251
continue;
217252
}
218253
} else {
219254

220-
int optionalParameterCount = (int) kotlinFunction.getParameters().stream().filter(it -> it.isOptional())
221-
.count();
255+
int optionalParameterCount = getOptionalParameterCount(constructorFunction);
222256
int syntheticParameters = KotlinDefaultMask.getExactMaskCount(optionalParameterCount);
223257

224-
if ((detectedConstructor.getParameterCount() + syntheticParameters) != candidate.getParameterCount()) {
258+
if ((detectedParameters.length + syntheticParameters) != candidate.getParameterCount()) {
225259
continue;
226260
}
227261
}
@@ -230,9 +264,8 @@ private static Constructor<?> doResolveKotlinConstructor(Constructor<?> detected
230264
continue;
231265
}
232266

233-
int userParameterCount = kotlinFunction != null ? kotlinFunction.getParameters().size()
234-
: detectedConstructor.getParameterCount();
235-
if (parametersMatch(detectedConstructorParameters, candidateParameters, userParameterCount)) {
267+
int userParameterCount = constructorFunction.getParameters().size();
268+
if (parametersMatch(detectedParameters, candidateParameters, userParameterCount)) {
236269
hit = candidate;
237270
}
238271
}
@@ -244,24 +277,48 @@ private static Constructor<?> doResolveKotlinConstructor(Constructor<?> detected
244277
return hit;
245278
}
246279

280+
private static int getOptionalParameterCount(KFunction<?> function) {
281+
282+
int count = 0;
283+
284+
for (KParameter parameter : function.getParameters()) {
285+
if (parameter.isOptional()) {
286+
count++;
287+
}
288+
}
289+
290+
return count;
291+
}
292+
247293
private static boolean parametersMatch(java.lang.reflect.Parameter[] constructorParameters,
248294
java.lang.reflect.Parameter[] candidateParameters, int userParameterCount) {
249295

250-
return IntStream.range(0, userParameterCount)
251-
.allMatch(i -> parametersMatch(constructorParameters[i], candidateParameters[i]));
296+
for (int i = 0; i < userParameterCount; i++) {
297+
if (!parametersMatch(constructorParameters[i].getType(), candidateParameters[i].getType())) {
298+
return false;
299+
}
300+
}
301+
return true;
252302
}
253303

254-
static boolean parametersMatch(java.lang.reflect.Parameter constructorParameter,
255-
java.lang.reflect.Parameter candidateParameter) {
304+
private static boolean parametersMatch(Class<?> constructorParameter, Class<?> candidateParameter) {
256305

257-
if (constructorParameter.getType().equals(candidateParameter.getType())) {
306+
if (constructorParameter.equals(candidateParameter)) {
258307
return true;
259308
}
260309

261310
// candidate can be also a wrapper
262-
Class<?> componentOrWrapperType = KotlinValueUtils.getConstructorValueHierarchy(candidateParameter.getType())
263-
.getActualType();
311+
Class<?> componentOrWrapperType = KotlinValueUtils.getConstructorValueHierarchy(candidateParameter).getActualType();
312+
313+
return constructorParameter.equals(componentOrWrapperType);
314+
}
315+
316+
private static boolean hasDefaultConstructorMarker(java.lang.reflect.Parameter[] parameters) {
317+
318+
return parameters.length > 0 && isDefaultConstructorMarker(parameters[parameters.length - 1].getType());
319+
}
264320

265-
return constructorParameter.getType().equals(componentOrWrapperType);
321+
private static boolean isDefaultConstructorMarker(Class<?> cls) {
322+
return cls.getName().equals("kotlin.jvm.internal.DefaultConstructorMarker");
266323
}
267324
}

0 commit comments

Comments
 (0)