diff --git a/src/main/java/org/springframework/data/javapoet/TypeNames.java b/src/main/java/org/springframework/data/javapoet/TypeNames.java index 055db183c..6112af9e2 100644 --- a/src/main/java/org/springframework/data/javapoet/TypeNames.java +++ b/src/main/java/org/springframework/data/javapoet/TypeNames.java @@ -84,24 +84,24 @@ public abstract class TypeNames { return TypeName.get(Object.class); } + if (resolvableType.hasResolvableGenerics()) { + return ParameterizedTypeName.get(ClassName.get(resolvableType.toClass()), + Arrays.stream(resolvableType.getGenerics()).map(TypeNames::resolvedTypeName).toArray(TypeName[]::new)); + } + if (!resolvableType.hasGenerics()) { + Class resolvedType = resolvableType.toClass(); - if (!resolvableType.isArray()) { - return TypeName.get(resolvedType); - } - if (resolvedType.isArray()) { + if (!resolvableType.isArray() || resolvedType.isArray()) { return TypeName.get(resolvedType); } + if (resolvableType.isArray()) { return ArrayTypeName.of(resolvedType); } - return TypeName.get(resolvedType); - } - if (resolvableType.hasResolvableGenerics()) { - return ParameterizedTypeName.get(ClassName.get(resolvableType.toClass()), - Arrays.stream(resolvableType.getGenerics()).map(TypeNames::resolvedTypeName).toArray(TypeName[]::new)); + return TypeName.get(resolvedType); } return ClassName.get(resolvableType.toClass()); diff --git a/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryCreator.java b/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryCreator.java index 8a6e20469..af98a0fc9 100644 --- a/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryCreator.java +++ b/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryCreator.java @@ -290,6 +290,7 @@ class AotRepositoryCreator { MethodContributor contributor = contributorFactory.create(method); if (contributor == null) { + if (logger.isTraceEnabled()) { logger.trace("Skipping method [%s.%s] contribution, no MethodContributor available" .formatted(repositoryInformation.getRepositoryInterface().getName(), method.getName())); @@ -298,24 +299,55 @@ class AotRepositoryCreator { return; } - // TODO: should we do this even before we do something with the method to protect the modules? - if (ResolvableType.forMethodReturnType(method, repositoryInformation.getRepositoryInterface()) - .hasUnresolvableGenerics()) { + if (hasUnresolvableGenerics(method)) { if (logger.isTraceEnabled()) { - logger.trace("Skipping method [%s.%s] contribution, unresolvable generic return" - .formatted(repositoryInformation.getRepositoryInterface().getName(), method.getName())); + logger.trace( + "Skipping implementation method [%s.%s] contribution. Method uses generics that currently cannot be resolved." + .formatted(repositoryInformation.getRepositoryInterface().getName(), method.getName())); } generationMetadata.addDelegateMethod(method, contributor); return; } - if (contributor.contributesMethodSpec() && !repositoryInformation.isReactiveRepository()) { - generationMetadata.addRepositoryMethod(method, contributor); - } else { + if (!contributor.contributesMethodSpec() || repositoryInformation.isReactiveRepository()) { + + if (repositoryInformation.isReactiveRepository() && logger.isTraceEnabled()) { + logger.trace( + "Skipping implementation method [%s.%s] contribution. AOT repositories are not supported for reactive repositories." + .formatted(repositoryInformation.getRepositoryInterface().getName(), method.getName())); + } + + if (!contributor.contributesMethodSpec() && logger.isTraceEnabled()) { + logger.trace( + "Skipping implementation method [%s.%s] contribution. Spring Data %s did not provide a method implementation." + .formatted(repositoryInformation.getRepositoryInterface().getName(), method.getName(), moduleName)); + } + generationMetadata.addDelegateMethod(method, contributor); + return; + } + + generationMetadata.addRepositoryMethod(method, contributor); + } + + private boolean hasUnresolvableGenerics(Method method) { + + if (ResolvableType.forMethodReturnType(method, repositoryInformation.getRepositoryInterface()) + .hasUnresolvableGenerics()) { + return true; } + + for (int i = 0; i < method.getParameterCount(); i++) { + + if (ResolvableType.forMethodParameter(method, i, repositoryInformation.getRepositoryInterface()) + .hasUnresolvableGenerics()) { + return true; + } + } + + return false; } /** diff --git a/src/test/java/example/BaseRepository.java b/src/test/java/example/BaseRepository.java deleted file mode 100644 index abdd147a3..000000000 --- a/src/test/java/example/BaseRepository.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2025-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package example; - -import org.springframework.data.repository.CrudRepository; -import org.springframework.data.repository.NoRepositoryBean; - -/** - * @author Christoph Strobl - */ -@NoRepositoryBean -public interface BaseRepository extends CrudRepository { - - T findInBaseRepository(ID id); -} diff --git a/src/test/java/example/UserRepository.java b/src/test/java/example/UserRepository.java index 5370d27a6..d9b35863e 100644 --- a/src/test/java/example/UserRepository.java +++ b/src/test/java/example/UserRepository.java @@ -24,7 +24,7 @@ import org.springframework.data.repository.CrudRepository; /** * @author Christoph Strobl */ -public interface UserRepository extends BaseRepository, UserRepositoryExtension { +public interface UserRepository extends CrudRepository, UserRepositoryExtension { User findByFirstname(String firstname); diff --git a/src/test/java/org/springframework/data/javapoet/TypeNamesUnitTests.java b/src/test/java/org/springframework/data/javapoet/TypeNamesUnitTests.java index 9ebab75b8..af78cd105 100644 --- a/src/test/java/org/springframework/data/javapoet/TypeNamesUnitTests.java +++ b/src/test/java/org/springframework/data/javapoet/TypeNamesUnitTests.java @@ -15,8 +15,10 @@ */ package org.springframework.data.javapoet; -import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; +import static org.assertj.core.api.AssertionsForInterfaceTypes.*; +import java.lang.reflect.Method; +import java.util.ArrayList; import java.util.List; import java.util.Set; import java.util.stream.Stream; @@ -25,6 +27,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; + import org.springframework.core.MethodParameter; import org.springframework.core.ResolvableType; import org.springframework.data.geo.Distance; @@ -37,7 +40,10 @@ import org.springframework.javapoet.TypeVariableName; import org.springframework.util.ReflectionUtils; /** + * Tests for {@link TypeNames}. + * * @author Christoph Strobl + * @author Mark Paluch */ class TypeNamesUnitTests { @@ -75,84 +81,104 @@ class TypeNamesUnitTests { assertThat(TypeNames.resolvedTypeName(resolvableType)).extracting(TypeName::toString).isEqualTo("java.util.List"); } - @Test // GH-3374 - void resolvedTypeNamesForMethodParameters() { + static List concreteMethods() { + + List methods = new ArrayList<>(); ReflectionUtils.doWithMethods(Concrete.class, method -> { if (!method.getName().contains("baseMethod")) { return; } - - MethodParameter refiedObjectMethodParameter = new MethodParameter(method, 0).withContainingClass(Concrete.class); - ResolvableType resolvedObjectParameterType = ResolvableType.forMethodParameter(refiedObjectMethodParameter); - assertThat(TypeNames.typeName(resolvedObjectParameterType)).isEqualTo(TypeVariableName.get("T")); - assertThat(TypeNames.resolvedTypeName(resolvedObjectParameterType)).isEqualTo(TypeName.get(MyType.class)); - - MethodParameter refiedCollectionMethodParameter = new MethodParameter(method, 1) - .withContainingClass(Concrete.class); - ResolvableType resolvedCollectionParameterType = ResolvableType - .forMethodParameter(refiedCollectionMethodParameter); - assertThat(TypeNames.typeName(resolvedCollectionParameterType)) - .isEqualTo(ParameterizedTypeName.get(ClassName.get(java.util.List.class), TypeVariableName.get("T"))); - assertThat(TypeNames.resolvedTypeName(resolvedCollectionParameterType)) - .isEqualTo(ParameterizedTypeName.get(java.util.List.class, MyType.class)); - - MethodParameter refiedArrayMethodParameter = new MethodParameter(method, 2).withContainingClass(Concrete.class); - ResolvableType resolvedArrayParameterType = ResolvableType.forMethodParameter(refiedArrayMethodParameter); - assertThat(TypeNames.typeName(resolvedArrayParameterType)).extracting(TypeName::toString).isEqualTo("T[]"); - assertThat(TypeNames.resolvedTypeName(resolvedArrayParameterType)).extracting(TypeName::toString) - .isEqualTo("org.springframework.data.javapoet.TypeNamesUnitTests.MyType[]"); - - ResolvableType resolvedReturnType = ResolvableType.forMethodReturnType(method, Concrete.class); - assertThat(TypeNames.typeName(resolvedReturnType)) - .isEqualTo(ParameterizedTypeName.get(ClassName.get(java.util.List.class), TypeVariableName.get("T"))); - assertThat(TypeNames.resolvedTypeName(resolvedReturnType)) - .isEqualTo(ParameterizedTypeName.get(java.util.List.class, MyType.class)); + methods.add(method); }); + return methods; + } + + static List otherMethods() { + + List methods = new ArrayList<>(); + ReflectionUtils.doWithMethods(Concrete.class, method -> { if (!method.getName().contains("otherMethod")) { return; } - - MethodParameter refiedObjectMethodParameter = new MethodParameter(method, 0).withContainingClass(Concrete.class); - ResolvableType resolvedObjectParameterType = ResolvableType.forMethodParameter(refiedObjectMethodParameter); - assertThat(TypeNames.typeName(resolvedObjectParameterType)).isEqualTo(TypeVariableName.get("RT")); - assertThat(TypeNames.resolvedTypeName(resolvedObjectParameterType)).isEqualTo(TypeName.get(Object.class)); - - MethodParameter refiedCollectionMethodParameter = new MethodParameter(method, 1) - .withContainingClass(Concrete.class); - ResolvableType resolvedCollectionParameterType = ResolvableType - .forMethodParameter(refiedCollectionMethodParameter); - assertThat(TypeNames.typeName(resolvedCollectionParameterType)) - .isEqualTo(ParameterizedTypeName.get(ClassName.get(java.util.List.class), TypeVariableName.get("RT"))); - assertThat(TypeNames.resolvedTypeName(resolvedCollectionParameterType)) - .isEqualTo(ClassName.get(java.util.List.class)); - - MethodParameter refiedArrayMethodParameter = new MethodParameter(method, 2).withContainingClass(Concrete.class); - ResolvableType resolvedArrayParameterType = ResolvableType.forMethodParameter(refiedArrayMethodParameter); - assertThat(TypeNames.typeName(resolvedArrayParameterType)).extracting(TypeName::toString).isEqualTo("RT[]"); - assertThat(TypeNames.resolvedTypeName(resolvedArrayParameterType)).extracting(TypeName::toString) - .isEqualTo("java.lang.Object[]"); - - ResolvableType resolvedReturnType = ResolvableType.forMethodReturnType(method, Concrete.class); - assertThat(TypeNames.typeName(resolvedReturnType)).extracting(TypeName::toString).isEqualTo("RT"); - assertThat(TypeNames.resolvedTypeName(resolvedReturnType)).isEqualTo(TypeName.get(Object.class)); + methods.add(method); }); - ReflectionUtils.doWithMethods(Concrete.class, method -> { - if (!method.getName().contains("findByLocationNear")) { - return; - } + return methods; + } - ResolvableType resolvedReturnType = ResolvableType.forMethodReturnType(method, Concrete.class); + @ParameterizedTest // GH-3374 + @MethodSource("concreteMethods") + void resolvedTypeNamesForMethodParameters(Method method) { + + MethodParameter refiedObjectMethodParameter = new MethodParameter(method, 0).withContainingClass(Concrete.class); + ResolvableType resolvedObjectParameterType = ResolvableType.forMethodParameter(refiedObjectMethodParameter); + assertThat(TypeNames.typeName(resolvedObjectParameterType)).isEqualTo(TypeVariableName.get("T")); + assertThat(TypeNames.resolvedTypeName(resolvedObjectParameterType)).isEqualTo(TypeName.get(MyType.class)); + + MethodParameter refiedCollectionMethodParameter = new MethodParameter(method, 1) + .withContainingClass(Concrete.class); + ResolvableType resolvedCollectionParameterType = ResolvableType.forMethodParameter(refiedCollectionMethodParameter); + assertThat(TypeNames.typeName(resolvedCollectionParameterType)) + .isEqualTo(ParameterizedTypeName.get(ClassName.get(java.util.List.class), TypeVariableName.get("T"))); + assertThat(TypeNames.resolvedTypeName(resolvedCollectionParameterType)) + .isEqualTo(ParameterizedTypeName.get(java.util.List.class, MyType.class)); + + MethodParameter refiedArrayMethodParameter = new MethodParameter(method, 2).withContainingClass(Concrete.class); + ResolvableType resolvedArrayParameterType = ResolvableType.forMethodParameter(refiedArrayMethodParameter); + assertThat(TypeNames.typeName(resolvedArrayParameterType)).extracting(TypeName::toString).isEqualTo("T[]"); + assertThat(TypeNames.resolvedTypeName(resolvedArrayParameterType)).extracting(TypeName::toString) + .isEqualTo("org.springframework.data.javapoet.TypeNamesUnitTests.MyType[]"); + + ResolvableType resolvedReturnType = ResolvableType.forMethodReturnType(method, Concrete.class); + assertThat(TypeNames.typeName(resolvedReturnType)) + .isEqualTo(ParameterizedTypeName.get(ClassName.get(java.util.List.class), TypeVariableName.get("T"))); + assertThat(TypeNames.resolvedTypeName(resolvedReturnType)) + .isEqualTo(ParameterizedTypeName.get(java.util.List.class, MyType.class)); - assertThat(TypeNames.typeName(resolvedReturnType)).extracting(TypeName::toString).isEqualTo( - "java.util.List>"); - assertThat(TypeNames.resolvedTypeName(resolvedReturnType)).isEqualTo(ParameterizedTypeName - .get(ClassName.get(java.util.List.class), ParameterizedTypeName.get(GeoResult.class, MyType.class))); - }); + } + + @ParameterizedTest // GH-3374 + @MethodSource("otherMethods") + void resolvedTypeNamesForOtherMethodParameters(Method method) { + + MethodParameter refiedObjectMethodParameter = new MethodParameter(method, 0).withContainingClass(Concrete.class); + ResolvableType resolvedObjectParameterType = ResolvableType.forMethodParameter(refiedObjectMethodParameter); + assertThat(TypeNames.typeName(resolvedObjectParameterType)).isEqualTo(TypeVariableName.get("RT")); + assertThat(TypeNames.resolvedTypeName(resolvedObjectParameterType)).isEqualTo(TypeName.get(Object.class)); + + MethodParameter refiedCollectionMethodParameter = new MethodParameter(method, 1) + .withContainingClass(Concrete.class); + ResolvableType resolvedCollectionParameterType = ResolvableType.forMethodParameter(refiedCollectionMethodParameter); + assertThat(TypeNames.typeName(resolvedCollectionParameterType)) + .isEqualTo(ParameterizedTypeName.get(ClassName.get(java.util.List.class), TypeVariableName.get("RT"))); + assertThat(TypeNames.resolvedTypeName(resolvedCollectionParameterType)) + .isEqualTo(ClassName.get(java.util.List.class)); + + MethodParameter refiedArrayMethodParameter = new MethodParameter(method, 2).withContainingClass(Concrete.class); + ResolvableType resolvedArrayParameterType = ResolvableType.forMethodParameter(refiedArrayMethodParameter); + assertThat(TypeNames.typeName(resolvedArrayParameterType)).extracting(TypeName::toString).isEqualTo("RT[]"); + assertThat(TypeNames.resolvedTypeName(resolvedArrayParameterType)).extracting(TypeName::toString) + .isEqualTo("java.lang.Object[]"); + + ResolvableType resolvedReturnType = ResolvableType.forMethodReturnType(method, Concrete.class); + assertThat(TypeNames.typeName(resolvedReturnType)).extracting(TypeName::toString).isEqualTo("RT"); + assertThat(TypeNames.resolvedTypeName(resolvedReturnType)).isEqualTo(TypeName.get(Object.class)); + } + + @Test // GH-3374 + void resolvesTypeNamesForMethodParameters() throws NoSuchMethodException { + + Method method = Concrete.class.getDeclaredMethod("findByLocationNear", Point.class, Distance.class); + + ResolvableType resolvedReturnType = ResolvableType.forMethodReturnType(method, Concrete.class); + assertThat(TypeNames.typeName(resolvedReturnType)).extracting(TypeName::toString).isEqualTo( + "java.util.List>"); + assertThat(TypeNames.resolvedTypeName(resolvedReturnType)).isEqualTo(ParameterizedTypeName + .get(ClassName.get(java.util.List.class), ParameterizedTypeName.get(GeoResult.class, MyType.class))); } interface GenericBase {