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