From 2cb88fd6ad8b32f1b91b48e3e3013795262c73af Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Thu, 28 Sep 2023 10:41:48 +0300 Subject: [PATCH] Take superclasses into account when looking up main method --- .../deployment/steps/MainClassBuildStep.java | 120 ++++++++++++------ ...ceMainInSuperClassCommandModeTestCase.java | 45 +++++++ ...InSuperClassNoArgsCommandModeTestCase.java | 42 ++++++ 3 files changed, 165 insertions(+), 42 deletions(-) create mode 100644 integration-tests/test-extension/extension/deployment/src/test/java/io/quarkus/commandmode/InstanceMainInSuperClassCommandModeTestCase.java create mode 100644 integration-tests/test-extension/extension/deployment/src/test/java/io/quarkus/commandmode/InstanceMainInSuperClassNoArgsCommandModeTestCase.java diff --git a/core/deployment/src/main/java/io/quarkus/deployment/steps/MainClassBuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/steps/MainClassBuildStep.java index 9bd5fb1ff631b6..88c9f757061395 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/steps/MainClassBuildStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/steps/MainClassBuildStep.java @@ -518,75 +518,111 @@ public MainMethodTransformer(IndexView index) { @Override public ClassVisitor apply(String mainClassName, ClassVisitor outputClassVisitor) { - ClassInfo classByName = index.getClassByName(mainClassName); - if (classByName == null) { + ClassInfo mainClassInfo = index.getClassByName(mainClassName); + if (mainClassInfo == null) { throw new IllegalStateException("mainClassName should have a corresponding ClassInfo at this point"); } ClassTransformer transformer = new ClassTransformer(mainClassName); - MethodInfo withStringArgs = classByName.method("main", STRING_ARRAY); - StringArgsPassState state = StringArgsPassState.NO_METHOD; + boolean allowStatic = true; + + return applyRec(mainClassName, outputClassVisitor, transformer, mainClassInfo, allowStatic); + } + + private ClassVisitor applyRec(String originalMainClassName, ClassVisitor originalVisitor, + ClassTransformer transformer, + ClassInfo currentClassInfo, boolean allowStatic) { + MethodInfo withStringArgs = currentClassInfo.method("main", STRING_ARRAY); + StringArgsPassState withArgsCheckState = StringArgsPassState.NO_METHOD; if (withStringArgs != null) { short modifiers = withStringArgs.flags(); if (Modifier.isStatic(modifiers)) { - if (Modifier.isPublic(modifiers)) { - // nothing to do - state = StringArgsPassState.EXIT; - } else { - // this is the simplest case where we just make the method public - transformer.modifyMethod(MethodDescriptor.of(withStringArgs)).removeModifiers(Modifier.PROTECTED) - .addModifiers(Modifier.PUBLIC); - state = StringArgsPassState.NO_MORE_ACTIONS_NEEDED; + if (allowStatic) { + if (Modifier.isPublic(modifiers)) { + // nothing to do + withArgsCheckState = StringArgsPassState.EXIT; + } else { + // this is the simplest case where we just make the method public + transformer.modifyMethod(MethodDescriptor.of(withStringArgs)).removeModifiers(Modifier.PROTECTED) + .addModifiers(Modifier.PUBLIC); + withArgsCheckState = StringArgsPassState.NO_MORE_ACTIONS_NEEDED; + } } } else { if (Modifier.isPrivate(modifiers)) { - state = StringArgsPassState.HAS_PRIVATE_MAIN; + withArgsCheckState = StringArgsPassState.HAS_PRIVATE_MAIN; } else { // here we need to construct an instance and call the instance method with the args parameter MethodCreator standardMain = createStandardMain(transformer); - ResultHandle instanceHandle = standardMain.newInstance(ofConstructor(mainClassName)); - standardMain.invokeVirtualMethod(ofMethod(mainClassName, "$$main$$", void.class, String[].class), + ResultHandle instanceHandle = standardMain.newInstance(ofConstructor(originalMainClassName)); + standardMain.invokeVirtualMethod( + ofMethod(originalMainClassName, "$$main$$", void.class, String[].class), instanceHandle, standardMain.getMethodParam(0)); standardMain.returnValue(null); transformer.modifyMethod(MethodDescriptor.of(withStringArgs)).rename("$$main$$"); - state = StringArgsPassState.NO_MORE_ACTIONS_NEEDED; + withArgsCheckState = StringArgsPassState.NO_MORE_ACTIONS_NEEDED; } } } - if (state == StringArgsPassState.EXIT) { - return outputClassVisitor; - } else if (state == StringArgsPassState.NO_MORE_ACTIONS_NEEDED) { - return transformer.applyTo(outputClassVisitor); - } - - MethodInfo withoutArgs = classByName.method("main"); - if (withoutArgs == null) { - if (state == StringArgsPassState.HAS_PRIVATE_MAIN) { - throw new IllegalStateException("Main method on class '" + mainClassName + "' cannot be private"); - } else { - throw new IllegalStateException("Unable to find main method on class '" + mainClassName + "'"); - } + if (withArgsCheckState == StringArgsPassState.EXIT) { + // no transformations were necessary, so just make the result a pass-through + return originalVisitor; + } else if (withArgsCheckState == StringArgsPassState.NO_MORE_ACTIONS_NEEDED) { + // no more transformations are needed, so just set the result + return transformer.applyTo(originalVisitor); } else { - short modifiers = withoutArgs.flags(); - if (Modifier.isPrivate(modifiers)) { - throw new IllegalStateException("Main method on class '" + mainClassName + "' cannot be private"); + boolean hasValidNoArgsMethod = true; + MethodInfo withoutArgs = currentClassInfo.method("main"); + if (withoutArgs == null) { + if (withArgsCheckState == StringArgsPassState.HAS_PRIVATE_MAIN) { + throw new IllegalStateException( + "Main method on class '" + originalMainClassName + "' cannot be private"); + } else { + hasValidNoArgsMethod = false; + } } else { - MethodCreator standardMain = createStandardMain(transformer); - if (Modifier.isStatic(modifiers)) { - // call the static main without any parameters - standardMain.invokeStaticMethod(MethodDescriptor.of(withoutArgs)); + short modifiers = withoutArgs.flags(); + if (Modifier.isPrivate(modifiers)) { + throw new IllegalStateException( + "Main method on class '" + originalMainClassName + "' cannot be private"); } else { - // here we need to construct an instance and call the instance method without any parameters - ResultHandle instanceHandle = standardMain.newInstance(ofConstructor(mainClassName)); - standardMain.invokeVirtualMethod(MethodDescriptor.of(withoutArgs), instanceHandle); + MethodCreator standardMain = createStandardMain(transformer); + if (Modifier.isStatic(modifiers)) { + if (allowStatic) { + // call the static main without any parameters + standardMain.invokeStaticMethod(MethodDescriptor.of(withoutArgs)); + } + } else { + // here we need to construct an instance and call the instance method without any parameters + ResultHandle instanceHandle = standardMain.newInstance(ofConstructor(originalMainClassName)); + standardMain.invokeVirtualMethod(MethodDescriptor.of(withoutArgs), instanceHandle); + } + standardMain.returnValue(null); } - standardMain.returnValue(null); } - } - return transformer.applyTo(outputClassVisitor); + if (hasValidNoArgsMethod) { + return transformer.applyTo(originalVisitor); + } else { + DotName superName = currentClassInfo.superName(); + if (superName.equals(OBJECT)) { + // no valid main method was found, so we need to fail + throw new IllegalStateException("Unable to find main method on class '" + originalMainClassName + "'"); + } + ClassInfo superClassInfo = index.getClassByName(superName); + if (superClassInfo == null) { + throw new IllegalStateException("Unable to find main method on class '" + originalMainClassName + + "' while it was also not possible to traverse the class hierarchy"); + } + return applyRec(originalMainClassName, originalVisitor, transformer, superClassInfo, /* + * static methods on + * super classes are + * not candidates + */ false); + } + } } private static MethodCreator createStandardMain(ClassTransformer transformer) { diff --git a/integration-tests/test-extension/extension/deployment/src/test/java/io/quarkus/commandmode/InstanceMainInSuperClassCommandModeTestCase.java b/integration-tests/test-extension/extension/deployment/src/test/java/io/quarkus/commandmode/InstanceMainInSuperClassCommandModeTestCase.java new file mode 100644 index 00000000000000..3d8b769e7a2101 --- /dev/null +++ b/integration-tests/test-extension/extension/deployment/src/test/java/io/quarkus/commandmode/InstanceMainInSuperClassCommandModeTestCase.java @@ -0,0 +1,45 @@ +package io.quarkus.commandmode; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.runtime.annotations.QuarkusMain; +import io.quarkus.test.QuarkusProdModeTest; + +public class InstanceMainInSuperClassCommandModeTestCase { + @RegisterExtension + static final QuarkusProdModeTest config = new QuarkusProdModeTest() + .withApplicationRoot((jar) -> jar + .addClasses(HelloWorldSuperSuper.class, HelloWorldSuper.class, HelloWorldMain.class)) + .setApplicationName("run-exit") + .setApplicationVersion("0.1-SNAPSHOT") + .setExpectExit(true) + .setRun(true); + + @Test + public void testRun() { + Assertions.assertThat(config.getStartupConsoleOutput()).contains("Hello World"); + Assertions.assertThat(config.getExitCode()).isEqualTo(0); + } + + @QuarkusMain + public static class HelloWorldMain extends HelloWorldSuper { + + } + + public static class HelloWorldSuperSuper { + + protected void main() { + System.out.println("Hello World"); + } + } + + public static class HelloWorldSuper extends HelloWorldSuperSuper { + + protected void main2() { + System.out.println("Hello"); + } + } + +} diff --git a/integration-tests/test-extension/extension/deployment/src/test/java/io/quarkus/commandmode/InstanceMainInSuperClassNoArgsCommandModeTestCase.java b/integration-tests/test-extension/extension/deployment/src/test/java/io/quarkus/commandmode/InstanceMainInSuperClassNoArgsCommandModeTestCase.java new file mode 100644 index 00000000000000..2f1edd4311f48b --- /dev/null +++ b/integration-tests/test-extension/extension/deployment/src/test/java/io/quarkus/commandmode/InstanceMainInSuperClassNoArgsCommandModeTestCase.java @@ -0,0 +1,42 @@ +package io.quarkus.commandmode; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.runtime.annotations.QuarkusMain; +import io.quarkus.test.QuarkusProdModeTest; + +public class InstanceMainInSuperClassNoArgsCommandModeTestCase { + @RegisterExtension + static final QuarkusProdModeTest config = new QuarkusProdModeTest() + .withApplicationRoot((jar) -> jar + .addClasses(HelloWorldSuper.class, HelloWorldMain.class)) + .setApplicationName("run-exit") + .setApplicationVersion("0.1-SNAPSHOT") + .setExpectExit(true) + .setRun(true); + + @Test + public void testRun() { + Assertions.assertThat(config.getStartupConsoleOutput()).contains("Hello World"); + Assertions.assertThat(config.getExitCode()).isEqualTo(0); + } + + @QuarkusMain + public static class HelloWorldMain extends HelloWorldSuper { + + } + + public static class HelloWorldSuper { + + void main() { + System.out.println("Hello World"); + } + + void main2() { + System.out.println("Hello"); + } + } + +}