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 24dbf83de..5bfcba6da 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
@@ -16,32 +16,23 @@
package org.springframework.data.repository.aot.generate;
import java.lang.reflect.Method;
-import java.lang.reflect.Type;
-import java.lang.reflect.TypeVariable;
-import java.lang.reflect.WildcardType;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
-import java.util.HashSet;
import java.util.List;
-import java.util.Set;
import java.util.function.Consumer;
-import java.util.function.Predicate;
import javax.lang.model.element.Modifier;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jspecify.annotations.Nullable;
-
-import org.springframework.core.ResolvableType;
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.core.support.RepositoryComposition;
import org.springframework.data.repository.core.support.RepositoryFragment;
import org.springframework.data.repository.query.QueryMethod;
import org.springframework.data.util.Lazy;
-import org.springframework.data.util.TypeInformation;
import org.springframework.javapoet.ClassName;
import org.springframework.javapoet.FieldSpec;
import org.springframework.javapoet.MethodSpec;
@@ -340,210 +331,6 @@ class AotRepositoryCreator {
generationMetadata.addRepositoryMethod(method, contributor);
}
- /**
- * Value object to determine whether generics in a given {@link Method} can be resolved. Resolvable generics are e.g.
- * declared on the method level (unbounded type variables, type variables using class boundaries). Considers
- * collections and map types.
- *
- * Considers resolvable:
- *
- * - Unbounded method-level type parameters {@code T foo(Class)}
- * - Bounded method-level type parameters that resolve to a class
- * {@code T foo(Class)}
- * - Simple references to interface variables {@code T foo(), List foo(…)}
- * - Unbounded wildcards {@code User foo(GeoJson>)}
- *
- * Considers non-resolvable:
- *
- *
- *
- * @author Mark Paluch
- */
- record ResolvableGenerics(Method method, Class> implClass, Set resolvableTypeVariables,
- Set unwantedMethodVariables) {
-
- /**
- * Create a new {@code ResolvableGenerics} object for the given {@link Method}.
- *
- * @param method
- * @return
- */
- public static ResolvableGenerics of(Method method, Class> implClass) {
- return new ResolvableGenerics(method, implClass, getResolvableTypeVariables(method),
- getUnwantedMethodVariables(method));
- }
-
- private static Set getResolvableTypeVariables(Method method) {
-
- Set simpleTypeVariables = new HashSet<>();
-
- for (TypeVariable typeParameter : method.getTypeParameters()) {
- if (isClassBounded(typeParameter.getBounds())) {
- simpleTypeVariables.add(typeParameter);
- }
- }
-
- return simpleTypeVariables;
- }
-
- private static Set getUnwantedMethodVariables(Method method) {
-
- Set unwanted = new HashSet<>();
-
- for (TypeVariable typeParameter : method.getTypeParameters()) {
- if (!isClassBounded(typeParameter.getBounds())) {
- unwanted.add(typeParameter);
- }
- }
- return unwanted;
- }
-
- /**
- * Check whether the {@link Method} has unresolvable generics when being considered in the context of the
- * implementation class.
- *
- * @return
- */
- public boolean hasUnresolvableGenerics() {
-
- ResolvableType resolvableType = ResolvableType.forMethodReturnType(method, implClass);
-
- if (isUnresolvable(resolvableType)) {
- return true;
- }
-
- for (int i = 0; i < method.getParameterCount(); i++) {
- if (isUnresolvable(ResolvableType.forMethodParameter(method, i, implClass))) {
- return true;
- }
- }
-
- return false;
- }
-
- private boolean isUnresolvable(TypeInformation> typeInformation) {
- return isUnresolvable(typeInformation.toResolvableType());
- }
-
- private boolean isUnresolvable(ResolvableType resolvableType) {
-
- if (isResolvable(resolvableType)) {
- return false;
- }
-
- if (isUnwanted(resolvableType)) {
- return true;
- }
-
- if (resolvableType.isAssignableFrom(Class.class)) {
- return isUnresolvable(resolvableType.getGeneric(0));
- }
-
- TypeInformation> typeInformation = TypeInformation.of(resolvableType);
- if (typeInformation.isMap() || typeInformation.isCollectionLike()) {
-
- for (ResolvableType type : resolvableType.getGenerics()) {
- if (isUnresolvable(type)) {
- return true;
- }
- }
-
- return false;
- }
-
- if (typeInformation.getActualType() != null && typeInformation.getActualType() != typeInformation) {
- return isUnresolvable(typeInformation.getRequiredActualType());
- }
-
- return resolvableType.hasUnresolvableGenerics();
- }
-
- private boolean isResolvable(Type[] types) {
-
- for (Type type : types) {
-
- if (resolvableTypeVariables.contains(type)) {
- continue;
- }
-
- if (isClass(type)) {
- continue;
- }
-
- return false;
- }
-
- return true;
- }
-
- private boolean isResolvable(ResolvableType resolvableType) {
-
- return testGenericType(resolvableType, it -> {
-
- if (resolvableTypeVariables.contains(it)) {
- return true;
- }
-
- if (it instanceof WildcardType wt) {
- return isClassBounded(wt.getLowerBounds()) && isClassBounded(wt.getUpperBounds());
- }
-
- return false;
- });
- }
-
- private boolean isUnwanted(ResolvableType resolvableType) {
-
- return testGenericType(resolvableType, o -> {
-
- if (o instanceof WildcardType wt) {
- return !isResolvable(wt.getLowerBounds()) || !isResolvable(wt.getUpperBounds());
- }
-
- return unwantedMethodVariables.contains(o);
- });
- }
-
- private static boolean testGenericType(ResolvableType resolvableType, Predicate predicate) {
-
- if (predicate.test(resolvableType.getType())) {
- return true;
- }
-
- ResolvableType[] generics = resolvableType.getGenerics();
- for (ResolvableType generic : generics) {
- if (testGenericType(generic, predicate)) {
- return true;
- }
- }
-
- return false;
- }
-
- private static boolean isClassBounded(Type[] bounds) {
-
- for (Type bound : bounds) {
-
- if (isClass(bound)) {
- continue;
- }
-
- return false;
- }
-
- return true;
- }
-
- private static boolean isClass(Type type) {
- return type instanceof Class;
- }
-
- }
-
/**
* Factory interface to conditionally create {@link MethodContributor} instances. An implementation may decide whether
* to return a {@link MethodContributor} or {@literal null}, if no method (code or metadata) should be contributed.
diff --git a/src/main/java/org/springframework/data/repository/aot/generate/MethodMetadata.java b/src/main/java/org/springframework/data/repository/aot/generate/MethodMetadata.java
index cebc6013e..a41729d2e 100644
--- a/src/main/java/org/springframework/data/repository/aot/generate/MethodMetadata.java
+++ b/src/main/java/org/springframework/data/repository/aot/generate/MethodMetadata.java
@@ -26,7 +26,6 @@ import java.util.Map.Entry;
import java.util.function.Function;
import org.jspecify.annotations.Nullable;
-
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.MethodParameter;
import org.springframework.core.ParameterNameDiscoverer;
@@ -36,6 +35,7 @@ import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.util.TypeInformation;
import org.springframework.javapoet.ParameterSpec;
import org.springframework.javapoet.TypeName;
+import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
/**
@@ -79,14 +79,17 @@ class MethodMetadata {
ResolvableType repositoryInterface, Map methodArguments,
Map methodParameters) {
+ Class> repositoryInterfaceType = repositoryInterface.toClass();
+
for (Parameter parameter : method.getParameters()) {
- MethodParameter methodParameter = MethodParameter.forParameter(parameter).withContainingClass(repositoryInterface.resolve());
+ MethodParameter methodParameter = MethodParameter.forParameter(parameter)
+ .withContainingClass(repositoryInterfaceType);
methodParameter.initParameterNameDiscovery(nameDiscoverer);
- ResolvableType resolvableParameterType = ResolvableType.forMethodParameter(methodParameter);
- TypeName parameterType = TypeNames.resolvedTypeName(resolvableParameterType);
+ TypeName parameterType = parameterTypeName(methodParameter, repositoryInterfaceType);
+ Assert.notNull(methodParameter.getParameterName(), "MethodParameter.getParameterName() must not be null");
ParameterSpec parameterSpec = ParameterSpec.builder(parameterType, methodParameter.getParameterName()).build();
if (methodArguments.containsKey(parameterSpec.name())) {
@@ -98,6 +101,20 @@ class MethodMetadata {
}
}
+ @SuppressWarnings("NullAway")
+ static TypeName parameterTypeName(MethodParameter methodParameter, Class> repositoryInterface) {
+
+ ResolvableType resolvableParameterType = ResolvableType.forMethodParameter(methodParameter);
+ if (ClassUtils.isPrimitiveOrWrapper(resolvableParameterType.toClass())) {
+ return TypeNames.className(resolvableParameterType);
+ }
+
+ ResolvableGenerics resolvableGenerics = ResolvableGenerics.of(methodParameter.getMethod(), repositoryInterface);
+ return resolvableGenerics.isFullyResolvableParameter(methodParameter.getParameter())
+ ? TypeNames.resolvedTypeName(resolvableParameterType)
+ : TypeNames.className(resolvableParameterType);
+ }
+
ResolvableType getReturnType() {
return returnType;
}
diff --git a/src/main/java/org/springframework/data/repository/aot/generate/ResolvableGenerics.java b/src/main/java/org/springframework/data/repository/aot/generate/ResolvableGenerics.java
new file mode 100644
index 000000000..f865d7366
--- /dev/null
+++ b/src/main/java/org/springframework/data/repository/aot/generate/ResolvableGenerics.java
@@ -0,0 +1,259 @@
+/*
+ * 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
+ *
+ * https://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 org.springframework.data.repository.aot.generate;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Parameter;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+import java.lang.reflect.WildcardType;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.function.Predicate;
+
+import org.springframework.core.MethodParameter;
+import org.springframework.core.ResolvableType;
+import org.springframework.data.util.TypeInformation;
+
+/**
+ * Value object to determine whether generics in a given {@link Method} can be resolved. Resolvable generics are e.g.
+ * declared on the method level (unbounded type variables, type variables using class boundaries). Considers collections
+ * and map types.
+ *
+ * Considers resolvable:
+ *
+ * - Unbounded method-level type parameters {@code T foo(Class)}
+ * - Bounded method-level type parameters that resolve to a class
+ * {@code T foo(Class)}
+ * - Simple references to interface variables {@code T foo(), List foo(…)}
+ * - Unbounded wildcards {@code User foo(GeoJson>)}
+ *
+ * Considers non-resolvable:
+ *
+ *
+ *
+ * @author Mark Paluch
+ */
+record ResolvableGenerics(Method method, Class> implClass, Set resolvableTypeVariables,
+ Set unwantedMethodVariables) {
+
+ /**
+ * Create a new {@code ResolvableGenerics} object for the given {@link Method}.
+ *
+ * @param method
+ * @return
+ */
+ public static ResolvableGenerics of(Method method, Class> implClass) {
+ return new ResolvableGenerics(method, implClass, getResolvableTypeVariables(method),
+ getUnwantedMethodVariables(method));
+ }
+
+ /**
+ * Checks if a given {@link Parameter} can be resolved fully. Unresolvable or unwanted type signatures should fall
+ * back to using raw types.
+ *
+ * @param parameter
+ * @return
+ */
+ public boolean isFullyResolvableParameter(Parameter parameter) {
+
+ MethodParameter methodParameter = MethodParameter.forParameter(parameter).withContainingClass(implClass);
+ ResolvableType resolvableType = ResolvableType.forMethodParameter(methodParameter);
+
+ return testGenericType(resolvableType, o -> {
+
+ if (o instanceof WildcardType wt) {
+ return !isResolvable(wt.getLowerBounds()) || !isResolvable(wt.getUpperBounds());
+ }
+
+ if (o instanceof ParameterizedType pt) {
+ return isResolvable(pt.getActualTypeArguments());
+ }
+
+ return unwantedMethodVariables.contains(o);
+ });
+ }
+
+ private static Set getResolvableTypeVariables(Method method) {
+
+ Set simpleTypeVariables = new HashSet<>();
+
+ for (TypeVariable typeParameter : method.getTypeParameters()) {
+ if (isClassBounded(typeParameter.getBounds())) {
+ simpleTypeVariables.add(typeParameter);
+ }
+ }
+
+ return simpleTypeVariables;
+ }
+
+ private static Set getUnwantedMethodVariables(Method method) {
+
+ Set unwanted = new HashSet<>();
+
+ for (TypeVariable typeParameter : method.getTypeParameters()) {
+ if (!isClassBounded(typeParameter.getBounds())) {
+ unwanted.add(typeParameter);
+ }
+ }
+ return unwanted;
+ }
+
+ /**
+ * Check whether the {@link Method} has unresolvable generics when being considered in the context of the
+ * implementation class.
+ *
+ * @return
+ */
+ public boolean hasUnresolvableGenerics() {
+
+ ResolvableType resolvableType = ResolvableType.forMethodReturnType(method, implClass);
+
+ if (isUnresolvable(resolvableType)) {
+ return true;
+ }
+
+ for (int i = 0; i < method.getParameterCount(); i++) {
+ if (isUnresolvable(ResolvableType.forMethodParameter(method, i, implClass))) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private boolean isUnresolvable(TypeInformation> typeInformation) {
+ return isUnresolvable(typeInformation.toResolvableType());
+ }
+
+ private boolean isUnresolvable(ResolvableType resolvableType) {
+
+ if (isResolvable(resolvableType)) {
+ return false;
+ }
+
+ if (isUnwanted(resolvableType)) {
+ return true;
+ }
+
+ if (resolvableType.isAssignableFrom(Class.class)) {
+ return isUnresolvable(resolvableType.getGeneric(0));
+ }
+
+ TypeInformation> typeInformation = TypeInformation.of(resolvableType);
+ if (typeInformation.isMap() || typeInformation.isCollectionLike()) {
+
+ for (ResolvableType type : resolvableType.getGenerics()) {
+ if (isUnresolvable(type)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ if (typeInformation.getActualType() != null && typeInformation.getActualType() != typeInformation) {
+ return isUnresolvable(typeInformation.getRequiredActualType());
+ }
+
+ return resolvableType.hasUnresolvableGenerics();
+ }
+
+ private boolean isResolvable(Type[] types) {
+
+ for (Type type : types) {
+
+ if (resolvableTypeVariables.contains(type)) {
+ continue;
+ }
+
+ if (isClass(type)) {
+ continue;
+ }
+
+ return false;
+ }
+
+ return true;
+ }
+
+ private boolean isResolvable(ResolvableType resolvableType) {
+
+ return testGenericType(resolvableType, it -> {
+
+ if (resolvableTypeVariables.contains(it)) {
+ return true;
+ }
+
+ if (it instanceof WildcardType wt) {
+ return isClassBounded(wt.getLowerBounds()) && isClassBounded(wt.getUpperBounds());
+ }
+
+ return false;
+ });
+ }
+
+ private boolean isUnwanted(ResolvableType resolvableType) {
+
+ return testGenericType(resolvableType, o -> {
+
+ if (o instanceof WildcardType wt) {
+ return !isResolvable(wt.getLowerBounds()) || !isResolvable(wt.getUpperBounds());
+ }
+
+ return unwantedMethodVariables.contains(o);
+ });
+ }
+
+ private static boolean testGenericType(ResolvableType resolvableType, Predicate predicate) {
+
+ if (predicate.test(resolvableType.getType())) {
+ return true;
+ }
+
+ ResolvableType[] generics = resolvableType.getGenerics();
+ for (ResolvableType generic : generics) {
+ if (testGenericType(generic, predicate)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private static boolean isClassBounded(Type[] bounds) {
+
+ for (Type bound : bounds) {
+
+ if (isClass(bound)) {
+ continue;
+ }
+
+ return false;
+ }
+
+ return true;
+ }
+
+ private static boolean isClass(Type type) {
+ return type instanceof Class;
+ }
+}
diff --git a/src/test/java/org/springframework/data/repository/aot/generate/AotRepositoryCreatorUnitTests.java b/src/test/java/org/springframework/data/repository/aot/generate/AotRepositoryCreatorUnitTests.java
index 393e05bcc..72cf43f24 100644
--- a/src/test/java/org/springframework/data/repository/aot/generate/AotRepositoryCreatorUnitTests.java
+++ b/src/test/java/org/springframework/data/repository/aot/generate/AotRepositoryCreatorUnitTests.java
@@ -15,9 +15,10 @@
*/
package org.springframework.data.repository.aot.generate;
-import static org.assertj.core.api.Assertions.*;
-import static org.assertj.core.api.Assumptions.*;
-import static org.mockito.Mockito.*;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assumptions.assumeThat;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
import example.UserRepository.User;
@@ -35,7 +36,6 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import org.mockito.Answers;
-
import org.springframework.aot.generate.Generated;
import org.springframework.aot.hint.TypeReference;
import org.springframework.core.ResolvableType;
@@ -51,10 +51,12 @@ import org.springframework.data.repository.core.support.RepositoryFragment;
import org.springframework.data.repository.query.DefaultParameters;
import org.springframework.data.repository.query.QueryMethod;
import org.springframework.javapoet.ClassName;
+import org.springframework.javapoet.CodeBlock;
import org.springframework.javapoet.JavaFile;
import org.springframework.javapoet.MethodSpec;
import org.springframework.javapoet.TypeSpec;
import org.springframework.stereotype.Repository;
+import org.springframework.util.ClassUtils;
/**
* Unit tests for {@link AotRepositoryCreator}.
@@ -73,8 +75,7 @@ class AotRepositoryCreatorUnitTests {
doReturn(UserRepository.class).when(repositoryInformation).getRepositoryInterface();
}
- @Test
- // GH-3279
+ @Test // GH-3279
void writesClassSkeleton() {
AotRepositoryCreator repositoryCreator = AotRepositoryCreator.forRepository(repositoryInformation, "Commons",
@@ -87,8 +88,7 @@ class AotRepositoryCreatorUnitTests {
.contains("public UserRepositoryImpl"); // default constructor if not arguments to wire
}
- @Test
- // GH-3279
+ @Test // GH-3279
void appliesCtorArguments() {
AotRepositoryCreator repositoryCreator = AotRepositoryCreator.forRepository(repositoryInformation, "Commons",
@@ -112,8 +112,7 @@ class AotRepositoryCreatorUnitTests {
.doesNotContain("this.ctorScoped = ctorScoped");
}
- @Test
- // GH-3279
+ @Test // GH-3279
void appliesCtorCodeBlock() {
AotRepositoryCreator repositoryCreator = AotRepositoryCreator.forRepository(repositoryInformation, "Commons",
@@ -127,8 +126,7 @@ class AotRepositoryCreatorUnitTests {
"UserRepositoryImpl() { throw new IllegalStateException(\"initialization error\"); }");
}
- @Test
- // GH-3279
+ @Test // GH-3279
void appliesClassCustomizations() {
AotRepositoryCreator repositoryCreator = AotRepositoryCreator.forRepository(repositoryInformation, "Commons",
@@ -156,8 +154,7 @@ class AotRepositoryCreatorUnitTests {
.containsIgnoringWhitespaces("void oops() { }");
}
- @Test
- // GH-3279
+ @Test // GH-3279
void appliesQueryMethodContributor() {
AotRepositoryInformation repositoryInformation = new AotRepositoryInformation(
@@ -186,8 +183,7 @@ class AotRepositoryCreatorUnitTests {
.containsIgnoringWhitespaces("void oops() { }");
}
- @Test
- // GH-3279
+ @Test // GH-3279
void shouldContributeFragmentImplementationMetadata() {
AotRepositoryInformation repositoryInformation = new AotRepositoryInformation(
@@ -207,8 +203,7 @@ class AotRepositoryCreatorUnitTests {
assertThat(method.fragment().implementation()).isEqualTo(DummyQuerydslPredicateExecutor.class.getName());
}
- @Test
- // GH-3339
+ @Test // GH-3339
void usesTargetTypeName() {
AotRepositoryCreator repositoryCreator = AotRepositoryCreator.forRepository(repositoryInformation, "Commons",
@@ -226,8 +221,7 @@ class AotRepositoryCreatorUnitTests {
.contains("public %s(Metric param1, String param2, Object ctorScoped)".formatted(targetType.getSimpleName()));
}
- @Test
- // GH-3339
+ @Test // GH-3339
void usesGenericConstructorArguments() {
AotRepositoryCreator repositoryCreator = AotRepositoryCreator.forRepository(repositoryInformation, "Commons",
@@ -246,8 +240,7 @@ class AotRepositoryCreatorUnitTests {
"public %s(List param1, String param2, Object ctorScoped)".formatted(targetType.getSimpleName()));
}
- @Test
- // GH-3374
+ @Test // GH-3374
void skipsMethodWithUnresolvableGenericReturnType() {
SpelAwareProxyProjectionFactory spelAwareProxyProjectionFactory = new SpelAwareProxyProjectionFactory();
@@ -261,27 +254,48 @@ class AotRepositoryCreatorUnitTests {
QueryMethod queryMethod = new QueryMethod(method, repositoryInformation, spelAwareProxyProjectionFactory,
DefaultParameters::new);
- return new MethodContributor<>(queryMethod, Map::of) {
- @Override
- public MethodSpec contribute(AotQueryMethodGenerationContext context) {
- return MethodSpec.methodBuilder(context.getMethod().getName()).addCode("// 1 = 1").build();
- }
- @Override
- public boolean contributesMethodSpec() {
- return true;
+ return MethodContributor.forQueryMethod(queryMethod).withMetadata(Map::of).contribute(context -> {
+
+ CodeBlock.Builder builder = CodeBlock.builder();
+ if (!ClassUtils.isVoidType(method.getReturnType())) {
+ builder.addStatement("return null");
}
- };
+ return builder.build();
+ });
});
// same package as source repo
String generated = generate(repositoryCreator);
- assertThat(generated).contains("someMethod").contains("findByFirstname").contains("project1ByFirstname")
- .contains("project2ByFirstname").contains("geoQuery").contains("rangeQuery");
+ assertThat(generated) //
+ // very simple ones with fixed type signature
+ .contains("findByFirstname") //
+ .contains("rangeQuery") //
+ .contains("public String someMethod()") //
+ .contains("public String findByPrimitive(int value)") //
+ .contains("public String findByPrimitiveArray(int[] values)") //
+ .containsSubsequence("public", "User typedInputParameter(", "User value)") //
+ .containsSubsequence("List<", "User> findByFirstnameIn(List firstname)") //
- assertThat(generated).doesNotContain("baseProjection").doesNotContain("upperBoundedProjection")
+ // List findByFirstname(String firstname, Class type);
+ .contains("public List findByFirstname(String firstname, Class type)")
+
+ // List project1ByFirstname(String firstname, Class type);
+ .contains("public List project2ByFirstname(String firstname, Class type)")
+
+ // List project2ByFirstname(String firstname, Class extends T> type)
+ .contains("public List project2ByFirstname(String firstname, Class type)")
+
+ // List geoQuery(GeoJson> geoJson);
+ .containsSubsequence("public List<", "User> geoQuery(", "GeoJson geoJson)")
+
+ // List genInputType(String firstname, T value);
+ .contains("public List genInputType(String firstname, Object value)")
+
+ .doesNotContain("baseProjection") //
+ .doesNotContain("upperBoundedProjection") //
.doesNotContain("lowerBoundedProjection()");
}
@@ -305,16 +319,14 @@ class AotRepositoryCreatorUnitTests {
@MethodSource("declaredUserRepositoryMethods")
void shouldResolveGenerics(Method method) {
- assertThat(AotRepositoryCreator.ResolvableGenerics.of(method, UserRepository.class).hasUnresolvableGenerics())
- .isFalse();
+ assertThat(ResolvableGenerics.of(method, UserRepository.class).hasUnresolvableGenerics()).isFalse();
}
@ParameterizedTest
@MethodSource("resolvedRepositoryMethods")
void shouldResolveInterfaceGenerics(Method method) {
- assertThat(AotRepositoryCreator.ResolvableGenerics.of(method, UserRepository.class).hasUnresolvableGenerics())
- .isFalse();
+ assertThat(ResolvableGenerics.of(method, UserRepository.class).hasUnresolvableGenerics()).isFalse();
}
@ParameterizedTest
@@ -323,8 +335,7 @@ class AotRepositoryCreatorUnitTests {
assumeThat(method.getDeclaringClass()).isEqualTo(BaseRepository.class);
- assertThat(AotRepositoryCreator.ResolvableGenerics.of(method, UserRepository.class).hasUnresolvableGenerics())
- .isTrue();
+ assertThat(ResolvableGenerics.of(method, UserRepository.class).hasUnresolvableGenerics()).isTrue();
}
private AotRepositoryCreator.AotBundle doCreate(AotRepositoryCreator creator) {
@@ -368,14 +379,24 @@ class AotRepositoryCreatorUnitTests {
T parametrizedSelection(String firstname);
+ T typedInputParameter(T value);
+
}
interface UserRepository extends BaseRepository {
String someMethod();
+ List findByFirstnameIn(List firstname);
+
+ String findByPrimitive(int value);
+
+ String findByPrimitiveArray(int[] values);
+
List findByFirstname(String firstname, Class type);
+ List genInputType(String firstname, T value);
+
List project1ByFirstname(String firstname, Class super T> type);
List project2ByFirstname(String firstname, Class extends T> type);