Skip to content
This repository was archived by the owner on Aug 31, 2022. It is now read-only.

Generated implementation #17

Merged
merged 10 commits into from
Aug 22, 2016
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
package io.rouz.task.cli;
package io.rouz.flo.cli;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import io.rouz.task.Task;
import io.rouz.task.TaskContext;
import io.rouz.flo.Task;
import io.rouz.flo.TaskConstructor;
import io.rouz.flo.TaskContext;
import joptsimple.NonOptionArgumentSpec;
import joptsimple.OptionParser;
import joptsimple.OptionSet;
Expand All @@ -14,9 +15,7 @@
import static java.lang.System.out;
import static java.util.Arrays.asList;

/**
* TODO: document.
*/
@Deprecated
public final class Cli {

private final List<TaskConstructor<?>> factories;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package io.rouz.task.processor;
package io.rouz.flo.processor;

import com.google.auto.value.AutoValue;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package io.rouz.task.processor;
package io.rouz.flo.processor;

import io.rouz.task.cli.TaskConstructor;
import io.rouz.task.processor.Binding.Argument;
import com.squareup.javapoet.AnnotationSpec;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
Expand All @@ -19,6 +17,8 @@
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;

import io.rouz.flo.TaskConstructor;
import io.rouz.flo.processor.Binding.Argument;
import joptsimple.OptionParser;
import joptsimple.OptionSet;
import joptsimple.OptionSpecBuilder;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
package io.rouz.task.processor;

import io.rouz.task.Task;
package io.rouz.flo.processor;

import java.util.LinkedHashMap;
import java.util.List;
Expand All @@ -16,6 +14,8 @@
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;

import io.rouz.flo.Task;

import static java.util.Arrays.asList;
import static javax.tools.Diagnostic.Kind.NOTE;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package io.rouz.task.processor;
package io.rouz.flo.processor;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package io.rouz.task.processor;
package io.rouz.flo.processor;

import java.io.IOException;
import java.util.ArrayList;
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@
io.rouz.task.processor.TaskBindingProcessor
io.rouz.flo.processor.TaskBindingProcessor
45 changes: 45 additions & 0 deletions flo-api-generator/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>io.rouz</groupId>
<artifactId>flo</artifactId>
<version>0.0.6-SNAPSHOT</version>
</parent>

<name>Flo API Generator</name>
<artifactId>flo-api-generator</artifactId>
<description>
Internal module containing an annotation processor for generating the TaskBuilder API
</description>

<dependencies>
<dependency>
<groupId>org.trimou</groupId>
<artifactId>trimou-core</artifactId>
</dependency>

<dependency>
<groupId>com.google.testing.compile</groupId>
<artifactId>compile-testing</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.truth</groupId>
<artifactId>truth</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<!-- explicitly disable running annotation processing on this module -->
<compilerArgument>-proc:none</compilerArgument>
</configuration>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
package io.rouz.flo.gen;

import org.trimou.engine.MustacheEngine;
import org.trimou.engine.MustacheEngineBuilder;
import org.trimou.engine.locator.ClassPathTemplateLocator;
import org.trimou.engine.resolver.MapResolver;
import org.trimou.engine.resolver.ReflectionResolver;
import org.trimou.util.ImmutableMap;

import java.io.IOException;
import java.io.Writer;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.Name;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import javax.tools.JavaFileObject;

import static javax.tools.Diagnostic.Kind.ERROR;
import static javax.tools.Diagnostic.Kind.NOTE;

/**
* TODO: document.
*/
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class ApiGeneratorProcessor extends AbstractProcessor {

static final String ANNOTATION = "@" + GenerateTaskBuilder.class.getSimpleName();

private Types types;
private Elements elements;
private Filer filer;
private Messager messager;

private MustacheEngine engine;
private Element processingElement;

@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
types = processingEnv.getTypeUtils();
elements = processingEnv.getElementUtils();
filer = processingEnv.getFiler();
messager = processingEnv.getMessager();

engine = MustacheEngineBuilder
.newBuilder()
.addResolver(new MapResolver())
.addResolver(new ReflectionResolver())
.addTemplateLocator(ClassPathTemplateLocator.builder(1)
.setClassLoader(this.getClass().getClassLoader())
.setSuffix("mustache").build())
.build();

messager.printMessage(NOTE, ApiGeneratorProcessor.class.getSimpleName() + " loaded");
}

@Override
public Set<String> getSupportedAnnotationTypes() {
final Set<String> annotationTypes = new LinkedHashSet<>();
annotationTypes.add(GenerateTaskBuilder.class.getCanonicalName());
return annotationTypes;
}

@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
for (TypeElement annotation : annotations) {
for (Element element : roundEnv.getElementsAnnotatedWith(annotation)) {
if (element.getKind() != ElementKind.INTERFACE) {
messager.printMessage(ERROR, "Only interfaces can be annotated with " + ANNOTATION, element);
return true;
}
processingElement = element;

final GenerateTaskBuilder genTaskBuilder = element.getAnnotation(GenerateTaskBuilder.class);
final TypeElement templateElement = (TypeElement) element;

try {
final Name packageName = elements.getPackageOf(templateElement).getQualifiedName();
final String interfaceName = templateElement.getSimpleName().toString().replaceAll("Template$", "");

writeApiInterface(genTaskBuilder, packageName, interfaceName);
writeApiImplementation(genTaskBuilder, packageName, interfaceName);
} catch (IOException e) {
messager.printMessage(ERROR, "Failed to write source for " + ANNOTATION + " bindings: " + e);
} catch (RuntimeException e) {
e.printStackTrace();
messager.printMessage(ERROR, "Error during " + ANNOTATION + " binding generation");
}
}
}

return true;
}

private void writeApiInterface(
GenerateTaskBuilder genTaskBuilder,
Name packageName,
String interfaceName) throws IOException {

final Map<String, Object> data = new HashMap<>();
data.put("packageName", packageName);
data.put("interfaceName", interfaceName);
data.put("genFn", IntStream.rangeClosed(0, genTaskBuilder.upTo())
.mapToObj(this::fn).toArray());
data.put("genBuilder", IntStream.range(1, genTaskBuilder.upTo())
.mapToObj(this::builder).toArray());
final String output = engine.getMustache("TaskBuilder").render(data);

final String fileName = packageName + "." + interfaceName;
final JavaFileObject filerSourceFile = filer.createSourceFile(fileName, processingElement);
try (final Writer writer = filerSourceFile.openWriter()) {
writer.write(output);
}
}

private void writeApiImplementation(
GenerateTaskBuilder genTaskBuilder,
Name packageName,
String interfaceName) throws IOException {

final String implClassName = interfaceName + "Impl";
final int n = genTaskBuilder.upTo() - 1;

final Map<String, Object> data = new HashMap<>();
data.put("packageName", packageName);
data.put("interfaceName", interfaceName);
data.put("implClassName", implClassName);
data.put("genBuilder", IntStream.range(1, n)
.mapToObj(this::builderImpl).toArray());
data.put("lastArity", n);
data.put("lastArityPlus", n + 1);
data.put("lastTypeArgs", typeArgs(n));
data.put("lastTypeArg", letters(n + 1).skip(n).findFirst().get());
data.put("lastArguments", arguments(n));
final String output = engine.getMustache("TaskBuilderImpl").render(data);

final String fileName = packageName + "." + implClassName;
final JavaFileObject filerSourceFile = filer.createSourceFile(fileName, processingElement);
try (final Writer writer = filerSourceFile.openWriter()) {
writer.write(output);
}
}

private Map<String, Object> builderImpl(int n) {
return ImmutableMap.of(
"arity", n,
"arityPlus", n + 1,
"nextArg", letters(n + 1).skip(n).findFirst().get(),
"typeArgs", typeArgs(n),
"arguments", arguments(n)
);
}

private Map<String, Object> builder(int n) {
return ImmutableMap.of(
"arity", n,
"arityPlus", n + 1,
"nextArg", letters(n + 1).skip(n).findFirst().get(),
"typeArgs", typeArgs(n),
"typeArgsMinus", typeArgs(n - 1)
);
}

private Map<String, Object> fn(int n) {
return ImmutableMap.of(
"arity", n,
"typeArgs", typeArgs(n),
"jdkInterface", jdkInterface(n),
"parameters", parameters(n)
);
}

private Stream<String> letters(int n) {
return IntStream.range(0, n)
.mapToObj(i -> Character.toString((char) ('A' + i)));
}

private String typeArgs(int n) {
return letters(n)
.collect(Collectors.joining(", "))
+ (n > 0 ? ", " : "");
}

private String parameters(int n) {
return letters(n)
.map(l -> l + " " + l.toLowerCase())
.collect(Collectors.joining(", "));
}

private String arguments(int n) {
return letters(n)
.map(String::toLowerCase)
.collect(Collectors.joining(", "));
}

private String jdkInterface(int n) {
switch (n) {
case 0: return "Supplier<Z>, ";
case 1: return "Function<A, Z>, ";
case 2: return "BiFunction<A, B, Z>, ";
default: return "";
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package io.rouz.flo.gen;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Annotation for generating the task builder API. Processed by {@link ApiGeneratorProcessor}.
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface GenerateTaskBuilder {
int upTo();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
io.rouz.flo.gen.ApiGeneratorProcessor
Loading