How to handle generics inside Java annotation processor?

I asked before for an example an “annotation handler” that will generate a proxy delegate for the interface, but did not receive a response and did not find anything on the Internet, so I made my own.

Until now, this worked well until I tried using generics inside the super-interface. If I use generics in an annotated interface, it works fine (more randomly than by design). But if the annotated interface extends another interface that accepts a type-type parameter, this parameter is not "bound" to the type that uses the annotated interface when expanding the superinterface. Example:

public interface TestFragment<E> { void test(E dummy); } @CreateWrapper public interface TestService extends TestFragment<String> { double myOwnMethod(); } 

This will create:

 // ... public void test(final E dummy) { wrapped.test(dummy); } // ... 

instead of the correct one:

 // ... public void test(final String dummy) { wrapped.test(dummy); } // ... 

The code that generates the parameters in the generated methods is as follows:

 int count = 0; for (VariableElement param : method.getParameters()) { if (count > 0) { pw.print(", "); } count++; pw.printf("final %s %s", param.asType().toString(), param.getSimpleName().toString()); } 

Is there any way to do this?

+6
source share
4 answers

What you need is a substitution, given a map of type variables for inputting arguments. In this case, E->String . Replace any E in any type of String

There is javax.lang.model.util.Types such support in javax.lang.model.util.Types , you need to minimize yours. Primarily

 void print(TypeMirror type, Map<TypeVariable,TypeMirror> substitution) if(substitution.containsKey(type)) // type is a var, E print( substitution.get(type) ); // String else if(type instanceof DeclaredType) // eg List<E> print( type.asElement().getSimpleName() ); // List for(TypeMirror arg : type.getTypeArguments() ) // E print(arg, substitution) etc. something like that 
+3
source

Copy-paste my original answer :

This seems to be a common question, so for those coming from Google: there is hope.

Dagger The DI project is licensed under the Apache 2.0 license and contains some utility for working with types in the annotation processor.

In particular, the Util class can be fully viewed in GitHub ( Util.java ) and defines the method public static String typeToString(TypeMirror type) . It uses TypeVisitor and some recursive calls to create a string representation of the 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 that generates class extensions. The Dagger method works for complex situations, including common inner classes. I have the following results:

My test class with field extension:

 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 processor provides the _bs field:

 accessor.type = DaggerUtils.typeToString(element.asType()); 

Generated source (custom, of course). Pay attention to the amazing nested generic types.

 public java.util.Set<AnnotationTest.B<java.lang.Integer>> AnnotationTest.A.getBsGenerated() { return this._bs; } 

EDIT: Adapting the concept to extract TypeMirror of the first common argument, null otherwise:

 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]; } 
+1
source

It can be pretty simple if you follow Ryan Wall's suggestion for using asMemberOf

 ExecutableType methodType = (ExecutableType) typeUtil .asMemberOf((DeclaredType) theAnnotatedClass.asType(), method); int count = 0; for (VariableElement param : method.getParameters()) { if (count > 0) { pw.print(", "); } TypeMirror actualParamType = methodType.getParameterTypes().get(count); pw.printf("final %s %s", actualParamType.toString(), param.getSimpleName().toString()); count++; } 
+1
source

Source: https://habr.com/ru/post/898236/


All Articles