Skip to content

Instantly share code, notes, and snippets.

@hamnis
Last active October 16, 2015 07:14
Show Gist options
  • Save hamnis/5552692d4044c802ab80 to your computer and use it in GitHub Desktop.
Save hamnis/5552692d4044c802ab80 to your computer and use it in GitHub Desktop.
package iso;
import java.util.function.Function;
public interface Iso<A, B> {
Function<A, B> get();
Function<B, A> reverseGet();
}
package iso;
import com.squareup.javapoet.*;
import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.*;
import javax.lang.model.type.TypeMirror;
import javax.tools.Diagnostic;
import java.io.IOException;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
@SupportedAnnotationTypes("iso.IsoTarget")
public class IsoProcessor extends AbstractProcessor {
private Filer filer;
private Messager messager;
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
filer = processingEnv.getFiler();
messager = processingEnv.getMessager();
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
Stream<TypeElement> classes = roundEnv.getElementsAnnotatedWith(IsoTarget.class).
stream().filter(e -> e.getKind() == ElementKind.CLASS).map(e -> (TypeElement) e);
classes.forEach(e -> {
try {
validate(e);
} catch (ProcessorException e1) {
messager.printMessage(Diagnostic.Kind.ERROR, e1.getMessage(), e);
return;
}
Stream<ExecutableElement> constructors = e.getEnclosedElements().stream().
filter(e2 -> e2.getKind() == ElementKind.CONSTRUCTOR).map(ctor -> (ExecutableElement) ctor);
List<? extends Element> methodsOrFields = methodsOrFields(e);
Optional<ExecutableElement> maybeCtor = constructors.
filter(c -> c.getParameters().size() == methodsOrFields.size()).findFirst();
if (!maybeCtor.isPresent()) {
messager.printMessage(Diagnostic.Kind.ERROR, "No usable constructor matching arity of fields found", e);
}
else {
ExecutableElement ctor = maybeCtor.get();
List<? extends VariableElement> parameters = ctor.getParameters();
if (!validateCtor(methodsOrFields, ctor, parameters)) {
return;
}
TypeSpec.Builder enumBuilder = TypeSpec.enumBuilder(e.getSimpleName() + "Iso");
TupleHandler tupleHandler = new TupleHandler(parameters).invoke();
ParameterizedTypeName tuple = tupleHandler.getTuple();
String tupleValues = tupleHandler.getTupleValues();
ClassName targetName = ClassName.get(e);
ParameterizedTypeName getF = ParameterizedTypeName.get(ClassName.get(Function.class), targetName, tuple);
ParameterizedTypeName reverseF = ParameterizedTypeName.get(ClassName.get(Function.class), tuple, targetName);
String targetValues = methodsOrFields.stream().map(e2 -> String.format("p.%s", e2.getSimpleName())).
collect(Collectors.joining(", "));
TypeSpec type = enumBuilder.
addSuperinterface(ParameterizedTypeName.get(ClassName.get(Iso.class), targetName, tuple)).
addModifiers(Modifier.PUBLIC).
addEnumConstant("INSTANCE").
addMethod(MethodSpec.methodBuilder("get").
addModifiers(Modifier.PUBLIC).
returns(getF).
addStatement("return p -> new $T<>($L)", tuple.rawType, targetValues).
build()).
addMethod(MethodSpec.methodBuilder("reverseGet").
addModifiers(Modifier.PUBLIC).
returns(reverseF).
addStatement("return t -> new $T($L)", targetName, tupleValues).
build()).
build();
JavaFile.Builder fb = JavaFile.builder(((PackageElement) e.getEnclosingElement()).getQualifiedName().toString(), type);
try {
fb.build().writeTo(filer);
} catch (IOException e1) {
messager.printMessage(
Diagnostic.Kind.ERROR,
String.format("Failed to generate IsoFile\n message '%s'", e1.getMessage()),
e
);
}
}
});
return true;
}
private boolean validateCtor(List<? extends Element> methodsOrFields, ExecutableElement ctor, List<? extends VariableElement> parameters) {
Iterator<? extends Element> fIterator = methodsOrFields.iterator();
Iterator<? extends VariableElement> pIterator = parameters.iterator();
while(fIterator.hasNext() && pIterator.hasNext()) {
VariableElement param = pIterator.next();
Element field = fIterator.next();
TypeName fType = getType(field);
TypeName pType = getType(param);
if (!pType.equals(fType)) {
messager.printMessage(Diagnostic.Kind.ERROR, "constructor did not match types for fields in order", ctor);
return false;
}
}
return true;
}
private TypeName getType(Element field) {
TypeMirror typeMirror = field.asType();
return ClassName.get(typeMirror);
}
private List<? extends Element> methodsOrFields(Element e) {
List<? extends Element> stream = e.getEnclosedElements().
stream().
filter(this::isValidField).
collect(Collectors.toList());
if ( stream.isEmpty() ) {
messager.printMessage(Diagnostic.Kind.WARNING, "Did not find any public final fields.", e);
}
List<? extends Element> stream1 = e.getEnclosedElements().
stream().
filter(this::isValidGetter).
collect(Collectors.toList());
if (stream.isEmpty() && stream1.isEmpty()) {
messager.printMessage(Diagnostic.Kind.WARNING, "Did not find any getters or public final fields.", e);
}
return stream.isEmpty() ? stream1 : stream;
}
private boolean isValidGetter(Element method) {
return method.getKind() == ElementKind.METHOD &&
method.getModifiers().contains(Modifier.PUBLIC) &&
method.getSimpleName().toString().startsWith("get");
}
private boolean isValidField(Element field) {
return field.getKind() == ElementKind.FIELD &&
field.getModifiers().containsAll(Arrays.asList(Modifier.PUBLIC, Modifier.FINAL));
}
private void validate(TypeElement e) throws ProcessorException {
if (!e.getModifiers().contains(Modifier.PUBLIC)) {
throw new ProcessorException(e, "The class %s is not public.", e.getQualifiedName().toString());
}
if (e.getModifiers().contains(Modifier.ABSTRACT)) {
throw new ProcessorException(e, "The class %s is abstract, Cannot instantate.", e.getQualifiedName().toString());
}
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
private class TupleHandler {
private List<? extends VariableElement> parameters;
private ParameterizedTypeName tuple;
private String tupleValues;
public TupleHandler(List<? extends VariableElement> parameters) {
this.parameters = parameters;
}
public ParameterizedTypeName getTuple() {
return tuple;
}
public String getTupleValues() {
return tupleValues;
}
public TupleHandler invoke() {
List<TypeName> list = parameters.stream().map(e1 -> getType(e1).box()).collect(Collectors.toList());
int arity = parameters.size();
tuple = ParameterizedTypeName.get(ClassName.get("javaslang", String.format("Tuple%s", arity)), list.toArray(new TypeName[list.size()]));
tupleValues = IntStream.range(1, arity + 1).mapToObj(i -> String.format("t._%s", i)).collect(Collectors.joining(", "));
return this;
}
}
}
class ProcessorException extends Exception {
Element element;
public ProcessorException(Element element, String msg, Object... args) {
super(String.format(msg, args));
this.element = element;
}
public Element getElement() {
return element;
}
}
package iso;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
public @interface IsoTarget {
}
package example;
import iso.IsoTarget;
@IsoTarget
public class Person {
public final String name;
public final int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment