Skip to content

Commit

Permalink
feat: use JavaDoc as comments (#2481)
Browse files Browse the repository at this point in the history
This is a very basic PoC implementation that can serve as a base for
further work.
  • Loading branch information
stuartwdouglas authored Aug 23, 2024
1 parent 637f20a commit cafa316
Show file tree
Hide file tree
Showing 5 changed files with 154 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,11 @@ public class ModuleBuilder {
private final Map<DotName, TopicsBuildItem.DiscoveredTopic> knownTopics;
private final Map<DotName, VerbClientBuildItem.DiscoveredClients> verbClients;
private final FTLRecorder recorder;
private final Map<String, String> verbDocs;

public ModuleBuilder(IndexView index, String moduleName, Map<DotName, TopicsBuildItem.DiscoveredTopic> knownTopics,
Map<DotName, VerbClientBuildItem.DiscoveredClients> verbClients, FTLRecorder recorder) {
Map<DotName, VerbClientBuildItem.DiscoveredClients> verbClients, FTLRecorder recorder,
Map<String, String> verbDocs) {
this.index = index;
this.moduleName = moduleName;
this.moduleBuilder = Module.newBuilder()
Expand All @@ -93,6 +95,7 @@ public ModuleBuilder(IndexView index, String moduleName, Map<DotName, TopicsBuil
this.knownTopics = knownTopics;
this.verbClients = verbClients;
this.recorder = recorder;
this.verbDocs = verbDocs;
}

public static @NotNull String methodToName(MethodInfo method) {
Expand Down Expand Up @@ -236,6 +239,9 @@ public void registerVerbMethod(MethodInfo method, String className,
.setExport(exported)
.setRequest(buildType(bodyParamType, exported))
.setResponse(buildType(method.returnType(), exported));
if (verbDocs.containsKey(verbName)) {
verbBuilder.addComments(verbDocs.get(verbName));
}

if (metadataCallback != null) {
metadataCallback.accept(verbBuilder);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.PosixFilePermission;
import java.util.Base64;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

Expand Down Expand Up @@ -107,9 +110,24 @@ public void generateSchema(CombinedIndexBuildItem index,
VerbClientBuildItem verbClientBuildItem,
List<SchemaContributorBuildItem> schemaContributorBuildItems) throws Exception {
String moduleName = moduleNameBuildItem.getModuleName();
Map<String, String> verbDocs = new HashMap<>();
try (var input = Thread.currentThread().getContextClassLoader().getResourceAsStream("META-INF/ftl-verbs.txt")) {
if (input != null) {
var contents = new String(input.readAllBytes(), StandardCharsets.UTF_8).split("\n");
for (var content : contents) {
var eq = content.indexOf('=');
if (eq == -1) {
continue;
}
String key = content.substring(0, eq);
String value = new String(Base64.getDecoder().decode(content.substring(eq + 1)), StandardCharsets.UTF_8);
verbDocs.put(key, value);
}
}
}

ModuleBuilder moduleBuilder = new ModuleBuilder(index.getComputingIndex(), moduleName, topicsBuildItem.getTopics(),
verbClientBuildItem.getVerbClients(), recorder);
verbClientBuildItem.getVerbClients(), recorder, verbDocs);

for (var i : schemaContributorBuildItems) {
i.getSchemaContributor().accept(moduleBuilder);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,13 @@ public String bytesHttp(byte[] data) {
return "Hello " + new String(data, StandardCharsets.UTF_8);
}

/**
* This is a verb FOOO
*
* @param name
* @param echoClient
* @return
*/
@Verb
public String hello(String name, EchoClient echoClient) {
return "Hello " + echoClient.call(new EchoRequest().setName(name)).getMessage();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package xyz.block.ftl.runtime.processor;

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import javax.annotation.processing.Completion;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic;
import javax.tools.FileObject;
import javax.tools.StandardLocation;

import xyz.block.ftl.Verb;

/**
* POC annotation processor for capturing JavaDoc, this needs a lot more work.
*/
public class AnnotationProcessor implements Processor {
private static final Pattern REMOVE_LEADING_SPACE = Pattern.compile("^ ", Pattern.MULTILINE);
private ProcessingEnvironment processingEnv;

final Map<String, String> saved = new HashMap<>();

@Override
public Set<String> getSupportedOptions() {
return Set.of();
}

@Override
public Set<String> getSupportedAnnotationTypes() {
return Set.of(Verb.class.getName());
}

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

@Override
public void init(ProcessingEnvironment processingEnv) {
this.processingEnv = processingEnv;
}

@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
//TODO: @VerbName, HTTP, CRON etc
roundEnv.getElementsAnnotatedWith(Verb.class)
.forEach(element -> {
Optional<String> javadoc = getJavadoc(element);
javadoc.ifPresent(doc -> saved.put(element.getSimpleName().toString(), doc));
});

if (roundEnv.processingOver()) {
write("META-INF/ftl-verbs.txt", saved.entrySet().stream().map(
e -> e.getKey() + "=" + Base64.getEncoder().encodeToString(e.getValue().getBytes(StandardCharsets.UTF_8)))
.collect(Collectors.toSet()));
}
return false;
}

/**
* This method uses the annotation processor Filer API and we shouldn't use a Path as paths containing \ are not supported.
*/
public void write(String filePath, Set<String> set) {
if (set.isEmpty()) {
return;
}
try {
final FileObject listResource = processingEnv.getFiler().createResource(StandardLocation.CLASS_OUTPUT, "",
filePath.toString());

try (BufferedWriter writer = new BufferedWriter(
new OutputStreamWriter(listResource.openOutputStream(), StandardCharsets.UTF_8))) {
for (String className : set) {
writer.write(className);
writer.newLine();
}
}
} catch (IOException e) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Failed to write " + filePath + ": " + e);
return;
}
}

@Override
public Iterable<? extends Completion> getCompletions(Element element, AnnotationMirror annotation, ExecutableElement member,
String userText) {
return null;
}

public Optional<String> getJavadoc(Element e) {
String docComment = processingEnv.getElementUtils().getDocComment(e);

if (docComment == null || docComment.isBlank()) {
return Optional.empty();
}

// javax.lang.model keeps the leading space after the "*" so we need to remove it.

return Optional.of(REMOVE_LEADING_SPACE.matcher(docComment)
.replaceAll("")
.trim());
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
xyz.block.ftl.runtime.processor.AnnotationProcessor

0 comments on commit cafa316

Please sign in to comment.