|  | 
| 16 | 16 | package org.springframework.data.repository.aot.generate; | 
| 17 | 17 | 
 | 
| 18 | 18 | import java.lang.reflect.Method; | 
|  | 19 | +import java.lang.reflect.Type; | 
|  | 20 | +import java.lang.reflect.TypeVariable; | 
|  | 21 | +import java.lang.reflect.WildcardType; | 
| 19 | 22 | import java.util.ArrayList; | 
| 20 | 23 | import java.util.Arrays; | 
| 21 | 24 | import java.util.Comparator; | 
|  | 25 | +import java.util.HashSet; | 
| 22 | 26 | import java.util.List; | 
|  | 27 | +import java.util.Set; | 
| 23 | 28 | import java.util.function.Consumer; | 
|  | 29 | +import java.util.function.Predicate; | 
| 24 | 30 | 
 | 
| 25 | 31 | import javax.lang.model.element.Modifier; | 
| 26 | 32 | 
 | 
| 27 | 33 | import org.apache.commons.logging.Log; | 
| 28 | 34 | import org.apache.commons.logging.LogFactory; | 
| 29 | 35 | import org.jspecify.annotations.Nullable; | 
|  | 36 | + | 
| 30 | 37 | import org.springframework.core.ResolvableType; | 
| 31 | 38 | import org.springframework.data.projection.ProjectionFactory; | 
| 32 | 39 | import org.springframework.data.repository.core.RepositoryInformation; | 
| 33 | 40 | import org.springframework.data.repository.core.support.RepositoryComposition; | 
| 34 | 41 | import org.springframework.data.repository.core.support.RepositoryFragment; | 
| 35 | 42 | import org.springframework.data.repository.query.QueryMethod; | 
| 36 | 43 | import org.springframework.data.util.Lazy; | 
|  | 44 | +import org.springframework.data.util.TypeInformation; | 
| 37 | 45 | import org.springframework.javapoet.ClassName; | 
| 38 | 46 | import org.springframework.javapoet.FieldSpec; | 
| 39 | 47 | import org.springframework.javapoet.MethodSpec; | 
| @@ -299,7 +307,7 @@ private void contributeMethod(Method method, @Nullable MethodContributorFactory | 
| 299 | 307 | 			return; | 
| 300 | 308 | 		} | 
| 301 | 309 | 
 | 
| 302 |  | -		if (hasUnresolvableGenerics(method)) { | 
|  | 310 | +		if (ResolvableGenerics.of(method, repositoryInformation.getRepositoryInterface()).hasUnresolvableGenerics()) { | 
| 303 | 311 | 
 | 
| 304 | 312 | 			if (logger.isTraceEnabled()) { | 
| 305 | 313 | 				logger.trace( | 
| @@ -332,22 +340,208 @@ private void contributeMethod(Method method, @Nullable MethodContributorFactory | 
| 332 | 340 | 		generationMetadata.addRepositoryMethod(method, contributor); | 
| 333 | 341 | 	} | 
| 334 | 342 | 
 | 
| 335 |  | -	private boolean hasUnresolvableGenerics(Method method) { | 
|  | 343 | +	/** | 
|  | 344 | +	 * Value object to determine whether generics in a given {@link Method} can be resolved. Resolvable generics are e.g. | 
|  | 345 | +	 * declared on the method level (unbounded type variables, type variables using class boundaries). Considers | 
|  | 346 | +	 * collections and map types. | 
|  | 347 | +	 * <p> | 
|  | 348 | +	 * Considers resolvable: | 
|  | 349 | +	 * <ul> | 
|  | 350 | +	 * <li>Unbounded method-level type parameters {@code <T> T foo(Class<T>)}</li> | 
|  | 351 | +	 * <li>Bounded method-level type parameters that resolve to a class | 
|  | 352 | +	 * {@code <T extends Serializable> T foo(Class<T>)}</li> | 
|  | 353 | +	 * <li>Simple references to interface variables {@code T foo(), List<T> foo(…)}</li> | 
|  | 354 | +	 * <li>Unbounded wildcards {@code User foo(GeoJson<?>)}</li> | 
|  | 355 | +	 * </ul> | 
|  | 356 | +	 * Considers non-resolvable: | 
|  | 357 | +	 * <ul> | 
|  | 358 | +	 * <li>Parametrized bounds referring to known variables on method-level type parameters | 
|  | 359 | +	 * {@code <P extends T> T foo(Class<T>), List<? super T> foo()}</li> | 
|  | 360 | +	 * <li>Generally unresolvable generics</li> | 
|  | 361 | +	 * </ul> | 
|  | 362 | +	 * </p> | 
|  | 363 | +	 * | 
|  | 364 | +	 * @author Mark Paluch | 
|  | 365 | +	 */ | 
|  | 366 | +	record ResolvableGenerics(Method method, Class<?> implClass, Set<Type> resolvableTypeVariables, | 
|  | 367 | +			Set<Type> unwantedMethodVariables) { | 
|  | 368 | + | 
|  | 369 | +		/** | 
|  | 370 | +		 * Create a new {@code ResolvableGenerics} object for the given {@link Method}. | 
|  | 371 | +		 * | 
|  | 372 | +		 * @param method | 
|  | 373 | +		 * @return | 
|  | 374 | +		 */ | 
|  | 375 | +		public static ResolvableGenerics of(Method method, Class<?> implClass) { | 
|  | 376 | +			return new ResolvableGenerics(method, implClass, getResolvableTypeVariables(method), | 
|  | 377 | +					getUnwantedMethodVariables(method)); | 
|  | 378 | +		} | 
|  | 379 | + | 
|  | 380 | +		private static Set<Type> getResolvableTypeVariables(Method method) { | 
|  | 381 | + | 
|  | 382 | +			Set<Type> simpleTypeVariables = new HashSet<>(); | 
|  | 383 | + | 
|  | 384 | +			for (TypeVariable<Method> typeParameter : method.getTypeParameters()) { | 
|  | 385 | +				if (isClassBounded(typeParameter.getBounds())) { | 
|  | 386 | +					simpleTypeVariables.add(typeParameter); | 
|  | 387 | +				} | 
|  | 388 | +			} | 
|  | 389 | + | 
|  | 390 | +			return simpleTypeVariables; | 
|  | 391 | +		} | 
|  | 392 | + | 
|  | 393 | +		private static Set<Type> getUnwantedMethodVariables(Method method) { | 
|  | 394 | + | 
|  | 395 | +			Set<Type> unwanted = new HashSet<>(); | 
|  | 396 | + | 
|  | 397 | +			for (TypeVariable<Method> typeParameter : method.getTypeParameters()) { | 
|  | 398 | +				if (!isClassBounded(typeParameter.getBounds())) { | 
|  | 399 | +					unwanted.add(typeParameter); | 
|  | 400 | +				} | 
|  | 401 | +			} | 
|  | 402 | +			return unwanted; | 
|  | 403 | +		} | 
|  | 404 | + | 
|  | 405 | +		/** | 
|  | 406 | +		 * Check whether the {@link Method} has unresolvable generics when being considered in the context of the | 
|  | 407 | +		 * implementation class. | 
|  | 408 | +		 * | 
|  | 409 | +		 * @return | 
|  | 410 | +		 */ | 
|  | 411 | +		public boolean hasUnresolvableGenerics() { | 
|  | 412 | + | 
|  | 413 | +			ResolvableType resolvableType = ResolvableType.forMethodReturnType(method, implClass); | 
|  | 414 | + | 
|  | 415 | +			if (isUnresolvable(resolvableType)) { | 
|  | 416 | +				return true; | 
|  | 417 | +			} | 
|  | 418 | + | 
|  | 419 | +			for (int i = 0; i < method.getParameterCount(); i++) { | 
|  | 420 | +				if (isUnresolvable(ResolvableType.forMethodParameter(method, i, implClass))) { | 
|  | 421 | +					return true; | 
|  | 422 | +				} | 
|  | 423 | +			} | 
|  | 424 | + | 
|  | 425 | +			return false; | 
|  | 426 | +		} | 
|  | 427 | + | 
|  | 428 | +		private boolean isUnresolvable(TypeInformation<?> typeInformation) { | 
|  | 429 | +			return isUnresolvable(typeInformation.toResolvableType()); | 
|  | 430 | +		} | 
|  | 431 | + | 
|  | 432 | +		private boolean isUnresolvable(ResolvableType resolvableType) { | 
|  | 433 | + | 
|  | 434 | +			if (isResolvable(resolvableType)) { | 
|  | 435 | +				return false; | 
|  | 436 | +			} | 
|  | 437 | + | 
|  | 438 | +			if (isUnwanted(resolvableType)) { | 
|  | 439 | +				return true; | 
|  | 440 | +			} | 
|  | 441 | + | 
|  | 442 | +			if (resolvableType.isAssignableFrom(Class.class)) { | 
|  | 443 | +				return isUnresolvable(resolvableType.getGeneric(0)); | 
|  | 444 | +			} | 
|  | 445 | + | 
|  | 446 | +			TypeInformation<?> typeInformation = TypeInformation.of(resolvableType); | 
|  | 447 | +			if (typeInformation.isMap() || typeInformation.isCollectionLike()) { | 
|  | 448 | + | 
|  | 449 | +				for (ResolvableType type : resolvableType.getGenerics()) { | 
|  | 450 | +					if (isUnresolvable(type)) { | 
|  | 451 | +						return true; | 
|  | 452 | +					} | 
|  | 453 | +				} | 
|  | 454 | + | 
|  | 455 | +				return false; | 
|  | 456 | +			} | 
|  | 457 | + | 
|  | 458 | +			if (typeInformation.getActualType() != null && typeInformation.getActualType() != typeInformation) { | 
|  | 459 | +				return isUnresolvable(typeInformation.getRequiredActualType()); | 
|  | 460 | +			} | 
|  | 461 | + | 
|  | 462 | +			return resolvableType.hasUnresolvableGenerics(); | 
|  | 463 | +		} | 
|  | 464 | + | 
|  | 465 | +		private boolean isResolvable(Type[] types) { | 
|  | 466 | + | 
|  | 467 | +			for (Type type : types) { | 
|  | 468 | + | 
|  | 469 | +				if (resolvableTypeVariables.contains(type)) { | 
|  | 470 | +					continue; | 
|  | 471 | +				} | 
|  | 472 | + | 
|  | 473 | +				if (isClass(type)) { | 
|  | 474 | +					continue; | 
|  | 475 | +				} | 
|  | 476 | + | 
|  | 477 | +				return false; | 
|  | 478 | +			} | 
| 336 | 479 | 
 | 
| 337 |  | -		if (ResolvableType.forMethodReturnType(method, repositoryInformation.getRepositoryInterface()) | 
| 338 |  | -				.hasUnresolvableGenerics()) { | 
| 339 | 480 | 			return true; | 
| 340 | 481 | 		} | 
| 341 | 482 | 
 | 
| 342 |  | -		for (int i = 0; i < method.getParameterCount(); i++) { | 
|  | 483 | +		private boolean isResolvable(ResolvableType resolvableType) { | 
|  | 484 | + | 
|  | 485 | +			return testGenericType(resolvableType, it -> { | 
|  | 486 | + | 
|  | 487 | +				if (resolvableTypeVariables.contains(it)) { | 
|  | 488 | +					return true; | 
|  | 489 | +				} | 
|  | 490 | + | 
|  | 491 | +				if (it instanceof WildcardType wt) { | 
|  | 492 | +					return isClassBounded(wt.getLowerBounds()) && isClassBounded(wt.getUpperBounds()); | 
|  | 493 | +				} | 
|  | 494 | + | 
|  | 495 | +				return false; | 
|  | 496 | +			}); | 
|  | 497 | +		} | 
|  | 498 | + | 
|  | 499 | +		private boolean isUnwanted(ResolvableType resolvableType) { | 
|  | 500 | + | 
|  | 501 | +			return testGenericType(resolvableType, o -> { | 
|  | 502 | + | 
|  | 503 | +				if (o instanceof WildcardType wt) { | 
|  | 504 | +					return !isResolvable(wt.getLowerBounds()) || !isResolvable(wt.getUpperBounds()); | 
|  | 505 | +				} | 
|  | 506 | + | 
|  | 507 | +				return unwantedMethodVariables.contains(o); | 
|  | 508 | +			}); | 
|  | 509 | +		} | 
|  | 510 | + | 
|  | 511 | +		private static boolean testGenericType(ResolvableType resolvableType, Predicate<Type> predicate) { | 
| 343 | 512 | 
 | 
| 344 |  | -			if (ResolvableType.forMethodParameter(method, i, repositoryInformation.getRepositoryInterface()) | 
| 345 |  | -					.hasUnresolvableGenerics()) { | 
|  | 513 | +			if (predicate.test(resolvableType.getType())) { | 
| 346 | 514 | 				return true; | 
| 347 | 515 | 			} | 
|  | 516 | + | 
|  | 517 | +			ResolvableType[] generics = resolvableType.getGenerics(); | 
|  | 518 | +			for (ResolvableType generic : generics) { | 
|  | 519 | +				if (testGenericType(generic, predicate)) { | 
|  | 520 | +					return true; | 
|  | 521 | +				} | 
|  | 522 | +			} | 
|  | 523 | + | 
|  | 524 | +			return false; | 
|  | 525 | +		} | 
|  | 526 | + | 
|  | 527 | +		private static boolean isClassBounded(Type[] bounds) { | 
|  | 528 | + | 
|  | 529 | +			for (Type bound : bounds) { | 
|  | 530 | + | 
|  | 531 | +				if (isClass(bound)) { | 
|  | 532 | +					continue; | 
|  | 533 | +				} | 
|  | 534 | + | 
|  | 535 | +				return false; | 
|  | 536 | +			} | 
|  | 537 | + | 
|  | 538 | +			return true; | 
|  | 539 | +		} | 
|  | 540 | + | 
|  | 541 | +		private static boolean isClass(Type type) { | 
|  | 542 | +			return type instanceof Class; | 
| 348 | 543 | 		} | 
| 349 | 544 | 
 | 
| 350 |  | -		return false; | 
| 351 | 545 | 	} | 
| 352 | 546 | 
 | 
| 353 | 547 | 	/** | 
|  | 
0 commit comments