Skip to content

Commit

Permalink
Add Native JStachio support (#567)
Browse files Browse the repository at this point in the history
* Add JStachio support for jex

* add it to javalin

* helidon

* Update ControllerMethodWriter.java
  • Loading branch information
SentryMan authored Feb 20, 2025
1 parent b2332ba commit 6840073
Show file tree
Hide file tree
Showing 14 changed files with 680 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,9 @@ protected void writeImports() {
writer.append("import static %s;", type).eol();
}
writer.eol();
for (String type : reader.importTypes()) {
var importTypes = reader.importTypes();
importTypes.removeIf(i -> i.substring(0, i.lastIndexOf(".")).equals(packageName));
for (String type : importTypes) {
writer.append("import %s;", type).eol();
}
writer.eol();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ public final class ControllerReader {
private boolean requestScope;
private boolean docHidden;
private final boolean hasInstrument;
private boolean hasJstache;

public ControllerReader(TypeElement beanType) {
this(beanType, "");
Expand Down Expand Up @@ -249,6 +250,7 @@ public void read(boolean withSingleton) {
}
}
deriveIncludeValidation();
jstacheImport();
addImports(withSingleton);
}

Expand All @@ -266,6 +268,25 @@ private boolean anyMethodHasValid() {
return false;
}

private void jstacheImport() {
for (final MethodReader method : methods) {
final var asTypeElement = APContext.asTypeElement(method.returnType());
if (ProcessingContext.isJstacheTemplate(method.returnType())) {
if ("JStachio.render".equals(ProcessingContext.jstacheRenderer(method.returnType()))) {
addImportType("io.jstach.jstachio.JStachio");
} else {
// jstachio generated classes don't have the parent type in the name
addImportType(
APContext.elements().getPackageOf(asTypeElement).getQualifiedName().toString()
+ "."
+ asTypeElement.getSimpleName()
+ "Renderer");
}
this.hasJstache = true;
}
}
}

private boolean anyMethodHasContentCache() {
for (final MethodReader method : methods) {
if (method.hasContentCache()) {
Expand Down Expand Up @@ -383,6 +404,10 @@ public boolean hasInstrument() {
return hasInstrument;
}

public boolean hasJstache() {
return hasJstache;
}

public static String sanitizeImports(String type) {
final int pos = type.indexOf("@");
if (pos == -1) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
package io.avaje.http.generator.core;

import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

import javax.annotation.processing.Generated;
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.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.util.ElementFilter;

/** A Prism representing a {@link io.jstach.jstache.JStacheConfig @JStacheConfig} annotation. */
@Generated("avaje-prism-generator")
final class JStacheConfigPrism {

/** store prism value of type */
private final String _type;

public static final String PRISM_TYPE = "io.jstach.jstache.JStacheConfig";

/**
* An instance of the Values inner class whose methods return the AnnotationValues used to build
* this prism. Primarily intended to support using Messager.
*/
final Values values;

/**
* Returns true if the mirror is an instance of {@link
* io.jstach.jstache.JStacheConfig @JStacheConfig} is present on the element, else false.
*
* @param mirror mirror.
* @return true if prism is present.
*/
static boolean isInstance(AnnotationMirror mirror) {
return getInstance(mirror) != null;
}

/**
* Returns true if {@link io.jstach.jstache.JStacheConfig @JStacheConfig} is present on the
* element, else false.
*
* @param element element.
* @return true if annotation is present on the element.
*/
static boolean isPresent(Element element) {
return getInstanceOn(element) != null;
}

/**
* Return a prism representing the {@link io.jstach.jstache.JStacheConfig @JStacheConfig}
* annotation present on the given element. similar to {@code
* element.getAnnotation(JStacheConfig.class)} except that an instance of this class rather than
* an instance of {@link io.jstach.jstache.JStacheConfig @JStacheConfig} is returned.
*
* @param element element.
* @return prism on element or null if no annotation is found.
*/
static JStacheConfigPrism getInstanceOn(Element element) {
final var mirror = getMirror(element);
if (mirror == null) return null;
return getInstance(mirror);
}

/**
* Return a Optional representing a nullable {@link
* io.jstach.jstache.JStacheConfig @JStacheConfig} annotation on the given element. similar to
* {@code element.getAnnotation(io.jstach.jstache.JStacheConfig.class)} except that an Optional of
* this class rather than an instance of {@link io.jstach.jstache.JStacheConfig} is returned.
*
* @param element element.
* @return prism optional for element.
*/
static Optional<JStacheConfigPrism> getOptionalOn(Element element) {
final var mirror = getMirror(element);
if (mirror == null) return Optional.empty();
return getOptional(mirror);
}

/**
* Return a prism of the {@link io.jstach.jstache.JStacheConfig @JStacheConfig} annotation from an
* annotation mirror.
*
* @param mirror mirror.
* @return prism for mirror or null if mirror is an incorrect type.
*/
static JStacheConfigPrism getInstance(AnnotationMirror mirror) {
if (mirror == null || !PRISM_TYPE.equals(mirror.getAnnotationType().toString())) return null;

return new JStacheConfigPrism(mirror);
}

/**
* Return an Optional representing a nullable {@link JStacheConfigPrism @JStacheConfigPrism} from
* an annotation mirror. similar to {@code e.getAnnotation(io.jstach.jstache.JStacheConfig.class)}
* except that an Optional of this class rather than an instance of {@link
* io.jstach.jstache.JStacheConfig @JStacheConfig} is returned.
*
* @param mirror mirror.
* @return prism optional for mirror.
*/
static Optional<JStacheConfigPrism> getOptional(AnnotationMirror mirror) {
if (mirror == null || !PRISM_TYPE.equals(mirror.getAnnotationType().toString()))
return Optional.empty();

return Optional.of(new JStacheConfigPrism(mirror));
}

private JStacheConfigPrism(AnnotationMirror mirror) {
for (final ExecutableElement key : mirror.getElementValues().keySet()) {
memberValues.put(key.getSimpleName().toString(), mirror.getElementValues().get(key));
}
for (final ExecutableElement member :
ElementFilter.methodsIn(mirror.getAnnotationType().asElement().getEnclosedElements())) {
defaults.put(member.getSimpleName().toString(), member.getDefaultValue());
}
VariableElement typeMirror = getValue("type", VariableElement.class);
valid = valid && typeMirror != null;
_type = typeMirror == null ? null : typeMirror.getSimpleName().toString();
this.values = new Values(memberValues);
this.mirror = mirror;
this.isValid = valid;
}

/**
* Returns a String representing the value of the {@code io.jstach.jstache.JStacheType type()}
* member of the Annotation.
*
* @see io.jstach.jstache.JStacheConfig#type()
*/
public String type() {
return _type;
}

/**
* Determine whether the underlying AnnotationMirror has no errors. True if the underlying
* AnnotationMirror has no errors. When true is returned, none of the methods will return null.
* When false is returned, a least one member will either return null, or another prism that is
* not valid.
*/
final boolean isValid;

/**
* The underlying AnnotationMirror of the annotation represented by this Prism. Primarily intended
* to support using Messager.
*/
final AnnotationMirror mirror;

/**
* A class whose members correspond to those of {@link
* io.jstach.jstache.JStacheConfig @JStacheConfig} but which each return the AnnotationValue
* corresponding to that member in the model of the annotations. Returns null for defaulted
* members. Used for Messager, so default values are not useful.
*/
static final class Values {
private final Map<String, AnnotationValue> values;

private Values(Map<String, AnnotationValue> values) {
this.values = values;
}

AnnotationValue type() {
return values.get("type");
}
}

private final Map<String, AnnotationValue> defaults = new HashMap<>(10);
private final Map<String, AnnotationValue> memberValues =
new HashMap<>(10);
private boolean valid = true;

private <T> T getValue(String name, Class<T> clazz) {
final T result = JStacheConfigPrism.getValue(memberValues, defaults, name, clazz);
if (result == null) valid = false;
return result;
}

private static AnnotationMirror getMirror(Element target) {
for (final var m : target.getAnnotationMirrors()) {
final CharSequence mfqn =
((TypeElement) m.getAnnotationType().asElement()).getQualifiedName();
if (PRISM_TYPE.contentEquals(mfqn)) return m;
}
return null;
}

private static <T> T getValue(
Map<String, AnnotationValue> memberValues,
Map<String, AnnotationValue> defaults,
String name,
Class<T> clazz) {
AnnotationValue av = memberValues.get(name);
if (av == null) av = defaults.get(name);
if (av == null) {
return null;
}
if (clazz.isInstance(av.getValue())) return clazz.cast(av.getValue());
return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package io.avaje.http.generator.core;

import javax.annotation.processing.Generated;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;

/** A Prism representing a {@link io.jstach.jstache.JStache @JStache} annotation. */
@Generated("avaje-prism-generator")
public final class JStachePrism {

public static final String PRISM_TYPE = "io.jstach.jstache.JStache";

/**
* Returns true if the mirror is an instance of {@link io.jstach.jstache.JStache @JStache} is
* present on the element, else false.
*
* @param mirror mirror.
* @return true if prism is present.
*/
public static boolean isInstance(AnnotationMirror mirror) {
return getInstance(mirror) != null;
}

/**
* Returns true if {@link io.jstach.jstache.JStache @JStache} is present on the element, else
* false.
*
* @param element element.
* @return true if annotation is present on the element.
*/
public static boolean isPresent(Element element) {
return getInstanceOn(element) != null;
}

/**
* Return a prism representing the {@link io.jstach.jstache.JStache @JStache} annotation present
* on the given element. similar to {@code element.getAnnotation(JStache.class)} except that an
* instance of this class rather than an instance of {@link io.jstach.jstache.JStache @JStache} is
* returned.
*
* @param element element.
* @return prism on element or null if no annotation is found.
*/
static JStachePrism getInstanceOn(Element element) {
final var mirror = getMirror(element);
if (mirror == null) return null;
return getInstance(mirror);
}

/**
* Return a prism of the {@link io.jstach.jstache.JStache @JStache} annotation from an annotation
* mirror.
*
* @param mirror mirror.
* @return prism for mirror or null if mirror is an incorrect type.
*/
static JStachePrism getInstance(AnnotationMirror mirror) {
if (mirror == null || !PRISM_TYPE.equals(mirror.getAnnotationType().toString())) return null;

return new JStachePrism(mirror);
}

private JStachePrism(AnnotationMirror mirror) {

this.mirror = mirror;
this.isValid = valid;
}

/**
* Determine whether the underlying AnnotationMirror has no errors. True if the underlying
* AnnotationMirror has no errors. When true is returned, none of the methods will return null.
* When false is returned, a least one member will either return null, or another prism that is
* not valid.
*/
final boolean isValid;

/**
* The underlying AnnotationMirror of the annotation represented by this Prism. Primarily intended
* to support using Messager.
*/
final AnnotationMirror mirror;

private final boolean valid = true;

private static AnnotationMirror getMirror(Element target) {
for (final var m : target.getAnnotationMirrors()) {
final CharSequence mfqn =
((TypeElement) m.getAnnotationType().asElement()).getQualifiedName();
if (PRISM_TYPE.contentEquals(mfqn)) return m;
}
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ public static Map<String, UType> jsonTypes(ControllerReader reader) {
if (!methodReader.isErrorMethod()) {
addJsonBodyType(methodReader, addToMap);
}
if (!methodReader.isVoid()) {
final var asTypeElement = APContext.asTypeElement(methodReader.returnType());
if (!methodReader.isVoid() && (asTypeElement == null || !JStachePrism.isPresent(asTypeElement))) {
var uType = UType.parse(methodReader.returnType());

if ("java.util.concurrent.CompletableFuture".equals(uType.mainType())) {
uType = uType.paramRaw();
}
Expand Down
Loading

0 comments on commit 6840073

Please sign in to comment.