Skip to content

Commit 52fc31e

Browse files
Refine TypeName discovery.
1 parent eb8eede commit 52fc31e

File tree

9 files changed

+100
-32
lines changed

9 files changed

+100
-32
lines changed

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

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package org.springframework.data.javapoet;
1717

1818
import org.springframework.core.ResolvableType;
19+
import org.springframework.javapoet.ParameterizedTypeName;
1920
import org.springframework.javapoet.TypeName;
2021
import org.springframework.util.ClassUtils;
2122

@@ -28,6 +29,7 @@
2829
* Mainly for internal use within the framework
2930
*
3031
* @author Mark Paluch
32+
* @author Christoph Strobl
3133
* @since 4.0
3234
*/
3335
public abstract class TypeNames {
@@ -65,6 +67,30 @@ public static TypeName className(ResolvableType resolvableType) {
6567
return TypeName.get(resolvableType.toClass());
6668
}
6769

70+
/**
71+
* Obtain a {@link TypeName} for the underlying type of the given {@link ResolvableType}. Can render a class name, a
72+
* type signature with resolved generics or a generic type variable.
73+
*
74+
* @param resolvableType the resolvable type represent.
75+
* @return the corresponding {@link TypeName}.
76+
*/
77+
public static TypeName resolvedTypeName(ResolvableType resolvableType) {
78+
79+
if (resolvableType.equals(ResolvableType.NONE)) {
80+
return TypeName.get(Object.class);
81+
}
82+
83+
if (!resolvableType.hasGenerics()) {
84+
return TypeName.get(resolvableType.toClass());
85+
}
86+
87+
if (resolvableType.hasResolvableGenerics()) {
88+
return ParameterizedTypeName.get(resolvableType.toClass(), resolvableType.resolveGenerics());
89+
}
90+
91+
return TypeName.get(resolvableType.getType());
92+
}
93+
6894
/**
6995
* Obtain a {@link TypeName} for the underlying type of the given {@link ResolvableType}. Can render a class name, a
7096
* type signature or a generic type variable.
@@ -98,7 +124,7 @@ public static TypeName typeNameOrWrapper(Class<?> type) {
98124
public static TypeName typeNameOrWrapper(ResolvableType resolvableType) {
99125
return ClassUtils.isPrimitiveOrWrapper(resolvableType.toClass())
100126
? TypeName.get(ClassUtils.resolvePrimitiveIfNecessary(resolvableType.toClass()))
101-
: typeName(resolvableType);
127+
: resolvedTypeName(resolvableType);
102128
}
103129

104130
private TypeNames() {}

src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryFragmentMetadata.java

Lines changed: 4 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import org.jspecify.annotations.Nullable;
2828

2929
import org.springframework.core.ResolvableType;
30+
import org.springframework.data.javapoet.TypeNames;
3031
import org.springframework.data.repository.core.support.RepositoryFragment;
3132
import org.springframework.data.repository.query.QueryMethod;
3233
import org.springframework.javapoet.ParameterizedTypeName;
@@ -147,26 +148,10 @@ public Map<String, DelegateMethod> getDelegateMethods() {
147148
}
148149

149150
static TypeName typeNameOf(ResolvableType type) {
151+
return TypeNames.resolvedTypeName(type);
152+
}
150153

151-
if (type.equals(ResolvableType.NONE)) {
152-
return TypeName.get(Object.class);
153-
}
154-
155-
if (!type.hasResolvableGenerics()) {
156-
return TypeName.get(type.getType());
157-
}
158-
159-
return ParameterizedTypeName.get(type.toClass(), type.resolveGenerics());
160-
}
161-
162-
/**
163-
* Constructor argument metadata.
164-
*
165-
* @param parameterName
166-
* @param parameterType
167-
* @param bindToField
168-
*/
169-
public record ConstructorArgument(String parameterName, ResolvableType parameterType, boolean bindToField,
154+
public record ConstructorArgument(String parameterName, ResolvableType parameterType, boolean bindToField,
170155
AotRepositoryConstructorBuilder.ParameterOrigin parameterOrigin) {
171156

172157
boolean isBoundToField() {

src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryMethodBuilder.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
import javax.lang.model.element.Modifier;
2626

27+
import org.springframework.data.javapoet.TypeNames;
2728
import org.springframework.javapoet.CodeBlock;
2829
import org.springframework.javapoet.MethodSpec;
2930
import org.springframework.javapoet.ParameterSpec;
@@ -101,7 +102,7 @@ public MethodSpec buildMethod() {
101102
private MethodSpec.Builder initializeMethodBuilder() {
102103

103104
MethodSpec.Builder builder = MethodSpec.methodBuilder(context.getMethod().getName()).addModifiers(Modifier.PUBLIC);
104-
builder.returns(TypeName.get(context.getReturnType().getType()));
105+
builder.returns(TypeNames.resolvedTypeName(context.getTargetMethodMetadata().getReturnType()));
105106

106107
TypeVariable<Method>[] tvs = context.getMethod().getTypeParameters();
107108
for (TypeVariable<Method> tv : tvs) {

src/main/java/org/springframework/data/repository/aot/generate/MethodMetadata.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import org.springframework.core.MethodParameter;
3232
import org.springframework.core.ParameterNameDiscoverer;
3333
import org.springframework.core.ResolvableType;
34+
import org.springframework.data.javapoet.TypeNames;
3435
import org.springframework.data.repository.core.RepositoryInformation;
3536
import org.springframework.data.util.TypeInformation;
3637
import org.springframework.javapoet.ParameterSpec;
@@ -80,11 +81,11 @@ private static void initializeMethodArguments(Method method, ParameterNameDiscov
8081

8182
for (Parameter parameter : method.getParameters()) {
8283

83-
MethodParameter methodParameter = MethodParameter.forParameter(parameter);
84+
MethodParameter methodParameter = MethodParameter.forParameter(parameter).withContainingClass(repositoryInterface.resolve());
8485
methodParameter.initParameterNameDiscovery(nameDiscoverer);
85-
ResolvableType resolvableParameterType = ResolvableType.forMethodParameter(methodParameter, repositoryInterface);
86+
ResolvableType resolvableParameterType = ResolvableType.forMethodParameter(methodParameter);
8687

87-
TypeName parameterType = TypeName.get(resolvableParameterType.getType());
88+
TypeName parameterType = TypeNames.resolvedTypeName(resolvableParameterType);
8889

8990
ParameterSpec parameterSpec = ParameterSpec.builder(parameterType, methodParameter.getParameterName()).build();
9091

src/main/java/org/springframework/data/repository/aot/generate/MethodReturn.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ public MethodReturn(ReturnedType returnedType, ResolvableType returnType) {
6161

6262
this.returnedType = returnedType;
6363
this.returnType = returnType;
64-
this.typeName = TypeNames.typeName(returnType);
64+
this.typeName = TypeNames.resolvedTypeName(returnType);
6565
this.className = TypeNames.className(returnType);
6666

6767
Class<?> returnClass = returnType.toClass();
@@ -72,7 +72,7 @@ public MethodReturn(ReturnedType returnedType, ResolvableType returnType) {
7272

7373
if (actualType != null) {
7474
this.actualType = actualType.toResolvableType();
75-
this.actualTypeName = TypeNames.typeName(this.actualType);
75+
this.actualTypeName = TypeNames.resolvedTypeName(this.actualType);
7676
this.actualClassName = TypeNames.className(this.actualType);
7777
this.actualReturnClass = actualType.getType();
7878
} else {
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright 2025-present the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package example;
17+
18+
import org.springframework.data.repository.CrudRepository;
19+
import org.springframework.data.repository.NoRepositoryBean;
20+
21+
/**
22+
* @author Christoph Strobl
23+
*/
24+
@NoRepositoryBean
25+
public interface BaseRepository<T, ID> extends CrudRepository<T, ID> {
26+
27+
T findInBaseRepository(ID id);
28+
}

src/test/java/example/UserRepository.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
/**
2525
* @author Christoph Strobl
2626
*/
27-
public interface UserRepository extends CrudRepository<User, Long>, UserRepositoryExtension {
27+
public interface UserRepository extends BaseRepository<User, Long>, UserRepositoryExtension {
2828

2929
User findByFirstname(String firstname);
3030

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

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,16 @@
2020
import java.util.Set;
2121
import java.util.stream.Stream;
2222

23+
import org.junit.jupiter.api.Test;
2324
import org.junit.jupiter.params.ParameterizedTest;
2425
import org.junit.jupiter.params.provider.Arguments;
2526
import org.junit.jupiter.params.provider.MethodSource;
27+
import org.springframework.core.MethodParameter;
2628
import org.springframework.core.ResolvableType;
2729
import org.springframework.javapoet.ParameterizedTypeName;
2830
import org.springframework.javapoet.TypeName;
31+
import org.springframework.javapoet.TypeVariableName;
32+
import org.springframework.util.ReflectionUtils;
2933

3034
/**
3135
* @author Christoph Strobl
@@ -59,4 +63,30 @@ void classNames(ResolvableType resolvableType, TypeName expected) {
5963
assertThat(TypeNames.className(resolvableType)).isEqualTo(expected);
6064
}
6165

66+
@Test
67+
void typeNameQuirksForMethodParameters() {
68+
69+
ReflectionUtils.doWithMethods(Concrete.class, method -> {
70+
if (!method.getName().contains("baseMethod")) {
71+
return;
72+
}
73+
74+
MethodParameter methodParameter = new MethodParameter(method, 0).withContainingClass(Concrete.class);
75+
ResolvableType resolvableType = ResolvableType.forMethodParameter(methodParameter);
76+
77+
assertThat(TypeNames.typeName(resolvableType)).isEqualTo(TypeVariableName.get("T"));
78+
assertThat(TypeNames.resolvedTypeName(resolvableType)).isEqualTo(TypeName.get(MyType.class));
79+
});
80+
}
81+
82+
interface GenericBase<T> {
83+
java.util.List<T> baseMethod(T arg0);
84+
}
85+
86+
interface Concrete extends GenericBase<MyType> {
87+
88+
}
89+
90+
class MyType {}
91+
6292
}

src/test/java/org/springframework/data/repository/aot/generate/AotRepositoryMethodBuilderUnitTests.java

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,6 @@ void generatesMethodSkeletonBasedOnGenerationMetadata() throws NoSuchMethodExcep
6060

6161
Method method = UserRepository.class.getMethod("findByFirstname", String.class);
6262
when(methodGenerationContext.getMethod()).thenReturn(method);
63-
when(methodGenerationContext.getReturnType()).thenReturn(ResolvableType.forClass(User.class));
6463
doReturn(TypeInformation.of(User.class)).when(repositoryInformation).getReturnType(any());
6564
doReturn(TypeInformation.of(User.class)).when(repositoryInformation).getReturnedDomainTypeInformation(any());
6665
MethodMetadata methodMetadata = new MethodMetadata(repositoryInformation, method);
@@ -76,11 +75,10 @@ void generatesMethodWithGenerics() throws NoSuchMethodException {
7675

7776
Method method = UserRepository.class.getMethod("findByFirstnameIn", List.class);
7877
when(methodGenerationContext.getMethod()).thenReturn(method);
79-
when(methodGenerationContext.getReturnType())
80-
.thenReturn(ResolvableType.forClassWithGenerics(List.class, User.class));
8178
doReturn(TypeInformation.of(User.class)).when(repositoryInformation).getReturnType(any());
8279
doReturn(TypeInformation.of(User.class)).when(repositoryInformation).getReturnedDomainTypeInformation(any());
83-
MethodMetadata methodMetadata = new MethodMetadata(repositoryInformation, method);
80+
MethodMetadata methodMetadata = spy(new MethodMetadata(repositoryInformation, method));
81+
when(methodMetadata.getReturnType()).thenReturn(ResolvableType.forClassWithGenerics(List.class, User.class));
8482
when(methodGenerationContext.getTargetMethodMetadata()).thenReturn(methodMetadata);
8583

8684
AotRepositoryMethodBuilder builder = new AotRepositoryMethodBuilder(methodGenerationContext);
@@ -95,7 +93,6 @@ void generatesExpressionMarkerIfInUse(ExpressionMarker expressionMarker) throws
9593

9694
Method method = UserRepository.class.getMethod("findByFirstname", String.class);
9795
when(methodGenerationContext.getMethod()).thenReturn(method);
98-
when(methodGenerationContext.getReturnType()).thenReturn(ResolvableType.forClass(User.class));
9996
doReturn(TypeInformation.of(User.class)).when(repositoryInformation).getReturnType(any());
10097
doReturn(TypeInformation.of(User.class)).when(repositoryInformation).getReturnedDomainTypeInformation(any());
10198
MethodMetadata methodMetadata = new MethodMetadata(repositoryInformation, method);

0 commit comments

Comments
 (0)