Question

I wish to create an annotation which will test the annotated type (which may be an interface) and all its subtypes, and throw a compile error if any tested type is a concrete class without a no-arg constructor. I'm able to test the type that actually bears the annotation, no problem. Testing the subtypes is where I'm lost. How can I accomplish this? Source code below:

NoArgConstructorRequired.java

package mshare.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
public @interface NoArgConstructorRequired {
}

NoArgConststructorRequiredProcessor.java

package mshare.annotation;

import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Messager;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.ExecutableType;
import javax.lang.model.type.TypeVisitor;
import javax.lang.model.util.SimpleTypeVisitor7;
import javax.tools.Diagnostic;

@SupportedAnnotationTypes("mshare.annotation.NoArgConstructorRequired")
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class NoArgConstructorRequiredProcessor extends AbstractProcessor {
    // Tests if the visited type has no args
    private static final TypeVisitor<Boolean, Void> NO_ARGS_VISITOR = new SimpleTypeVisitor7<Boolean, Void>() {
        @Override
        public Boolean visitExecutable(ExecutableType t, Void v) {
            return Boolean.valueOf(t.getParameterTypes().isEmpty());
        }
    };

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        Messager messager = processingEnv.getMessager();

        for (TypeElement typeElement : annotations) {
            for (Element element : roundEnv.getElementsAnnotatedWith(typeElement)) {
                if (!isOkay(element)) {
                    messager.printMessage(
                            Diagnostic.Kind.ERROR,
                            element.getSimpleName() + " lacks a no-arg constructor",
                            element
                            );
                }
            }
        }

        return true;
    }

    private static boolean isOkay(Element element) {
        if (element.getModifiers().contains(Modifier.ABSTRACT)) {
            // class is not concrete
            return true;
        }

        for (Element subElement : element.getEnclosedElements()) {
            if (subElement.getKind() == ElementKind.CONSTRUCTOR) {
                if (subElement.asType().accept(NO_ARGS_VISITOR, null).booleanValue()) {
                    // has a no-arg constructor
                    return true;
                }
            }
        }

        return false;
    }
}
Was it helpful?

Solution

Perhaps the annotation should be marked @Inherited, which marks annotations as "descending through subclasses."

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top