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