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 the addSuperinterface
method, you'll need to pass something with the specific type details you have in mind. This method has two overloads - one which takes java.lang.reflect.Type
(and Class
is a subtype of this), and another which one which takes com.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 a Class
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 using element.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:
/** Returns a parameterized type, applying {@code typeArguments} to {@code rawType}. */
public static ParameterizedTypeName get(ClassName rawType, TypeName... typeArguments)
In you code, you would use it roughly like this:
...
.addSuperinterface(ParameterizedTypeName.get(
ClassName.get(JsonSerializer.class),//rawType
ClassName.get(whateverTShouldBe) //the value for T
))
...
Chance are excellent that T
could eventually be generic here too, like List<String>
, so you should take care to properly build the type which is passed in there - it might itself be a ParameterizedTypeName
. Keep an eye on the various methods in TypeName
for this too - the TypeName.get(TypeMirror)
overload for example will take an already-parameterized declared type mirror and give you the expected ParameterizedTypeName
back again.
With that said, according to your other code, T
today cannot be generic - you look for the @JsonSerialize
annotation on an Element
, which means it would be the equivelent of List<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:
.addParameter(ParameterSpec.builder(TypeName.get(element.asType()), "obj", Modifier.FINAL).build())
This means the final code would probably be
...
.addSuperinterface(ParameterizedTypeName.get(
ClassName.get(JsonSerializer.class),//rawType
TypeName.get(element.asType()) //the value for T
))
...