All Downloads are FREE. Search and download functionalities are using the official Maven repository.

framework.src.org.checkerframework.framework.type.AnnotationClassLoader Maven / Gradle / Ivy

Go to download

The Checker Framework enhances Java’s type system to make it more powerful and useful. This lets software developers detect and prevent errors in their Java programs. The Checker Framework includes compiler plug-ins ("checkers") that find bugs or verify their absence. It also permits you to write your own compiler plug-ins.

There is a newer version: 3.42.0
Show newest version
package org.checkerframework.framework.type;

import org.checkerframework.common.basetype.BaseTypeChecker;
import org.checkerframework.framework.util.AnnotatedTypes;
import org.checkerframework.framework.util.AnnotationBuilder;
import org.checkerframework.javacutil.ErrorReporter;

import java.io.File;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
import java.net.JarURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.regex.Pattern;

import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.AnnotationMirror;
import javax.tools.Diagnostic.Kind;

/*>>>
import org.checkerframework.checker.nullness.qual.Nullable;
 */

/**
 * This class assists the {@link AnnotatedTypeFactory} by reflectively looking
 * up the list of annotation class names in each checker's qual directory, and
 * then loading and returning it as a set of annotation classes. It can also
 * look up and load annotation classes from external directories that are passed
 * as arguments to checkers that have extension capabilities such as the
 * Subtyping Checker, Fenum Checker, and Units Checker.
 *
 * To load annotations using this class, their directory structure and package
 * structure must be identical.
 *
 * Only annotation classes that have the {@link Target} meta-annotation with the
 * value of {@link ElementType#TYPE_USE} (and optionally
 * {@link ElementType#TYPE_PARAMETER}) are loaded. If it has other
 * {@link ElementType} values, it won't be loaded. Other annotation classes must
 * be manually listed in a checker's annotated type factory by overriding
 * {@link AnnotatedTypeFactory#createSupportedTypeQualifiers()}.
 *
 * Checker writers may wish to subclass this class if they wish to implement
 * some custom rules to filter or process loaded annotation classes, by
 * providing an override implementation of
 * {@link #isSupportedAnnotationClass(Class)}. See
 * {@link org.checkerframework.checker.units.UnitsAnnotationClassLoader
 * UnitsAnnotationClassLoader} for an example.
 *
 * @author Jeff Luo
 */
public class AnnotationClassLoader {
    // For issuing errors to the user
    private final BaseTypeChecker checker;

    // For loading from a source package directory
    private final String packageName;
    private final String packageNameWithSlashes;
    private final List fullyQualifiedPackageNameSegments;
    private static final String QUAL_PACKAGE_SUFFIX = ".qual";

    // For loading from a Jar file
    private static final String JAR_SUFFIX = ".jar";
    private static final String CLASS_SUFFIX = ".class";

    // For loading from external directories
    private static final String JAVA_SUFFIX = ".java";

    // Constants
    private static final char DOT = '.';
    private static final char SLASH = '/';

    /**
     * Processing Env used to create an {@link AnnotationBuilder}, which is in
     * turn used to build the annotation mirror from the loaded class.
     */
    protected final ProcessingEnvironment processingEnv;

    /** The resource URL of the qual directory of a checker class */
    private final URL resourceURL;

    /** The loaded annotation classes */
    private final Set> loadedAnnotations;

    /**
     * Constructor for loading annotations defined for a checker.
     *
     * @param checker a {@link BaseTypeChecker} or its subclass
     */
    public AnnotationClassLoader(final BaseTypeChecker checker) {
        this.checker = checker;
        processingEnv = checker.getProcessingEnvironment();

        // package name must use dots, this is later prepended to annotation
        // class names as we load the classes using the class loader
        packageName = checker.getClass().getPackage().getName() + QUAL_PACKAGE_SUFFIX;

        // the package name with dots replaced by slashes will be used to scan
        // file directories
        packageNameWithSlashes = packageName.replace(DOT, SLASH);

        // each component of the fully qualified package name will be used later
        // to recursively descend from a root directory to see if the package
        // exists in some particular root directory
        fullyQualifiedPackageNameSegments = new ArrayList();

        // from the fully qualified package name, split it at every dot then add
        // to the list
        fullyQualifiedPackageNameSegments.addAll(Arrays.asList(Pattern.compile(Character.toString(DOT), Pattern.LITERAL).split(packageName)));

        // create the data structure to hold all loaded annotation classes
        loadedAnnotations = new LinkedHashSet>();

        ClassLoader applicationClassloader = getAppClassLoader();

        if (applicationClassloader != null) {
            // if the application classloader is accessible, then directly
            // retrieve the resource URL of the qual package
            // resource URLs must use slashes
            resourceURL = applicationClassloader.getResource(packageNameWithSlashes);

            // thread based application classloader, if needed in the future:
            // resourceURL = Thread.currentThread().getContextClassLoader().getResource(packageNameWithSlashes);
        } else {
            // if the application classloader is not accessible (which means the
            // checker class was loaded using the bootstrap classloader)
            // then scan the classpaths to find a jar or directory which
            // contains the qual package and set the resource URL to that jar or
            // qual directory
            resourceURL = getURLFromClasspaths();
        }

        // load the annotation classes using reflective lookup
        loadBundledAnnotationClasses();
    }

    /**
     * Scans all classpaths and returns the resource URL to the jar which
     * contains the checker's qual package, or the qual package directory if it
     * exists, or null if no jar or directory contains the package
     *
     * @return a URL to the jar that contains the qual package, or to the qual
     *         package's directory, or null if no jar or directory contains the
     *         qual package
     */
    private final /*@Nullable*/ URL getURLFromClasspaths() {
        // Debug use, uncomment if needed to see all of the classpaths (boot
        // classpath, extension classpath, and classpath)
        // printPaths();

        URL url = null;

        // obtain all classpaths
        Set paths = getClasspaths();

        // In checkers, there will be a resource URL for the qual directory. But
        // when called in the framework (eg GeneralAnnotatedTypeFactory), there
        // won't be a resourceURL since there isn't a qual directory

        // each path from the set of classpaths will be checked to see if it
        // contains the qual directory of a checker, if so, the the first
        // directory or jar that contains the package will be used as the source
        // for loading classes from the qual package

        // if either a directory or a jar contains the package, resourceURL will
        // be updated to refer to that source, otherwise resourceURL remains as
        // null

        // if both a jar and a directory contain the qual package, then the
        // order of the jar and the directory in the command line option(s)
        // or environment variables will decide which one gets examined first
        for (String path : paths) {
            // see if the current classpath segment is a jar or a directory
            if (path.endsWith(JAR_SUFFIX)) {
                // current classpath segment is a jar
                url = getJarURL(path);

                // see if the jar contains the package
                if (url != null && containsPackage(url)) {
                    return url;
                }
            } else {
                // current classpath segment is a directory
                url = getDirectoryURL(path);

                // see if the directory contains the package
                if (url != null && containsPackage(url)) {
                    // append a slash if necessary
                    if (!path.endsWith(Character.toString(SLASH))) {
                        path += SLASH;
                    }

                    // update URL to the qual directory
                    url = getDirectoryURL(path + packageNameWithSlashes);

                    return url;
                }
            }
        }

        // if no jar or directory contains the qual package, then return null
        return null;
    }

    /**
     * Checks to see if the jar or directory referred by the URL contains the
     * qual package of a specific checker
     *
     * @param url a URL referring to either a jar or a directory
     * @return true if the jar or the directory contains the qual package, false
     *         otherwise
     */
    private final boolean containsPackage(final URL url) {
        // see whether the resource URL has a protocol of jar or file
        if (url.getProtocol().equals("jar")) {
            // try to open up the jar file
            try {
                JarURLConnection connection = (JarURLConnection) url.openConnection();
                JarFile jarFile = connection.getJarFile();

                // check to see if the jar file contains the package
                return checkJarForPackage(jarFile);
            } catch (IOException e) {
                // do nothing for missing or un-openable Jar files
            }
        } else if (url.getProtocol().equals("file")) {
            // open up the directory
            File rootDir = new File(url.getFile());

            // check to see if the directory contains the package
            return checkDirForPackage(rootDir, fullyQualifiedPackageNameSegments.iterator());
        }

        return false;
    }

    /**
     * Checks to see if the jar file contains the qual package of a specific
     * checker
     *
     * @param jar a jar file
     * @return true if the jar file contains the qual package, false otherwise
     */
    private final boolean checkJarForPackage(final JarFile jar) {
        Enumeration jarEntries = jar.entries();

        // loop through the entries in the jar
        while (jarEntries.hasMoreElements()) {
            JarEntry je = jarEntries.nextElement();

            // each entry is the fully qualified path and file name to a
            // particular artifact in the jar file (eg a class file)
            // if the jar has the package, one of the entry's name will begin
            // with the package name in slash notation
            String entryName = je.getName();
            if (entryName.startsWith(packageNameWithSlashes)) {
                return true;
            }
        }

        return false;
    }

    /**
     * Checks to see if the current directory contains the qual package through
     * recursion currentDir starts at the root directory (a directory passed in
     * as part of the classpaths), the iterator goes through each segment of the
     * fully qualified package name (each segment is separated by a dot)
     *
     * Each step of the recursion checks to see if there's a subdirectory in the
     * current directory which has a name matching the package name segment, if
     * so, it recursively descends into that subdirectory to check the next
     * package name segment
     *
     * If there's no more segments left, then we've found the qual directory of
     * interest
     *
     * If we've checked every subdirectory and none of them match the current
     * package name segment, then the qual directory of interest does not exist
     * in the given root directory (at the beginning of recursion)
     *
     * @param currentDir current directory
     * @param pkgNames an iterator which provides each segment of the fully
     *            qualified qual package name
     * @return true if the qual package exists within the root directory, false
     *         otherwise
     */
    private final boolean checkDirForPackage(final File currentDir, final Iterator pkgNames) {
        // if the iterator has no more package name segments, then we've found
        // the qual directory of interest
        if (!pkgNames.hasNext()) {
            return true;
        }
        // if the file doesn't exist or it isn't a directory, return false
        if (currentDir == null || !currentDir.isDirectory()) {
            return false;
        }

        // if it isn't empty, dequeue one segment of the fully qualified package
        // name
        String currentPackageDirName = pkgNames.next();

        // scan current directory to see if there's a sub-directory that has a
        // matching name as the package name segment
        for (File file : currentDir.listFiles()) {
            if (file.isDirectory() && file.getName().equals(currentPackageDirName)) {
                // if so, recursively descend and look at the next segment of
                // the package name
                return checkDirForPackage(file, pkgNames);
            }
        }

        // if no sub-directory has a matching name, then that means there isn't
        // a matching qual package
        return false;
    }

    /**
     * Given an absolute path to a directory, this method will return a URL
     * reference to that directory
     *
     * @param absolutePathToDirectory an absolute path to a directory
     * @return a URL reference to the directory, or null if the URL is malformed
     */
    private final /*@Nullable*/ URL getDirectoryURL(final String absolutePathToDirectory) {
        URL directoryURL = null;

        try {
            directoryURL = new File(absolutePathToDirectory).toURI().toURL();
        } catch (MalformedURLException e) {
            processingEnv.getMessager().printMessage(Kind.NOTE, "Directory URL " + absolutePathToDirectory + " is malformed");
        }

        return directoryURL;
    }

    /**
     * Given an absolute path to a jar file, this method will return a URL
     * reference to that jar file
     *
     * @param absolutePathToJarFile an absolute path to a jar file
     * @return a URL reference to the jar file, or null if the URL is malformed
     */
    private final /*@Nullable*/ URL getJarURL(final String absolutePathToJarFile) {
        URL jarURL = null;

        try {
            jarURL = new URL("jar:file:" + absolutePathToJarFile + "!/");
        } catch (MalformedURLException e) {
            processingEnv.getMessager().printMessage(Kind.NOTE, "Jar URL " + absolutePathToJarFile + " is malformed");
        }

        return jarURL;
    }

    /**
     * Obtains and returns a set of the classpaths from compiler options, system
     * environment variables, and by examining the classloader to see what paths
     * it has access to
     *
     * The classpaths will be obtained in the order of: 1) boot classpaths (set
     * using -bootclasspath -Xbootclasspath or -J-Xbootclasspath) 2) extension
     * paths (from java.ext.dirs) 3) classpaths (set in CLASSPATH, or through
     * -classpath and -cp) 4) paths accessible and examined by the classloader
     *
     * In each of these paths, the order of the paths as specified in the
     * command line options or environment variables will be the order returned
     * in the set
     *
     * @return an immutable linked hashset of the classpaths
     */
    private final Set getClasspaths() {
        Set paths = new LinkedHashSet();

        // add all paths in Xbootclasspath
        paths.addAll(Arrays.asList(System.getProperty("sun.boot.class.path").split(":")));

        // add all extension paths
        paths.addAll(Arrays.asList(System.getProperty("java.ext.dirs").split(":")));

        // add all paths in CLASSPATH, -cp, and -classpath
        paths.addAll(Arrays.asList(System.getProperty("java.class.path").split(":")));

        // add all paths that are examined by the classloader
        ClassLoader applicationClassloader = getAppClassLoader();

        if (applicationClassloader != null) {
            URL[] urls = ((URLClassLoader) applicationClassloader).getURLs();
            for (int i = 0; i < urls.length; i++) {
                paths.add(urls[i].getFile().toString());
            }
        }

        return Collections.unmodifiableSet(paths);
    }

    /**
     * Obtains the classloader used to load the checker class, if that isn't
     * available then it will try to obtain the system classloader
     *
     * @return the classloader used to load the checker class, or the system
     *         classloader, or null if both are unavailable
     */
    private final /*@Nullable*/ ClassLoader getAppClassLoader() {
        ClassLoader applicationClassLoader = checker.getClass().getClassLoader();

        // see if we can access the application classloader
        if (applicationClassLoader == null) {
            // if the application classloader for the checker isn't available,
            // then try to obtain the System application classloader
            applicationClassLoader = ClassLoader.getSystemClassLoader();

            // Debug use:
            // processingEnv.getMessager().printMessage(Kind.NOTE, "Using System application class loader!");
        }

        return applicationClassLoader;
    }

    /**
     * Debug Use Displays all classpaths
     */
    private final void printPaths() {
        // all paths in Xbootclasspath
        String[] bootclassPaths = System.getProperty("sun.boot.class.path").split(":");
        processingEnv.getMessager().printMessage(Kind.NOTE, "bootclass path:");
        for (String path : bootclassPaths) {
            processingEnv.getMessager().printMessage(Kind.NOTE, "\t" + path);
        }

        // all extension paths
        String[] extensionDirs = System.getProperty("java.ext.dirs").split(":");
        processingEnv.getMessager().printMessage(Kind.NOTE, "extension dirs:");
        for (String path : extensionDirs) {
            processingEnv.getMessager().printMessage(Kind.NOTE, "\t" + path);
        }

        // all paths in CLASSPATH, -cp, and -classpath
        String[] javaclassPaths = System.getProperty("java.class.path").split(":");
        processingEnv.getMessager().printMessage(Kind.NOTE, "java classpaths:");
        for (String path : javaclassPaths) {
            processingEnv.getMessager().printMessage(Kind.NOTE, "\t" + path);
        }

        // add all paths that are examined by the classloader
        ClassLoader applicationClassLoader = getAppClassLoader();
        processingEnv.getMessager().printMessage(Kind.NOTE, "classloader examined paths:");
        if (applicationClassLoader != null) {
            URL[] urls = ((URLClassLoader) applicationClassLoader).getURLs();
            for (int i = 0; i < urls.length; i++) {
                processingEnv.getMessager().printMessage(Kind.NOTE, "\t" + urls[i].getFile());
            }
        } else {
            processingEnv.getMessager().printMessage(Kind.NOTE, "classloader unavailable");
        }
    }

    /**
     * Gets the set of the loaded annotation classes. Note that the returned set
     * from this method is mutable. This method is intended to be called within
     * {@link AnnotatedTypeFactory#createSupportedTypeQualifiers()
     * createSupportedTypeQualifiers()} (or its helper methods) to help define
     * the set of supported qualifiers.
     * {@link AnnotatedTypeFactory#createSupportedTypeQualifiers()
     * createSupportedTypeQualifiers()} must return an immutable set, and it is
     * the responsibility of that method (or helper methods it calls) to convert
     * the set returned by this method, along with any additional annotation
     * classes, into an immutable set.
     *
     * @return the set of loaded annotation classes
     */
    public final Set> getLoadedAnnotationClasses() {
        return loadedAnnotations;
    }

    /**
     * Loads type annotations located in the qual directory of a checker
     * (referenced by the resourceURL) via reflective annotation class name
     * lookup.
     */
    private final void loadBundledAnnotationClasses() {
        if (resourceURL == null) {
            // if there's no resourceURL, then there's nothing we can load
            return;
        }

        // retrieve the fully qualified class names of the annotations
        Set annotationNames = null;

        // see whether the resource URL has a protocol of jar or file
        if (resourceURL.getProtocol().equals("jar")) {
            // if the checker class file is contained within a jar, then the
            // resource URL for the qual directory will have the protocol
            // "jar". This means the whole checker is loaded as a jar file.

            // open up that jar file and extract annotation class names
            try {
                JarURLConnection connection = (JarURLConnection) resourceURL.openConnection();
                JarFile jarFile = connection.getJarFile();

                // get class names inside the jar file within the particular
                // package
                annotationNames = getBundledAnnotationNamesFromJar(jarFile);
            } catch (IOException e) {
                ErrorReporter.errorAbort("AnnotatedTypeLoader: cannot open the Jar file " + resourceURL.getFile());
            }
        } else if (resourceURL.getProtocol().equals("file")) {
            // if the checker class file is found within the file system itself
            // within some directory (usually development build directories),
            // then process the package as a file directory in the file system
            // and load the annotations contained in the qual directory

            // open up the directory
            File packageDir = new File(resourceURL.getFile());
            annotationNames = getAnnotationNamesFromDirectory(packageName + DOT, resourceURL.getFile(), packageDir, CLASS_SUFFIX);
        }

        loadedAnnotations.addAll(loadAnnotationClasses(annotationNames));
    }

    /**
     * Retrieves the annotation class file names from the qual directory
     * contained inside a jar
     *
     * @param jar the JarFile containing the annotation class files
     * @return a set of fully qualified class names of the annotations
     */
    private final Set getBundledAnnotationNamesFromJar(final JarFile jar) {
        Set annos = new LinkedHashSet();

        // get an enumeration iterator for all the content entries in the jar
        // file
        Enumeration jarEntries = jar.entries();

        // enumerate through the entries
        while (jarEntries.hasMoreElements()) {
            JarEntry je = jarEntries.nextElement();
            // filter out directories and non-class files
            if (je.isDirectory() || !je.getName().endsWith(CLASS_SUFFIX)) {
                continue;
            }

            // get rid of the .class suffix
            String className = je.getName().substring(0, je.getName().lastIndexOf('.'));
            // convert path notation to class notation
            className = className.replace(SLASH, DOT);

            // filter for qual package
            if (className.startsWith(packageName)) {
                // add to set
                annos.add(className);
            }
        }

        return annos;
    }

    /**
     * This method takes as input the canonical name of an external annotation
     * class and loads and returns that class via the class loader.
     *
     * @param annoName canonical name of an external annotation class, e.g.
     *            "myproject.qual.myannotation"
     * @return the loaded annotation class
     */
    public final /*@Nullable*/ Class loadExternalAnnotationClass(final String annoName) {
        try {
            final Class annoClass = Class.forName(annoName).asSubclass(Annotation.class);
            return annoClass;
        } catch (ClassNotFoundException e) {
            checker.userErrorAbort(checker.getClass().getSimpleName() + ": could not load class for annotation: " + annoName + "; ensure that your classpath is correct");
        } catch (ClassCastException e) {
            checker.userErrorAbort(checker.getClass().getSimpleName() + ": class " + annoName + " is not an annotation");
        }
        return null;
    }

    /**
     * This method takes as input a fully qualified path to a directory, and
     * loads and returns the set of all annotation classes from that directory.
     *
     * @param dirName absolute path to a directory containing annotation classes
     * @return a set of annotation classes
     */
    public final Set> loadExternalAnnotationClassesFromDirectory(final String dirName) {
        File rootDirectory = new File(dirName);
        Set annoNames = getAnnotationNamesFromDirectory("", dirName, rootDirectory, JAVA_SUFFIX);
        return loadAnnotationClasses(annoNames);
    }

    /**
     * Retrieves all annotation names from the current directory, and
     * recursively descends and retrieves annotation names from sub-directories.
     *
     * @param packageName a string storing the name of the package that contains
     *            the qual package
     * @param rootDirectory a string storing the absolute path of the root
     *            directory of a set of annotations, which is subtracted from
     *            class names to retrieve each class's fully qualified class
     *            names
     * @param currentDirectory a {@link File} object representing the current
     *            sub-directory of the root directory
     * @param fileExtension a file extension suffix that a file must have to be
     *            considered an annotation file, normally either
     *            {@link #CLASS_SUFFIX} or {@link #JAVA_SUFFIX} is passed in as
     *            its value.
     * @return a set of strings where each string is the fully qualified class
     *         name of an annotation in the root directory or its
     *         sub-directories
     */
    private final Set getAnnotationNamesFromDirectory(final String packageName, final String rootDirectory, final File currentDirectory, final String fileExtension) {
        Set results = new LinkedHashSet();

        // check every file and directory within the current directory
        File[] directoryContents = currentDirectory.listFiles();
        Arrays.sort(directoryContents, new Comparator() {
            @Override
            public int compare(File o1, File o2) {
                return o1.getName().compareTo(o2.getName());
            }
        });
        for (File file : directoryContents) {
            if (file.isFile()) {
                // Full file name, including path to file
                String fullFileName = file.getAbsolutePath();
                // Simple file name
                String fileName = fullFileName.substring(fullFileName.lastIndexOf(File.separator) + 1, fullFileName.length());
                // Path to file
                String filePath = fullFileName.substring(0, fullFileName.lastIndexOf(File.separator));
                // Package name beginning with "qual"
                String qualPackageName = "";
                if (!filePath.equals(rootDirectory)) {
                    qualPackageName = filePath.substring(rootDirectory.length() + 1, filePath.length()).replace(SLASH, DOT) + DOT;
                }
                // Annotation name, which is the same as the file name but with
                // file extension removed
                String annotationName = fileName;
                if (fileName.lastIndexOf(DOT) != -1) {
                    annotationName = fileName.substring(0, fileName.lastIndexOf(DOT));
                }

                // Fully qualified annotation class name
                String fullyQualifiedAnnoName = packageName + qualPackageName + annotationName;

                if (fileName.endsWith(fileExtension)) {
                    // add the fully qualified annotation class name to the set
                    results.add(fullyQualifiedAnnoName);
                }
            } else if (file.isDirectory()) {
                // recursively add all sub directories's fully qualified annotation class name
                results.addAll(getAnnotationNamesFromDirectory(packageName, rootDirectory, file, fileExtension));
            }
        }

        return results;
    }

    /**
     * Loads the class indicated by the fullyQualifiedClassName, and checks to
     * see if it is an annotation that is supported by a checker.
     *
     * @param fullyQualifiedClassName the fully qualified name of the class
     * @return the loaded annotation class if it is defined with
     *         ElementType.TYPE_USE and is a supported annotation, null
     *         otherwise
     */
    private final /*@Nullable*/ Class loadAnnotationClass(final String fullyQualifiedClassName) {
        Class cls = null;

        try {
            // load the class
            cls = Class.forName(fullyQualifiedClassName);
        } catch (ClassNotFoundException e) {
            // do nothing: projects can have annotation class files and regular
            // source files located within the same directory, and as such when
            // it tires to load an uncompiled source file, it will throw
            // ClassNotFoundException
        }

        // ensure that the freshly loaded class is an annotation, and has
        // the @Target annotation
        if (cls != null && cls.isAnnotation() && cls.getAnnotation(Target.class) != null) {
            // retrieve the set of ElementTypes in the @Target
            // meta-annotation and check to see if this annotation is
            // supported for automatic loading
            if (AnnotatedTypes.hasTypeQualifierElementTypes(cls.getAnnotation(Target.class).value(), cls)) {
                // if it is supported, then subclass it as an Annotation
                // class
                Class annoClass = cls.asSubclass(Annotation.class);

                // see if the annotation is supported by a checker
                if (isSupportedAnnotationClass(annoClass)) {
                    return annoClass;
                }
            }
        }

        return null;
    }

    /**
     * Loads a set of annotations indicated by fullyQualifiedAnnoNames.
     *
     * @param fullyQualifiedAnnoNames a set of strings where each string is a single
     *            annotation class's fully qualified name
     * @return a set of loaded annotation classes.
     *
     * @see #loadAnnotationClass(String)
     */
    private final Set> loadAnnotationClasses(final /*@Nullable*/ Set fullyQualifiedAnnoNames) {
        Set> loadedClasses = new LinkedHashSet>();

        if (fullyQualifiedAnnoNames != null && !fullyQualifiedAnnoNames.isEmpty()) {
            // loop through each class name & load the class
            for (String fullyQualifiedAnnoName : fullyQualifiedAnnoNames) {
                Class annoClass = loadAnnotationClass(fullyQualifiedAnnoName);
                if (annoClass != null) {
                    loadedClasses.add(annoClass);
                }
            }
        }

        return loadedClasses;
    }

    /**
     * Checks to see whether a particular annotation class is supported.
     *
     * Every subclass of AnnotatedTypeLoader can override this method to
     * indicate whether a particular annotation is supported by its checker.
     *
     * @param annoClass an annotation class
     * @return true if the annotation is supported, false if it isn't
     */
    protected boolean isSupportedAnnotationClass(final Class annoClass) {
        if (loadedAnnotations.contains(annoClass)) {
            // if it has already been checked before, return true
            return true;
        } else {
            // The standard way to see if an annotation is supported is to build
            // its annotation mirror if there's no problems building the
            // mirror, then it is supported
            AnnotationBuilder builder = new AnnotationBuilder(processingEnv, annoClass);
            AnnotationMirror annoMirroResult = builder.build();
            // TODO: build() internally will error abort if it fails, can we
            // gracefully resume here?
            return (annoMirroResult != null);
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy