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

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.44.0
Show newest version
package org.checkerframework.framework.type;

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.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic.Kind;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.checker.signature.qual.BinaryName;
import org.checkerframework.checker.signature.qual.DotSeparatedIdentifiers;
import org.checkerframework.checker.signature.qual.Identifier;
import org.checkerframework.common.basetype.BaseTypeChecker;
import org.checkerframework.javacutil.AnnotationBuilder;
import org.checkerframework.javacutil.AnnotationUtils;
import org.checkerframework.javacutil.BugInCF;
import org.checkerframework.javacutil.InternalUtils;
import org.checkerframework.javacutil.UserError;
import org.plumelib.reflection.Signatures;

/**
 * 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 {@code * org.checkerframework.checker.units.UnitsAnnotationClassLoader} for an example. */ public class AnnotationClassLoader { /** For issuing errors to the user. */ protected final BaseTypeChecker checker; // For loading from a source package directory /** The package name. */ private final @DotSeparatedIdentifiers String packageName; /** The package name, with periods replaced by slashes. */ private final String packageNameWithSlashes; /** The atomic package names (the package name split at dots). */ private final List<@Identifier String> fullyQualifiedPackageNameSegments; /** The name of a Checker's qualifier package. */ private static final String QUAL_PACKAGE = "qual"; // For loading from a Jar file /** The suffix for a .jar file. */ private static final String JAR_SUFFIX = ".jar"; /** The suffix for a .class file. */ private static final String CLASS_SUFFIX = ".class"; // Constants /** The package separator. */ private static final char DOT = '.'; /** The path separator, in .jar files, binary names, etc. */ 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 class loader used to load annotation classes. */ protected final URLClassLoader classLoader; /** * The annotation classes bundled with a checker (located in its qual directory) that are deemed * supported by the checker (non-alias annotations). Each checker can override {@link * #isSupportedAnnotationClass(Class)} to determine whether an annotation is supported or not. * Call {@link #getBundledAnnotationClasses()} to obtain a reference to the set of classes. */ private final Set> supportedBundledAnnotationClasses; /** The package separator: ".". */ private static final Pattern DOT_LITERAL_PATTERN = Pattern.compile(Character.toString(DOT), Pattern.LITERAL); /** * Constructor for loading annotations defined for a checker. * * @param checker a {@link BaseTypeChecker} or its subclass */ @SuppressWarnings("signature") // TODO: reduce use of string manipulation 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 Package checkerPackage = checker.getClass().getPackage(); packageName = checkerPackage != null && !checkerPackage.getName().isEmpty() ? checkerPackage.getName() + DOT + QUAL_PACKAGE : QUAL_PACKAGE; // 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(DOT_LITERAL_PATTERN.split(packageName))); classLoader = getClassLoader(); URL localResourceURL; if (classLoader != null) { // if the application classloader is accessible, then directly retrieve the resource URL of // the qual package resource URLs must use slashes localResourceURL = classLoader.getResource(packageNameWithSlashes); // thread based application classloader, if needed in the future: // resourceURL = // Thread.currentThread().getContextClassLoader().getResource(packageNameWithSlashes); } else { // Signal failure to find resource localResourceURL = null; } if (localResourceURL == null) { // if the application classloader is not accessible (which means the checker class was loaded // using the bootstrap classloader) // or if the classloader didn't find the package, // 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 localResourceURL = getURLFromClasspaths(); } resourceURL = localResourceURL; supportedBundledAnnotationClasses = new LinkedHashSet<>(); 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() { // TODO: This method could probably be replaced with // io.github.classgraph.ClassGraph#getClasspathURIs() // 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 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 */ @SuppressWarnings("JdkObsolete") 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 + SLASH)) { 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 * that 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. extension paths (from java.ext.dirs) *
  2. classpaths (set in {@code CLASSPATH}, or through {@code -classpath} and {@code -cp}) *
  3. 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 extension paths String extdirs = System.getProperty("java.ext.dirs"); if (extdirs != null && !extdirs.isEmpty()) { paths.addAll(Arrays.asList(extdirs.split(File.pathSeparator))); } // add all paths in CLASSPATH, -cp, and -classpath paths.addAll(Arrays.asList(System.getProperty("java.class.path").split(File.pathSeparator))); // add all paths that are examined by the classloader if (classLoader != null) { URL[] urls = classLoader.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 URLClassLoader getClassLoader() { ClassLoader result = InternalUtils.getClassLoaderForClass(checker.getClass()); if (result instanceof URLClassLoader) { return (@Nullable URLClassLoader) result; } else { // Java 9+ use an internal classloader that doesn't support getting URLs. Ignore. return null; } } /** Debug Use: Displays all classpaths examined by the class loader. */ @SuppressWarnings("unused") // for debugging protected final void printPaths() { // all paths in Xbootclasspath String[] bootclassPaths = System.getProperty("sun.boot.class.path").split(File.pathSeparator); 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(File.pathSeparator); 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 processingEnv.getMessager().printMessage(Kind.NOTE, "java.class.path property:"); for (String path : System.getProperty("java.class.path").split(File.pathSeparator)) { processingEnv.getMessager().printMessage(Kind.NOTE, "\t" + path); } // add all paths that are examined by the classloader processingEnv.getMessager().printMessage(Kind.NOTE, "classloader examined paths:"); if (classLoader != null) { URL[] urls = classLoader.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"); } } /** * Loads the set of annotation classes in the qual directory of a checker shipped with the Checker * Framework. */ private void loadBundledAnnotationClasses() { // retrieve the fully qualified class names of the annotations Set<@BinaryName String> annotationNames; // see whether the resource URL has a protocol of jar or file if (resourceURL != null && resourceURL.getProtocol().contentEquals("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. JarURLConnection connection; // create a connection to the jar file try { connection = (JarURLConnection) resourceURL.openConnection(); // disable caching / connection sharing of the low level URLConnection to the Jar file connection.setDefaultUseCaches(false); connection.setUseCaches(false); // connect to the Jar file connection.connect(); } catch (IOException e) { throw new BugInCF( "AnnotationClassLoader: cannot open a connection to the Jar file " + resourceURL.getFile()); } // open up that jar file and extract annotation class names try (JarFile jarFile = connection.getJarFile()) { // get class names inside the jar file within the particular package annotationNames = getBundledAnnotationNamesFromJar(jarFile); } catch (IOException e) { throw new BugInCF( "AnnotationClassLoader: cannot open the Jar file " + resourceURL.getFile()); } } else if (resourceURL != null && resourceURL.getProtocol().contentEquals("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, packageDir, packageDir); } else { // We do not support a resource URL with any other protocols, so create an empty set. annotationNames = Collections.emptySet(); } if (annotationNames.isEmpty()) { PackageElement pkgEle = checker.getElementUtils().getPackageElement(packageName); if (pkgEle != null) { for (Element e : pkgEle.getEnclosedElements()) { if (e.getKind() == ElementKind.ANNOTATION_TYPE) { @SuppressWarnings("signature:assignment") // Elements needs to be annotated. @BinaryName String annoBinName = checker.getElementUtils().getBinaryName((TypeElement) e).toString(); annotationNames.add(annoBinName); } } } } supportedBundledAnnotationClasses.addAll(loadAnnotationClasses(annotationNames)); } /** * Gets the set of annotation classes in the qual directory of a checker shipped with the Checker * Framework. 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. * * @see AnnotatedTypeFactory#createSupportedTypeQualifiers() * @return a mutable set of the loaded bundled annotation classes */ public final Set> getBundledAnnotationClasses() { return supportedBundledAnnotationClasses; } /** * 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 */ @SuppressWarnings("JdkObsolete") private final Set<@BinaryName String> getBundledAnnotationNamesFromJar(final JarFile jar) { Set<@BinaryName String> 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; } String className = Signatures.classfilenameToBinaryName(je.getName()); // filter for qual package if (className.startsWith(packageName + DOT)) { // 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. This method returns null if the external annotation * class was loaded successfully but was deemed not supported by a checker. Errors are issued if * the external class is not an annotation, or if it could not be loaded successfully. * * @param annoName canonical name of an external annotation class, e.g. * "myproject.qual.myannotation" * @return the loaded annotation class, or null if it was not a supported annotation as decided by * {@link #isSupportedAnnotationClass(Class)} */ public final @Nullable Class loadExternalAnnotationClass( final @BinaryName String annoName) { return loadAnnotationClass(annoName, true); } /** * This method takes as input a fully qualified path to a directory, and loads and returns the set * of all supported 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<@BinaryName String> annoNames = getAnnotationNamesFromDirectory(null, rootDirectory, rootDirectory); return loadAnnotationClasses(annoNames); } /** * Retrieves all annotation names from the current directory, and recursively descends and * retrieves annotation names from sub-directories. * * @param packageName the name of the package that contains the qual package, or null * @param rootDirectory a {@link File} object representing 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 * @return a set fully qualified annotation class name, for annotations in the root directory or * its sub-directories */ @SuppressWarnings("signature") // TODO: reduce use of string manipulation private final Set<@BinaryName String> getAnnotationNamesFromDirectory( final @Nullable @DotSeparatedIdentifiers String packageName, final File rootDirectory, final File currentDirectory) { Set<@BinaryName String> results = new LinkedHashSet<>(); // Full path to root directory String rootPath = rootDirectory.getAbsolutePath(); // 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()) { // TODO: simplify all this string manipulation. // 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 qualPackage = null; if (!filePath.equals(rootPath)) { qualPackage = filePath.substring(rootPath.length() + 1, filePath.length()).replace(SLASH, DOT); } // Simple annotation name, which is the same as the file name (without directory) // but with file extension removed. @BinaryName String annotationName = fileName; if (fileName.lastIndexOf(DOT) != -1) { annotationName = fileName.substring(0, fileName.lastIndexOf(DOT)); } // Fully qualified annotation class name (a @BinaryName, not a @FullyQualifiedName) @BinaryName String fullyQualifiedAnnoName = Signatures.addPackage(packageName, Signatures.addPackage(qualPackage, annotationName)); if (fileName.endsWith(CLASS_SUFFIX)) { // 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)); } } return results; } /** * Loads the class indicated by the name, and checks to see if it is an annotation that is * supported by a checker. * * @param className the name of the class, in binary name format * @param issueError set to true to issue a warning when a loaded annotation is not a type * annotation. It is useful to set this to true if a given annotation must be a well-defined * type annotation (eg for annotation class names given as command line arguments). It should * be set to false if the annotation is a meta-annotation or non-type annotation. * @return the loaded annotation class if it has a {@code @Target} meta-annotation with the * required ElementType values, and is a supported annotation by a checker. If the annotation * is not supported by a checker, null is returned. */ protected final @Nullable Class loadAnnotationClass( final @BinaryName String className, boolean issueError) { // load the class Class cls = null; try { if (classLoader != null) { cls = Class.forName(className, true, classLoader); } else { cls = Class.forName(className); } } catch (ClassNotFoundException e) { throw new UserError( checker.getClass().getSimpleName() + ": could not load class for annotation: " + className + ". Ensure that it is a type annotation" + " and your classpath is correct."); } // If the freshly loaded class is not an annotation, then issue error if required and then // return null if (!cls.isAnnotation()) { if (issueError) { throw new UserError( checker.getClass().getSimpleName() + ": the loaded class: " + cls.getCanonicalName() + " is not a type annotation."); } return null; } Class annoClass = cls.asSubclass(Annotation.class); // Check the loaded annotation to see if it has a @Target meta-annotation with the required // ElementType values if (hasWellDefinedTargetMetaAnnotation(annoClass)) { // If so, return the loaded annotation if it is supported by a checker return isSupportedAnnotationClass(annoClass) ? annoClass : null; } else if (issueError) { // issueError is set to true for loading explicitly named external annotations. // We issue an error here when one of those annotations is not well-defined, since the // user expects these external annotations to be loaded. throw new UserError( checker.getClass().getSimpleName() + ": the loaded annotation: " + annoClass.getCanonicalName() + " is not a type annotation." + " Check its @Target meta-annotation."); } else { // issueError is set to false for loading the qual directory or any external directories. // We don't issue any errors since there may be meta-annotations or non-type annotations // in such directories. return null; } } /** * Loads a set of annotations indicated by their names. * * @param annoNames a set of binary names for annotation classes * @return a set of loaded annotation classes * @see #loadAnnotationClass(String, boolean) */ protected final Set> loadAnnotationClasses( final @Nullable Set<@BinaryName String> annoNames) { Set> loadedClasses = new LinkedHashSet<>(); if (annoNames != null && !annoNames.isEmpty()) { // loop through each class name & load the class for (String annoName : annoNames) { Class annoClass = loadAnnotationClass(annoName, false); if (annoClass != null) { loadedClasses.add(annoClass); } } } return loadedClasses; } /** * Checks to see whether a particular annotation class has the {@link Target} meta-annotation, and * has the required {@link ElementType} values. * *

A subclass may override this method to load annotations that are not intended to be * annotated in source code. E.g.: {@code SubtypingChecker} overrides this method to load {@code * Unqualified}. * * @param annoClass an annotation class * @return true if the annotation is well defined, false if it isn't */ protected boolean hasWellDefinedTargetMetaAnnotation( final Class annoClass) { return annoClass.getAnnotation(Target.class) != null && AnnotationUtils.hasTypeQualifierElementTypes( annoClass.getAnnotation(Target.class).value(), annoClass); } /** * Checks to see whether a particular annotation class is supported. * *

By default, all loaded annotations that pass the basic checks in {@link * #loadAnnotationClass(String, boolean)} are supported. * *

Individual checkers can create a subclass of AnnotationClassLoader and override this method * to indicate whether a particular annotation is supported. * * @param annoClass an annotation class * @return true if the annotation is supported, false if it isn't */ protected boolean isSupportedAnnotationClass(final Class annoClass) { return true; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy