Last active
October 16, 2015 07:14
-
-
Save hamnis/5552692d4044c802ab80 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package iso; | |
import java.util.function.Function; | |
public interface Iso<A, B> { | |
Function<A, B> get(); | |
Function<B, A> reverseGet(); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 { | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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