Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 36c9f42

Browse files
committed
Fix method and annotation metadata for ClassFile variant
Prior to this commit, the ClassFile variant for annotation and method metadata would report incorrect metadata for: * the method return type names in case of primitives and array types * `toString` values for methods * `equals` and `hashcode` information for methods This commit expands the test suite and ensures that the ASM and ClassFile variants are aligned. Fixes gh-36577
1 parent b01fdb0 commit 36c9f42

6 files changed

Lines changed: 97 additions & 116 deletions

File tree

spring-core/src/main/java24/org/springframework/core/type/classreading/ClassFileAnnotationDelegate.java

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
import java.lang.classfile.AnnotationElement;
2121
import java.lang.classfile.AnnotationValue;
2222
import java.lang.classfile.attribute.RuntimeVisibleAnnotationsAttribute;
23-
import java.lang.constant.ClassDesc;
2423
import java.lang.reflect.Array;
2524
import java.util.Collections;
2625
import java.util.LinkedHashMap;
@@ -63,7 +62,7 @@ static MergedAnnotations createMergedAnnotations(
6362
private static <A extends java.lang.annotation.Annotation> @Nullable MergedAnnotation<A> createMergedAnnotation(
6463
String className, Annotation annotation, @Nullable ClassLoader classLoader) {
6564

66-
String typeName = fromTypeDescriptor(annotation.className().stringValue());
65+
String typeName = ClassFileAnnotationMetadata.resolveTypeName(annotation.classSymbol());
6766
if (AnnotationFilter.PLAIN.matches(typeName)) {
6867
return null;
6968
}
@@ -97,7 +96,7 @@ static MergedAnnotations createMergedAnnotations(
9796
return createMergedAnnotation(className, annotationValue.annotation(), classLoader);
9897
}
9998
case AnnotationValue.OfClass classValue -> {
100-
return fromTypeDescriptor(classValue.className().stringValue());
99+
return ClassFileAnnotationMetadata.resolveTypeName(classValue.classSymbol());
101100
}
102101
case AnnotationValue.OfEnum enumValue -> {
103102
return parseEnum(enumValue, classLoader);
@@ -108,12 +107,6 @@ static MergedAnnotations createMergedAnnotations(
108107
}
109108
}
110109

111-
private static String fromTypeDescriptor(String descriptor) {
112-
ClassDesc classDesc = ClassDesc.ofDescriptor(descriptor);
113-
return (classDesc.isPrimitive() ? classDesc.displayName() :
114-
classDesc.packageName() + "." + classDesc.displayName());
115-
}
116-
117110
private static Object parseArrayValue(String className, @Nullable ClassLoader classLoader, AnnotationValue.OfArray arrayValue) {
118111
if (arrayValue.values().isEmpty()) {
119112
return new Object[0];
@@ -145,7 +138,7 @@ private static <E extends Enum<E>> Enum<E> parseEnum(AnnotationValue.OfEnum enum
145138
}
146139

147140
private static Class<?> loadEnumClass(AnnotationValue.OfEnum enumValue, @Nullable ClassLoader classLoader) {
148-
String className = fromTypeDescriptor(enumValue.className().stringValue());
141+
String className = ClassFileAnnotationMetadata.resolveTypeName(enumValue.classSymbol());
149142
return ClassUtils.resolveClassName(className, classLoader);
150143
}
151144

spring-core/src/main/java24/org/springframework/core/type/classreading/ClassFileAnnotationMetadata.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import java.lang.classfile.attribute.NestHostAttribute;
2727
import java.lang.classfile.attribute.RuntimeVisibleAnnotationsAttribute;
2828
import java.lang.classfile.constantpool.ClassEntry;
29+
import java.lang.constant.ClassDesc;
2930
import java.lang.reflect.AccessFlag;
3031
import java.util.Collections;
3132
import java.util.LinkedHashSet;
@@ -221,6 +222,17 @@ static ClassFileAnnotationMetadata of(ClassModel classModel, @Nullable ClassLoad
221222
return builder.build();
222223
}
223224

225+
static String resolveTypeName(ClassDesc type) {
226+
if (type.isPrimitive()) {
227+
return type.displayName();
228+
}
229+
if (type.isArray()) {
230+
return resolveTypeName(type.componentType()) + "[]";
231+
}
232+
String packageName = type.packageName();
233+
return (packageName.isEmpty() ? type.displayName() : packageName + "." + type.displayName());
234+
}
235+
224236

225237
static class Builder {
226238

spring-core/src/main/java24/org/springframework/core/type/classreading/ClassFileMethodMetadata.java

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import java.lang.reflect.AccessFlag;
2525
import java.util.Collections;
2626
import java.util.Locale;
27+
import java.util.Objects;
2728
import java.util.stream.Collectors;
2829
import java.util.stream.Stream;
2930

@@ -143,7 +144,7 @@ static ClassFileMethodMetadata of(MethodModel methodModel, ClassLoader classLoad
143144
AccessFlags flags = methodModel.flags();
144145
String declaringClassName = methodModel.parent().map(parent -> ClassUtils.convertResourcePathToClassName(parent.thisClass().name().stringValue())).orElse(null);
145146
ClassDesc returnType = methodModel.methodTypeSymbol().returnType();
146-
String returnTypeName = resolveTypeName(returnType);
147+
String returnTypeName = ClassFileAnnotationMetadata.resolveTypeName(returnType);
147148
Source source = new Source(declaringClassName, flags, methodName, methodModel.methodTypeSymbol());
148149
MergedAnnotations annotations = methodModel.elementStream()
149150
.filter(element -> element instanceof RuntimeVisibleAnnotationsAttribute)
@@ -154,18 +155,6 @@ static ClassFileMethodMetadata of(MethodModel methodModel, ClassLoader classLoad
154155
}
155156

156157

157-
private static String resolveTypeName(ClassDesc type) {
158-
if (type.isPrimitive()) {
159-
return type.displayName();
160-
}
161-
if (type.isArray()) {
162-
return resolveTypeName(type.componentType()) + "[]";
163-
}
164-
String packageName = type.packageName();
165-
return (packageName.isEmpty() ? type.displayName() : packageName + "." + type.displayName());
166-
}
167-
168-
169158
/**
170159
* {@link MergedAnnotation} source.
171160
* @param declaringClassName the name of the declaring class
@@ -175,16 +164,30 @@ private static String resolveTypeName(ClassDesc type) {
175164
*/
176165
record Source(@Nullable String declaringClassName, AccessFlags flags, String methodName, MethodTypeDesc descriptor) {
177166

167+
@Override
168+
public boolean equals(Object o) {
169+
if (!(o instanceof Source source)) {
170+
return false;
171+
}
172+
return Objects.equals(this.flags.flagsMask(), source.flags.flagsMask()) &&
173+
Objects.equals(this.methodName, source.methodName) &&
174+
Objects.equals(this.declaringClassName, source.declaringClassName) &&
175+
Objects.equals(this.descriptor.descriptorString(), source.descriptor.descriptorString());
176+
}
177+
178+
@Override
179+
public int hashCode() {
180+
return Objects.hash(this.declaringClassName, this.flags.flagsMask(), this.methodName, this.descriptor.descriptorString());
181+
}
182+
178183
@Override
179184
public String toString() {
180185
StringBuilder builder = new StringBuilder();
181186
this.flags.flags().forEach(flag -> {
182187
builder.append(flag.name().toLowerCase(Locale.ROOT));
183188
builder.append(' ');
184189
});
185-
builder.append(this.descriptor.returnType().packageName());
186-
builder.append(".");
187-
builder.append(this.descriptor.returnType().displayName());
190+
builder.append(ClassFileAnnotationMetadata.resolveTypeName(this.descriptor.returnType()));
188191
builder.append(' ');
189192
builder.append(this.declaringClassName);
190193
builder.append('.');

spring-core/src/test/java/org/springframework/core/type/AbstractMethodMetadataTests.java

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828

2929
import static org.assertj.core.api.Assertions.assertThat;
3030
import static org.assertj.core.api.Assertions.entry;
31+
import static org.junit.jupiter.api.Assumptions.assumeTrue;
3132

3233
/**
3334
* Base class for {@link MethodMetadata} tests.
@@ -76,13 +77,19 @@ void verifyHashCode() {
7677
@Test
7778
void verifyToString() {
7879
assertThat(getTagged(WithMethod.class).toString())
79-
.endsWith(WithMethod.class.getName() + ".test()");
80+
.isEqualTo("public java.lang.String " + WithMethod.class.getName() + ".test()");
8081

8182
assertThat(getTagged(WithMethodWithOneArgument.class).toString())
82-
.endsWith(WithMethodWithOneArgument.class.getName() + ".test(java.lang.String)");
83+
.isEqualTo("public java.lang.String " + WithMethodWithOneArgument.class.getName() + ".test(java.lang.String)");
8384

8485
assertThat(getTagged(WithMethodWithTwoArguments.class).toString())
85-
.endsWith(WithMethodWithTwoArguments.class.getName() + ".test(java.lang.String,java.lang.Integer)");
86+
.isEqualTo("public java.lang.String " + WithMethodWithTwoArguments.class.getName() + ".test(java.lang.String,java.lang.Integer)");
87+
88+
assertThat(getTagged(WithPrimitiveArrayMethod.class).toString())
89+
.isEqualTo("public int[] " + WithPrimitiveArrayMethod.class.getName() + ".test()");
90+
91+
assertThat(getTagged(WithStringArrayMethod.class).toString())
92+
.isEqualTo("public java.lang.String[] " + WithStringArrayMethod.class.getName() + ".test()");
8693
}
8794

8895
@Test
@@ -107,6 +114,34 @@ void getReturnTypeReturnsVoidForVoidReturnType() {
107114
assertThat(getTagged(WithVoidMethod.class).getReturnTypeName()).isEqualTo("void");
108115
}
109116

117+
@Test
118+
void getReturnTypeReturnsPrimitiveArrayForPrimitiveArrayReturnTypeForStandardReflection() {
119+
MethodMetadata methodMetadata = getTagged(WithPrimitiveArrayMethod.class);
120+
assumeTrue(methodMetadata instanceof StandardMethodMetadata, "skipped for ASM and ClassFile");
121+
assertThat(methodMetadata.getReturnTypeName()).isEqualTo("[I");
122+
}
123+
124+
@Test
125+
void getReturnTypeReturnsPrimitiveArrayForPrimitiveArrayReturnType() {
126+
MethodMetadata methodMetadata = getTagged(WithPrimitiveArrayMethod.class);
127+
assumeTrue(!(methodMetadata instanceof StandardMethodMetadata), "skipped for standard reflection");
128+
assertThat(methodMetadata.getReturnTypeName()).isEqualTo("int[]");
129+
}
130+
131+
@Test
132+
void getReturnTypeReturnsStringArrayForStringArrayReturnTypeForStandardReflection() {
133+
MethodMetadata methodMetadata = getTagged(WithStringArrayMethod.class);
134+
assumeTrue(methodMetadata instanceof StandardMethodMetadata, "skipped for ASM and ClassFile");
135+
assertThat(methodMetadata.getReturnTypeName()).isEqualTo("[Ljava.lang.String;");
136+
}
137+
138+
@Test
139+
void getReturnTypeReturnsStringArrayForStringArrayReturnType() {
140+
MethodMetadata methodMetadata = getTagged(WithStringArrayMethod.class);
141+
assumeTrue(!(methodMetadata instanceof StandardMethodMetadata), "skipped for standard reflection");
142+
assertThat(methodMetadata.getReturnTypeName()).isEqualTo("java.lang.String[]");
143+
}
144+
110145
@Test
111146
void isAbstractWhenAbstractReturnsTrue() {
112147
assertThat(getTagged(WithAbstractMethod.class).isAbstract()).isTrue();
@@ -229,6 +264,20 @@ public void test() {}
229264

230265
}
231266

267+
public static class WithPrimitiveArrayMethod {
268+
269+
@Tag
270+
public int[] test() { return new int[0];}
271+
272+
}
273+
274+
public static class WithStringArrayMethod {
275+
276+
@Tag
277+
public String[] test() { return new String[0];}
278+
279+
}
280+
232281
public static class WithMethodWithOneArgument {
233282

234283
@Tag

spring-core/src/test/java/org/springframework/core/type/classreading/SimpleMethodMetadataTests.java renamed to spring-core/src/test/java/org/springframework/core/type/classreading/DefaultMethodMetadataTests.java

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,25 +16,30 @@
1616

1717
package org.springframework.core.type.classreading;
1818

19+
import java.io.IOException;
20+
1921
import org.springframework.core.type.AbstractMethodMetadataTests;
2022
import org.springframework.core.type.AnnotationMetadata;
2123

24+
2225
/**
2326
* Tests for {@link SimpleMethodMetadata} and
24-
* {@link SimpleMethodMetadataReadingVisitor}.
27+
* {@link SimpleMethodMetadataReadingVisitor} on Java < 24,
28+
* and for the ClassFile API variant on Java >= 24.
2529
*
2630
* @author Phillip Webb
31+
* @author Brian Clozel
2732
*/
28-
class SimpleMethodMetadataTests extends AbstractMethodMetadataTests {
33+
class DefaultMethodMetadataTests extends AbstractMethodMetadataTests {
34+
2935

3036
@Override
3137
protected AnnotationMetadata get(Class<?> source) {
3238
try {
33-
return new SimpleMetadataReaderFactory(
34-
source.getClassLoader()).getMetadataReader(
35-
source.getName()).getAnnotationMetadata();
39+
return MetadataReaderFactory.create(source.getClassLoader())
40+
.getMetadataReader(source.getName()).getAnnotationMetadata();
3641
}
37-
catch (Exception ex) {
42+
catch (IOException ex) {
3843
throw new IllegalStateException(ex);
3944
}
4045
}

spring-core/src/test/java24/org/springframework/core/type/classreading/ClassFileMethodMetadataTests.java

Lines changed: 0 additions & 81 deletions
This file was deleted.

0 commit comments

Comments
 (0)