Skip to content

Instantly share code, notes, and snippets.

@AhmedMourad0
Last active January 17, 2018 10:20
Show Gist options
  • Save AhmedMourad0/50ba123464d6390b47b6a11767aad1bd to your computer and use it in GitHub Desktop.
Save AhmedMourad0/50ba123464d6390b47b6a11767aad1bd to your computer and use it in GitHub Desktop.
This project is still in its early stages of construction, it's not in Alpha, it's not in Beta, it's not even in Gamma it's still underdeveloped!
package com.jpattern.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface Builder {
}
package com.jpattern.processors;
import com.google.common.base.CaseFormat;
import com.google.common.collect.ImmutableSet;
import com.jpattern.annotations.Builder;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;
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.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.util.ElementFilter;
import javax.tools.Diagnostic;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import static javax.lang.model.element.Modifier.FINAL;
import static javax.lang.model.element.Modifier.PRIVATE;
import static javax.lang.model.element.Modifier.PUBLIC;
public class BuilderProcessor extends AbstractProcessor {
private Messager messager;
private Filer filer;
public BuilderProcessor() {
super();
}
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
this.messager = processingEnv.getMessager();
this.filer = processingEnv.getFiler();
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
@Override
public Set<String> getSupportedAnnotationTypes() {
return ImmutableSet.of(Builder.class.getCanonicalName());
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
for (Element element : roundEnv.getElementsAnnotatedWith(Builder.class)) {
if (element.getKind() != ElementKind.CLASS) {
messager.printMessage(Diagnostic.Kind.ERROR, Builder.class.getSimpleName() + " annotation can only be applied to a class.");
return true;
}
// get element metadata
String packageName = getPackageName(element);
String targetName = lowerCamelCase(element.getSimpleName().toString(), element);
List<VariableElement> vars = getNonPrivateVariables(element);
String builderName = String.format("%sBuilder", element.getSimpleName());
ClassName builderType = ClassName.get(packageName, builderName);
// create private fields and public setters
List<FieldSpec> fields = new ArrayList<>(vars.size());
List<MethodSpec> setters = new ArrayList<>(vars.size());
for (VariableElement var : vars) {
TypeName typeName = TypeName.get(var.asType());
String name = var.getSimpleName().toString();
// create the field
fields.add(FieldSpec.builder(typeName, name, PRIVATE).build());
// create the setter
setters.add(MethodSpec.methodBuilder(name)
.addModifiers(PUBLIC)
.returns(builderType)
.addParameter(typeName, name)
.addStatement("this.$N = $N", name, name)
.addStatement("return this")
.build());
}
// create the build method
TypeName targetType = TypeName.get(element.asType());
MethodSpec.Builder buildMethodBuilder =
MethodSpec.methodBuilder("build")
.addModifiers(PUBLIC)
.returns(targetType)
.addStatement("$1T $2N = new $1T()", targetType, targetName);
for (FieldSpec field : fields) {
buildMethodBuilder
.addStatement("$1N.$2N = this.$2N", targetName, field);
}
buildMethodBuilder.addStatement("return $N", targetName);
MethodSpec buildMethod = buildMethodBuilder.build();
// create the builder type
TypeSpec builder = TypeSpec.classBuilder(builderType)
.addModifiers(PUBLIC, FINAL)
.addFields(fields)
.addMethods(setters)
.addMethod(buildMethod)
.build();
// write the java source file
JavaFile file = JavaFile
.builder(builderType.packageName(), builder)
.build();
try {
file.writeTo(filer);
} catch (IOException e) {
messager.printMessage(Diagnostic.Kind.ERROR,"Failed to write file for element", element);
}
}
return true;
}
private String getPackageName(Element element) {
return processingEnv.getElementUtils().getPackageOf(element).getQualifiedName().toString();
}
private String lowerCamelCase(String n, Element element) {
String name;
if (n.length() == 1)
return n.toLowerCase();
try {
name = CaseFormat.valueOf(n).to(CaseFormat.LOWER_CAMEL, n);
} catch (IllegalArgumentException e) {
name = Character.toLowerCase(n.charAt(0)) + n.substring(1);
messager.printMessage(Diagnostic.Kind.WARNING,"Unknown class name format", element);
}
return name;
}
private List<VariableElement> getNonPrivateVariables(Element element) {
return ElementFilter.fieldsIn(element.getEnclosedElements());
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment