I am writing an annotation processor that generates JSON serialization code. Here's my annotation that I use to identify the POJO
s 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.Class
to theaddSuperinterface
method, 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
(andClass
is 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.TypeName
has a number of subclasses,ClassName
,ParameterizedTypeName
are probably the main ones to focus on here. In an annotation processor, they have some big advantages over using aClass
instance - 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
T
could 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 inTypeName
for this too - theTypeName.get(TypeMirror)
overload for example will take an already-parameterized declared type mirror and give you the expectedParameterizedTypeName
back again.With that said, according to your other code,
T
today cannot be generic - you look for the@JsonSerialize
annotation 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