Skip to content

Commit

Permalink
Take superclasses into account when looking up main method
Browse files Browse the repository at this point in the history
  • Loading branch information
geoand committed Sep 28, 2023
1 parent 0387740 commit 2cb88fd
Show file tree
Hide file tree
Showing 3 changed files with 165 additions and 42 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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");
}
}

}
Original file line number Diff line number Diff line change
@@ -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");
}
}

}

0 comments on commit 2cb88fd

Please sign in to comment.