2626import java .util .List ;
2727import java .util .Map ;
2828import java .util .function .Function ;
29- import java .util .stream .IntStream ;
3029
3130import org .springframework .data .mapping .InstanceCreatorMetadata ;
3231import org .springframework .data .mapping .Parameter ;
4847 */
4948class 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