Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,7 @@ build/
*.Module
dependency-reduced-pom.xml
.DS_Store
tests/test-sigma/avaje-processors.txt
*avaje-processors.txt
*controllers.txt
tests/test-sigma/io.avaje.jsonb.spi.JsonbExtension
tests/test-javalin-jsonb/*.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
package io.avaje.http.generator.core;

import static java.util.stream.Collectors.toList;

import java.util.List;
import java.util.Map.Entry;
import java.util.regex.Pattern;

import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.VariableElement;

final class AnnotationCopier {
private AnnotationCopier() {}

private static final Pattern ANNOTATION_TYPE_PATTERN = Pattern.compile("@([\\w.]+)\\.");

static String trimAnnotationString(String input) {
return ANNOTATION_TYPE_PATTERN.matcher(input).replaceAll("@");
}

static void copyAnnotations(Append writer, Element element, boolean newLines) {
copyAnnotations(writer, element, "", newLines);
}

static void copyAnnotations(Append writer, Element element, String indent, boolean newLines) {
for (final AnnotationMirror annotationMirror : element.getAnnotationMirrors()) {
final var type = annotationMirror.getAnnotationType().asElement().asType().toString();
if (!type.contains("io.avaje.http.api.")
|| type.contains("Produces")
|| type.contains("Consumes")
|| type.contains("InstrumentServerContext")
|| type.contains("Default")
|| type.contains("OpenAPI")
|| type.contains("Valid")) {
continue;
}

String annotationString = toAnnotationString(indent, annotationMirror, false);

annotationString =
annotationString
.replace("io.avaje.http.api.", "")
.replace("value=", "")
.replace("(\"\")", "");

writer.append(annotationString);

if (newLines) {
writer.eol();
} else {
writer.append(" ");
}
}
}

static String toSimpleAnnotationString(AnnotationMirror annotationMirror) {
return trimAnnotationString(toAnnotationString("", annotationMirror, true)).substring(1);
}

static String toAnnotationString(
String indent, AnnotationMirror annotationMirror, boolean simpleEnums) {
final String annotationName = annotationMirror.getAnnotationType().toString();

final StringBuilder sb =
new StringBuilder(indent).append("@").append(annotationName).append("(");
boolean first = true;

for (final var entry : sortedValues(annotationMirror)) {
if (!first) {
sb.append(", ");
}
sb.append(entry.getKey().getSimpleName()).append("=");
writeVal(sb, entry.getValue(), simpleEnums);
first = false;
}

return sb.append(")").toString().replace("()", "");
}

private static List<Entry<? extends ExecutableElement, ? extends AnnotationValue>> sortedValues(
AnnotationMirror annotationMirror) {
return APContext.elements().getElementValuesWithDefaults(annotationMirror).entrySet().stream()
.sorted(AnnotationCopier::compareBySimpleName)
.collect(toList());
}

private static int compareBySimpleName(
Entry<? extends ExecutableElement, ? extends AnnotationValue> entry1,
Entry<? extends ExecutableElement, ? extends AnnotationValue> entry2) {
return entry1
.getKey()
.getSimpleName()
.toString()
.compareTo(entry2.getKey().getSimpleName().toString());
}

@SuppressWarnings("unchecked")
private static void writeVal(
final StringBuilder sb, final AnnotationValue annotationValue, boolean simpleEnums) {
final var value = annotationValue.getValue();
if (value instanceof List) {
// handle array values
sb.append("{");
boolean first = true;
for (final AnnotationValue listValue : (List<AnnotationValue>) value) {
if (!first) {
sb.append(", ");
}
writeVal(sb, listValue, simpleEnums);
first = false;
}
sb.append("}");

} else if (value instanceof VariableElement) {
// Handle enum values
final var element = (VariableElement) value;
final var type = element.asType();
final var str = simpleEnums ? element : type.toString() + "." + element;
sb.append(str);

} else if (value instanceof AnnotationMirror) {
// handle annotation values
final var mirror = (AnnotationMirror) value;
final String annotationName = mirror.getAnnotationType().toString();
sb.append("@").append(annotationName).append("(");
boolean first = true;

for (final var entry : sortedValues(mirror)) {
if (!first) {
sb.append(", ");
}
sb.append(entry.getKey().getSimpleName()).append("=");
writeVal(sb, entry.getValue(), simpleEnums);
first = false;
}
sb.append(")");

} else {
sb.append(annotationValue);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@
import static java.util.stream.Collectors.toMap;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.StandardOpenOption;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
Expand All @@ -27,28 +30,54 @@

@GenerateAPContext
@GenerateModuleInfoReader
@SupportedOptions({"useJavax", "useSingleton", "instrumentRequests","disableDirectWrites","disableJsonB"})
@SupportedOptions({
"useJavax",
"useSingleton",
"instrumentRequests",
"disableDirectWrites",
"disableJsonB"
})
public abstract class BaseProcessor extends AbstractProcessor {

private static final String HTTP_CONTROLLERS_TXT = "testAPI/controllers.txt";
protected String contextPathString;

protected Map<String, String> packagePaths = new HashMap<>();

private final Set<String> clientFQNs = new HashSet<>();

@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latest();
}

@Override
public Set<String> getSupportedAnnotationTypes() {
return Set.of(PathPrism.PRISM_TYPE, ControllerPrism.PRISM_TYPE, OpenAPIDefinitionPrism.PRISM_TYPE);
return Set.of(
PathPrism.PRISM_TYPE, ControllerPrism.PRISM_TYPE, OpenAPIDefinitionPrism.PRISM_TYPE);
}

@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
APContext.init(processingEnv);
ProcessingContext.init(processingEnv, providePlatformAdapter());

try {
var txtFilePath = APContext.getBuildResource(HTTP_CONTROLLERS_TXT);

if (txtFilePath.toFile().exists()) {
Files.lines(txtFilePath).forEach(clientFQNs::add);
}
if (APContext.isTestCompilation()) {
for (var path : clientFQNs) {
TestClientWriter.writeActual(path);
}
}
} catch (IOException e) {
e.printStackTrace();
// not worth failing over
}
}

/** Provide the platform specific adapter to use for Javalin, Helidon etc. */
Expand Down Expand Up @@ -82,19 +111,34 @@ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment
readSecuritySchemes(round);
}

for (final Element controller : round.getElementsAnnotatedWith(typeElement(ControllerPrism.PRISM_TYPE))) {
for (final var controller :
ElementFilter.typesIn(
round.getElementsAnnotatedWith(typeElement(ControllerPrism.PRISM_TYPE)))) {
writeAdapter(controller);
}

if (round.processingOver()) {
writeOpenAPI();
ProcessingContext.validateModule();

if (!APContext.isTestCompilation()) {
try {
Files.write(
APContext.getBuildResource(HTTP_CONTROLLERS_TXT),
clientFQNs,
StandardOpenOption.CREATE,
StandardOpenOption.WRITE);
} catch (IOException e) {
// not worth failing over
}
}
}
return false;
}

private void readOpenApiDefinition(RoundEnvironment round) {
for (final Element element : round.getElementsAnnotatedWith(typeElement(OpenAPIDefinitionPrism.PRISM_TYPE))) {
for (final Element element :
round.getElementsAnnotatedWith(typeElement(OpenAPIDefinitionPrism.PRISM_TYPE))) {
doc().readApiDefinition(element);
}
}
Expand All @@ -103,16 +147,19 @@ private void readTagDefinitions(RoundEnvironment round) {
for (final Element element : round.getElementsAnnotatedWith(typeElement(TagPrism.PRISM_TYPE))) {
doc().addTagDefinition(element);
}
for (final Element element : round.getElementsAnnotatedWith(typeElement(TagsPrism.PRISM_TYPE))) {
for (final Element element :
round.getElementsAnnotatedWith(typeElement(TagsPrism.PRISM_TYPE))) {
doc().addTagsDefinition(element);
}
}

private void readSecuritySchemes(RoundEnvironment round) {
for (final Element element : round.getElementsAnnotatedWith(typeElement(SecuritySchemePrism.PRISM_TYPE))) {
for (final Element element :
round.getElementsAnnotatedWith(typeElement(SecuritySchemePrism.PRISM_TYPE))) {
doc().addSecurityScheme(element);
}
for (final Element element : round.getElementsAnnotatedWith(typeElement(SecuritySchemesPrism.PRISM_TYPE))) {
for (final Element element :
round.getElementsAnnotatedWith(typeElement(SecuritySchemesPrism.PRISM_TYPE))) {
doc().addSecuritySchemes(element);
}
}
Expand All @@ -121,31 +168,42 @@ private void writeOpenAPI() {
doc().writeApi();
}

private void writeAdapter(Element controller) {
if (controller instanceof TypeElement) {
final var packageFQN = elements().getPackageOf(controller).getQualifiedName().toString();
final var contextPath = Util.combinePath(contextPathString, packagePath(packageFQN));
final var reader = new ControllerReader((TypeElement) controller, contextPath);
reader.read(true);
try {
writeControllerAdapter(reader);
} catch (final Throwable e) {
logError(reader.beanType(), "Failed to write $Route class " + e);
private void writeAdapter(TypeElement controller) {
final var packageFQN = elements().getPackageOf(controller).getQualifiedName().toString();
final var contextPath = Util.combinePath(contextPathString, packagePath(packageFQN));
final var reader = new ControllerReader(controller, contextPath);
reader.read(true);
try {

writeControllerAdapter(reader);
writeClientAdapter(reader);

} catch (final Throwable e) {
logError(reader.beanType(), "Failed to write $Route class " + e);
}
}

private void writeClientAdapter(ControllerReader reader) {

try {
if (reader.beanType().getInterfaces().isEmpty()
&& "java.lang.Object".equals(reader.beanType().getSuperclass().toString())) {
new TestClientWriter(reader).write();
clientFQNs.add(reader.beanType().getQualifiedName().toString() + "$TestAPI");
}
} catch (final IOException e) {
logError(reader.beanType(), "Failed to write $Route class " + e);
}
}

private String packagePath(String packageFQN) {
return packagePaths.entrySet().stream()
.filter(k -> packageFQN.startsWith(k.getKey()))
.map(Entry::getValue)
.reduce(Util::combinePath)
.orElse(null);
.filter(k -> packageFQN.startsWith(k.getKey()))
.map(Entry::getValue)
.reduce(Util::combinePath)
.orElse(null);
}

/**
* Write the adapter code for the given controller.
*/
/** Write the adapter code for the given controller. */
public abstract void writeControllerAdapter(ControllerReader reader) throws IOException;

}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ private PrimitiveUtil() {}
"short", "Short",
"double", "Double",
"float", "Float",
"boolean", "Boolean");
"boolean", "Boolean",
"void", "Void");

public static String wrap(String shortName) {
final var wrapped = wrapperMap.get(shortName);
Expand Down
Loading