开发者

How to handle generics inside a Java "annotation processor"?

开发者 https://www.devze.com 2023-04-08 21:41 出处:网络
I asked before for an example \"annotation processor\" that would generate a Proxy/Delegate for an interface, but got no answer, and did not find anything on the Internet, so I made my own.

I asked before for an example "annotation processor" that would generate a Proxy/Delegate for an interface, but got no answer, and did not find anything on the Internet, so I made my own.

So far it worked well, until I tried to use generics inside a super-interface. If I use generics in the annotated interface, it works fine (more by accident than by design). But if the annotated interface extends another interface that takes a generic type parameter, that parameter is not "bound" to the type that the annotated interface use when extending the super-interface. Example:

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

This would generate:

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

instead of the correct:

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

The code that generates the parameters in the generated methods look like this:

int count = 0;
for (VariableElement param : method.getParameters()) {
    if (count > 0) {
        pw.print(", ");
    }
    count++;
    pw.printf("final %s %s", param.asType().toSt开发者_C百科ring(),
        param.getSimpleName().toString());
}

Is there a way to do this?


Have a look at http://docs.oracle.com/javase/6/docs/api/javax/lang/model/util/Types.html#asMemberOf%28javax.lang.model.type.DeclaredType,%20javax.lang.model.element.Element%29

Might be helpful. I used it to solve a very similar problem.


This can be quite simple if you follow Ryan Walls suggestion of 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++;
}


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

There is no such support in javax.lang.model.util.Types, you need to roll your own. Basically

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) // e.g. List<E>
        print( type.asElement().getSimpleName() );  // List
        for(TypeMirror arg : type.getTypeArguments() ) // E
            print(arg, substitution)

    etc. something like that


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;
}

EDIT: adapting the concept to extract a TypeMirror of the first generic 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];
}
0

精彩评论

暂无评论...
验证码 换一张
取 消