Close

Java Annotation Processor - Creating Processor, RoundEnvironment and ProcessingEnvironment Examples

Java Annotation Processor Java 

import com.logicbig.example.annotation.AutoImplement;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
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.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.ElementFilter;
import javax.tools.Diagnostic;
import javax.tools.JavaFileObject;
import java.io.IOException;
import java.io.Writer;
import java.util.*;

@SupportedAnnotationTypes(
{"com.logicbig.example.annotation.AutoImplement"})
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class AutoGenerateProcessor extends AbstractProcessor {

@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
if (annotations.size() == 0) {
return false;
}

Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(AutoImplement.class);


List<String> uniqueIdCheckList = new ArrayList<>();

for (Element element : elements) {
AutoImplement autoImplement = element.getAnnotation(AutoImplement.class);

if (element.getKind() != ElementKind.INTERFACE) {
error("The annotation @AutoImplement can only be applied on interfaces: ",
element);

} else {
boolean error = false;

if (uniqueIdCheckList.contains(autoImplement.as())) {
error("AutoImplement#as should be uniquely defined", element);
error = true;
}

error = !checkIdValidity(autoImplement.as(), element);

if (!error) {
uniqueIdCheckList.add(autoImplement.as());
try {
generateClass(autoImplement, element);
} catch (Exception e) {
error(e.getMessage(), null);
}
}
}
}
return false;
}

private void generateClass(AutoImplement autoImplement, Element element)
throws Exception {

String pkg = getPackageName(element);

//delegate some processing to our FieldInfo class
FieldInfo fieldInfo = FieldInfo.get(element);

//the target interface name
String interfaceName = getTypeName(element);

//using our JClass to delegate most of the string appending there
JClass implClass = new JClass();
implClass.definePackage(pkg);
implClass.defineClass("public class ", autoImplement.as(), "implements " + interfaceName);

//nested builder class
JClass builder = null;
String builderClassName = null;

if (autoImplement.builder()) {
builder = new JClass();
builder.defineClass("public static class",
builderClassName = autoImplement.as() + "Builder", null);
}

//adding class fields
implClass.addFields(fieldInfo.getFields());
if (builder != null) {
builder.addFields(fieldInfo.getFields());
}

//adding constructor with mandatory fields
implClass.addConstructor(builder == null ? "public" : "private",
fieldInfo.getMandatoryFields());
if (builder != null) {
builder.addConstructor("private", fieldInfo.getMandatoryFields());
}

//generate methods
for (Map.Entry<String, String> entry : fieldInfo.getFields().entrySet()) {
String name = entry.getKey();
String type = entry.getValue();
boolean mandatory = fieldInfo.getMandatoryFields().contains(name);

implClass.createGetterForField(name);

//if no builder generation specified then crete setters for non mandatory fields
if (builder == null && !mandatory) {
implClass.createSetterForField(name);
}

if (builder != null && !mandatory) {
builder.addMethod(new JMethod()
.defineSignature("public", false, builderClassName)
.name(name)
.addParam(type, name)
.defineBody(" this." + name + " = " + name + ";"
+ JClass.LINE_BREAK
+ " return this;"
)
);
}
}

if (builder != null) {

//generate create() method of the Builder class
JMethod createMethod = new JMethod()
.defineSignature("public", true, builderClassName)
.name("create");


String paramString = "(";
int i = 0;
for (String s : fieldInfo.getMandatoryFields()) {
createMethod.addParam(fieldInfo.getFields().get(s), s);
paramString += (i != 0 ? ", " : "") + s;
i++;
}
paramString += ");";

createMethod.defineBody("return new " + builderClassName
+ paramString);

builder.addMethod(createMethod);

//generate build() method of the builder class.
JMethod buildMethod = new JMethod()
.defineSignature("public", false, autoImplement.as())
.name("build");
StringBuilder buildBody = new StringBuilder();
buildBody.append(autoImplement.as())
.append(" a = new ")
.append(autoImplement.as())
.append(paramString)
.append(JClass.LINE_BREAK);
for (String s : fieldInfo.getFields().keySet()) {
if (fieldInfo.getMandatoryFields().contains(s)) {
continue;
}
buildBody.append("a.")
.append(s)
.append(" = ")
.append(s)
.append(";")
.append(JClass.LINE_BREAK);
}
buildBody.append("return a;")
.append(JClass.LINE_BREAK);
buildMethod.defineBody(buildBody.toString());

builder.addMethod(buildMethod);
implClass.addNestedClass(builder);

}
//finally generate class via Filer
generateClass(pkg + "." + autoImplement.as(), implClass.end());
}

private String getPackageName(Element element) {
List<PackageElement> packageElements =
ElementFilter.packagesIn(Arrays.asList(element.getEnclosingElement()));

Optional<PackageElement> packageElement = packageElements.stream().findAny();
return packageElement.isPresent() ?
packageElement.get().getQualifiedName().toString() : null;

}

private void generateClass(String qfn, String end) throws IOException {
JavaFileObject sourceFile = processingEnv.getFiler().createSourceFile(qfn);
Writer writer = sourceFile.openWriter();
writer.write(end);
writer.close();
}

/**
* Checking if the class to be generated is a valid java identifier
* Also the name should be not same as the target interface
*/
private boolean checkIdValidity(String name, Element e) {
boolean valid = true;
for (int i = 0; i < name.length(); i++) {
if (i == 0 ? !Character.isJavaIdentifierStart(name.charAt(i)) :
!Character.isJavaIdentifierPart(name.charAt(i))) {
error("AutoImplement#as should be valid java " +
"identifier for code generation: " + name, e);
valid = false;
}
}
if (name.equals(getTypeName(e))) {
error("AutoImplement#as should be different than the Interface name ", e);
}
return valid;
}

/**
* Get the simple name of the TypeMirror
*/
private static String getTypeName(Element e) {
TypeMirror typeMirror = e.asType();
String[] split = typeMirror.toString().split("\\.");
return split.length > 0 ? split[split.length - 1] : null;
}

private void error(String msg, Element e) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, msg, e);
}
}
Original Post




import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Messager;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
import javax.tools.Diagnostic;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

public class DateFormatProcessor extends AbstractProcessor {

@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) {
Messager messager = processingEnv.getMessager();
for (TypeElement typeElement : annotations) {
for (Element element : env.getElementsAnnotatedWith(typeElement)) {
DateFormat annotation = element.getAnnotation(DateFormat.class);
TypeMirror typeMirror = element.asType();

//checking the filed type
//we don't have field class type information as code is not yet built
//so we have to compare the strings
if (!typeMirror.toString().equals(LocalDate.class.getName())) {
messager.printMessage(Diagnostic.Kind.ERROR,
"The type annotated with @DateFormat must be LocalDate");
}

try {
//validating date format
DateTimeFormatter simpleDateFormat =
DateTimeFormatter.ofPattern(annotation.value());
LocalDate.now().format(simpleDateFormat);

} catch (Exception e) {
messager.printMessage(Diagnostic.Kind.ERROR,
"Not a valid date format " + annotation.value());
}
}
}
return false;
}

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

@Override
public Set<String> getSupportedAnnotationTypes() {
return new HashSet<>(Arrays.asList(DateFormat.class.getName()));
}
}
Original Post




import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.ElementFilter;
import javax.tools.Diagnostic;
import java.util.List;
import java.util.Set;

@SupportedAnnotationTypes("*")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class EqualMethodProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations,
RoundEnvironment roundEnv) {

ElementFilter.typesIn(roundEnv.getRootElements())
.stream()
.filter(this::implementsDataInterface)
.forEach(typeElement -> {

List<ExecutableElement> methods =
ElementFilter.methodsIn(typeElement.getEnclosedElements());

if (!methods.stream().anyMatch(m -> m.getSimpleName().contentEquals("equals"))) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
"'equals' method must be overridden for the class implementing " +
"com.logicbig.example.Data. \n Error class: " + typeElement);
}
});
return false;
}

private boolean implementsDataInterface(TypeElement typeElement) {
List<? extends TypeMirror> interfaces = typeElement.getInterfaces();
return interfaces.stream().anyMatch(theMirror ->
theMirror.toString().equals(Data.class.getName()));
}
}
Original Post




See Also