Skip to content

Commit

Permalink
jooby apt: generate source code
Browse files Browse the repository at this point in the history
- bind existing `mvc` to new annotation processor
- deprecate existing `mvc` methods
- ref #2968
  • Loading branch information
jknack committed Jun 2, 2024
1 parent 0320844 commit 5e3bd89
Show file tree
Hide file tree
Showing 7 changed files with 110 additions and 14 deletions.
15 changes: 10 additions & 5 deletions modules/jooby-apt/src/main/java/io/jooby/apt/MvcContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -23,7 +24,7 @@ public class MvcContext {
private final boolean services;
private int round;
private final Messager messager;
private final Map<Object, Object> attributes = new HashMap<>();
private final List<MvcRouter> routers = new ArrayList<>();

public MvcContext(ProcessingEnvironment processingEnvironment, Messager messager) {
this.processingEnvironment = processingEnvironment;
Expand All @@ -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<MvcRouter> getRouters() {
return routers;
}

public Map<Object, Object> getAttributes() {
return attributes;
public ProcessingEnvironment getProcessingEnvironment() {
return processingEnvironment;
}

public boolean isHttpMethod(TypeElement annotated) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -55,7 +57,7 @@ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment

if (roundEnv.processingOver()) {
if (context.generateServices()) {
// TODO: doServices(processingEnv.getFiler(), modules);
doServices(context.getProcessingEnvironment().getFiler(), context.getRouters());
}
return false;
} else {
Expand All @@ -66,6 +68,7 @@ public boolean process(Set<? extends TypeElement> 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);
}
Expand All @@ -74,6 +77,26 @@ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment
}
}

private void doServices(Filer filer, List<MvcRouter> 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<TypeElement, MvcRouter> buildRouteRegistry(
Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
Map<TypeElement, MvcRouter> registry = new LinkedHashMap<>();
Expand Down Expand Up @@ -193,4 +216,48 @@ public Set<String> 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.
*
* <p>Example usage:
*
* <pre>public void run() {
* throw sneakyThrow(new IOException("You don't need to catch me!"));
* }</pre>
*
* <p>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.
*
* <p>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 <em>always</em> 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 <E> Exception type.
* @throws E Exception to throw.
*/
@SuppressWarnings("unchecked")
private static <E extends Throwable> void sneakyThrow0(final Throwable x) throws E {
throw (E) x;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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) {
Expand Down Expand Up @@ -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());
Expand Down Expand Up @@ -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();
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@
io.jooby.apt.JoobyProcessor,dynamic
io.jooby.apt.MvcSourceCodeProcessor,dynamic
Original file line number Diff line number Diff line change
@@ -1 +1 @@
io.jooby.apt.JoobyProcessor
io.jooby.apt.MvcSourceCodeProcessor
2 changes: 1 addition & 1 deletion tests/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -263,8 +263,8 @@
<id>compile</id>
<configuration>
<sourceDirs>
<sourceDir>${project.basedir}/src/main/kotlin</sourceDir>
<sourceDir>${project.basedir}/src/main/java</sourceDir>
<sourceDir>${project.basedir}/src/main/kotlin</sourceDir>
</sourceDirs>
<jvmTarget>17</jvmTarget>
</configuration>
Expand Down
4 changes: 2 additions & 2 deletions tests/src/test/java/io/jooby/i2031/Issue2031.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public void shouldWorkWithRxJava(ServerTestRunner runner) {
runner
.define(
app -> {
app.mvc(new C2031_());
app.mvc(new C2031());
})
.ready(
http -> {
Expand All @@ -44,7 +44,7 @@ public void shouldWorkWithReactor(ServerTestRunner runner) {
runner
.define(
app -> {
app.mvc(new C2031());
app.mvc(new C2031_());
})
.ready(
http -> {
Expand Down

0 comments on commit 5e3bd89

Please sign in to comment.