I am writing an annotation processor that generates JSON serialization code. Here's my annotation that I use to identify the POJOs that need a serializer
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface JsonSerialize {
}
And here's the base interface of my serializer
public interface JsonSerializer<T> {
String serialize(T t);
}
Here's the annotation processor code that looks for that annotation and generates the serializer code
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
for (Element element : roundEnv.getElementsAnnotatedWith(JsonSerialize.class)) {
if (element.getKind() == ElementKind.CLASS) {
MethodSpec serializeMethod = MethodSpec
.methodBuilder("serialize")
.addModifiers(Modifier.PUBLIC)
.addParameter(ParameterSpec.builder(TypeName.get(element.asType()), "obj", Modifier.FINAL).build())
.returns(String.class)
.addStatement("return \"dummy string\"")
.build();
TypeSpec serializer = TypeSpec
.classBuilder(element.getSimpleName().toString() + "JsonSerializer")
.addSuperinterface(JsonSerializer.class) // THIS LINE IS WRONG
.addModifiers(Modifier.PUBLIC)
.addMethod(serializeMethod)
.build();
try {
JavaFile.builder(processingEnv.getElementUtils().getPackageOf(element).toString(), serializer)
.build()
.writeTo(processingEnv.getFiler());
} catch (IOException e) {
e.printStackTrace();
}
}
}
return true;
}
But I get a compile error, because my generated class is not specifying the generic parameter in it's inheritance. How can I specify that?
Instead of passing a
java.lang.Classto theaddSuperinterfacemethod, you'll need to pass something with the specific type details you have in mind. This method has two overloads - one which takesjava.lang.reflect.Type(andClassis a subtype of this), and another which one which takescom.squareup.javapoet.TypeName). Technically either works, though since you are already using JavaPoet, I'd encourage trying to create the TypeName instance.TypeNamehas a number of subclasses,ClassName,ParameterizedTypeNameare probably the main ones to focus on here. In an annotation processor, they have some big advantages over using aClassinstance - mostly that you don't need to actually be able to load or reference the class you are talking about - kind of like how you are usingelement.getSimpleName().toString()elsewhere in your code.These classes have static methods to create them, which can be based on a variety of things. The one we're interested in here is this:
In you code, you would use it roughly like this:
Chance are excellent that
Tcould eventually be generic here too, likeList<String>, so you should take care to properly build the type which is passed in there - it might itself be aParameterizedTypeName. Keep an eye on the various methods inTypeNamefor this too - theTypeName.get(TypeMirror)overload for example will take an already-parameterized declared type mirror and give you the expectedParameterizedTypeNameback again.With that said, according to your other code,
Ttoday cannot be generic - you look for the@JsonSerializeannotation on anElement, which means it would be the equivelent ofList<T>rather than the usage of it,List<String>. Then, in this line, you make the Element into a TypeMirror to build the type name as I've described above:This means the final code would probably be