Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
io.jooby.internal.apt.MvcRouter Maven / Gradle / Ivy
/*
* Jooby https://jooby.io
* Apache License Version 2.0 https://jooby.io/LICENSE.txt
* Copyright 2014 Edgar Espina
*/
package io.jooby.internal.apt;
import static io.jooby.internal.apt.AnnotationSupport.findAnnotationByName;
import static io.jooby.internal.apt.CodeBlock.indent;
import static io.jooby.internal.apt.CodeBlock.semicolon;
import static java.util.Collections.emptyList;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.lang.model.element.*;
import javax.tools.JavaFileObject;
public class MvcRouter {
private final MvcContext context;
/** MVC router class. */
private final TypeElement clazz;
/** MVC route methods. */
private final Map routes = new LinkedHashMap<>();
public MvcRouter(MvcContext context, TypeElement clazz) {
this.context = context;
this.clazz = clazz;
}
public MvcRouter(TypeElement clazz, MvcRouter parent) {
this.context = parent.context;
this.clazz = clazz;
for (var e : parent.routes.entrySet()) {
this.routes.put(e.getKey(), new MvcRoute(context, this, e.getValue()));
}
}
public boolean isKt() {
return context
.getProcessingEnvironment()
.getElementUtils()
.getAllAnnotationMirrors(getTargetType())
.stream()
.anyMatch(it -> it.getAnnotationType().asElement().toString().equals("kotlin.Metadata"));
}
public TypeElement getTargetType() {
return clazz;
}
public String getGeneratedType() {
return context.generateRouterName(getTargetType().getQualifiedName().toString());
}
public String getGeneratedFilename() {
return getGeneratedType().replace('.', '/')
+ (isKt() ? ".kt" : JavaFileObject.Kind.SOURCE.extension);
}
public MvcRouter put(TypeElement httpMethod, ExecutableElement route) {
var routeKey = route.toString();
var existing = routes.get(routeKey);
if (existing == null) {
routes.put(routeKey, new MvcRoute(context, this, route).addHttpMethod(httpMethod));
} else {
if (existing.getMethod().getEnclosingElement().equals(getTargetType())) {
existing.addHttpMethod(httpMethod);
} else {
// Favor override version of same method
routes.put(routeKey, new MvcRoute(context, this, route).addHttpMethod(httpMethod));
}
}
return this;
}
public List getRoutes() {
return routes.values().stream().toList();
}
public boolean isAbstract() {
return clazz.getModifiers().contains(Modifier.ABSTRACT);
}
public String getPackageName() {
var classname = getGeneratedType();
var pkgEnd = classname.lastIndexOf('.');
return pkgEnd > 0 ? classname.substring(0, pkgEnd) : "";
}
/**
* Generate the controller extension for MVC controller:
*
* {@code
* public class Controller_ implements MvcExtension, MvcFactory {
* ....
* }
*
* }
*
* @return
*/
public String toSourceCode(Boolean generateKotlin) throws IOException {
var kt = generateKotlin == Boolean.TRUE || isKt();
var generateTypeName = context.generateRouterName(getTargetType().getSimpleName().toString());
try (var in = getClass().getResourceAsStream("Source" + (kt ? ".kt" : ".java"))) {
Objects.requireNonNull(in);
var routes = this.routes.values();
var suspended = routes.stream().filter(MvcRoute::isSuspendFun).toList();
var noSuspended = routes.stream().filter(it -> !it.isSuspendFun()).toList();
var buffer = new StringBuilder();
context.generateStaticImports(
this,
(owner, fn) ->
buffer.append(
CodeBlock.statement(
"import ", kt ? "" : "static ", owner, ".", fn, semicolon(kt))));
var imports = buffer.toString();
buffer.setLength(0);
// begin install
if (kt) {
buffer.append(indent(4)).append("@Throws(Exception::class)").append(System.lineSeparator());
buffer
.append(indent(4))
.append("override fun install(app: io.jooby.Jooby) {")
.append(System.lineSeparator());
} else {
buffer
.append(indent(4))
.append("public void install(io.jooby.Jooby app) throws Exception {")
.append(System.lineSeparator());
}
if (!suspended.isEmpty()) {
buffer.append(CodeBlock.statement(indent(6), "val kooby = app as io.jooby.kt.Kooby"));
buffer.append(CodeBlock.statement(indent(6), "kooby.coroutine {"));
suspended.stream()
.flatMap(it -> it.generateMapping(kt).stream())
.forEach(line -> buffer.append(CodeBlock.indent(8)).append(line));
trimr(buffer);
buffer.append(System.lineSeparator()).append(CodeBlock.statement(indent(6), "}"));
}
noSuspended.stream()
.flatMap(it -> it.generateMapping(kt).stream())
.forEach(line -> buffer.append(CodeBlock.indent(6)).append(line));
trimr(buffer);
buffer
.append(System.lineSeparator())
.append(indent(4))
.append("}")
.append(System.lineSeparator())
.append(System.lineSeparator());
// end install
routes.stream()
.flatMap(it -> it.generateHandlerCall(kt).stream())
.forEach(line -> buffer.append(CodeBlock.indent(4)).append(line));
return new String(in.readAllBytes(), StandardCharsets.UTF_8)
.replace("${packageName}", getPackageName())
.replace("${imports}", imports)
.replace("${className}", getTargetType().getSimpleName())
.replace("${generatedClassName}", generateTypeName)
.replace("${constructors}", constructors(generateTypeName, kt))
.replace("${methods}", trimr(buffer));
}
}
private StringBuilder trimr(StringBuilder buffer) {
var i = buffer.length() - 1;
while (i > 0 && Character.isWhitespace(buffer.charAt(i))) {
buffer.deleteCharAt(i);
i = buffer.length() - 1;
}
return buffer;
}
private StringBuilder constructors(String generatedName, boolean kt) {
var constructors =
getTargetType().getEnclosedElements().stream()
.filter(
it ->
it.getKind() == ElementKind.CONSTRUCTOR
&& it.getModifiers().contains(Modifier.PUBLIC))
.map(ExecutableElement.class::cast)
.toList();
var targetType = getTargetType().getSimpleName();
var buffer = new StringBuilder();
buffer.append(System.lineSeparator());
// Inject could be at constructor or field level.
var injectConstructor =
constructors.stream().filter(hasInjectAnnotation()).findFirst().orElse(null);
var inject =
injectConstructor != null
|| getTargetType().getEnclosedElements().stream().anyMatch(hasInjectAnnotation());
final var defaultConstructor =
constructors.stream().filter(it -> it.getParameters().isEmpty()).findFirst().orElse(null);
if (inject) {
constructor(
generatedName,
kt,
buffer,
List.of(),
(output, params) -> {
output
.append("this(")
.append(targetType)
.append(kt ? "::class" : ".class")
.append(")")
.append(semicolon(kt))
.append(System.lineSeparator());
});
} else {
if (defaultConstructor != null) {
constructor(
generatedName,
kt,
buffer,
List.of(),
(output, params) -> {
output
.append("this(")
.append(kt ? "" : "new ")
.append(targetType)
.append("())")
.append(semicolon(kt))
.append(System.lineSeparator());
});
}
}
var skip =
Stream.of(injectConstructor, defaultConstructor)
.filter(Objects::nonNull)
.collect(Collectors.toSet());
for (ExecutableElement constructor : constructors) {
if (!skip.contains(constructor)) {
constructor(
generatedName,
kt,
buffer,
constructor.getParameters().stream()
.map(it -> Map.entry(it.asType(), it.getSimpleName().toString()))
.toList(),
(output, params) -> {
var separator = ", ";
output.append("this(").append(kt ? "" : "new ").append(targetType).append("(");
params.forEach(e -> output.append(e.getValue()).append(separator));
output.setLength(output.length() - separator.length());
output.append("))").append(semicolon(kt)).append(System.lineSeparator());
});
}
}
if (inject) {
if (kt) {
constructor(
generatedName,
true,
buffer,
List.of(Map.entry("kotlin.reflect.KClass<" + targetType + ">", "type")),
(output, params) -> {
// this(java.util.function.Function { ctx:
// io.jooby.Context -> ctx.require<${className}>(type.java) })
output
.append("this(java.util.function.Function { ctx: io.jooby.Context -> ")
.append("ctx.require<")
.append(targetType)
.append(">(type.java)")
.append(" })")
.append(System.lineSeparator());
});
} else {
constructor(
generatedName,
false,
buffer,
List.of(Map.entry("Class<" + targetType + ">", "type")),
(output, params) -> {
output
.append("this(")
.append("ctx -> ctx.require(type)")
.append(")")
.append(";")
.append(System.lineSeparator());
});
}
}
return trimr(buffer).append(System.lineSeparator());
}
private static Predicate hasInjectAnnotation() {
var injectAnnotations = Set.of("javax.inject.Inject", "jakarta.inject.Inject");
return it ->
injectAnnotations.stream()
.anyMatch(annotation -> findAnnotationByName(it, annotation) != null);
}
private void constructor(
String generatedName,
boolean kt,
StringBuilder buffer,
List> parameters,
BiConsumer>> body) {
buffer.append(indent(4));
if (kt) {
buffer.append("constructor").append("(");
} else {
buffer.append("public ").append(generatedName).append("(");
}
var separator = ", ";
parameters.forEach(
e -> {
if (kt) {
buffer.append(e.getValue()).append(": ").append(e.getKey()).append(separator);
} else {
buffer.append(e.getKey()).append(" ").append(e.getValue()).append(separator);
}
});
if (!parameters.isEmpty()) {
buffer.setLength(buffer.length() - separator.length());
}
buffer.append(")");
if (!kt) {
buffer.append(" {").append(System.lineSeparator());
buffer.append(indent(6));
} else {
buffer.append(" : ");
}
body.accept(buffer, parameters);
if (!kt) {
buffer.append(indent(4)).append("}");
}
buffer.append(System.lineSeparator()).append(System.lineSeparator());
}
@Override
public String toString() {
StringBuilder buffer = new StringBuilder();
var annotations = Optional.ofNullable(clazz.getAnnotationMirrors()).orElse(emptyList());
annotations.forEach(
annotation -> {
buffer
.append("@")
.append(annotation.getAnnotationType().asElement().getSimpleName())
.append("(");
buffer.append(annotation.getElementValues()).append(") ");
});
buffer.append(clazz.asType().toString()).append(" {\n");
routes.forEach(
(httpMethod, route) -> {
buffer.append(" ").append(route).append("\n");
});
buffer.append("}");
return buffer.toString();
}
}