Skip to content

Commit 50fe8ac

Browse files
committed
start
1 parent b970e0d commit 50fe8ac

File tree

4 files changed

+276
-1
lines changed

4 files changed

+276
-1
lines changed
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
package io.avaje.http.generator.core;
2+
3+
import static java.util.stream.Collectors.toList;
4+
5+
import java.util.List;
6+
import java.util.Map.Entry;
7+
import java.util.regex.Pattern;
8+
9+
import javax.lang.model.element.AnnotationMirror;
10+
import javax.lang.model.element.AnnotationValue;
11+
import javax.lang.model.element.Element;
12+
import javax.lang.model.element.ExecutableElement;
13+
import javax.lang.model.element.VariableElement;
14+
15+
final class AnnotationCopier {
16+
private AnnotationCopier() {}
17+
18+
private static final Pattern ANNOTATION_TYPE_PATTERN = Pattern.compile("@([\\w.]+)\\.");
19+
20+
static String trimAnnotationString(String input) {
21+
return ANNOTATION_TYPE_PATTERN.matcher(input).replaceAll("@");
22+
}
23+
24+
static void copyAnnotations(Append writer, Element element, boolean newLines) {
25+
copyAnnotations(writer, element, "", newLines);
26+
}
27+
28+
static void copyAnnotations(Append writer, Element element, String indent, boolean newLines) {
29+
for (final AnnotationMirror annotationMirror : element.getAnnotationMirrors()) {
30+
final var type = annotationMirror.getAnnotationType().asElement().asType().toString();
31+
if (!type.contains("io.avaje.http.api.")
32+
|| type.contains("Produces")
33+
|| type.contains("Consumes")) {
34+
continue;
35+
}
36+
37+
String annotationString = toAnnotationString(indent, annotationMirror, false);
38+
39+
annotationString =
40+
annotationString
41+
.replace("io.avaje.http.api.", "")
42+
.replace("value=", "")
43+
.replace("(\"\")", "");
44+
45+
writer.append(annotationString);
46+
47+
if (newLines) {
48+
writer.eol();
49+
} else {
50+
writer.append(" ");
51+
}
52+
}
53+
}
54+
55+
static String toSimpleAnnotationString(AnnotationMirror annotationMirror) {
56+
return trimAnnotationString(toAnnotationString("", annotationMirror, true)).substring(1);
57+
}
58+
59+
static String toAnnotationString(
60+
String indent, AnnotationMirror annotationMirror, boolean simpleEnums) {
61+
final String annotationName = annotationMirror.getAnnotationType().toString();
62+
63+
final StringBuilder sb =
64+
new StringBuilder(indent).append("@").append(annotationName).append("(");
65+
boolean first = true;
66+
67+
for (final var entry : sortedValues(annotationMirror)) {
68+
if (!first) {
69+
sb.append(", ");
70+
}
71+
sb.append(entry.getKey().getSimpleName()).append("=");
72+
writeVal(sb, entry.getValue(), simpleEnums);
73+
first = false;
74+
}
75+
76+
return sb.append(")").toString().replace("()", "");
77+
}
78+
79+
private static List<Entry<? extends ExecutableElement, ? extends AnnotationValue>> sortedValues(
80+
AnnotationMirror annotationMirror) {
81+
return APContext.elements().getElementValuesWithDefaults(annotationMirror).entrySet().stream()
82+
.sorted(AnnotationCopier::compareBySimpleName)
83+
.collect(toList());
84+
}
85+
86+
private static int compareBySimpleName(
87+
Entry<? extends ExecutableElement, ? extends AnnotationValue> entry1,
88+
Entry<? extends ExecutableElement, ? extends AnnotationValue> entry2) {
89+
return entry1
90+
.getKey()
91+
.getSimpleName()
92+
.toString()
93+
.compareTo(entry2.getKey().getSimpleName().toString());
94+
}
95+
96+
@SuppressWarnings("unchecked")
97+
private static void writeVal(
98+
final StringBuilder sb, final AnnotationValue annotationValue, boolean simpleEnums) {
99+
final var value = annotationValue.getValue();
100+
if (value instanceof List) {
101+
// handle array values
102+
sb.append("{");
103+
boolean first = true;
104+
for (final AnnotationValue listValue : (List<AnnotationValue>) value) {
105+
if (!first) {
106+
sb.append(", ");
107+
}
108+
writeVal(sb, listValue, simpleEnums);
109+
first = false;
110+
}
111+
sb.append("}");
112+
113+
} else if (value instanceof VariableElement) {
114+
// Handle enum values
115+
final var element = (VariableElement) value;
116+
final var type = element.asType();
117+
final var str = simpleEnums ? element : type.toString() + "." + element;
118+
sb.append(str);
119+
120+
} else if (value instanceof AnnotationMirror) {
121+
// handle annotation values
122+
final var mirror = (AnnotationMirror) value;
123+
final String annotationName = mirror.getAnnotationType().toString();
124+
sb.append("@").append(annotationName).append("(");
125+
boolean first = true;
126+
127+
for (final var entry : sortedValues(mirror)) {
128+
if (!first) {
129+
sb.append(", ");
130+
}
131+
sb.append(entry.getKey().getSimpleName()).append("=");
132+
writeVal(sb, entry.getValue(), simpleEnums);
133+
first = false;
134+
}
135+
sb.append(")");
136+
137+
} else {
138+
sb.append(annotationValue);
139+
}
140+
}
141+
}

http-generator-core/src/main/java/io/avaje/http/generator/core/BaseProcessor.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import static io.avaje.http.generator.core.ProcessingContext.elements;
55
import static io.avaje.http.generator.core.ProcessingContext.isOpenApiAvailable;
66
import static io.avaje.http.generator.core.ProcessingContext.logError;
7+
import static io.avaje.http.generator.core.ProcessingContext.logWarn;
78
import static io.avaje.http.generator.core.ProcessingContext.typeElement;
89
import static java.util.stream.Collectors.toMap;
910

@@ -132,6 +133,13 @@ private void writeAdapter(Element controller) {
132133
} catch (final Throwable e) {
133134
logError(reader.beanType(), "Failed to write $Route class " + e);
134135
}
136+
try {
137+
if (((TypeElement) controller).getInterfaces().isEmpty()) {
138+
new TestClientWriter(reader).write();
139+
}
140+
} catch (Exception e) {
141+
logWarn(reader.beanType(), "Failed to write test class " + e);
142+
}
135143
}
136144
}
137145

http-generator-core/src/main/java/io/avaje/http/generator/core/PrimitiveUtil.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ private PrimitiveUtil() {}
1515
"short", "Short",
1616
"double", "Double",
1717
"float", "Float",
18-
"boolean", "Boolean");
18+
"boolean", "Boolean",
19+
"void", "Void");
1920

2021
public static String wrap(String shortName) {
2122
final var wrapped = wrapperMap.get(shortName);
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
package io.avaje.http.generator.core;
2+
3+
import java.io.IOException;
4+
import java.util.Set;
5+
import java.util.TreeSet;
6+
7+
import javax.lang.model.element.TypeElement;
8+
import javax.lang.model.type.TypeMirror;
9+
10+
11+
public class TestClientWriter {
12+
13+
private static final String AT_GENERATED = "@Generated(\"avaje-http-generator\")";
14+
private final Set<String> importTypes = new TreeSet<>();
15+
private final ControllerReader reader;
16+
private String originName;
17+
private String shortName;
18+
private String packageName;
19+
private String fullName;
20+
private Append writer;
21+
22+
TestClientWriter(ControllerReader reader) throws IOException {
23+
24+
this.reader = reader;
25+
final TypeElement origin = reader.beanType();
26+
this.originName = origin.getQualifiedName().toString();
27+
this.shortName = origin.getSimpleName().toString();
28+
this.packageName = initPackageName(originName);
29+
this.fullName = packageName + "." + shortName + "$TestAPI";
30+
writer = new Append(APContext.createSourceFile(fullName, reader.beanType()).openWriter());
31+
}
32+
33+
protected String initPackageName(String originName) {
34+
final int dp = originName.lastIndexOf('.');
35+
return dp > -1 ? originName.substring(0, dp) : null;
36+
}
37+
38+
void write() {
39+
writePackage();
40+
writeImports();
41+
writeClassStart();
42+
writeAddRoutes();
43+
}
44+
45+
protected void writePackage() {
46+
if (packageName != null) {
47+
writer.append("package %s;", packageName).eol().eol();
48+
}
49+
}
50+
51+
protected void writeImports() {
52+
importTypes.add("java.net.http.HttpResponse");
53+
54+
reader
55+
.methods()
56+
.forEach(
57+
m -> {
58+
importTypes.addAll(UType.parse(m.returnType()).importTypes());
59+
m.params()
60+
.forEach(
61+
p -> importTypes.addAll(UType.parse(p.element().asType()).importTypes()));
62+
});
63+
64+
importTypes.addAll(reader.importTypes());
65+
66+
importTypes.removeIf(
67+
i ->
68+
i.startsWith("java.lang")
69+
|| PrimitiveUtil.wrapperMap.containsKey(i)
70+
|| i.contains(".") && i.substring(0, i.lastIndexOf(".")).equals(packageName));
71+
for (String type : importTypes) {
72+
writer.append("import %s;", type).eol();
73+
}
74+
writer.eol();
75+
}
76+
77+
private void writeClassStart() {
78+
writer.append(AT_GENERATED).eol();
79+
writer.append("@Client(\"%s\")", reader.path()).eol();
80+
writer.append("public interface %s$TestAPI {", shortName).eol().eol();
81+
}
82+
83+
private void writeAddRoutes() {
84+
85+
reader.methods().stream()
86+
.filter(MethodReader::isWebMethod)
87+
.filter(
88+
m ->
89+
m.webMethod() instanceof CoreWebMethod
90+
&& m.webMethod() != CoreWebMethod.ERROR
91+
&& m.webMethod() != CoreWebMethod.FILTER
92+
&& m.webMethod() != CoreWebMethod.OTHER)
93+
.forEach(this::writeRoute);
94+
95+
writer.append("}").eol();
96+
writer.close();
97+
}
98+
99+
private void writeRoute(MethodReader method) {
100+
TypeMirror returnType = method.returnType();
101+
var isJstache = ProcessingContext.isJstacheTemplate(returnType);
102+
AnnotationCopier.copyAnnotations(writer, method.element(), true);
103+
104+
var returnTypeStr = PrimitiveUtil.wrap(UType.parse(returnType).shortType());
105+
writer.append(
106+
"HttpResponse<%s> %s(", isJstache ? "String" : returnTypeStr, method.simpleName());
107+
boolean first = true;
108+
for (var param : method.params()) {
109+
110+
if (first) {
111+
first = false;
112+
} else {
113+
writer.append(", ");
114+
}
115+
var type = UType.parse(param.element().asType());
116+
117+
AnnotationCopier.copyAnnotations(writer, param.element(), false);
118+
writer.append("%s %s", type.shortType(), param.name());
119+
}
120+
writer.append(");");
121+
122+
writer.eol();
123+
writer.eol();
124+
}
125+
}

0 commit comments

Comments
 (0)