2626import java .util .List ;
2727import java .util .Map ;
2828import java .util .function .Function ;
29- import java .util .stream .IntStream ;
3029
3130import org .jspecify .annotations .Nullable ;
3231
4948 */
5049class KotlinInstantiationDelegate {
5150
52- private final KFunction <?> constructor ;
51+ private final PreferredConstructor <?, ?> constructor ;
52+ private final KFunction <?> constructorFunction ;
5353 private final List <KParameter > kParameters ;
5454 private final Map <KParameter , Integer > indexByKParameter ;
55- private final List <Function <@ Nullable Object , @ Nullable Object >> wrappers = new ArrayList <>() ;
56- private final Constructor <?> constructorToInvoke ;
55+ private final List <Function <@ Nullable Object , @ Nullable Object >> wrappers ;
56+ private final boolean hasDefaultConstructorMarker ;
5757
58- public KotlinInstantiationDelegate (PreferredConstructor <?, ?> preferredConstructor ,
59- Constructor <?> constructorToInvoke ) {
58+ private KotlinInstantiationDelegate (PreferredConstructor <?, ?> constructor , KFunction <?> constructorFunction ) {
6059
61- KFunction <?> kotlinConstructor = ReflectJvmMapping .getKotlinFunction (preferredConstructor .getConstructor ());
60+ this .constructor = constructor ;
61+ this .hasDefaultConstructorMarker = hasDefaultConstructorMarker (getConstructor ().getParameters ());
6262
63- if (kotlinConstructor == null ) {
64- throw new IllegalArgumentException (
65- "No corresponding Kotlin constructor found for " + preferredConstructor .getConstructor ());
66- }
67-
68- this .constructor = kotlinConstructor ;
69- this .kParameters = kotlinConstructor .getParameters ();
70- this .indexByKParameter = new IdentityHashMap <>();
63+ this .constructorFunction = constructorFunction ;
64+ this .kParameters = constructorFunction .getParameters ();
65+ this .indexByKParameter = new IdentityHashMap <>(kParameters .size ());
7166
7267 for (int i = 0 ; i < kParameters .size (); i ++) {
7368 indexByKParameter .put (kParameters .get (i ), i );
7469 }
7570
76- this .constructorToInvoke = constructorToInvoke ;
71+ this .wrappers = new ArrayList <>( kParameters . size ()) ;
7772
7873 for (KParameter kParameter : kParameters ) {
7974
@@ -82,17 +77,32 @@ public KotlinInstantiationDelegate(PreferredConstructor<?, ?> preferredConstruct
8277 }
8378 }
8479
85- static boolean hasDefaultConstructorMarker (java .lang .reflect .Parameter [] parameters ) {
80+ /**
81+ * @return the constructor to invoke. {@link PreferredConstructor#getParameters() Constructor parameters} describe the
82+ * detected (i.e. user-facing) constructor parameters and not {@link PreferredConstructor#getConstructor()}
83+ * parameters and therefore do not contain any synthetic parameters.
84+ * @since 4.0
85+ */
86+ public InstanceCreatorMetadata <?> getInstanceCreator () {
87+ return constructor ;
88+ }
8689
87- return parameters .length > 0
88- && parameters [parameters .length - 1 ].getType ().getName ().equals ("kotlin.jvm.internal.DefaultConstructorMarker" );
90+ /**
91+ * @return the constructor to invoke. {@link PreferredConstructor#getParameters() Constructor parameters} describe the
92+ * detected (i.e. user-facing) constructor parameters and not {@link PreferredConstructor#getConstructor()}
93+ * parameters and therefore do not contain any synthetic parameters.
94+ * @since 4.0
95+ */
96+ public Constructor <?> getConstructor () {
97+ return constructor .getConstructor ();
8998 }
9099
91100 /**
92- * @return number of constructor arguments.
101+ * @return number of actual constructor arguments.
102+ * @see #getConstructor()
93103 */
94104 public int getRequiredParameterCount () {
95- return constructorToInvoke .getParameterCount ();
105+ return getConstructor () .getParameterCount ();
96106 }
97107
98108 /**
@@ -107,7 +117,6 @@ public <P extends PersistentProperty<P>> void extractInvocationArguments(@Nullab
107117 }
108118
109119 int userParameterCount = kParameters .size ();
110-
111120 List <Parameter <Object , P >> parameters = entityCreator .getParameters ();
112121
113122 // Prepare user-space arguments
@@ -117,54 +126,83 @@ public <P extends PersistentProperty<P>> void extractInvocationArguments(@Nullab
117126 params [i ] = provider .getParameterValue (parameter );
118127 }
119128
120- KotlinDefaultMask defaultMask = KotlinDefaultMask .forConstructor (constructor , it -> {
129+ // late rewrapping to indicate potential absence of parameters for defaulting
130+ for (int i = 0 ; i < userParameterCount ; i ++) {
131+ params [i ] = wrappers .get (i ).apply (params [i ]);
132+ }
121133
122- int index = indexByKParameter . get ( it );
134+ if ( hasDefaultConstructorMarker ) {
123135
124- Parameter <Object , P > parameter = parameters .get (index );
125- Class <Object > type = parameter .getType ().getType ();
136+ KotlinDefaultMask defaultMask = KotlinDefaultMask .forConstructor (constructorFunction , it -> {
126137
127- if (it .isOptional () && (params [index ] == null )) {
128- if (type .isPrimitive ()) {
138+ int index = indexByKParameter .get (it );
129139
130- // apply primitive defaulting to prevent NPE on primitive downcast
131- params [index ] = ReflectionUtils .getPrimitiveDefault (type );
140+ Parameter <Object , P > parameter = parameters .get (index );
141+ Class <Object > type = parameter .getType ().getType ();
142+
143+ if (it .isOptional () && (params [index ] == null )) {
144+ if (type .isPrimitive ()) {
145+
146+ // apply primitive defaulting to prevent NPE on primitive downcast
147+ params [index ] = ReflectionUtils .getPrimitiveDefault (type );
148+ }
149+ return false ;
132150 }
133- return false ;
134- }
135151
136- return true ;
137- });
152+ return true ;
153+ });
138154
139- // late rewrapping to indicate potential absence of parameters for defaulting
140- for (int i = 0 ; i < userParameterCount ; i ++) {
141- params [i ] = wrappers .get (i ).apply (params [i ]);
155+ int [] defaulting = defaultMask .getDefaulting ();
156+ // append nullability masks to creation arguments
157+ for (int i = 0 ; i < defaulting .length ; i ++) {
158+ params [userParameterCount + i ] = defaulting [i ];
159+ }
142160 }
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 4.0
171+ */
172+ public static @ Nullable KotlinInstantiationDelegate resolve (PreferredConstructor <?, ?> preferredConstructor ) {
143173
144- int [] defaulting = defaultMask . getDefaulting ( );
145- // append nullability masks to creation arguments
146- for ( int i = 0 ; i < defaulting . length ; i ++ ) {
147- params [ userParameterCount + i ] = defaulting [ i ] ;
174+ KFunction <?> constructorFunction = ReflectJvmMapping . getKotlinFunction ( preferredConstructor . getConstructor () );
175+
176+ if ( constructorFunction == null ) {
177+ return null ;
148178 }
179+
180+ PreferredConstructor <?, ?> resolved = resolveKotlinJvmConstructor (preferredConstructor , constructorFunction );
181+ return resolved != null ? new KotlinInstantiationDelegate (resolved , constructorFunction ) : null ;
149182 }
150183
151184 /**
152185 * Resolves a {@link PreferredConstructor} to the constructor to be invoked. This can be a synthetic Kotlin
153186 * constructor accepting the same user-space parameters suffixed by Kotlin-specifics required for defaulting and the
154187 * {@code kotlin.jvm.internal.DefaultConstructorMarker} or an actual non-synthetic constructor (i.e. private
155188 * 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.
156194 *
195+ * @return the resolved constructor or {@literal null} if the constructor could not be resolved.
157196 * @since 2.0
158- * @author Mark Paluch
159197 */
160198 @ SuppressWarnings ("unchecked" )
161199 @ Nullable
162- public static PreferredConstructor <?, ?> resolveKotlinJvmConstructor (
163- PreferredConstructor <?, ?> preferredConstructor ) {
200+ private static PreferredConstructor <?, ?> resolveKotlinJvmConstructor (PreferredConstructor <?, ?> preferredConstructor ,
201+ KFunction <?> constructorFunction ) {
164202
165- Constructor <?> hit = doResolveKotlinConstructor (preferredConstructor .getConstructor ());
203+ Constructor <?> hit = findKotlinConstructor (preferredConstructor .getConstructor (), constructorFunction );
166204
167- if (hit == preferredConstructor .getConstructor ()) {
205+ if (preferredConstructor .getConstructor (). equals ( hit )) {
168206 return preferredConstructor ;
169207 }
170208
@@ -176,17 +214,23 @@ public <P extends PersistentProperty<P>> void extractInvocationArguments(@Nullab
176214 }
177215
178216 @ Nullable
179- private static Constructor <?> doResolveKotlinConstructor (Constructor <?> detectedConstructor ) {
217+ private static Constructor <?> findKotlinConstructor (Constructor <?> preferredConstructor ,
218+ KFunction <?> constructorFunction ) {
180219
181- Class <?> entityType = detectedConstructor .getDeclaringClass ();
220+ Class <?> entityType = preferredConstructor .getDeclaringClass ();
182221 Constructor <?> hit = null ;
183222 Constructor <?> privateFallback = null ;
184- KFunction <?> kotlinFunction = ReflectJvmMapping .getKotlinFunction (detectedConstructor );
223+ java .lang .reflect .Parameter [] detectedParameters = preferredConstructor .getParameters ();
224+ boolean hasDefaultConstructorMarker = KotlinInstantiationDelegate .hasDefaultConstructorMarker (detectedParameters );
185225
186226 for (Constructor <?> candidate : entityType .getDeclaredConstructors ()) {
187227
228+ java .lang .reflect .Parameter [] candidateParameters = preferredConstructor .equals (candidate )
229+ ? detectedParameters
230+ : candidate .getParameters ();
231+
188232 if (Modifier .isPrivate (candidate .getModifiers ())) {
189- if (detectedConstructor .equals (candidate )) {
233+ if (preferredConstructor .equals (candidate )) {
190234 privateFallback = candidate ;
191235 }
192236 }
@@ -196,26 +240,22 @@ private static Constructor<?> doResolveKotlinConstructor(Constructor<?> detected
196240 continue ;
197241 }
198242
199- java .lang .reflect .Parameter [] detectedConstructorParameters = detectedConstructor .getParameters ();
200- java .lang .reflect .Parameter [] candidateParameters = candidate .getParameters ();
201-
202- if (!KotlinInstantiationDelegate .hasDefaultConstructorMarker (detectedConstructorParameters )) {
243+ if (!hasDefaultConstructorMarker ) {
203244
204245 // candidates must contain at least two additional parameters (int, DefaultConstructorMarker).
205246 // Number of defaulting masks derives from the original constructor arg count
206- int syntheticParameters = KotlinDefaultMask .getMaskCount (detectedConstructor . getParameterCount () )
247+ int syntheticParameters = KotlinDefaultMask .getMaskCount (detectedParameters . length )
207248 + /* DefaultConstructorMarker */ 1 ;
208249
209- if ((detectedConstructor . getParameterCount () + syntheticParameters ) != candidate .getParameterCount ()) {
250+ if ((detectedParameters . length + syntheticParameters ) != candidate .getParameterCount ()) {
210251 continue ;
211252 }
212- } else if ( kotlinFunction != null ) {
253+ } else {
213254
214- int optionalParameterCount = (int ) kotlinFunction .getParameters ().stream ().filter (KParameter ::isOptional )
215- .count ();
255+ int optionalParameterCount = getOptionalParameterCount (constructorFunction );
216256 int syntheticParameters = KotlinDefaultMask .getExactMaskCount (optionalParameterCount );
217257
218- if ((detectedConstructor . getParameterCount () + syntheticParameters ) != candidate .getParameterCount ()) {
258+ if ((detectedParameters . length + syntheticParameters ) != candidate .getParameterCount ()) {
219259 continue ;
220260 }
221261 }
@@ -224,9 +264,8 @@ private static Constructor<?> doResolveKotlinConstructor(Constructor<?> detected
224264 continue ;
225265 }
226266
227- int userParameterCount = kotlinFunction != null ? kotlinFunction .getParameters ().size ()
228- : detectedConstructor .getParameterCount ();
229- if (parametersMatch (detectedConstructorParameters , candidateParameters , userParameterCount )) {
267+ int userParameterCount = constructorFunction .getParameters ().size ();
268+ if (parametersMatch (detectedParameters , candidateParameters , userParameterCount )) {
230269 hit = candidate ;
231270 }
232271 }
@@ -238,24 +277,48 @@ private static Constructor<?> doResolveKotlinConstructor(Constructor<?> detected
238277 return hit ;
239278 }
240279
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+
241293 private static boolean parametersMatch (java .lang .reflect .Parameter [] constructorParameters ,
242294 java .lang .reflect .Parameter [] candidateParameters , int userParameterCount ) {
243295
244- return IntStream .range (0 , userParameterCount )
245- .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 ;
246302 }
247303
248- static boolean parametersMatch (java .lang .reflect .Parameter constructorParameter ,
249- java .lang .reflect .Parameter candidateParameter ) {
304+ private static boolean parametersMatch (Class <?> constructorParameter , Class <?> candidateParameter ) {
250305
251- if (constructorParameter .getType (). equals (candidateParameter . getType () )) {
306+ if (constructorParameter .equals (candidateParameter )) {
252307 return true ;
253308 }
254309
255310 // candidate can be also a wrapper
256- Class <?> componentOrWrapperType = KotlinValueUtils .getConstructorValueHierarchy (candidateParameter .getType ())
257- .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+ }
258320
259- return constructorParameter .getType ().equals (componentOrWrapperType );
321+ private static boolean isDefaultConstructorMarker (Class <?> cls ) {
322+ return cls .getName ().equals ("kotlin.jvm.internal.DefaultConstructorMarker" );
260323 }
261324}
0 commit comments