2020import  kotlin .reflect .KFunction ;
2121import  kotlin .reflect .KParameter ;
2222import  kotlin .reflect .KParameter .Kind ;
23+ import  kotlin .reflect .KType ;
2324import  kotlin .reflect .full .KClasses ;
2425import  kotlin .reflect .jvm .ReflectJvmMapping ;
2526
2627import  java .lang .reflect .Method ;
2728import  java .lang .reflect .Modifier ;
29+ import  java .lang .reflect .Type ;
2830import  java .util .ArrayList ;
2931import  java .util .Arrays ;
3032import  java .util .List ;
3739import  org .springframework .util .Assert ;
3840
3941/** 
40-  * Value object to represent a Kotlin {@code copy} method. 
42+  * Value object to represent a Kotlin {@code copy} method. The lookup requires a {@code copy} method that matches the 
43+  * primary constructor of the class regardless of whether the primary constructor is the persistence constructor. 
4144 * 
4245 * @author Mark Paluch 
4346 * @since 2.1 
@@ -59,13 +62,14 @@ private KotlinCopyMethod(Method publicCopyMethod, Method syntheticCopyMethod) {
5962		this .publicCopyMethod  = publicCopyMethod ;
6063		this .syntheticCopyMethod  = syntheticCopyMethod ;
6164		this .copyFunction  = ReflectJvmMapping .getKotlinFunction (publicCopyMethod );
62- 		this .parameterCount  = copyFunction .getParameters ().size ();
65+ 		this .parameterCount  = copyFunction  !=  null  ?  copyFunction .getParameters ().size () :  0 ;
6366	}
6467
6568	/** 
6669	 * Attempt to lookup the Kotlin {@code copy} method. Lookup happens in two stages: Find the synthetic copy method and 
6770	 * then attempt to resolve its public variant. 
6871	 * 
72+ 	 * @param property the property that must be included in the copy method. 
6973	 * @param type the class. 
7074	 * @return {@link Optional} {@link KotlinCopyMethod}. 
7175	 */ 
@@ -155,7 +159,6 @@ boolean shouldUsePublicCopyMethod(PersistentEntity<?, ?> entity) {
155159		return  true ;
156160	}
157161
158- 	@ SuppressWarnings ("unchecked" )
159162	private  static  Optional <Method > findPublicCopyMethod (Method  defaultKotlinMethod ) {
160163
161164		Class <?> type  = defaultKotlinMethod .getDeclaringClass ();
@@ -167,10 +170,7 @@ private static Optional<Method> findPublicCopyMethod(Method defaultKotlinMethod)
167170			return  Optional .empty ();
168171		}
169172
170- 		List <KParameter > constructorArguments  = primaryConstructor .getParameters () // 
171- 				.stream () // 
172- 				.filter (it  -> it .getKind () == Kind .VALUE ) // 
173- 				.collect (Collectors .toList ());
173+ 		List <KParameter > constructorArguments  = getComponentArguments (primaryConstructor );
174174
175175		return  Arrays .stream (type .getDeclaredMethods ()).filter (it  -> it .getName ().equals ("copy" ) // 
176176				&& !it .isSynthetic () // 
@@ -207,7 +207,7 @@ private static boolean parameterMatches(List<KParameter> constructorArguments, K
207207
208208			KParameter  constructorParameter  = constructorArguments .get (constructorArgIndex );
209209
210- 			if  (!constructorParameter .getName ().equals (parameter .getName ())
210+ 			if  (constructorParameter . getName () ==  null  ||  !constructorParameter .getName ().equals (parameter .getName ())
211211					|| !constructorParameter .getType ().equals (parameter .getType ())) {
212212				return  false ;
213213			}
@@ -220,14 +220,70 @@ private static boolean parameterMatches(List<KParameter> constructorArguments, K
220220
221221	private  static  Optional <Method > findSyntheticCopyMethod (Class <?> type ) {
222222
223+ 		KClass <?> kotlinClass  = JvmClassMappingKt .getKotlinClass (type );
224+ 		KFunction <?> primaryConstructor  = KClasses .getPrimaryConstructor (kotlinClass );
225+ 
226+ 		if  (primaryConstructor  == null ) {
227+ 			return  Optional .empty ();
228+ 		}
229+ 
223230		return  Arrays .stream (type .getDeclaredMethods ()) // 
224231				.filter (it  -> it .getName ().equals ("copy$default" ) // 
225232						&& Modifier .isStatic (it .getModifiers ()) // 
226233						&& it .getReturnType ().equals (type ))
227234				.filter (Method ::isSynthetic ) // 
235+ 				.filter (it  -> matchesPrimaryConstructor (it .getParameterTypes (), primaryConstructor ))
228236				.findFirst ();
229237	}
230238
239+ 	/** 
240+ 	 * Verify that the {@code parameterTypes} match arguments of the {@link KFunction primaryConstructor}. 
241+ 	 */ 
242+ 	private  static  boolean  matchesPrimaryConstructor (Class <?>[] parameterTypes , KFunction <?> primaryConstructor ) {
243+ 
244+ 		List <KParameter > constructorArguments  = getComponentArguments (primaryConstructor );
245+ 
246+ 		int  defaultingArgs  = KotlinDefaultMask .from (primaryConstructor , kParameter  -> false ).getDefaulting ().length ;
247+ 
248+ 		if  (parameterTypes .length  != 1  /* $this */  + constructorArguments .size () + defaultingArgs  + 1  /* object marker */ ) {
249+ 			return  false ;
250+ 		}
251+ 
252+ 		// $this comes first 
253+ 		if  (!isAssignableFrom (parameterTypes [0 ], primaryConstructor .getReturnType ())) {
254+ 			return  false ;
255+ 		}
256+ 
257+ 		for  (int  i  = 0 ; i  < constructorArguments .size (); i ++) {
258+ 
259+ 			KParameter  kParameter  = constructorArguments .get (i );
260+ 
261+ 			if  (!isAssignableFrom (parameterTypes [i  + 1 ], kParameter .getType ())) {
262+ 				return  false ;
263+ 			}
264+ 		}
265+ 
266+ 		return  true ;
267+ 	}
268+ 
269+ 	private  static  List <KParameter > getComponentArguments (KFunction <?> primaryConstructor ) {
270+ 		return  primaryConstructor .getParameters () // 
271+ 				.stream () // 
272+ 				.filter (it  -> it .getKind () == Kind .VALUE ) // 
273+ 				.collect (Collectors .toList ());
274+ 	}
275+ 
276+ 	private  static  boolean  isAssignableFrom (Class <?> target , KType  source ) {
277+ 
278+ 		Type  parameterType  = ReflectJvmMapping .getJavaType (source );
279+ 
280+ 		if  (parameterType  instanceof  Class ) {
281+ 			return  target .isAssignableFrom ((Class <?>) parameterType );
282+ 		}
283+ 
284+ 		return  false ;
285+ 	}
286+ 
231287	/** 
232288	 * Value object to represent Kotlin {@literal copy$default} invocation metadata. 
233289	 * 
0 commit comments