Browse Source

Polishing.

Split tests into parametrized tests.

Original Pull Request: #3378
pull/3386/head
Mark Paluch 2 months ago committed by Christoph Strobl
parent
commit
cd44725b2d
No known key found for this signature in database
GPG Key ID: E6054036D0C37A4B
  1. 18
      src/main/java/org/springframework/data/javapoet/TypeNames.java
  2. 48
      src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryCreator.java
  3. 28
      src/test/java/example/BaseRepository.java
  4. 2
      src/test/java/example/UserRepository.java
  5. 152
      src/test/java/org/springframework/data/javapoet/TypeNamesUnitTests.java

18
src/main/java/org/springframework/data/javapoet/TypeNames.java

@ -84,24 +84,24 @@ public abstract class TypeNames {
return TypeName.get(Object.class); 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()) { if (!resolvableType.hasGenerics()) {
Class<?> resolvedType = resolvableType.toClass(); Class<?> resolvedType = resolvableType.toClass();
if (!resolvableType.isArray()) {
return TypeName.get(resolvedType);
}
if (resolvedType.isArray()) { if (!resolvableType.isArray() || resolvedType.isArray()) {
return TypeName.get(resolvedType); return TypeName.get(resolvedType);
} }
if (resolvableType.isArray()) { if (resolvableType.isArray()) {
return ArrayTypeName.of(resolvedType); return ArrayTypeName.of(resolvedType);
} }
return TypeName.get(resolvedType);
}
if (resolvableType.hasResolvableGenerics()) { return TypeName.get(resolvedType);
return ParameterizedTypeName.get(ClassName.get(resolvableType.toClass()),
Arrays.stream(resolvableType.getGenerics()).map(TypeNames::resolvedTypeName).toArray(TypeName[]::new));
} }
return ClassName.get(resolvableType.toClass()); return ClassName.get(resolvableType.toClass());

48
src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryCreator.java

@ -290,6 +290,7 @@ class AotRepositoryCreator {
MethodContributor<? extends QueryMethod> contributor = contributorFactory.create(method); MethodContributor<? extends QueryMethod> contributor = contributorFactory.create(method);
if (contributor == null) { if (contributor == null) {
if (logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
logger.trace("Skipping method [%s.%s] contribution, no MethodContributor available" logger.trace("Skipping method [%s.%s] contribution, no MethodContributor available"
.formatted(repositoryInformation.getRepositoryInterface().getName(), method.getName())); .formatted(repositoryInformation.getRepositoryInterface().getName(), method.getName()));
@ -298,24 +299,55 @@ class AotRepositoryCreator {
return; return;
} }
// TODO: should we do this even before we do something with the method to protect the modules? if (hasUnresolvableGenerics(method)) {
if (ResolvableType.forMethodReturnType(method, repositoryInformation.getRepositoryInterface())
.hasUnresolvableGenerics()) {
if (logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
logger.trace("Skipping method [%s.%s] contribution, unresolvable generic return" logger.trace(
.formatted(repositoryInformation.getRepositoryInterface().getName(), method.getName())); "Skipping implementation method [%s.%s] contribution. Method uses generics that currently cannot be resolved."
.formatted(repositoryInformation.getRepositoryInterface().getName(), method.getName()));
} }
generationMetadata.addDelegateMethod(method, contributor); generationMetadata.addDelegateMethod(method, contributor);
return; return;
} }
if (contributor.contributesMethodSpec() && !repositoryInformation.isReactiveRepository()) { if (!contributor.contributesMethodSpec() || repositoryInformation.isReactiveRepository()) {
generationMetadata.addRepositoryMethod(method, contributor);
} else { 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); 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;
} }
/** /**

28
src/test/java/example/BaseRepository.java

@ -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<T, ID> extends CrudRepository<T, ID> {
T findInBaseRepository(ID id);
}

2
src/test/java/example/UserRepository.java

@ -24,7 +24,7 @@ import org.springframework.data.repository.CrudRepository;
/** /**
* @author Christoph Strobl * @author Christoph Strobl
*/ */
public interface UserRepository extends BaseRepository<User, Long>, UserRepositoryExtension { public interface UserRepository extends CrudRepository<User, Long>, UserRepositoryExtension {
User findByFirstname(String firstname); User findByFirstname(String firstname);

152
src/test/java/org/springframework/data/javapoet/TypeNamesUnitTests.java

@ -15,8 +15,10 @@
*/ */
package org.springframework.data.javapoet; 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.List;
import java.util.Set; import java.util.Set;
import java.util.stream.Stream; 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.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.MethodSource;
import org.springframework.core.MethodParameter; import org.springframework.core.MethodParameter;
import org.springframework.core.ResolvableType; import org.springframework.core.ResolvableType;
import org.springframework.data.geo.Distance; import org.springframework.data.geo.Distance;
@ -37,7 +40,10 @@ import org.springframework.javapoet.TypeVariableName;
import org.springframework.util.ReflectionUtils; import org.springframework.util.ReflectionUtils;
/** /**
* Tests for {@link TypeNames}.
*
* @author Christoph Strobl * @author Christoph Strobl
* @author Mark Paluch
*/ */
class TypeNamesUnitTests { class TypeNamesUnitTests {
@ -75,84 +81,104 @@ class TypeNamesUnitTests {
assertThat(TypeNames.resolvedTypeName(resolvableType)).extracting(TypeName::toString).isEqualTo("java.util.List"); assertThat(TypeNames.resolvedTypeName(resolvableType)).extracting(TypeName::toString).isEqualTo("java.util.List");
} }
@Test // GH-3374 static List<Method> concreteMethods() {
void resolvedTypeNamesForMethodParameters() {
List<Method> methods = new ArrayList<>();
ReflectionUtils.doWithMethods(Concrete.class, method -> { ReflectionUtils.doWithMethods(Concrete.class, method -> {
if (!method.getName().contains("baseMethod")) { if (!method.getName().contains("baseMethod")) {
return; return;
} }
methods.add(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));
}); });
return methods;
}
static List<Method> otherMethods() {
List<Method> methods = new ArrayList<>();
ReflectionUtils.doWithMethods(Concrete.class, method -> { ReflectionUtils.doWithMethods(Concrete.class, method -> {
if (!method.getName().contains("otherMethod")) { if (!method.getName().contains("otherMethod")) {
return; return;
} }
methods.add(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));
}); });
ReflectionUtils.doWithMethods(Concrete.class, method -> { return methods;
if (!method.getName().contains("findByLocationNear")) { }
return;
}
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<org.springframework.data.geo.GeoResult<org.springframework.data.javapoet.TypeNamesUnitTests.MyType>>");
assertThat(TypeNames.resolvedTypeName(resolvedReturnType)).isEqualTo(ParameterizedTypeName @ParameterizedTest // GH-3374
.get(ClassName.get(java.util.List.class), ParameterizedTypeName.get(GeoResult.class, MyType.class))); @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<org.springframework.data.geo.GeoResult<org.springframework.data.javapoet.TypeNamesUnitTests.MyType>>");
assertThat(TypeNames.resolvedTypeName(resolvedReturnType)).isEqualTo(ParameterizedTypeName
.get(ClassName.get(java.util.List.class), ParameterizedTypeName.get(GeoResult.class, MyType.class)));
} }
interface GenericBase<T> { interface GenericBase<T> {

Loading…
Cancel
Save