简体   繁体   中英

Select classes with given annotation

I have a bunch of source files for java classes. I want to find those classes which are annotated by a given annotation class. The names of those classes should be written to a service provider list file.

Is there any machinery I could use to help me with this task? Or do I have to implement this myself from scratch?

If I had to do this myself, there are several approaches I can think of.

  1. Write an Ant Task in Java. Have it create a ClassLoader using a suitable (probably configurable) class path. Use that loader to (attempt to) load the classes matching the input files, in order to inspect their annotations. Requires annotation retention at runtime, and full initialization of all involved classes and their dependencies.

  2. Use javap to inspect the classes. Since I don't know of a programmatic interface to javap (do you?), this probably means iterating over the files and running a new process for each of them, then massaging the created output in a suitable way. Perhaps a <scriptdef> -ed task could be used for this. This would work with class-file annotation retention, and require no initialization.

  3. Use an annotation processor to collect the information at compile-time. This should be able to work with sourcecode-only retention. But I have no experience writing or using annotation compilers, so I'm not sure this will work, and will need a lot of research to figure out some of the details. In particular how to activate the task for use by ant ( Java 6 annotation processing configuration with Ant gives some pointers on this, as does What is the default annotation processors discovery process? ) and when to create the output file (in each round, or only in the last round).

Which of these do you think has the greatest chances of success? Can you suggest code samples for one of these, which might be close to what I want and which I could adapt appropriately?

Encouraged by Thomas' comment, I gave approach 3 a try and got the following annotation processor working reasonably well:

import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.QualifiedNameable;
import javax.lang.model.element.TypeElement;
import javax.tools.StandardLocation;

@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class AnnotationServiceProcessor extends AbstractProcessor {

    // Map name of the annotation to name of the corresponding service interface
    private static Map<String, String> annotationToServiceMap = new HashMap<>();

    static {
        // Adapt this to your use, or make it configurable somehow
        annotationToServiceMap.put("Annotation1", "Service1");
        annotationToServiceMap.put("Annotation2", "Service2");
    }

    @Override public Set<String> getSupportedAnnotationTypes() {
        return annotationToServiceMap.keySet();
    }

    // Map name of the annotation to list of names
    // of the classes which carry that annotation
    private Map<String, List<String>> classLists;

    @Override public void init(ProcessingEnvironment env) {
        super.init(env);
        classLists = new HashMap<>();
        for (String ann: getSupportedAnnotationTypes())
            classLists.put(ann, new ArrayList<String>());
    }

    public boolean process(Set<? extends TypeElement> annotations,
                           RoundEnvironment env) {
        for (TypeElement ann: annotations) {
            List<String> classes =
                classLists.get(ann.getQualifiedName().toString());
            for (Element elt: env.getElementsAnnotatedWith(ann)) {
                QualifiedNameable qn = (QualifiedNameable)elt;
                classes.add(qn.getQualifiedName().toString());
            }
        }
        if (env.processingOver()) { // Only write results at the end
            for (String ann: getSupportedAnnotationTypes()) {
                try {
                    write(ann, classLists.get(ann));
                } catch (IOException e) {
                    throw new RuntimeException(e); // UGLY!
                }
            }
        }
        return true;
    }

    // Write the service file for each annotation we found
    private void write(String ann, List<String> classes) throws IOException {
        if (classes.isEmpty())
            return;
        String service = annotationToServiceMap.get(ann);
        Writer w = processingEnv.getFiler()
            .createResource(StandardLocation.CLASS_OUTPUT,
                            "", "META-INF/services/" + service)
            .openWriter();
        classes.sort(null); // Make the processing order irrelevant
        for (String cls: classes) {
            w.write(cls);
            w.write('\n');
        }
        w.close();
    }

}

So far I've hooked this up to ant using <compilerarg> s from https://stackoverflow.com/a/3644624/1468366 . I'll try something better and if I succeed will edit this post to include some ant snippet.

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