diff --git a/modules/jooby-apt/src/main/java/io/jooby/apt/MvcContext.java b/modules/jooby-apt/src/main/java/io/jooby/apt/MvcContext.java index 0e5acc4951..c4d70857e6 100644 --- a/modules/jooby-apt/src/main/java/io/jooby/apt/MvcContext.java +++ b/modules/jooby-apt/src/main/java/io/jooby/apt/MvcContext.java @@ -15,6 +15,7 @@ import io.jooby.internal.apt.Annotations; import io.jooby.internal.apt.Opts; +import io.jooby.internal.newapt.MvcRouter; public class MvcContext { private final ProcessingEnvironment processingEnvironment; @@ -23,7 +24,7 @@ public class MvcContext { private final boolean services; private int round; private final Messager messager; - private final Map attributes = new HashMap<>(); + private final List routers = new ArrayList<>(); public MvcContext(ProcessingEnvironment processingEnvironment, Messager messager) { this.processingEnvironment = processingEnvironment; @@ -36,12 +37,16 @@ public MvcContext(ProcessingEnvironment processingEnvironment, Messager messager debug("Generation of service provider configuration is turned %s.", services ? "ON" : "OFF"); } - public ProcessingEnvironment getProcessingEnvironment() { - return processingEnvironment; + public void add(MvcRouter router) { + routers.add(router); + } + + public List getRouters() { + return routers; } - public Map getAttributes() { - return attributes; + public ProcessingEnvironment getProcessingEnvironment() { + return processingEnvironment; } public boolean isHttpMethod(TypeElement annotated) { diff --git a/modules/jooby-apt/src/main/java/io/jooby/apt/MvcSourceCodeProcessor.java b/modules/jooby-apt/src/main/java/io/jooby/apt/MvcSourceCodeProcessor.java index 55a46e9bc5..b01764218c 100644 --- a/modules/jooby-apt/src/main/java/io/jooby/apt/MvcSourceCodeProcessor.java +++ b/modules/jooby-apt/src/main/java/io/jooby/apt/MvcSourceCodeProcessor.java @@ -8,6 +8,7 @@ import static java.util.Optional.ofNullable; import java.io.IOException; +import java.io.PrintWriter; import java.util.*; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -16,6 +17,7 @@ import javax.lang.model.SourceVersion; import javax.lang.model.element.*; import javax.lang.model.type.DeclaredType; +import javax.tools.StandardLocation; import io.jooby.internal.apt.Annotations; import io.jooby.internal.apt.Opts; @@ -55,7 +57,7 @@ public boolean process(Set annotations, RoundEnvironment if (roundEnv.processingOver()) { if (context.generateServices()) { - // TODO: doServices(processingEnv.getFiler(), modules); + doServices(context.getProcessingEnvironment().getFiler(), context.getRouters()); } return false; } else { @@ -66,6 +68,7 @@ public boolean process(Set annotations, RoundEnvironment var javaFile = router.toSourceCode(); context.debug("%s", javaFile); javaFile.writeTo(filer); + context.add(router); } catch (IOException cause) { throw new RuntimeException("Unable to generate: " + router.getTargetType(), cause); } @@ -74,6 +77,26 @@ public boolean process(Set annotations, RoundEnvironment } } + private void doServices(Filer filer, List routers) { + try { + var location = "META-INF/services/io.jooby.MvcFactory"; + context.debug("%s", location); + + var resource = filer.createResource(StandardLocation.CLASS_OUTPUT, "", location); + var content = new StringBuilder(); + for (var router : routers) { + String classname = router.getGeneratedType(); + context.debug(" %s", classname); + content.append(classname).append(System.lineSeparator()); + } + try (PrintWriter writer = new PrintWriter(resource.openOutputStream())) { + writer.println(content); + } + } catch (IOException cause) { + throw propagate(cause); + } + } + private Map buildRouteRegistry( Set annotations, RoundEnvironment roundEnv) { Map registry = new LinkedHashMap<>(); @@ -193,4 +216,48 @@ public Set getSupportedOptions() { return options; } + + /** + * Throws any throwable 'sneakily' - you don't need to catch it, nor declare that you throw it + * onwards. The exception is still thrown - javac will just stop whining about it. + * + *

Example usage: + * + *

public void run() {
+   *     throw sneakyThrow(new IOException("You don't need to catch me!"));
+   * }
+ * + *

NB: The exception is not wrapped, ignored, swallowed, or redefined. The JVM actually does + * not know or care about the concept of a 'checked exception'. All this method does is hide the + * act of throwing a checked exception from the java compiler. + * + *

Note that this method has a return type of {@code RuntimeException}; it is advised you + * always call this method as argument to the {@code throw} statement to avoid compiler errors + * regarding no return statement and similar problems. This method won't of course return an + * actual {@code RuntimeException} - it never returns, it always throws the provided exception. + * + * @param x The throwable to throw without requiring you to catch its type. + * @return A dummy RuntimeException; this method never returns normally, it always throws + * an exception! + */ + public static RuntimeException propagate(final Throwable x) { + if (x == null) { + throw new NullPointerException("x"); + } + + sneakyThrow0(x); + return null; + } + + /** + * Make a checked exception un-checked and rethrow it. + * + * @param x Exception to throw. + * @param Exception type. + * @throws E Exception to throw. + */ + @SuppressWarnings("unchecked") + private static void sneakyThrow0(final Throwable x) throws E { + throw (E) x; + } } diff --git a/modules/jooby-apt/src/main/java/io/jooby/internal/newapt/MvcRouter.java b/modules/jooby-apt/src/main/java/io/jooby/internal/newapt/MvcRouter.java index 1a77c83ac4..580348f911 100644 --- a/modules/jooby-apt/src/main/java/io/jooby/internal/newapt/MvcRouter.java +++ b/modules/jooby-apt/src/main/java/io/jooby/internal/newapt/MvcRouter.java @@ -16,6 +16,7 @@ import com.squareup.javapoet.*; import io.jooby.apt.MvcContext; +import io.jooby.internal.apt.TypeDefinition; public class MvcRouter { private final MvcContext context; @@ -36,7 +37,7 @@ public TypeElement getTargetType() { } public String getGeneratedType() { - return getTargetType().getQualifiedName().toString(); + return getTargetType().getQualifiedName().toString() + "_"; } public MvcRouter put(TypeElement httpMethod, ExecutableElement route) { @@ -83,12 +84,13 @@ public JavaFile toSourceCode() { routerType); var supplierType = ParameterizedTypeName.get( - ClassName.get(elements.getTypeElement("java.util.function.Supplier")), routerType); + ClassName.get(elements.getTypeElement("jakarta.inject.Provider")), routerType); var classType = ParameterizedTypeName.get( ClassName.get(elements.getTypeElement("java.lang.Class")), routerType); - var source = TypeSpec.classBuilder(getTargetType().getSimpleName() + "_"); + var generateTypeName = getTargetType().getSimpleName() + "_"; + var source = TypeSpec.classBuilder(generateTypeName); source.addModifiers(Modifier.PUBLIC); source.addSuperinterface( environment.getElementUtils().getTypeElement("io.jooby.MvcExtension").asType()); @@ -128,6 +130,28 @@ public JavaFile toSourceCode() { routes.stream().map(MvcRoute::generateHandlerCall).forEach(source::addMethod); + // TODO: remove at some point + source.addSuperinterface( + environment.getElementUtils().getTypeElement("io.jooby.MvcFactory").asType()); + var supports = MethodSpec.methodBuilder("supports"); + supports.addModifiers(Modifier.PUBLIC); + supports.addParameter(ParameterSpec.builder(Class.class, "type").build()); + supports.addStatement(CodeBlock.of("return type == $L.class", getTargetType().getSimpleName())); + supports.returns(boolean.class); + source.addMethod(supports.build()); + + var create = MethodSpec.methodBuilder("create"); + create.addModifiers(Modifier.PUBLIC); + var rawProvider = + new TypeDefinition( + context.getProcessingEnvironment().getTypeUtils(), + elements.getTypeElement("jakarta.inject.Provider").asType()); + create.addParameter( + ParameterSpec.builder(TypeName.get(rawProvider.getRawType()), "provider").build()); + create.addStatement("return new $L(provider)", generateTypeName); + create.returns(TypeName.get(elements.getTypeElement("io.jooby.Extension").asType())); + source.addMethod(create.build()); + return JavaFile.builder(getPackageName(), source.build()).build(); } diff --git a/modules/jooby-apt/src/main/resources/META-INF/gradle/incremental.annotation.processors b/modules/jooby-apt/src/main/resources/META-INF/gradle/incremental.annotation.processors index 8a45168a75..8571de5ad3 100644 --- a/modules/jooby-apt/src/main/resources/META-INF/gradle/incremental.annotation.processors +++ b/modules/jooby-apt/src/main/resources/META-INF/gradle/incremental.annotation.processors @@ -1 +1 @@ -io.jooby.apt.JoobyProcessor,dynamic +io.jooby.apt.MvcSourceCodeProcessor,dynamic diff --git a/modules/jooby-apt/src/main/resources/META-INF/services/javax.annotation.processing.Processor b/modules/jooby-apt/src/main/resources/META-INF/services/javax.annotation.processing.Processor index 8c5519211e..3be52a1365 100644 --- a/modules/jooby-apt/src/main/resources/META-INF/services/javax.annotation.processing.Processor +++ b/modules/jooby-apt/src/main/resources/META-INF/services/javax.annotation.processing.Processor @@ -1 +1 @@ -io.jooby.apt.JoobyProcessor +io.jooby.apt.MvcSourceCodeProcessor diff --git a/tests/pom.xml b/tests/pom.xml index 897655db06..416a97a248 100644 --- a/tests/pom.xml +++ b/tests/pom.xml @@ -263,8 +263,8 @@ compile - ${project.basedir}/src/main/kotlin ${project.basedir}/src/main/java + ${project.basedir}/src/main/kotlin 17 diff --git a/tests/src/test/java/io/jooby/i2031/Issue2031.java b/tests/src/test/java/io/jooby/i2031/Issue2031.java index 1131e23879..9825ac743f 100644 --- a/tests/src/test/java/io/jooby/i2031/Issue2031.java +++ b/tests/src/test/java/io/jooby/i2031/Issue2031.java @@ -31,7 +31,7 @@ public void shouldWorkWithRxJava(ServerTestRunner runner) { runner .define( app -> { - app.mvc(new C2031_()); + app.mvc(new C2031()); }) .ready( http -> { @@ -44,7 +44,7 @@ public void shouldWorkWithReactor(ServerTestRunner runner) { runner .define( app -> { - app.mvc(new C2031()); + app.mvc(new C2031_()); }) .ready( http -> {