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.AnnotationMirror;
import javax.tools.Diagnostic.Kind;
import org.checkerframework.common.basetype.BaseTypeChecker;
import org.checkerframework.framework.util.AnnotatedTypes;
import org.checkerframework.framework.util.AnnotationBuilder;
import org.checkerframework.javacutil.ErrorReporter;
import org.checkerframework.javacutil.InternalUtils;
/*>>>
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.
*
* <p>To load annotations using this class, their directory structure and package structure must be
* identical.
*
* <p>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()}.
*
* <p>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<String> 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. Call {@link #getLoadedAnnotationClasses} rather than using
* this field directly as it may be null.
*/
private Set<Class<? extends Annotation>> 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() != null
? checker.getClass().getPackage().getName() + QUAL_PACKAGE_SUFFIX
: QUAL_PACKAGE_SUFFIX.substring(1);
// 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<String>();
// 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)));
// Only load annotations if requested. This avoids issuing an error
// if the qual package contains an annotation that is not a qualifier,
// but the checker does not try to use it as a qualifier.
loadedAnnotations = null;
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();
}
}
/**
* 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<String> 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
*/
private final boolean checkJarForPackage(final JarFile jar) {
Enumeration<JarEntry> 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)
*
* <p>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
*
* <p>If there's no more segments left, then we've found the qual directory of interest
*
* <p>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<String> 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
*
* <p>The classpaths will be obtained in the order of:
*
* <ol>
* <li>extension paths (from java.ext.dirs)
* <li>classpaths (set in {@code CLASSPATH}, or through {@code -classpath} and {@code -cp})
* <li>paths accessible and examined by the classloader
* </ol>
*
* 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<String> getClasspaths() {
Set<String> paths = new LinkedHashSet<String>();
// 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() {
return InternalUtils.getClassLoaderForClass(checker.getClass());
}
/** Debug Use Displays all classpaths */
@SuppressWarnings("unused") // for debugging
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<Class<? extends Annotation>> getLoadedAnnotationClasses() {
if (loadedAnnotations == null) {
loadedAnnotations = new LinkedHashSet<Class<? extends Annotation>>();
if (resourceURL == null) {
// if there's no resourceURL, then there's nothing we can load
return loadedAnnotations;
}
// retrieve the fully qualified class names of the annotations
Set<String> 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));
}
return loadedAnnotations;
}
/**
* 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<String> getBundledAnnotationNamesFromJar(final JarFile jar) {
Set<String> annos = new LinkedHashSet<String>();
// get an enumeration iterator for all the content entries in the jar
// file
Enumeration<JarEntry> 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<? extends Annotation> loadExternalAnnotationClass(
final String annoName) {
try {
final Class<? extends Annotation> annoClass =
Class.forName(annoName, true, getAppClassLoader()).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<Class<? extends Annotation>> loadExternalAnnotationClassesFromDirectory(
final String dirName) {
File rootDirectory = new File(dirName);
Set<String> 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<String> getAnnotationNamesFromDirectory(
final String packageName,
final String rootDirectory,
final File currentDirectory,
final String fileExtension) {
Set<String> results = new LinkedHashSet<String>();
// check every file and directory within the current directory
File[] directoryContents = currentDirectory.listFiles();
Arrays.sort(
directoryContents,
new Comparator<File>() {
@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<? extends Annotation> loadAnnotationClass(
final String fullyQualifiedClassName) {
Class<?> cls = null;
try {
// load the class
cls = Class.forName(fullyQualifiedClassName, true, getAppClassLoader());
} 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<? extends Annotation> 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<Class<? extends Annotation>> loadAnnotationClasses(
final /*@Nullable*/ Set<String> fullyQualifiedAnnoNames) {
Set<Class<? extends Annotation>> loadedClasses =
new LinkedHashSet<Class<? extends Annotation>>();
if (fullyQualifiedAnnoNames != null && !fullyQualifiedAnnoNames.isEmpty()) {
// loop through each class name & load the class
for (String fullyQualifiedAnnoName : fullyQualifiedAnnoNames) {
Class<? extends Annotation> annoClass = loadAnnotationClass(fullyQualifiedAnnoName);
if (annoClass != null) {
loadedClasses.add(annoClass);
}
}
}
return loadedClasses;
}
/**
* Checks to see whether a particular annotation class is supported.
*
* <p>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<? extends Annotation> annoClass) {
if (getLoadedAnnotationClasses().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);
}
}
}