I am developing a small code generator using JDK 6\'s Annotation Processing API and am stuck trying to get the actual generic type of a field in the class. To be clearer, let\'
Copy-paste of my original answer:
This seems to be a common question so, for those arriving from Google: there is hope.
The Dagger DI project is licensed under the Apache 2.0 License and contains some utility methods for working with types in an annotation processor.
In particular, the Util
class can be viewed in full on GitHub (Util.java) and defines a method public static String typeToString(TypeMirror type)
. It uses a TypeVisitor and some recursive calls to build up a string representation of a type. Here is a snippet for reference:
public static void typeToString(final TypeMirror type, final StringBuilder result, final char innerClassSeparator)
{
type.accept(new SimpleTypeVisitor6<Void, Void>()
{
@Override
public Void visitDeclared(DeclaredType declaredType, Void v)
{
TypeElement typeElement = (TypeElement) declaredType.asElement();
rawTypeToString(result, typeElement, innerClassSeparator);
List<? extends TypeMirror> typeArguments = declaredType.getTypeArguments();
if (!typeArguments.isEmpty())
{
result.append("<");
for (int i = 0; i < typeArguments.size(); i++)
{
if (i != 0)
{
result.append(", ");
}
// NOTE: Recursively resolve the types
typeToString(typeArguments.get(i), result, innerClassSeparator);
}
result.append(">");
}
return null;
}
@Override
public Void visitPrimitive(PrimitiveType primitiveType, Void v) { ... }
@Override
public Void visitArray(ArrayType arrayType, Void v) { ... }
@Override
public Void visitTypeVariable(TypeVariable typeVariable, Void v)
{
result.append(typeVariable.asElement().getSimpleName());
return null;
}
@Override
public Void visitError(ErrorType errorType, Void v) { ... }
@Override
protected Void defaultAction(TypeMirror typeMirror, Void v) { ... }
}, null);
}
I am busy with my own project which generates class extensions. The Dagger method works for complex situations, including generic inner classes. I have the following results:
My test class with field to extend:
public class AnnotationTest
{
...
public static class A
{
@MyAnnotation
private Set<B<Integer>> _bs;
}
public static class B<T>
{
private T _value;
}
}
Calling the Dagger method on the Element
the processor provides for the _bs
field:
accessor.type = DaggerUtils.typeToString(element.asType());
The generated source (custom, of course). Note the awesome nested generic types.
public java.util.Set<AnnotationTest.B<java.lang.Integer>> AnnotationTest.A.getBsGenerated()
{
return this._bs;
}
public static TypeMirror getGenericType(final TypeMirror type)
{
final TypeMirror[] result = { null };
type.accept(new SimpleTypeVisitor6<Void, Void>()
{
@Override
public Void visitDeclared(DeclaredType declaredType, Void v)
{
List<? extends TypeMirror> typeArguments = declaredType.getTypeArguments();
if (!typeArguments.isEmpty())
{
result[0] = typeArguments.get(0);
}
return null;
}
@Override
public Void visitPrimitive(PrimitiveType primitiveType, Void v)
{
return null;
}
@Override
public Void visitArray(ArrayType arrayType, Void v)
{
return null;
}
@Override
public Void visitTypeVariable(TypeVariable typeVariable, Void v)
{
return null;
}
@Override
public Void visitError(ErrorType errorType, Void v)
{
return null;
}
@Override
protected Void defaultAction(TypeMirror typeMirror, Void v)
{
throw new UnsupportedOperationException();
}
}, null);
return result[0];
}
All the other answers, while having lots of good points. Don't really show you the problem you have and it's solution.
The problem in your code is here
TypeElement collectionType = elementUtils.getTypeElement("java.util.Collection");
if (typeUtils.isAssignable(typeElement.asType(), collectionType.asType())) {
...
Your type is not extending java.util.Collection
but rather java.util.Collection<*>
. Let's rewrite the above block to reflect this:
WildcardType WILDCARD_TYPE_NULL = this.typeUtils.getWildcardType(null, null);
final TypeElement collectionTypeElement = this.elementUtils.getTypeElement(Collection.class.getName());
TypeMirror[] typex = {WILDCARD_TYPE_NULL};
DeclaredType collectionType=this.typeUtils.getDeclaredType(collectionTypeElement, typex);
if (typeUtils.isAssignable(typeElement.asType(), collectionType)){
...
That should make it work
Looks like there are a couple of problems. One, the isAssignable() isnt working as expected. Second, in the above code you are trying to get the generic parameters of the Set type (T), rather than the variable declaration (Role).
Nevertheless, the following code should demonstrate what you need:
@SupportedAnnotationTypes({ "xxx.MyAnnotation" })
@SupportedSourceVersion(SourceVersion.RELEASE_6)
public class MongoDocumentAnnotationProcessor extends AbstractProcessor {
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
if (roundEnv.processingOver() || annotations.size() == 0) {
return false;
}
for (Element element : roundEnv.getRootElements()) {
if (element.getKind() == ElementKind.CLASS && isAnnotatedWithMongoDocument(element)) {
System.out.println("Running " + getClass().getSimpleName());
for (VariableElement variableElement : ElementFilter.fieldsIn(element.getEnclosedElements())) {
if(variableElement.asType() instanceof DeclaredType){
DeclaredType declaredType = (DeclaredType) variableElement.asType();
for (TypeMirror typeMirror : declaredType.getTypeArguments()) {
System.out.println(typeMirror.toString());
}
}
}
}
}
return true; //processed
}
private boolean isAnnotatedWithMongoDocument(Element element) {
return element.getAnnotation(MyAnnotation.class) != null;
}
}
This code should output:
xxx.Role