diff --git a/spring-core/src/main/java24/org/springframework/core/type/classreading/ClassFileAnnotationDelegate.java b/spring-core/src/main/java24/org/springframework/core/type/classreading/ClassFileAnnotationDelegate.java index edc25f6937c3..810636236da3 100644 --- a/spring-core/src/main/java24/org/springframework/core/type/classreading/ClassFileAnnotationDelegate.java +++ b/spring-core/src/main/java24/org/springframework/core/type/classreading/ClassFileAnnotationDelegate.java @@ -20,7 +20,6 @@ import java.lang.classfile.AnnotationElement; import java.lang.classfile.AnnotationValue; import java.lang.classfile.attribute.RuntimeVisibleAnnotationsAttribute; -import java.lang.constant.ClassDesc; import java.lang.reflect.Array; import java.util.Collections; import java.util.LinkedHashMap; @@ -63,7 +62,7 @@ static MergedAnnotations createMergedAnnotations( private static @Nullable MergedAnnotation createMergedAnnotation( String className, Annotation annotation, @Nullable ClassLoader classLoader) { - String typeName = fromTypeDescriptor(annotation.className().stringValue()); + String typeName = ClassFileAnnotationMetadata.resolveTypeName(annotation.classSymbol()); if (AnnotationFilter.PLAIN.matches(typeName)) { return null; } @@ -97,7 +96,7 @@ static MergedAnnotations createMergedAnnotations( return createMergedAnnotation(className, annotationValue.annotation(), classLoader); } case AnnotationValue.OfClass classValue -> { - return fromTypeDescriptor(classValue.className().stringValue()); + return ClassFileAnnotationMetadata.resolveTypeName(classValue.classSymbol()); } case AnnotationValue.OfEnum enumValue -> { return parseEnum(enumValue, classLoader); @@ -108,12 +107,6 @@ static MergedAnnotations createMergedAnnotations( } } - private static String fromTypeDescriptor(String descriptor) { - ClassDesc classDesc = ClassDesc.ofDescriptor(descriptor); - return (classDesc.isPrimitive() ? classDesc.displayName() : - classDesc.packageName() + "." + classDesc.displayName()); - } - private static Object parseArrayValue(String className, @Nullable ClassLoader classLoader, AnnotationValue.OfArray arrayValue) { if (arrayValue.values().isEmpty()) { return new Object[0]; @@ -145,7 +138,7 @@ private static > Enum parseEnum(AnnotationValue.OfEnum enum } private static Class loadEnumClass(AnnotationValue.OfEnum enumValue, @Nullable ClassLoader classLoader) { - String className = fromTypeDescriptor(enumValue.className().stringValue()); + String className = ClassFileAnnotationMetadata.resolveTypeName(enumValue.classSymbol()); return ClassUtils.resolveClassName(className, classLoader); } diff --git a/spring-core/src/main/java24/org/springframework/core/type/classreading/ClassFileAnnotationMetadata.java b/spring-core/src/main/java24/org/springframework/core/type/classreading/ClassFileAnnotationMetadata.java index 99c5bab22b02..e205385ff18d 100644 --- a/spring-core/src/main/java24/org/springframework/core/type/classreading/ClassFileAnnotationMetadata.java +++ b/spring-core/src/main/java24/org/springframework/core/type/classreading/ClassFileAnnotationMetadata.java @@ -26,6 +26,7 @@ import java.lang.classfile.attribute.NestHostAttribute; import java.lang.classfile.attribute.RuntimeVisibleAnnotationsAttribute; import java.lang.classfile.constantpool.ClassEntry; +import java.lang.constant.ClassDesc; import java.lang.reflect.AccessFlag; import java.util.Collections; import java.util.LinkedHashSet; @@ -221,6 +222,17 @@ static ClassFileAnnotationMetadata of(ClassModel classModel, @Nullable ClassLoad return builder.build(); } + static String resolveTypeName(ClassDesc type) { + if (type.isPrimitive()) { + return type.displayName(); + } + if (type.isArray()) { + return resolveTypeName(type.componentType()) + "[]"; + } + String packageName = type.packageName(); + return (packageName.isEmpty() ? type.displayName() : packageName + "." + type.displayName()); + } + static class Builder { diff --git a/spring-core/src/main/java24/org/springframework/core/type/classreading/ClassFileMethodMetadata.java b/spring-core/src/main/java24/org/springframework/core/type/classreading/ClassFileMethodMetadata.java index bc61f08101a7..902774906f93 100644 --- a/spring-core/src/main/java24/org/springframework/core/type/classreading/ClassFileMethodMetadata.java +++ b/spring-core/src/main/java24/org/springframework/core/type/classreading/ClassFileMethodMetadata.java @@ -24,6 +24,7 @@ import java.lang.reflect.AccessFlag; import java.util.Collections; import java.util.Locale; +import java.util.Objects; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -143,7 +144,7 @@ static ClassFileMethodMetadata of(MethodModel methodModel, ClassLoader classLoad AccessFlags flags = methodModel.flags(); String declaringClassName = methodModel.parent().map(parent -> ClassUtils.convertResourcePathToClassName(parent.thisClass().name().stringValue())).orElse(null); ClassDesc returnType = methodModel.methodTypeSymbol().returnType(); - String returnTypeName = returnType.packageName() + "." + returnType.displayName(); + String returnTypeName = ClassFileAnnotationMetadata.resolveTypeName(returnType); Source source = new Source(declaringClassName, flags, methodName, methodModel.methodTypeSymbol()); MergedAnnotations annotations = methodModel.elementStream() .filter(element -> element instanceof RuntimeVisibleAnnotationsAttribute) @@ -163,6 +164,22 @@ static ClassFileMethodMetadata of(MethodModel methodModel, ClassLoader classLoad */ record Source(@Nullable String declaringClassName, AccessFlags flags, String methodName, MethodTypeDesc descriptor) { + @Override + public boolean equals(Object o) { + if (!(o instanceof Source source)) { + return false; + } + return Objects.equals(this.flags.flagsMask(), source.flags.flagsMask()) && + Objects.equals(this.methodName, source.methodName) && + Objects.equals(this.declaringClassName, source.declaringClassName) && + Objects.equals(this.descriptor.descriptorString(), source.descriptor.descriptorString()); + } + + @Override + public int hashCode() { + return Objects.hash(this.declaringClassName, this.flags.flagsMask(), this.methodName, this.descriptor.descriptorString()); + } + @Override public String toString() { StringBuilder builder = new StringBuilder(); @@ -170,9 +187,7 @@ public String toString() { builder.append(flag.name().toLowerCase(Locale.ROOT)); builder.append(' '); }); - builder.append(this.descriptor.returnType().packageName()); - builder.append("."); - builder.append(this.descriptor.returnType().displayName()); + builder.append(ClassFileAnnotationMetadata.resolveTypeName(this.descriptor.returnType())); builder.append(' '); builder.append(this.declaringClassName); builder.append('.'); diff --git a/spring-core/src/test/java/org/springframework/core/type/AbstractMethodMetadataTests.java b/spring-core/src/test/java/org/springframework/core/type/AbstractMethodMetadataTests.java index 4d52c7f44940..a1fe7ead4c24 100644 --- a/spring-core/src/test/java/org/springframework/core/type/AbstractMethodMetadataTests.java +++ b/spring-core/src/test/java/org/springframework/core/type/AbstractMethodMetadataTests.java @@ -28,6 +28,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.entry; +import static org.junit.jupiter.api.Assumptions.assumeTrue; /** * Base class for {@link MethodMetadata} tests. @@ -76,13 +77,19 @@ void verifyHashCode() { @Test void verifyToString() { assertThat(getTagged(WithMethod.class).toString()) - .endsWith(WithMethod.class.getName() + ".test()"); + .isEqualTo("public java.lang.String " + WithMethod.class.getName() + ".test()"); assertThat(getTagged(WithMethodWithOneArgument.class).toString()) - .endsWith(WithMethodWithOneArgument.class.getName() + ".test(java.lang.String)"); + .isEqualTo("public java.lang.String " + WithMethodWithOneArgument.class.getName() + ".test(java.lang.String)"); assertThat(getTagged(WithMethodWithTwoArguments.class).toString()) - .endsWith(WithMethodWithTwoArguments.class.getName() + ".test(java.lang.String,java.lang.Integer)"); + .isEqualTo("public java.lang.String " + WithMethodWithTwoArguments.class.getName() + ".test(java.lang.String,java.lang.Integer)"); + + assertThat(getTagged(WithPrimitiveArrayMethod.class).toString()) + .isEqualTo("public int[] " + WithPrimitiveArrayMethod.class.getName() + ".test()"); + + assertThat(getTagged(WithStringArrayMethod.class).toString()) + .isEqualTo("public java.lang.String[] " + WithStringArrayMethod.class.getName() + ".test()"); } @Test @@ -102,6 +109,39 @@ void getReturnTypeReturnsReturnType() { String.class.getName()); } + @Test + void getReturnTypeReturnsVoidForVoidReturnType() { + assertThat(getTagged(WithVoidMethod.class).getReturnTypeName()).isEqualTo("void"); + } + + @Test + void getReturnTypeReturnsPrimitiveArrayForPrimitiveArrayReturnTypeForStandardReflection() { + MethodMetadata methodMetadata = getTagged(WithPrimitiveArrayMethod.class); + assumeTrue(methodMetadata instanceof StandardMethodMetadata, "skipped for ASM and ClassFile"); + assertThat(methodMetadata.getReturnTypeName()).isEqualTo("[I"); + } + + @Test + void getReturnTypeReturnsPrimitiveArrayForPrimitiveArrayReturnType() { + MethodMetadata methodMetadata = getTagged(WithPrimitiveArrayMethod.class); + assumeTrue(!(methodMetadata instanceof StandardMethodMetadata), "skipped for standard reflection"); + assertThat(methodMetadata.getReturnTypeName()).isEqualTo("int[]"); + } + + @Test + void getReturnTypeReturnsStringArrayForStringArrayReturnTypeForStandardReflection() { + MethodMetadata methodMetadata = getTagged(WithStringArrayMethod.class); + assumeTrue(methodMetadata instanceof StandardMethodMetadata, "skipped for ASM and ClassFile"); + assertThat(methodMetadata.getReturnTypeName()).isEqualTo("[Ljava.lang.String;"); + } + + @Test + void getReturnTypeReturnsStringArrayForStringArrayReturnType() { + MethodMetadata methodMetadata = getTagged(WithStringArrayMethod.class); + assumeTrue(!(methodMetadata instanceof StandardMethodMetadata), "skipped for standard reflection"); + assertThat(methodMetadata.getReturnTypeName()).isEqualTo("java.lang.String[]"); + } + @Test void isAbstractWhenAbstractReturnsTrue() { assertThat(getTagged(WithAbstractMethod.class).isAbstract()).isTrue(); @@ -217,6 +257,27 @@ public String test() { } + public static class WithVoidMethod { + + @Tag + public void test() {} + + } + + public static class WithPrimitiveArrayMethod { + + @Tag + public int[] test() { return new int[0];} + + } + + public static class WithStringArrayMethod { + + @Tag + public String[] test() { return new String[0];} + + } + public static class WithMethodWithOneArgument { @Tag diff --git a/spring-core/src/test/java/org/springframework/core/type/classreading/SimpleMethodMetadataTests.java b/spring-core/src/test/java/org/springframework/core/type/classreading/DefaultMethodMetadataTests.java similarity index 71% rename from spring-core/src/test/java/org/springframework/core/type/classreading/SimpleMethodMetadataTests.java rename to spring-core/src/test/java/org/springframework/core/type/classreading/DefaultMethodMetadataTests.java index 2e75b49c3cdf..5b539cfd6ff1 100644 --- a/spring-core/src/test/java/org/springframework/core/type/classreading/SimpleMethodMetadataTests.java +++ b/spring-core/src/test/java/org/springframework/core/type/classreading/DefaultMethodMetadataTests.java @@ -16,25 +16,30 @@ package org.springframework.core.type.classreading; +import java.io.IOException; + import org.springframework.core.type.AbstractMethodMetadataTests; import org.springframework.core.type.AnnotationMetadata; + /** * Tests for {@link SimpleMethodMetadata} and - * {@link SimpleMethodMetadataReadingVisitor}. + * {@link SimpleMethodMetadataReadingVisitor} on Java < 24, + * and for the ClassFile API variant on Java >= 24. * * @author Phillip Webb + * @author Brian Clozel */ -class SimpleMethodMetadataTests extends AbstractMethodMetadataTests { +class DefaultMethodMetadataTests extends AbstractMethodMetadataTests { + @Override protected AnnotationMetadata get(Class source) { try { - return new SimpleMetadataReaderFactory( - source.getClassLoader()).getMetadataReader( - source.getName()).getAnnotationMetadata(); + return MetadataReaderFactory.create(source.getClassLoader()) + .getMetadataReader(source.getName()).getAnnotationMetadata(); } - catch (Exception ex) { + catch (IOException ex) { throw new IllegalStateException(ex); } }