Skip to content

Commit 6840073

Browse files
authored
Add Native JStachio support (#567)
* Add JStachio support for jex * add it to javalin * helidon * Update ControllerMethodWriter.java
1 parent b2332ba commit 6840073

File tree

14 files changed

+680
-30
lines changed

14 files changed

+680
-30
lines changed

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,9 @@ protected void writeImports() {
7373
writer.append("import static %s;", type).eol();
7474
}
7575
writer.eol();
76-
for (String type : reader.importTypes()) {
76+
var importTypes = reader.importTypes();
77+
importTypes.removeIf(i -> i.substring(0, i.lastIndexOf(".")).equals(packageName));
78+
for (String type : importTypes) {
7779
writer.append("import %s;", type).eol();
7880
}
7981
writer.eol();

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

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ public final class ControllerReader {
5959
private boolean requestScope;
6060
private boolean docHidden;
6161
private final boolean hasInstrument;
62+
private boolean hasJstache;
6263

6364
public ControllerReader(TypeElement beanType) {
6465
this(beanType, "");
@@ -249,6 +250,7 @@ public void read(boolean withSingleton) {
249250
}
250251
}
251252
deriveIncludeValidation();
253+
jstacheImport();
252254
addImports(withSingleton);
253255
}
254256

@@ -266,6 +268,25 @@ private boolean anyMethodHasValid() {
266268
return false;
267269
}
268270

271+
private void jstacheImport() {
272+
for (final MethodReader method : methods) {
273+
final var asTypeElement = APContext.asTypeElement(method.returnType());
274+
if (ProcessingContext.isJstacheTemplate(method.returnType())) {
275+
if ("JStachio.render".equals(ProcessingContext.jstacheRenderer(method.returnType()))) {
276+
addImportType("io.jstach.jstachio.JStachio");
277+
} else {
278+
// jstachio generated classes don't have the parent type in the name
279+
addImportType(
280+
APContext.elements().getPackageOf(asTypeElement).getQualifiedName().toString()
281+
+ "."
282+
+ asTypeElement.getSimpleName()
283+
+ "Renderer");
284+
}
285+
this.hasJstache = true;
286+
}
287+
}
288+
}
289+
269290
private boolean anyMethodHasContentCache() {
270291
for (final MethodReader method : methods) {
271292
if (method.hasContentCache()) {
@@ -383,6 +404,10 @@ public boolean hasInstrument() {
383404
return hasInstrument;
384405
}
385406

407+
public boolean hasJstache() {
408+
return hasJstache;
409+
}
410+
386411
public static String sanitizeImports(String type) {
387412
final int pos = type.indexOf("@");
388413
if (pos == -1) {
Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
package io.avaje.http.generator.core;
2+
3+
import java.util.HashMap;
4+
import java.util.Map;
5+
import java.util.Optional;
6+
7+
import javax.annotation.processing.Generated;
8+
import javax.lang.model.element.AnnotationMirror;
9+
import javax.lang.model.element.AnnotationValue;
10+
import javax.lang.model.element.Element;
11+
import javax.lang.model.element.ExecutableElement;
12+
import javax.lang.model.element.TypeElement;
13+
import javax.lang.model.element.VariableElement;
14+
import javax.lang.model.util.ElementFilter;
15+
16+
/** A Prism representing a {@link io.jstach.jstache.JStacheConfig @JStacheConfig} annotation. */
17+
@Generated("avaje-prism-generator")
18+
final class JStacheConfigPrism {
19+
20+
/** store prism value of type */
21+
private final String _type;
22+
23+
public static final String PRISM_TYPE = "io.jstach.jstache.JStacheConfig";
24+
25+
/**
26+
* An instance of the Values inner class whose methods return the AnnotationValues used to build
27+
* this prism. Primarily intended to support using Messager.
28+
*/
29+
final Values values;
30+
31+
/**
32+
* Returns true if the mirror is an instance of {@link
33+
* io.jstach.jstache.JStacheConfig @JStacheConfig} is present on the element, else false.
34+
*
35+
* @param mirror mirror.
36+
* @return true if prism is present.
37+
*/
38+
static boolean isInstance(AnnotationMirror mirror) {
39+
return getInstance(mirror) != null;
40+
}
41+
42+
/**
43+
* Returns true if {@link io.jstach.jstache.JStacheConfig @JStacheConfig} is present on the
44+
* element, else false.
45+
*
46+
* @param element element.
47+
* @return true if annotation is present on the element.
48+
*/
49+
static boolean isPresent(Element element) {
50+
return getInstanceOn(element) != null;
51+
}
52+
53+
/**
54+
* Return a prism representing the {@link io.jstach.jstache.JStacheConfig @JStacheConfig}
55+
* annotation present on the given element. similar to {@code
56+
* element.getAnnotation(JStacheConfig.class)} except that an instance of this class rather than
57+
* an instance of {@link io.jstach.jstache.JStacheConfig @JStacheConfig} is returned.
58+
*
59+
* @param element element.
60+
* @return prism on element or null if no annotation is found.
61+
*/
62+
static JStacheConfigPrism getInstanceOn(Element element) {
63+
final var mirror = getMirror(element);
64+
if (mirror == null) return null;
65+
return getInstance(mirror);
66+
}
67+
68+
/**
69+
* Return a Optional representing a nullable {@link
70+
* io.jstach.jstache.JStacheConfig @JStacheConfig} annotation on the given element. similar to
71+
* {@code element.getAnnotation(io.jstach.jstache.JStacheConfig.class)} except that an Optional of
72+
* this class rather than an instance of {@link io.jstach.jstache.JStacheConfig} is returned.
73+
*
74+
* @param element element.
75+
* @return prism optional for element.
76+
*/
77+
static Optional<JStacheConfigPrism> getOptionalOn(Element element) {
78+
final var mirror = getMirror(element);
79+
if (mirror == null) return Optional.empty();
80+
return getOptional(mirror);
81+
}
82+
83+
/**
84+
* Return a prism of the {@link io.jstach.jstache.JStacheConfig @JStacheConfig} annotation from an
85+
* annotation mirror.
86+
*
87+
* @param mirror mirror.
88+
* @return prism for mirror or null if mirror is an incorrect type.
89+
*/
90+
static JStacheConfigPrism getInstance(AnnotationMirror mirror) {
91+
if (mirror == null || !PRISM_TYPE.equals(mirror.getAnnotationType().toString())) return null;
92+
93+
return new JStacheConfigPrism(mirror);
94+
}
95+
96+
/**
97+
* Return an Optional representing a nullable {@link JStacheConfigPrism @JStacheConfigPrism} from
98+
* an annotation mirror. similar to {@code e.getAnnotation(io.jstach.jstache.JStacheConfig.class)}
99+
* except that an Optional of this class rather than an instance of {@link
100+
* io.jstach.jstache.JStacheConfig @JStacheConfig} is returned.
101+
*
102+
* @param mirror mirror.
103+
* @return prism optional for mirror.
104+
*/
105+
static Optional<JStacheConfigPrism> getOptional(AnnotationMirror mirror) {
106+
if (mirror == null || !PRISM_TYPE.equals(mirror.getAnnotationType().toString()))
107+
return Optional.empty();
108+
109+
return Optional.of(new JStacheConfigPrism(mirror));
110+
}
111+
112+
private JStacheConfigPrism(AnnotationMirror mirror) {
113+
for (final ExecutableElement key : mirror.getElementValues().keySet()) {
114+
memberValues.put(key.getSimpleName().toString(), mirror.getElementValues().get(key));
115+
}
116+
for (final ExecutableElement member :
117+
ElementFilter.methodsIn(mirror.getAnnotationType().asElement().getEnclosedElements())) {
118+
defaults.put(member.getSimpleName().toString(), member.getDefaultValue());
119+
}
120+
VariableElement typeMirror = getValue("type", VariableElement.class);
121+
valid = valid && typeMirror != null;
122+
_type = typeMirror == null ? null : typeMirror.getSimpleName().toString();
123+
this.values = new Values(memberValues);
124+
this.mirror = mirror;
125+
this.isValid = valid;
126+
}
127+
128+
/**
129+
* Returns a String representing the value of the {@code io.jstach.jstache.JStacheType type()}
130+
* member of the Annotation.
131+
*
132+
* @see io.jstach.jstache.JStacheConfig#type()
133+
*/
134+
public String type() {
135+
return _type;
136+
}
137+
138+
/**
139+
* Determine whether the underlying AnnotationMirror has no errors. True if the underlying
140+
* AnnotationMirror has no errors. When true is returned, none of the methods will return null.
141+
* When false is returned, a least one member will either return null, or another prism that is
142+
* not valid.
143+
*/
144+
final boolean isValid;
145+
146+
/**
147+
* The underlying AnnotationMirror of the annotation represented by this Prism. Primarily intended
148+
* to support using Messager.
149+
*/
150+
final AnnotationMirror mirror;
151+
152+
/**
153+
* A class whose members correspond to those of {@link
154+
* io.jstach.jstache.JStacheConfig @JStacheConfig} but which each return the AnnotationValue
155+
* corresponding to that member in the model of the annotations. Returns null for defaulted
156+
* members. Used for Messager, so default values are not useful.
157+
*/
158+
static final class Values {
159+
private final Map<String, AnnotationValue> values;
160+
161+
private Values(Map<String, AnnotationValue> values) {
162+
this.values = values;
163+
}
164+
165+
AnnotationValue type() {
166+
return values.get("type");
167+
}
168+
}
169+
170+
private final Map<String, AnnotationValue> defaults = new HashMap<>(10);
171+
private final Map<String, AnnotationValue> memberValues =
172+
new HashMap<>(10);
173+
private boolean valid = true;
174+
175+
private <T> T getValue(String name, Class<T> clazz) {
176+
final T result = JStacheConfigPrism.getValue(memberValues, defaults, name, clazz);
177+
if (result == null) valid = false;
178+
return result;
179+
}
180+
181+
private static AnnotationMirror getMirror(Element target) {
182+
for (final var m : target.getAnnotationMirrors()) {
183+
final CharSequence mfqn =
184+
((TypeElement) m.getAnnotationType().asElement()).getQualifiedName();
185+
if (PRISM_TYPE.contentEquals(mfqn)) return m;
186+
}
187+
return null;
188+
}
189+
190+
private static <T> T getValue(
191+
Map<String, AnnotationValue> memberValues,
192+
Map<String, AnnotationValue> defaults,
193+
String name,
194+
Class<T> clazz) {
195+
AnnotationValue av = memberValues.get(name);
196+
if (av == null) av = defaults.get(name);
197+
if (av == null) {
198+
return null;
199+
}
200+
if (clazz.isInstance(av.getValue())) return clazz.cast(av.getValue());
201+
return null;
202+
}
203+
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package io.avaje.http.generator.core;
2+
3+
import javax.annotation.processing.Generated;
4+
import javax.lang.model.element.AnnotationMirror;
5+
import javax.lang.model.element.Element;
6+
import javax.lang.model.element.TypeElement;
7+
8+
/** A Prism representing a {@link io.jstach.jstache.JStache @JStache} annotation. */
9+
@Generated("avaje-prism-generator")
10+
public final class JStachePrism {
11+
12+
public static final String PRISM_TYPE = "io.jstach.jstache.JStache";
13+
14+
/**
15+
* Returns true if the mirror is an instance of {@link io.jstach.jstache.JStache @JStache} is
16+
* present on the element, else false.
17+
*
18+
* @param mirror mirror.
19+
* @return true if prism is present.
20+
*/
21+
public static boolean isInstance(AnnotationMirror mirror) {
22+
return getInstance(mirror) != null;
23+
}
24+
25+
/**
26+
* Returns true if {@link io.jstach.jstache.JStache @JStache} is present on the element, else
27+
* false.
28+
*
29+
* @param element element.
30+
* @return true if annotation is present on the element.
31+
*/
32+
public static boolean isPresent(Element element) {
33+
return getInstanceOn(element) != null;
34+
}
35+
36+
/**
37+
* Return a prism representing the {@link io.jstach.jstache.JStache @JStache} annotation present
38+
* on the given element. similar to {@code element.getAnnotation(JStache.class)} except that an
39+
* instance of this class rather than an instance of {@link io.jstach.jstache.JStache @JStache} is
40+
* returned.
41+
*
42+
* @param element element.
43+
* @return prism on element or null if no annotation is found.
44+
*/
45+
static JStachePrism getInstanceOn(Element element) {
46+
final var mirror = getMirror(element);
47+
if (mirror == null) return null;
48+
return getInstance(mirror);
49+
}
50+
51+
/**
52+
* Return a prism of the {@link io.jstach.jstache.JStache @JStache} annotation from an annotation
53+
* mirror.
54+
*
55+
* @param mirror mirror.
56+
* @return prism for mirror or null if mirror is an incorrect type.
57+
*/
58+
static JStachePrism getInstance(AnnotationMirror mirror) {
59+
if (mirror == null || !PRISM_TYPE.equals(mirror.getAnnotationType().toString())) return null;
60+
61+
return new JStachePrism(mirror);
62+
}
63+
64+
private JStachePrism(AnnotationMirror mirror) {
65+
66+
this.mirror = mirror;
67+
this.isValid = valid;
68+
}
69+
70+
/**
71+
* Determine whether the underlying AnnotationMirror has no errors. True if the underlying
72+
* AnnotationMirror has no errors. When true is returned, none of the methods will return null.
73+
* When false is returned, a least one member will either return null, or another prism that is
74+
* not valid.
75+
*/
76+
final boolean isValid;
77+
78+
/**
79+
* The underlying AnnotationMirror of the annotation represented by this Prism. Primarily intended
80+
* to support using Messager.
81+
*/
82+
final AnnotationMirror mirror;
83+
84+
private final boolean valid = true;
85+
86+
private static AnnotationMirror getMirror(Element target) {
87+
for (final var m : target.getAnnotationMirrors()) {
88+
final CharSequence mfqn =
89+
((TypeElement) m.getAnnotationType().asElement()).getQualifiedName();
90+
if (PRISM_TYPE.contentEquals(mfqn)) return m;
91+
}
92+
return null;
93+
}
94+
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,9 @@ public static Map<String, UType> jsonTypes(ControllerReader reader) {
2929
if (!methodReader.isErrorMethod()) {
3030
addJsonBodyType(methodReader, addToMap);
3131
}
32-
if (!methodReader.isVoid()) {
32+
final var asTypeElement = APContext.asTypeElement(methodReader.returnType());
33+
if (!methodReader.isVoid() && (asTypeElement == null || !JStachePrism.isPresent(asTypeElement))) {
3334
var uType = UType.parse(methodReader.returnType());
34-
3535
if ("java.util.concurrent.CompletableFuture".equals(uType.mainType())) {
3636
uType = uType.paramRaw();
3737
}

0 commit comments

Comments
 (0)