diff --git a/spring-shell-core/src/main/java/org/springframework/shell/AvailabilityReflectiveProcessor.java b/spring-shell-core/src/main/java/org/springframework/shell/AvailabilityReflectiveProcessor.java new file mode 100644 index 00000000..b7467e7f --- /dev/null +++ b/spring-shell-core/src/main/java/org/springframework/shell/AvailabilityReflectiveProcessor.java @@ -0,0 +1,56 @@ +/* + * Copyright 2023 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.shell; + +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Method; + +import org.springframework.aot.hint.ExecutableMode; +import org.springframework.aot.hint.ReflectionHints; +import org.springframework.aot.hint.annotation.ReflectiveProcessor; +import org.springframework.util.ClassUtils; +import org.springframework.util.ReflectionUtils; + +/** + * A {@link ReflectiveProcessor} implementation that registers methods of a + * return type {@link Availability} from a target which is a class. + * + * @author Janne Valkealahti + */ +public final class AvailabilityReflectiveProcessor implements ReflectiveProcessor { + + @Override + public void registerReflectionHints(ReflectionHints hints, AnnotatedElement element) { + if (element instanceof Class type) { + ReflectionUtils.doWithMethods(type, method -> { + registerMethodHint(hints, method); + }, mf -> { + return ClassUtils.isAssignable(mf.getReturnType(), Availability.class) + && mf.getDeclaringClass() != Object.class; + }); + } + } + + /** + * Register {@link ReflectionHints} against the specified {@link Method}. + * + * @param hints the reflection hints instance to use + * @param method the method to process + */ + protected void registerMethodHint(ReflectionHints hints, Method method) { + hints.registerMethod(method, ExecutableMode.INVOKE); + } +} diff --git a/spring-shell-core/src/test/java/org/springframework/shell/AvailabilityReflectiveProcessorTests.java b/spring-shell-core/src/test/java/org/springframework/shell/AvailabilityReflectiveProcessorTests.java new file mode 100644 index 00000000..529af771 --- /dev/null +++ b/spring-shell-core/src/test/java/org/springframework/shell/AvailabilityReflectiveProcessorTests.java @@ -0,0 +1,55 @@ +/* + * Copyright 2023 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.shell; + +import org.junit.jupiter.api.Test; + +import org.springframework.aot.hint.ExecutableMode; +import org.springframework.aot.hint.ReflectionHints; +import org.springframework.aot.hint.TypeReference; +import org.springframework.aot.hint.annotation.Reflective; + +import static org.assertj.core.api.Assertions.assertThat; + +class AvailabilityReflectiveProcessorTests { + + private final AvailabilityReflectiveProcessor processor = new AvailabilityReflectiveProcessor(); + + private final ReflectionHints hints = new ReflectionHints(); + + @Test + void registerReflectiveHintsForMethod() throws NoSuchMethodException { + processor.registerReflectionHints(hints, SampleBean.class); + assertThat(hints.typeHints()).singleElement().satisfies(typeHint -> { + assertThat(typeHint.getType()).isEqualTo(TypeReference.of(SampleBean.class)); + assertThat(typeHint.getMemberCategories()).isEmpty(); + assertThat(typeHint.constructors()).isEmpty(); + assertThat(typeHint.fields()).isEmpty(); + assertThat(typeHint.methods()).singleElement().satisfies(methodHint -> { + assertThat(methodHint.getName()).isEqualTo("test"); + assertThat(methodHint.getMode()).isEqualTo(ExecutableMode.INVOKE); + }); + }); + } + + @Reflective(AvailabilityReflectiveProcessor.class) + static class SampleBean { + + public Availability test() { + return null; + } + } +} diff --git a/spring-shell-standard/src/main/java/org/springframework/shell/standard/ShellComponent.java b/spring-shell-standard/src/main/java/org/springframework/shell/standard/ShellComponent.java index 02c98631..06f5b41b 100644 --- a/spring-shell-standard/src/main/java/org/springframework/shell/standard/ShellComponent.java +++ b/spring-shell-standard/src/main/java/org/springframework/shell/standard/ShellComponent.java @@ -22,6 +22,8 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.springframework.aot.hint.annotation.Reflective; +import org.springframework.shell.AvailabilityReflectiveProcessor; import org.springframework.stereotype.Component; /** @@ -38,6 +40,7 @@ import org.springframework.stereotype.Component; @Target(ElementType.TYPE) @Documented @Component +@Reflective(AvailabilityReflectiveProcessor.class) public @interface ShellComponent { /**