diff --git a/spring-core-test/src/main/java/org/springframework/aot/agent/InstrumentedMethod.java b/spring-core-test/src/main/java/org/springframework/aot/agent/InstrumentedMethod.java index ab74ca02e6..2291d0daa1 100644 --- a/spring-core-test/src/main/java/org/springframework/aot/agent/InstrumentedMethod.java +++ b/spring-core-test/src/main/java/org/springframework/aot/agent/InstrumentedMethod.java @@ -63,8 +63,8 @@ enum InstrumentedMethod { */ CLASS_GETCLASSES(Class.class, "getClasses", HintType.REFLECTION, invocation -> { - TypeReference thisType = invocation.getInstanceTypeReference(); - return RuntimeHintsPredicates.reflection().onType(thisType) + Class thisClass = invocation.getInstance(); + return RuntimeHintsPredicates.reflection().onType(TypeReference.of(thisClass)) .withAnyMemberCategory(MemberCategory.DECLARED_CLASSES, MemberCategory.PUBLIC_CLASSES); } ), @@ -87,8 +87,8 @@ enum InstrumentedMethod { */ CLASS_GETCONSTRUCTORS(Class.class, "getConstructors", HintType.REFLECTION, invocation -> { - TypeReference thisType = invocation.getInstanceTypeReference(); - return RuntimeHintsPredicates.reflection().onType(thisType).withAnyMemberCategory( + Class thisClass = invocation.getInstance(); + return RuntimeHintsPredicates.reflection().onType(TypeReference.of(thisClass)).withAnyMemberCategory( MemberCategory.INTROSPECT_PUBLIC_CONSTRUCTORS, MemberCategory.INTROSPECT_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS); } @@ -99,8 +99,8 @@ enum InstrumentedMethod { */ CLASS_GETDECLAREDCLASSES(Class.class, "getDeclaredClasses", HintType.REFLECTION, invocation -> { - TypeReference thisType = invocation.getInstanceTypeReference(); - return RuntimeHintsPredicates.reflection().onType(thisType).withMemberCategory(MemberCategory.DECLARED_CLASSES); + Class thisClass = invocation.getInstance(); + return RuntimeHintsPredicates.reflection().onType(TypeReference.of(thisClass)).withMemberCategory(MemberCategory.DECLARED_CLASSES); } ), @@ -124,8 +124,8 @@ enum InstrumentedMethod { */ CLASS_GETDECLAREDCONSTRUCTORS(Class.class, "getDeclaredConstructors", HintType.REFLECTION, invocation -> { - TypeReference thisType = invocation.getInstanceTypeReference(); - return RuntimeHintsPredicates.reflection().onType(thisType) + Class thisClass = invocation.getInstance(); + return RuntimeHintsPredicates.reflection().onType(TypeReference.of(thisClass)) .withAnyMemberCategory(MemberCategory.INTROSPECT_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS); }), @@ -149,8 +149,8 @@ enum InstrumentedMethod { */ CLASS_GETDECLAREDFIELDS(Class.class, "getDeclaredFields", HintType.REFLECTION, invocation -> { - TypeReference thisType = invocation.getInstanceTypeReference(); - return RuntimeHintsPredicates.reflection().onType(thisType).withMemberCategory(MemberCategory.DECLARED_FIELDS); + Class thisClass = invocation.getInstance(); + return RuntimeHintsPredicates.reflection().onType(TypeReference.of(thisClass)).withMemberCategory(MemberCategory.DECLARED_FIELDS); } ), @@ -175,8 +175,8 @@ enum InstrumentedMethod { */ CLASS_GETDECLAREDMETHODS(Class.class, "getDeclaredMethods", HintType.REFLECTION, invocation -> { - TypeReference thisType = invocation.getInstanceTypeReference(); - return RuntimeHintsPredicates.reflection().onType(thisType) + Class thisClass = invocation.getInstance(); + return RuntimeHintsPredicates.reflection().onType(TypeReference.of(thisClass)) .withAnyMemberCategory(MemberCategory.INTROSPECT_DECLARED_METHODS, MemberCategory.INVOKE_DECLARED_METHODS); } ), @@ -202,8 +202,8 @@ enum InstrumentedMethod { */ CLASS_GETFIELDS(Class.class, "getFields", HintType.REFLECTION, invocation -> { - TypeReference thisType = invocation.getInstanceTypeReference(); - return RuntimeHintsPredicates.reflection().onType(thisType) + Class thisClass = invocation.getInstance(); + return RuntimeHintsPredicates.reflection().onType(TypeReference.of(thisClass)) .withAnyMemberCategory(MemberCategory.PUBLIC_FIELDS, MemberCategory.DECLARED_FIELDS); } ), @@ -231,8 +231,8 @@ enum InstrumentedMethod { */ CLASS_GETMETHODS(Class.class, "getMethods", HintType.REFLECTION, invocation -> { - TypeReference thisType = invocation.getInstanceTypeReference(); - return RuntimeHintsPredicates.reflection().onType(thisType).withAnyMemberCategory( + Class thisClass = invocation.getInstance(); + return RuntimeHintsPredicates.reflection().onType(TypeReference.of(thisClass)).withAnyMemberCategory( MemberCategory.INTROSPECT_PUBLIC_METHODS, MemberCategory.INTROSPECT_DECLARED_METHODS, MemberCategory.INVOKE_PUBLIC_METHODS, MemberCategory.INVOKE_DECLARED_METHODS); } @@ -297,9 +297,9 @@ enum InstrumentedMethod { */ CLASS_GETRESOURCE(Class.class, "getResource", HintType.RESOURCE_PATTERN, invocation -> { - TypeReference thisType = invocation.getInstanceTypeReference(); + Class thisClass = invocation.getInstance(); String resourceName = invocation.getArgument(0); - return RuntimeHintsPredicates.resource().forResource(thisType, resourceName); + return RuntimeHintsPredicates.resource().forResource(TypeReference.of(thisClass), resourceName); }), /** diff --git a/spring-core-test/src/main/java/org/springframework/aot/agent/RecordedInvocation.java b/spring-core-test/src/main/java/org/springframework/aot/agent/RecordedInvocation.java index 239abc12ac..cfcdc3cfd0 100644 --- a/spring-core-test/src/main/java/org/springframework/aot/agent/RecordedInvocation.java +++ b/spring-core-test/src/main/java/org/springframework/aot/agent/RecordedInvocation.java @@ -110,12 +110,17 @@ public final class RecordedInvocation { */ public TypeReference getInstanceTypeReference() { Assert.notNull(this.instance, "Cannot resolve 'this' for static invocations"); - if (this.instance instanceof Class) { - return TypeReference.of((Class) this.instance); - } return TypeReference.of(this.instance.getClass()); } + /** + * Return whether the current invocation is static. + * @return {@code true} if the invocation is static + */ + public boolean isStatic() { + return this.instance == null; + } + /** * Return the argument values used for the current reflection invocation. * @return the invocation arguments @@ -172,8 +177,15 @@ public final class RecordedInvocation { @Override public String toString() { - return String.format("<%s> invocation of <%s> on type <%s> with arguments %s", - getHintType().hintClassName(), getMethodReference(), getInstanceTypeReference(), getArguments()); + if(isStatic()) { + return String.format("<%s> invocation of <%s> with arguments %s", + getHintType().hintClassName(), getMethodReference(), getArguments()); + } + else { + Class instanceType = (getInstance() instanceof Class) ? getInstance() : getInstance().getClass(); + return String.format("<%s> invocation of <%s> on type <%s> with arguments %s", + getHintType().hintClassName(), getMethodReference(), instanceType.getCanonicalName(), getArguments()); + } } /** diff --git a/spring-core-test/src/main/java/org/springframework/aot/test/agent/RuntimeHintsInvocationsAssert.java b/spring-core-test/src/main/java/org/springframework/aot/test/agent/RuntimeHintsInvocationsAssert.java index 18209ba7fc..0d0b52eced 100644 --- a/spring-core-test/src/main/java/org/springframework/aot/test/agent/RuntimeHintsInvocationsAssert.java +++ b/spring-core-test/src/main/java/org/springframework/aot/test/agent/RuntimeHintsInvocationsAssert.java @@ -92,10 +92,18 @@ public class RuntimeHintsInvocationsAssert extends AbstractAssert for invocation <%s> on type <%s> %nwith arguments %s.%nStacktrace:%n<%s>", - invocation.getHintType().hintClassName(), invocation.getMethodReference(), - invocation.getInstanceTypeReference(), invocation.getArguments(), - formatStackTrace(invocation.getStackFrames())); + if (invocation.isStatic()) { + return new BasicErrorMessageFactory("%nMissing <%s> for invocation <%s>%nwith arguments %s.%nStacktrace:%n<%s>", + invocation.getHintType().hintClassName(), invocation.getMethodReference(), + invocation.getArguments(), formatStackTrace(invocation.getStackFrames())); + } + else { + Class instanceType = (invocation.getInstance() instanceof Class) ? invocation.getInstance() : invocation.getInstance().getClass(); + return new BasicErrorMessageFactory("%nMissing <%s> for invocation <%s> on type <%s> %nwith arguments %s.%nStacktrace:%n<%s>", + invocation.getHintType().hintClassName(), invocation.getMethodReference(), + instanceType, invocation.getArguments(), + formatStackTrace(invocation.getStackFrames())); + } } private String formatStackTrace(Stream stackTraceElements) { diff --git a/spring-core-test/src/test/java/org/springframework/aot/agent/RecordedInvocationTests.java b/spring-core-test/src/test/java/org/springframework/aot/agent/RecordedInvocationTests.java new file mode 100644 index 0000000000..bf46e804e8 --- /dev/null +++ b/spring-core-test/src/test/java/org/springframework/aot/agent/RecordedInvocationTests.java @@ -0,0 +1,94 @@ +/* + * Copyright 2002-2022 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.aot.agent; + + +import java.lang.reflect.Method; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.springframework.aot.hint.TypeReference; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +/** + * Tests for {@link RecordedInvocation}. + * + * @author Brian Clozel + */ +class RecordedInvocationTests { + + private RecordedInvocation staticInvocation; + + private RecordedInvocation instanceInvocation; + + @BeforeEach + void setup() throws Exception { + staticInvocation = RecordedInvocation.of(InstrumentedMethod.CLASS_FORNAME) + .withArgument(String.class.getCanonicalName()) + .returnValue(String.class) + .build(); + instanceInvocation = RecordedInvocation.of(InstrumentedMethod.CLASS_GETMETHOD) + .onInstance(String.class) + .withArguments("toString", new Class[0]) + .returnValue(String.class.getMethod("toString")) + .build(); + } + + @Test + void buildValidStaticInvocation() { + assertThat(staticInvocation.getHintType()).isEqualTo(HintType.REFLECTION); + assertThat(staticInvocation.getMethodReference()).isEqualTo(InstrumentedMethod.CLASS_FORNAME.methodReference()); + assertThat(staticInvocation.getArguments()).containsOnly(String.class.getCanonicalName()); + assertThat(staticInvocation.getArgumentTypes()).containsOnly(TypeReference.of(String.class)); + assertThat((Class) staticInvocation.getReturnValue()).isEqualTo(String.class); + assertThat(staticInvocation.isStatic()).isTrue(); + } + + @Test + void staticInvocationShouldThrowWhenGetInstance() { + assertThatThrownBy(staticInvocation::getInstance).isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy(staticInvocation::getInstanceTypeReference).isInstanceOf(IllegalArgumentException.class); + } + + @Test + void staticInvocationToString() { + assertThat(staticInvocation.toString()).contains("ReflectionHints", "java.lang.Class#forName", "[java.lang.String]"); + } + + @Test + void buildValidInstanceInvocation() throws Exception { + assertThat(instanceInvocation.getHintType()).isEqualTo(HintType.REFLECTION); + assertThat(instanceInvocation.getMethodReference()).isEqualTo(InstrumentedMethod.CLASS_GETMETHOD.methodReference()); + assertThat(instanceInvocation.getArguments()).containsOnly("toString", new Class[0]); + assertThat(instanceInvocation.getArgumentTypes()).containsOnly(TypeReference.of(String.class), TypeReference.of(Class[].class)); + Method toString = String.class.getMethod("toString"); + assertThat((Method) instanceInvocation.getReturnValue()).isEqualTo(toString); + assertThat(instanceInvocation.isStatic()).isFalse(); + assertThat((Class) instanceInvocation.getInstance()).isEqualTo(String.class); + assertThat(instanceInvocation.getInstanceTypeReference()).isEqualTo(TypeReference.of(Class.class)); + } + + @Test + void instanceInvocationToString() { + assertThat(instanceInvocation.toString()).contains("ReflectionHints", "", "java.lang.Class#getMethod", + "java.lang.String", "[toString, [Ljava.lang.Class;"); + } + +}