简体   繁体   中英

Javax Annotation Processing: Check if an annotated type is a sub-type of another type

I'm creating an AnnotationProcessor and I need to check if the annotated type is a subtype of an specified class. The annotation:

public @interface Component {
    Class<?> supertype();
}

Example: (correct)

@Component(supertype = MyInterface.class)
public class MyClass implements MyInterface {
   // ...
}

Example: (incorrect, this must not compile because MyClass isn't a subtype of String )

@Component(supertype = String.class)
public class MyClass implements MyInterface {

}

I know that I cannot get the annotated Class because it isn't compiled yet.

You don't mention it in your question, but I assume you're running into TypeMirror issues.

Here's a very old and hacky solution to get to the TypeMirror of your supertype field, and compare it against the type's base class:

@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
    for (Element element : roundEnvironment.getElementsAnnotatedWith(Component.class)) {
        // TODO: Ensure element is a class before continuing

        TypeElement typeElement = (TypeElement) element;
        Component componentAnnotation = typeElement.getAnnotation(Component.class);
        TypeMirror superType = null;
        try {
            // Hack to quickly get to TypeMirror of the annotation property
            componentAnnotation.supertype();
        } catch (MirroredTypeException mte) {
            superType = mte.getTypeMirror();
        }

        // TODO: superType null check

        Set<TypeMirror> validTypes = Sets.newHashSet(typeElement.getInterfaces());
        validTypes.add(typeElement.getSuperclass());

        if (!validTypes.contains(superType)) {
            // TODO: throw something better
            throw new IllegalArgumentException(typeElement.toString() + " does not implement or inherit from "
                    + superType.toString() + " declared in @Component annotation");
        }
    }

    return true;
}

If I were you I'd look into this a bit more to see if there are better options these days, but if not this will solve your problem.

This version does not rely on exceptions, it uses Element.getAnnotationMirrors which is probably there for this type of use. It's a lot more verbose though:


    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        for (Element element : roundEnvironment.getElementsAnnotatedWith(Component.class)) {
            // TODO: Ensure element is a class before continuing

            TypeMirror superType = null;

            boolean found = false;
            for (AnnotationMirror mirror : element.getAnnotationMirrors()) {
                TypeElement annotationElement = (TypeElement) mirror.getAnnotationType().asElement();
                if (annotationElement.getQualifiedName().contentEquals(Component.class.getName())) {
                    found = true;

                    for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> kv :
                            mirror.getElementValues().entrySet()) {

                        ExecutableElement member = kv.getKey();
                        if (member.getKind() == ElementKind.METHOD &&
                                member.getSimpleName().contentEquals("supertype")) {
                            superType = (TypeMirror) kv.getValue().getValue();
                            break;
                        }
                    }
                }

                if (found) {
                    break;
                }
            }

            // TODO: superType null check

            TypeElement typeElement = (TypeElement) element;

            Set<TypeMirror> validTypes = Sets.newHashSet(typeElement.getInterfaces());
            validTypes.add(typeElement.getSuperclass());

            if (!validTypes.contains(superType)) {
                // TODO: throw something better
                throw new IllegalArgumentException(typeElement.toString() + " does not implement or inherit from "
                        + superType.toString() + " declared in @Component annotation");
            }
        }

        return true;
    }

Use the following code to get the TypeMirror of the class value in the annotation

public Optional<TypeMirror> getClassValueFromAnnotation(Element element, Class<? extends Annotation> annotation, String paramName) {
    for (AnnotationMirror am : element.getAnnotationMirrors()) {
        if (types.isSameType(am.getAnnotationType(), elements.getTypeElement(annotation.getCanonicalName()).asType())) {
            for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry : am.getElementValues().entrySet()) {
                if (paramName.equals(entry.getKey().getSimpleName().toString())) {
                    AnnotationValue annotationValue = entry.getValue();
                    return Optional.of((DeclaredType) annotationValue.getValue());
                }
            }
        }
    }
    return Optional.empty();
}

then use this code to check if it is a super type of a specific class

 /**
     * A wrapper over {@link Types#isAssignable(TypeMirror, TypeMirror)} which will apply type erasure on the targetClass before calling the wrapped method.
     *
     * @param typeMirror  a {@link javax.lang.model.type.TypeMirror} object.
     * @param targetClass a {@link java.lang.Class} object.
     * @return a boolean.
     */
    public boolean isAssignableFrom(TypeMirror typeMirror, Class<?> targetClass) {
        return types.isAssignable(typeMirror, types.erasure(elements.getTypeElement(targetClass.getCanonicalName()).asType()));
    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM