package org.checkerframework.framework.stub;
/*>>>
import org.checkerframework.checker.nullness.qual.*;
*/
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.ElementFilter;
import javax.lang.model.util.Elements;
import org.checkerframework.framework.qual.FromStubFile;
import org.checkerframework.framework.type.AnnotatedTypeFactory;
import org.checkerframework.framework.type.AnnotatedTypeMirror;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType;
import org.checkerframework.framework.type.visitor.AnnotatedTypeMerger;
import org.checkerframework.framework.util.AnnotationBuilder;
import org.checkerframework.javacutil.AnnotationUtils;
import org.checkerframework.javacutil.ElementUtils;
import org.checkerframework.javacutil.ErrorReporter;
import org.checkerframework.javacutil.Pair;
import org.checkerframework.javacutil.TypesUtils;
import org.checkerframework.stubparser.JavaParser;
import org.checkerframework.stubparser.ast.CompilationUnit;
import org.checkerframework.stubparser.ast.ImportDeclaration;
import org.checkerframework.stubparser.ast.IndexUnit;
import org.checkerframework.stubparser.ast.PackageDeclaration;
import org.checkerframework.stubparser.ast.TypeParameter;
import org.checkerframework.stubparser.ast.body.BodyDeclaration;
import org.checkerframework.stubparser.ast.body.ClassOrInterfaceDeclaration;
import org.checkerframework.stubparser.ast.body.ConstructorDeclaration;
import org.checkerframework.stubparser.ast.body.FieldDeclaration;
import org.checkerframework.stubparser.ast.body.MethodDeclaration;
import org.checkerframework.stubparser.ast.body.Parameter;
import org.checkerframework.stubparser.ast.body.TypeDeclaration;
import org.checkerframework.stubparser.ast.body.VariableDeclarator;
import org.checkerframework.stubparser.ast.expr.AnnotationExpr;
import org.checkerframework.stubparser.ast.expr.ArrayInitializerExpr;
import org.checkerframework.stubparser.ast.expr.BooleanLiteralExpr;
import org.checkerframework.stubparser.ast.expr.Expression;
import org.checkerframework.stubparser.ast.expr.FieldAccessExpr;
import org.checkerframework.stubparser.ast.expr.IntegerLiteralExpr;
import org.checkerframework.stubparser.ast.expr.MarkerAnnotationExpr;
import org.checkerframework.stubparser.ast.expr.MemberValuePair;
import org.checkerframework.stubparser.ast.expr.NameExpr;
import org.checkerframework.stubparser.ast.expr.NormalAnnotationExpr;
import org.checkerframework.stubparser.ast.expr.SingleMemberAnnotationExpr;
import org.checkerframework.stubparser.ast.expr.StringLiteralExpr;
import org.checkerframework.stubparser.ast.type.ClassOrInterfaceType;
import org.checkerframework.stubparser.ast.type.ReferenceType;
import org.checkerframework.stubparser.ast.type.Type;
import org.checkerframework.stubparser.ast.type.WildcardType;
/** Main entry point is: {@link StubParser#parse(Map, Map)} */
// Full entry point signature:
// parse(Map<Element, AnnotatedTypeMirror>, Map<String, Set<AnnotationMirror>>)}
public class StubParser {
/**
* Whether to print warnings about types/members that were not found. The warning is about
* whether a class/field in the stub file is not found on the user's real classpath. Since the
* stub file may contain packages that are not on the classpath, this can be OK, so default to
* false.
*/
private final boolean warnIfNotFound;
/** Whether to print warnings about stub files that overwrite annotations from bytecode. */
private final boolean warnIfStubOverwritesBytecode;
private final boolean debugStubParser;
/** The file being parsed (makes error messages more informative). */
private final String filename;
private final IndexUnit index;
private final ProcessingEnvironment processingEnv;
private final AnnotatedTypeFactory atypeFactory;
private final Elements elements;
/**
* The supported annotations. Keys are simple (unqualified) names. (This may be a problem in the
* unlikely occurrence that a type-checker supports two annotations with the same simple name.)
*/
private final Map<String, AnnotationMirror> supportedAnnotations;
/** A list of imports that are not annotation types. Used for importing enums. */
private final List<String> imports;
/**
* Mapping of a field access expression that has already been encountered to the resolved
* variable element.
*/
private final Map<FieldAccessExpr, VariableElement> faexprcache;
/**
* Mapping of a name access expression that has already been encountered to the resolved
* variable element.
*/
private final Map<NameExpr, VariableElement> nexprcache;
/** Annotation to added to every method and constructor in the stub file. */
private final AnnotationMirror fromStubFile;
/**
* List of AnnotatedTypeMirrors for class or method type parameters that are in scope of the
* elements currently parsed.
*/
private final List<AnnotatedTypeVariable> typeParameters = new ArrayList<>();
/**
* @param filename name of stub file
* @param inputStream of stub file to parse
* @param factory AnnotatedtypeFactory to use
* @param env ProcessingEnviroment to use
*/
public StubParser(
String filename,
InputStream inputStream,
AnnotatedTypeFactory factory,
ProcessingEnvironment env) {
this.filename = filename;
this.atypeFactory = factory;
this.processingEnv = env;
this.elements = env.getElementUtils();
imports = new ArrayList<String>();
// getSupportedAnnotations uses these for warnings
Map<String, String> options = env.getOptions();
this.warnIfNotFound = options.containsKey("stubWarnIfNotFound");
this.warnIfStubOverwritesBytecode = options.containsKey("stubWarnIfOverwritesBytecode");
this.debugStubParser = options.containsKey("stubDebug");
if (debugStubParser) {
stubDebug(String.format("parsing stub file %s%n", filename));
}
IndexUnit parsedindex;
try {
parsedindex = JavaParser.parse(inputStream);
} catch (Exception e) {
ErrorReporter.errorAbort(
"StubParser: exception from JavaParser.parse for file " + filename, e);
parsedindex = null; // dead code, but needed for def. assignment checks
}
this.index = parsedindex;
// getSupportedAnnotations also sets imports. This should be refactored to be nicer.
supportedAnnotations = getSupportedAnnotations();
if (supportedAnnotations.isEmpty()) {
stubWarnIfNotFound(
String.format(
"No supported annotations found! This likely means stub file %s doesn't import them correctly.",
filename));
}
faexprcache = new HashMap<FieldAccessExpr, VariableElement>();
nexprcache = new HashMap<NameExpr, VariableElement>();
this.fromStubFile = AnnotationUtils.fromClass(elements, FromStubFile.class);
}
/** All annotations defined in the package. Keys are simple names. */
private Map<String, AnnotationMirror> annosInPackage(PackageElement packageElement) {
return createImportedAnnotationsMap(
ElementFilter.typesIn(packageElement.getEnclosedElements()));
}
/** All annotations declarations nested inside of a class. */
private Map<String, AnnotationMirror> annosInType(TypeElement typeElement) {
return createImportedAnnotationsMap(
ElementFilter.typesIn(typeElement.getEnclosedElements()));
}
private Map<String, AnnotationMirror> createImportedAnnotationsMap(
List<TypeElement> typeElements) {
Map<String, AnnotationMirror> r = new HashMap<String, AnnotationMirror>();
for (TypeElement typeElm : typeElements) {
if (typeElm.getKind() == ElementKind.ANNOTATION_TYPE) {
AnnotationMirror anno =
AnnotationUtils.fromName(elements, typeElm.getQualifiedName());
putNew(r, typeElm.getSimpleName().toString(), anno);
}
}
return r;
}
/**
* Get all members of a Type that are useful in a stub file. Currently these are values of
* enums, or compile time constants.
*
* @return a list fully qualified member names
*/
private static List<String> getImportableMembers(TypeElement typeElement) {
List<String> result = new ArrayList<String>();
List<VariableElement> memberElements =
ElementFilter.fieldsIn(typeElement.getEnclosedElements());
for (VariableElement varElement : memberElements) {
if (varElement.getConstantValue() != null
|| varElement.getKind() == ElementKind.ENUM_CONSTANT) {
result.add(
String.format(
"%s.%s",
typeElement.getQualifiedName().toString(),
varElement.getSimpleName().toString()));
}
}
return result;
}
/** @see #supportedAnnotations */
private Map<String, AnnotationMirror> getSupportedAnnotations() {
assert !index.getCompilationUnits().isEmpty();
CompilationUnit cu = index.getCompilationUnits().get(0);
Map<String, AnnotationMirror> result = new HashMap<String, AnnotationMirror>();
if (cu.getImports() == null) {
return result;
}
for (ImportDeclaration importDecl : cu.getImports()) {
String imported = importDecl.getName().toString();
try {
if (importDecl.isAsterisk()) {
// Static determines if we are importing members
// of a type (class or interface) or of a package
if (importDecl.isStatic()) {
// Members of a type (according to JLS)
TypeElement element = findType(imported, "Imported type not found");
if (element != null) {
// Find nested annotations
// Find compile time constant fields, or values of an enum
putAllNew(result, annosInType(element));
imports.addAll(getImportableMembers(element));
}
} else {
// Members of a package (according to JLS)
PackageElement element = findPackage(imported);
if (element != null) {
putAllNew(result, annosInPackage(element));
}
}
} else {
// Process a single import
final TypeElement importType = elements.getTypeElement(imported);
if (importType == null && !importDecl.isStatic()) {
// Class or nested class (according to JSL), but we can't resolve
stubWarnIfNotFound("Imported type not found: " + imported);
} else if (importType == null) {
// Nested Field
Pair<String, String> typeParts = StubUtil.partitionQualifiedName(imported);
String type = typeParts.first;
String fieldName = typeParts.second;
TypeElement enclType =
findType(
type,
String.format(
"Enclosing type of static field %s not found",
fieldName));
if (enclType != null) {
if (findFieldElement(enclType, fieldName) != null) {
imports.add(imported);
}
}
} else if (importType.getKind() == ElementKind.ANNOTATION_TYPE) {
// Single annotation or nested annotation
AnnotationMirror anno = AnnotationUtils.fromName(elements, imported);
if (anno != null) {
Element annoElt = anno.getAnnotationType().asElement();
putNew(result, annoElt.getSimpleName().toString(), anno);
} else {
stubWarnIfNotFound("Could not load import: " + imported);
}
} else {
// Class or nested class
imports.add(imported);
}
}
} catch (AssertionError error) {
stubWarnIfNotFound("" + error);
}
}
return result;
}
/** The main entry point. Side-effects the arguments. */
public void parse(
Map<Element, AnnotatedTypeMirror> atypes,
Map<String, Set<AnnotationMirror>> declAnnos) {
parse(this.index, atypes, declAnnos);
}
private void parse(
IndexUnit index,
Map<Element, AnnotatedTypeMirror> atypes,
Map<String, Set<AnnotationMirror>> declAnnos) {
for (CompilationUnit cu : index.getCompilationUnits()) {
parse(cu, atypes, declAnnos);
}
}
private CompilationUnit theCompilationUnit;
private void parse(
CompilationUnit cu,
Map<Element, AnnotatedTypeMirror> atypes,
Map<String, Set<AnnotationMirror>> declAnnos) {
theCompilationUnit = cu;
final String packageName;
final List<AnnotationExpr> packageAnnos;
if (cu.getPackage() == null) {
packageName = null;
packageAnnos = null;
} else {
packageName = cu.getPackage().getName().toString();
packageAnnos = cu.getPackage().getAnnotations();
parsePackage(cu.getPackage(), atypes, declAnnos);
}
if (cu.getTypes() != null) {
for (TypeDeclaration typeDecl : cu.getTypes()) {
parse(typeDecl, packageName, packageAnnos, atypes, declAnnos);
}
}
}
private void parsePackage(
PackageDeclaration packDecl,
Map<Element, AnnotatedTypeMirror> atypes,
Map<String, Set<AnnotationMirror>> declAnnos) {
assert (packDecl != null);
String packageName = packDecl.getName().toString();
Element elem = elements.getPackageElement(packageName);
// If the element lookup fails, it's because we have an annotation for a
// package that isn't on the classpath, which is fine.
if (elem != null) {
annotateDecl(declAnnos, elem, packDecl.getAnnotations());
}
// TODO: Handle atypes???
}
// typeDecl's name may be a binary name such as "A$B".
// That is a hack because the StubParser does not handle nested classes.
private void parse(
TypeDeclaration typeDecl,
String packageName,
List<AnnotationExpr> packageAnnos,
Map<Element, AnnotatedTypeMirror> atypes,
Map<String, Set<AnnotationMirror>> declAnnos) {
// Fully-qualified name of the type being parsed
String typeName =
(packageName == null ? "" : packageName + ".")
+ typeDecl.getName().replace('$', '.');
TypeElement typeElt = elements.getTypeElement(typeName);
// couldn't find type. not in class path
if (typeElt == null) {
boolean warn = true;
if (typeDecl.getAnnotations() != null) {
for (AnnotationExpr anno : typeDecl.getAnnotations()) {
if (anno.getName().getName().contentEquals("NoStubParserWarning")) {
warn = false;
}
}
}
if (packageAnnos != null) {
for (AnnotationExpr anno : packageAnnos) {
if (anno.getName().getName().contentEquals("NoStubParserWarning")) {
warn = false;
}
}
}
warn = warn || debugStubParser;
if (warn) {
stubWarnIfNotFound("Type not found: " + typeName);
}
return;
}
if (typeElt.getKind() == ElementKind.ENUM) {
stubWarnIfNotFound("Skipping enum type: " + typeName);
} else if (typeElt.getKind() == ElementKind.ANNOTATION_TYPE) {
stubWarnIfNotFound("Skipping annotation type: " + typeName);
} else if (typeDecl instanceof ClassOrInterfaceDeclaration) {
typeParameters.addAll(
parseType((ClassOrInterfaceDeclaration) typeDecl, typeElt, atypes, declAnnos));
} // else it's an EmptyTypeDeclaration. TODO: An EmptyTypeDeclaration can have annotations, right?
Map<Element, BodyDeclaration> elementsToDecl = getMembers(typeElt, typeDecl);
for (Map.Entry<Element, BodyDeclaration> entry : elementsToDecl.entrySet()) {
final Element elt = entry.getKey();
final BodyDeclaration decl = entry.getValue();
if (elt.getKind().isField()) {
parseField((FieldDeclaration) decl, (VariableElement) elt, atypes, declAnnos);
} else if (elt.getKind() == ElementKind.CONSTRUCTOR) {
parseConstructor(
(ConstructorDeclaration) decl, (ExecutableElement) elt, atypes, declAnnos);
} else if (elt.getKind() == ElementKind.METHOD) {
parseMethod((MethodDeclaration) decl, (ExecutableElement) elt, atypes, declAnnos);
} else {
/* do nothing */
stubWarnIfNotFound("StubParser ignoring: " + elt);
}
}
typeParameters.clear();
}
/** @return list of AnnotatedTypeVariable of the type's type parameter declarations */
private List<AnnotatedTypeVariable> parseType(
ClassOrInterfaceDeclaration decl,
TypeElement elt,
Map<Element, AnnotatedTypeMirror> atypes,
Map<String, Set<AnnotationMirror>> declAnnos) {
annotateDecl(declAnnos, elt, decl.getAnnotations());
AnnotatedDeclaredType type = atypeFactory.fromElement(elt);
annotate(type, decl.getAnnotations());
final List<? extends AnnotatedTypeMirror> typeArguments = type.getTypeArguments();
final List<TypeParameter> typeParameters = decl.getTypeParameters();
// It can be the case that args=[] and params=null.
// if ((typeParameters == null) != (typeArguments == null)) {
// throw new Error(String.format("parseType (%s, %s): inconsistent nullness for args and params%n args = %s%n params = %s%n", decl, elt, typeArguments, typeParameters));
// }
if ((typeParameters == null) && (typeArguments.size() != 0)) {
// TODO: Class EventListenerProxy in Java 6 does not have type parameters, but in Java 7 does.
// To handle both with one specification, we currently ignore the problem.
// Investigate what a cleaner solution is, e.g. having a separate Java 7 specification that overrides
// the Java 6 specification.
// System.out.printf("Dying. theCompilationUnit=%s%n", theCompilationUnit);
if (debugStubParser) {
stubDebug(
String.format(
"parseType: mismatched sizes for params and args%n decl=%s%n typeParameters=%s%n elt=%s (%s)%n type=%s (%s)%n typeArguments (size %d)=%s%n theCompilationUnit=%s%nEnd of message for parseType: mismatched sizes for params and args%n",
decl,
typeParameters,
elt,
elt.getClass(),
type,
type.getClass(),
typeArguments.size(),
typeArguments,
theCompilationUnit));
}
/*
throw new Error(String.format("parseType: mismatched sizes for params and args%n decl=%s%n typeParameters=%s%n elt=%s (%s)%n type=%s (%s)%n typeArguments (size %d)=%s%n",
decl, typeParameters,
elt, elt.getClass(), type, type.getClass(), typeArguments.size(), typeArguments));
*/
}
if ((typeParameters != null) && (typeParameters.size() != typeArguments.size())) {
// TODO: decide how severe this problem really is; see comment above.
// System.out.printf("Dying. theCompilationUnit=%s%n", theCompilationUnit);
if (debugStubParser) {
stubDebug(
String.format(
"parseType: mismatched sizes for params and args%n decl=%s%n typeParameters (size %d)=%s%n elt=%s (%s)%n type=%s (%s)%n typeArguments (size %d)=%s%n theCompilationUnit=%s%nEnd of message for parseType: mismatched sizes for params and args%n",
decl,
typeParameters.size(),
typeParameters,
elt,
elt.getClass(),
type,
type.getClass(),
typeArguments.size(),
typeArguments,
theCompilationUnit));
}
/*
throw new Error(String.format("parseType: mismatched sizes for params and args%n decl=%s%n typeParameters (size %d)=%s%n elt=%s (%s)%n type=%s (%s)%n typeArguments (size %d)=%s%n",
decl, typeParameters.size(), typeParameters,
elt, elt.getClass(), type, type.getClass(), typeArguments.size(), typeArguments));
*/
}
annotateTypeParameters(atypes, typeArguments, typeParameters);
annotateSupertypes(decl, type);
putNew(atypes, elt, type);
List<AnnotatedTypeVariable> typeVariables = new ArrayList<>();
for (AnnotatedTypeMirror typeV : type.getTypeArguments()) {
if (typeV.getKind() != TypeKind.TYPEVAR) {
stubAlwaysWarn(
"Expected an AnnotatedTypeVariable but found type kind"
+ typeV.getKind()
+ ": "
+ typeV);
} else {
typeVariables.add((AnnotatedTypeVariable) typeV);
}
}
return typeVariables;
}
private void annotateSupertypes(
ClassOrInterfaceDeclaration typeDecl, AnnotatedDeclaredType type) {
if (typeDecl.getExtends() != null) {
for (ClassOrInterfaceType superType : typeDecl.getExtends()) {
AnnotatedDeclaredType foundType = findType(superType, type.directSuperTypes());
assert foundType != null
: "StubParser: could not find superclass "
+ superType
+ " from type "
+ type
+ "\nStub file does not match bytecode";
if (foundType != null) {
annotate(foundType, superType);
}
}
}
if (typeDecl.getImplements() != null) {
for (ClassOrInterfaceType superType : typeDecl.getImplements()) {
AnnotatedDeclaredType foundType = findType(superType, type.directSuperTypes());
// TODO: Java 7 added a few AutoCloseable superinterfaces to classes.
// We specify those as superinterfaces in the jdk.astub file. Let's ignore
// this addition to be compatible with Java 6.
assert foundType != null
|| (superType.toString().equals("AutoCloseable")
|| superType.toString().equals("java.io.Closeable")
|| superType.toString().equals("Closeable"))
: "StubParser: could not find superinterface "
+ superType
+ " from type "
+ type
+ "\nStub file does not match bytecode";
if (foundType != null) {
annotate(foundType, superType);
}
}
}
}
private void parseMethod(
MethodDeclaration decl,
ExecutableElement elt,
Map<Element, AnnotatedTypeMirror> atypes,
Map<String, Set<AnnotationMirror>> declAnnos) {
annotateDecl(declAnnos, elt, decl.getAnnotations());
// StubParser parses all annotations in type annotation position as type annotations
annotateDecl(declAnnos, elt, decl.getType().getAnnotations());
addDeclAnnotations(declAnnos, elt);
AnnotatedExecutableType methodType = atypeFactory.fromElement(elt);
annotateTypeParameters(atypes, methodType.getTypeVariables(), decl.getTypeParameters());
typeParameters.addAll(methodType.getTypeVariables());
annotate(methodType.getReturnType(), decl.getType());
List<Parameter> params = decl.getParameters();
List<? extends VariableElement> paramElts = elt.getParameters();
List<? extends AnnotatedTypeMirror> paramTypes = methodType.getParameterTypes();
for (int i = 0; i < methodType.getParameterTypes().size(); ++i) {
VariableElement paramElt = paramElts.get(i);
AnnotatedTypeMirror paramType = paramTypes.get(i);
Parameter param = params.get(i);
annotateDecl(declAnnos, paramElt, param.getAnnotations());
annotateDecl(declAnnos, paramElt, param.getType().getAnnotations());
if (param.isVarArgs()) {
// workaround
assert paramType.getKind() == TypeKind.ARRAY;
annotate(((AnnotatedArrayType) paramType).getComponentType(), param.getType());
} else {
annotate(paramType, param.getType());
}
}
if (methodType.getReceiverType() == null
&& decl.getReceiverAnnotations() != null
&& !decl.getReceiverAnnotations().isEmpty()) {
stubAlwaysWarn(
String.format(
"parseMethod: static methods cannot have receiver annotations%n"
+ "Method: %s%n"
+ "Receiver annotations: %s",
methodType, decl.getReceiverAnnotations()));
} else {
annotate(methodType.getReceiverType(), decl.getReceiverAnnotations());
}
putNew(atypes, elt, methodType);
typeParameters.removeAll(methodType.getTypeVariables());
}
/**
* Handle existing annotations on the type. Stub files should override the existing annotations
* on a type. Using {@code replaceAnnotation} is usually good enough to achieve this; however,
* for annotations on type variables, the stub file sometimes needs to be able to remove an
* existing annotation, leaving no annotation on the type variable. This method achieves this by
* calling {@code clearAnnotations}.
*
* @param atype the type to modify
* @param typeDef the type from the stub file, for warnings
*/
private void handleExistingAnnotations(AnnotatedTypeMirror atype, Type typeDef) {
Set<AnnotationMirror> annos = atype.getAnnotations();
if (annos != null && !annos.isEmpty() && !"flow.astub".equals(filename)) {
// TODO: instead of comparison against flow.astub, this should
// check whether the stub file is @AnnotatedFor the current type system.
// flow.astub isn't annotated for any particular type system, so let's
// not warn for now, as @AnnotatedFor isn't integrated in stub files yet.
stubWarnIfOverwritesBytecode(
String.format(
"in file %s at line %s ignored existing annotations on type: %s%n",
filename.substring(filename.lastIndexOf('/') + 1),
typeDef.getBeginLine(),
atype.toString(true)));
// TODO: filename is the simple "jdk.astub" and "flow.astub" for those pre-defined files,
// without complete path, but the full path in other situations.
// All invocations should provide the short path or the full path.
// For testing it is easier if only the file name is used.
// Clear existing annotations, which only makes a difference for
// type variables, but doesn't hurt in other cases.
atype.clearAnnotations();
}
}
/** Adds a declAnnotation to every method in the stub file. */
private void addDeclAnnotations(Map<String, Set<AnnotationMirror>> declAnnos, Element elt) {
if (fromStubFile != null) {
Set<AnnotationMirror> annos = declAnnos.get(ElementUtils.getVerboseName(elt));
if (annos == null) {
annos = AnnotationUtils.createAnnotationSet();
putOrAddToMap(declAnnos, ElementUtils.getVerboseName(elt), annos);
}
annos.add(fromStubFile);
}
}
/**
* List of all array component types. Example input: int[][] Example output: int, int[], int[][]
*/
private List<AnnotatedTypeMirror> arrayAllComponents(AnnotatedArrayType atype) {
LinkedList<AnnotatedTypeMirror> arrays = new LinkedList<AnnotatedTypeMirror>();
AnnotatedTypeMirror type = atype;
while (type.getKind() == TypeKind.ARRAY) {
arrays.addFirst(type);
type = ((AnnotatedArrayType) type).getComponentType();
}
arrays.add(type);
return arrays;
}
private void annotateAsArray(AnnotatedArrayType atype, ReferenceType typeDef) {
List<AnnotatedTypeMirror> arrayTypes = arrayAllComponents(atype);
assert typeDef.getArrayCount() == arrayTypes.size() - 1
||
// We want to allow simply using "Object" as return type of a
// method, regardless of what the real type is.
typeDef.getArrayCount() == 0
: "Mismatched array lengths; typeDef: "
+ typeDef.getArrayCount()
+ " vs. arrayTypes: "
+ (arrayTypes.size() - 1)
+ "\n typedef: "
+ typeDef
+ "\n arraytypes: "
+ arrayTypes;
/* Separate TODO: the check for zero above ensures that "Object" can be
* used as return type, even when the real method uses something else.
* However, why was this needed for the RequiredPermissions declaration annotation?
* It looks like the StubParser ignored the target for annotations.
*/
for (int i = 0; i < typeDef.getArrayCount(); ++i) {
List<AnnotationExpr> annotations = typeDef.getAnnotationsAtLevel(i);
handleExistingAnnotations(arrayTypes.get(i), typeDef);
if (annotations != null) {
annotate(arrayTypes.get(i), annotations);
}
}
// handle generic type on base
handleExistingAnnotations(arrayTypes.get(arrayTypes.size() - 1), typeDef);
annotate(arrayTypes.get(arrayTypes.size() - 1), typeDef.getAnnotations());
}
private ClassOrInterfaceType unwrapDeclaredType(Type type) {
if (type instanceof ClassOrInterfaceType) {
return (ClassOrInterfaceType) type;
} else if (type instanceof ReferenceType && ((ReferenceType) type).getArrayCount() == 0) {
return unwrapDeclaredType(((ReferenceType) type).getType());
} else {
return null;
}
}
private void annotate(AnnotatedTypeMirror atype, Type typeDef) {
if (atype.getKind() == TypeKind.ARRAY) {
annotateAsArray((AnnotatedArrayType) atype, (ReferenceType) typeDef);
return;
}
handleExistingAnnotations(atype, typeDef);
ClassOrInterfaceType declType = unwrapDeclaredType(typeDef);
if (atype.getKind() == TypeKind.DECLARED && declType != null) {
AnnotatedDeclaredType adeclType = (AnnotatedDeclaredType) atype;
if (declType.getTypeArgs() != null
&& !declType.getTypeArgs().isEmpty()
&& !adeclType.getTypeArguments().isEmpty()) {
assert declType.getTypeArgs().size() == adeclType.getTypeArguments().size()
: String.format(
"Mismatch in type argument size between %s (%d) and %s (%d)",
declType,
declType.getTypeArgs().size(),
adeclType,
adeclType.getTypeArguments().size());
for (int i = 0; i < declType.getTypeArgs().size(); ++i) {
annotate(adeclType.getTypeArguments().get(i), declType.getTypeArgs().get(i));
}
}
} else if (atype.getKind() == TypeKind.WILDCARD) {
AnnotatedWildcardType wildcardType = (AnnotatedWildcardType) atype;
WildcardType wildcardDef = (WildcardType) typeDef;
if (wildcardDef.getExtends() != null) {
annotate(wildcardType.getExtendsBound(), wildcardDef.getExtends());
annotate(wildcardType.getSuperBound(), typeDef.getAnnotations());
} else if (wildcardDef.getSuper() != null) {
annotate(wildcardType.getSuperBound(), wildcardDef.getSuper());
annotate(wildcardType.getExtendsBound(), typeDef.getAnnotations());
} else {
annotate(atype, typeDef.getAnnotations());
}
} else if (atype.getKind() == TypeKind.TYPEVAR) {
//Add annotations from the declaration of the TypeVariable
AnnotatedTypeVariable typeVarUse = (AnnotatedTypeVariable) atype;
for (AnnotatedTypeVariable typePar : typeParameters) {
if (typePar.getUnderlyingType() == atype.getUnderlyingType()) {
AnnotatedTypeMerger.merge(typePar.getUpperBound(), typeVarUse.getUpperBound());
AnnotatedTypeMerger.merge(typePar.getLowerBound(), typeVarUse.getLowerBound());
}
}
}
if (typeDef.getAnnotations() != null && atype.getKind() != TypeKind.WILDCARD) {
annotate(atype, typeDef.getAnnotations());
}
}
private void parseConstructor(
ConstructorDeclaration decl,
ExecutableElement elt,
Map<Element, AnnotatedTypeMirror> atypes,
Map<String, Set<AnnotationMirror>> declAnnos) {
annotateDecl(declAnnos, elt, decl.getAnnotations());
AnnotatedExecutableType methodType = atypeFactory.fromElement(elt);
addDeclAnnotations(declAnnos, elt);
annotate(methodType.getReturnType(), decl.getAnnotations());
for (int i = 0; i < methodType.getParameterTypes().size(); ++i) {
AnnotatedTypeMirror paramType = methodType.getParameterTypes().get(i);
Parameter param = decl.getParameters().get(i);
annotate(paramType, param.getType());
}
if (methodType.getReceiverType() == null
&& decl.getReceiverAnnotations() != null
&& !decl.getReceiverAnnotations().isEmpty()) {
stubAlwaysWarn(
String.format(
"parseConstructor: constructor of a top-level class cannot have receiver annotations%n"
+ "Constructor: %s%n"
+ "Receiver annotations: %s",
methodType, decl.getReceiverAnnotations()));
} else {
annotate(methodType.getReceiverType(), decl.getReceiverAnnotations());
}
putNew(atypes, elt, methodType);
}
private void parseField(
FieldDeclaration decl,
VariableElement elt,
Map<Element, AnnotatedTypeMirror> atypes,
Map<String, Set<AnnotationMirror>> declAnnos) {
addDeclAnnotations(declAnnos, elt);
annotateDecl(declAnnos, elt, decl.getAnnotations());
// StubParser parses all annotations in type annotation position as type annotations
annotateDecl(declAnnos, elt, decl.getType().getAnnotations());
AnnotatedTypeMirror fieldType = atypeFactory.fromElement(elt);
annotate(fieldType, decl.getType());
putNew(atypes, elt, fieldType);
}
private void annotate(AnnotatedTypeMirror type, List<AnnotationExpr> annotations) {
if (annotations == null) {
return;
}
for (AnnotationExpr annotation : annotations) {
AnnotationMirror annoMirror = getAnnotation(annotation, supportedAnnotations);
if (annoMirror != null) {
type.replaceAnnotation(annoMirror);
}
}
}
private void annotateDecl(
Map<String, Set<AnnotationMirror>> declAnnos,
Element elt,
List<AnnotationExpr> annotations) {
if (annotations == null) {
return;
}
Set<AnnotationMirror> annos = AnnotationUtils.createAnnotationSet();
for (AnnotationExpr annotation : annotations) {
AnnotationMirror annoMirror = getAnnotation(annotation, supportedAnnotations);
if (annoMirror != null) {
annos.add(annoMirror);
}
}
String key = ElementUtils.getVerboseName(elt);
putOrAddToMap(declAnnos, key, annos);
}
private void annotateTypeParameters(
Map<Element, AnnotatedTypeMirror> atypes,
List<? extends AnnotatedTypeMirror> typeArguments,
List<TypeParameter> typeParameters) {
if (typeParameters == null) {
return;
}
if (typeParameters.size() != typeArguments.size()) {
stubAlwaysWarn(
String.format(
"annotateTypeParameters: mismatched sizes%n typeParameters (size %d)=%s%n typeArguments (size %d)=%s%n For more details, run with -AstubDebug%n",
typeParameters.size(),
typeParameters,
typeArguments.size(),
typeArguments));
}
for (int i = 0; i < typeParameters.size(); ++i) {
TypeParameter param = typeParameters.get(i);
AnnotatedTypeVariable paramType = (AnnotatedTypeVariable) typeArguments.get(i);
if (param.getTypeBound() == null || param.getTypeBound().isEmpty()) {
// No bound so annotations are both lower and upper bounds
annotate(paramType, param.getAnnotations());
} else if (param.getTypeBound() != null && param.getTypeBound().size() > 0) {
annotate(paramType.getLowerBound(), param.getAnnotations());
annotate(paramType.getUpperBound(), param.getTypeBound().get(0));
if (param.getTypeBound().size() > 1) {
// TODO: add support for intersection types
stubWarnIfNotFound("Annotations on intersection types are not yet supported");
}
}
putNew(atypes, paramType.getUnderlyingType().asElement(), paramType);
}
}
private static final Set<String> nestedClassWarnings = new HashSet<String>();
private Map<Element, BodyDeclaration> getMembers(
TypeElement typeElt, TypeDeclaration typeDecl) {
assert (typeElt.getSimpleName().contentEquals(typeDecl.getName())
|| typeDecl.getName().endsWith("$" + typeElt.getSimpleName().toString()))
: String.format("%s %s", typeElt.getSimpleName(), typeDecl.getName());
Map<Element, BodyDeclaration> result = new HashMap<>();
for (BodyDeclaration member : typeDecl.getMembers()) {
if (member instanceof MethodDeclaration) {
Element elt = findElement(typeElt, (MethodDeclaration) member);
if (elt != null) {
putNew(result, elt, member);
}
} else if (member instanceof ConstructorDeclaration) {
Element elt = findElement(typeElt, (ConstructorDeclaration) member);
if (elt != null) {
putNew(result, elt, member);
}
} else if (member instanceof FieldDeclaration) {
FieldDeclaration fieldDecl = (FieldDeclaration) member;
for (VariableDeclarator var : fieldDecl.getVariables()) {
Element varelt = findElement(typeElt, var);
if (varelt != null) {
putNew(result, varelt, fieldDecl);
}
}
} else if (member instanceof ClassOrInterfaceDeclaration) {
// TODO: handle nested classes
ClassOrInterfaceDeclaration ciDecl = (ClassOrInterfaceDeclaration) member;
String nestedClass = typeDecl.getName() + "." + ciDecl.getName();
if (nestedClassWarnings.add(nestedClass)) { // avoid duplicate warnings
stubAlwaysWarn(
String.format(
"Warning: ignoring nested class in %s at line %d:%n class %s { class %s { ... } }%n",
filename,
ciDecl.getBeginLine(),
typeDecl.getName(),
ciDecl.getName())
+ "\n"
+ String.format(
" Instead, write the nested class as a top-level class:%n class %s { ... }%n class %s$%s { ... }%n",
typeDecl.getName(),
typeDecl.getName(),
ciDecl.getName()));
}
} else {
stubWarnIfNotFound(
String.format(
"Ignoring element of type %s in getMembers", member.getClass()));
}
}
// // remove null keys, which can result from findElement returning null
// result.remove(null);
return result;
}
private AnnotatedDeclaredType findType(
ClassOrInterfaceType type, List<AnnotatedDeclaredType> types) {
String typeString = type.getName();
for (AnnotatedDeclaredType superType : types) {
if (superType
.getUnderlyingType()
.asElement()
.getSimpleName()
.contentEquals(typeString)) {
return superType;
}
}
stubWarnIfNotFound("Type " + typeString + " not found");
if (debugStubParser) {
for (AnnotatedDeclaredType superType : types) {
stubDebug(String.format(" %s%n", superType));
}
}
return null;
}
private ExecutableElement findElement(TypeElement typeElt, MethodDeclaration methodDecl) {
final String wantedMethodName = methodDecl.getName();
final int wantedMethodParams =
(methodDecl.getParameters() == null) ? 0 : methodDecl.getParameters().size();
final String wantedMethodString = StubUtil.toString(methodDecl);
for (ExecutableElement method : ElementUtils.getAllMethodsIn(elements, typeElt)) {
// do heuristics first
if (wantedMethodParams == method.getParameters().size()
&& wantedMethodName.contentEquals(method.getSimpleName())
&& StubUtil.toString(method).equals(wantedMethodString)) {
return method;
}
}
stubWarnIfNotFound("Method " + wantedMethodString + " not found in type " + typeElt);
if (debugStubParser) {
for (ExecutableElement method :
ElementFilter.methodsIn(typeElt.getEnclosedElements())) {
stubDebug(String.format(" %s%n", method));
}
}
return null;
}
private ExecutableElement findElement(TypeElement typeElt, ConstructorDeclaration methodDecl) {
final int wantedMethodParams =
(methodDecl.getParameters() == null) ? 0 : methodDecl.getParameters().size();
final String wantedMethodString = StubUtil.toString(methodDecl);
for (ExecutableElement method :
ElementFilter.constructorsIn(typeElt.getEnclosedElements())) {
// do heuristics first
if (wantedMethodParams == method.getParameters().size()
&& StubUtil.toString(method).equals(wantedMethodString)) {
return method;
}
}
stubWarnIfNotFound("Constructor " + wantedMethodString + " not found in type " + typeElt);
if (debugStubParser) {
for (ExecutableElement method :
ElementFilter.constructorsIn(typeElt.getEnclosedElements())) {
stubDebug(String.format(" %s%n", method));
}
}
return null;
}
private VariableElement findElement(TypeElement typeElt, VariableDeclarator variable) {
final String fieldName = variable.getId().getName();
return findFieldElement(typeElt, fieldName);
}
private VariableElement findFieldElement(TypeElement typeElt, String fieldName) {
for (VariableElement field : ElementUtils.getAllFieldsIn(elements, typeElt)) {
// field.getSimpleName() is a CharSequence, not a String
if (fieldName.equals(field.getSimpleName().toString())) {
return field;
}
}
stubWarnIfNotFound("Field " + fieldName + " not found in type " + typeElt);
if (debugStubParser) {
for (VariableElement field : ElementFilter.fieldsIn(typeElt.getEnclosedElements())) {
stubDebug(String.format(" %s%n", field));
}
}
return null;
}
private TypeElement findType(String typeName, String... msg) {
TypeElement classElement = elements.getTypeElement(typeName);
if (classElement == null) {
if (msg.length == 0) {
stubWarnIfNotFound("Type not found: " + typeName);
} else {
stubWarnIfNotFound(msg[0] + ": " + typeName);
}
}
return classElement;
}
private PackageElement findPackage(String packageName) {
PackageElement packageElement = elements.getPackageElement(packageName);
if (packageElement == null) {
stubWarnIfNotFound("Imported package not found: " + packageName);
}
return packageElement;
}
/** The line separator */
private static final String LINE_SEPARATOR = System.getProperty("line.separator").intern();
/** Just like Map.put, but errs if the key is already in the map. */
private static <K, V> void putNew(Map<K, V> m, K key, V value) {
if (key == null) {
ErrorReporter.errorAbort("StubParser: key is null!");
return;
}
if (m.containsKey(key) && !m.get(key).equals(value)) {
ErrorReporter.errorAbort(
"StubParser: key is already in map: "
+ LINE_SEPARATOR
+ " "
+ key
+ " => "
+ m.get(key)
+ LINE_SEPARATOR
+ "while adding: "
+ LINE_SEPARATOR
+ " "
+ key
+ " => "
+ value);
}
m.put(key, value);
}
/**
* If the key is already in the map, then add the annos to the list. Otherwise put the key and
* the annos in the map
*/
private static void putOrAddToMap(
Map<String, Set<AnnotationMirror>> map, String key, Set<AnnotationMirror> annos) {
if (map.containsKey(key)) {
map.get(key).addAll(annos);
} else {
map.put(key, annos);
}
}
/**
* Just like Map.put, but does not throw an error if the key with the same value is already in
* the map.
*/
private static void putNew(
Map<Element, AnnotatedTypeMirror> m, Element key, AnnotatedTypeMirror value) {
if (key == null) {
ErrorReporter.errorAbort("StubParser: key is null!");
return;
}
if (m.containsKey(key)) {
AnnotatedTypeMirror value2 = m.get(key);
AnnotatedTypeMerger.merge(value, value2);
m.put(key, value2);
} else {
m.put(key, value);
}
}
/** Just like Map.putAll, but errs if any key is already in the map. */
private static <K, V> void putAllNew(Map<K, V> m, Map<K, V> m2) {
for (Map.Entry<K, V> e2 : m2.entrySet()) {
putNew(m, e2.getKey(), e2.getValue());
}
}
private static Set<String> warnings = new HashSet<String>();
/**
* Issues the given warning about missing elements, only if it has not been previously issued.
*/
private void stubWarnIfNotFound(String warning) {
if (warnings.add(warning) && (warnIfNotFound || debugStubParser)) {
processingEnv
.getMessager()
.printMessage(javax.tools.Diagnostic.Kind.WARNING, "StubParser: " + warning);
}
}
/**
* Issues the given warning about overwriting bytecode, only if it has not been previously
* issued.
*/
private void stubWarnIfOverwritesBytecode(String warning) {
if (warnings.add(warning) && (warnIfStubOverwritesBytecode || debugStubParser)) {
processingEnv
.getMessager()
.printMessage(javax.tools.Diagnostic.Kind.WARNING, "StubParser: " + warning);
}
}
/**
* Issues a warning even if {@code -AstubWarnIfNotFound} or {@code -AstubDebugs} options are not
* passed.
*/
private void stubAlwaysWarn(String warning) {
if (warnings.add(warning)) {
processingEnv
.getMessager()
.printMessage(javax.tools.Diagnostic.Kind.WARNING, "StubParser: " + warning);
}
}
private void stubDebug(String warning) {
if (warnings.add(warning) && debugStubParser) {
processingEnv
.getMessager()
.printMessage(javax.tools.Diagnostic.Kind.NOTE, "StubParser: " + warning);
}
}
private AnnotationMirror getAnnotation(
AnnotationExpr annotation, Map<String, AnnotationMirror> supportedAnnotations) {
AnnotationMirror annoMirror;
if (annotation instanceof MarkerAnnotationExpr) {
String annoName = ((MarkerAnnotationExpr) annotation).getName().getName();
annoMirror = supportedAnnotations.get(annoName);
} else if (annotation instanceof NormalAnnotationExpr) {
NormalAnnotationExpr nrmanno = (NormalAnnotationExpr) annotation;
String annoName = nrmanno.getName().getName();
annoMirror = supportedAnnotations.get(annoName);
if (annoMirror == null) {
// Not a supported qualifier -> ignore
return null;
}
AnnotationBuilder builder = new AnnotationBuilder(processingEnv, annoMirror);
List<MemberValuePair> pairs = nrmanno.getPairs();
if (pairs != null) {
for (MemberValuePair mvp : pairs) {
String meth = mvp.getName();
Expression exp = mvp.getValue();
handleExpr(builder, meth, exp);
}
}
return builder.build();
} else if (annotation instanceof SingleMemberAnnotationExpr) {
SingleMemberAnnotationExpr sglanno = (SingleMemberAnnotationExpr) annotation;
String annoName = sglanno.getName().getName();
annoMirror = supportedAnnotations.get(annoName);
if (annoMirror == null) {
// Not a supported qualifier -> ignore
return null;
}
AnnotationBuilder builder = new AnnotationBuilder(processingEnv, annoMirror);
Expression valexpr = sglanno.getMemberValue();
handleExpr(builder, "value", valexpr);
return builder.build();
} else {
ErrorReporter.errorAbort("StubParser: unknown annotation type: " + annotation);
annoMirror = null; // dead code
}
return annoMirror;
}
/*
* Handles expressions in annotations.
* Supports String, int, and boolean literals, but not other literals
* as documented in the stub file limitation section of the manual.
*/
private void handleExpr(AnnotationBuilder builder, String name, Expression expr) {
if (expr instanceof FieldAccessExpr || expr instanceof NameExpr) {
VariableElement elem;
if (expr instanceof FieldAccessExpr) {
elem = findVariableElement((FieldAccessExpr) expr);
} else {
elem = findVariableElement((NameExpr) expr);
}
if (elem == null) {
// A warning was already issued by findVariableElement;
return;
}
ExecutableElement var = builder.findElement(name);
TypeMirror expected = var.getReturnType();
if (expected.getKind() == TypeKind.DECLARED) {
if (elem.getConstantValue() != null) {
builder.setValue(name, (String) elem.getConstantValue());
} else {
builder.setValue(name, elem);
}
} else if (expected.getKind() == TypeKind.ARRAY) {
if (elem.getConstantValue() != null) {
String[] arr = {(String) elem.getConstantValue()};
builder.setValue(name, arr);
} else {
VariableElement[] arr = {elem};
builder.setValue(name, arr);
}
} else {
ErrorReporter.errorAbort(
"StubParser: unhandled annotation attribute type: "
+ expr
+ " and expected: "
+ expected);
}
} else if (expr instanceof IntegerLiteralExpr) {
IntegerLiteralExpr ilexpr = (IntegerLiteralExpr) expr;
ExecutableElement var = builder.findElement(name);
TypeMirror expected = var.getReturnType();
if (expected.getKind() == TypeKind.DECLARED || TypesUtils.isIntegral(expected)) {
builder.setValue(name, Integer.valueOf(ilexpr.getValue()));
} else if (expected.getKind() == TypeKind.ARRAY) {
Integer[] arr = {Integer.valueOf(ilexpr.getValue())};
builder.setValue(name, arr);
} else {
ErrorReporter.errorAbort(
"StubParser: unhandled annotation attribute type: "
+ ilexpr
+ " and expected: "
+ expected);
}
} else if (expr instanceof StringLiteralExpr) {
StringLiteralExpr slexpr = (StringLiteralExpr) expr;
ExecutableElement var = builder.findElement(name);
TypeMirror expected = var.getReturnType();
if (expected.getKind() == TypeKind.DECLARED) {
builder.setValue(name, slexpr.getValue());
} else if (expected.getKind() == TypeKind.ARRAY) {
String[] arr = {slexpr.getValue()};
builder.setValue(name, arr);
} else {
ErrorReporter.errorAbort(
"StubParser: unhandled annotation attribute type: "
+ slexpr
+ " and expected: "
+ expected);
}
} else if (expr instanceof ArrayInitializerExpr) {
ExecutableElement var = builder.findElement(name);
TypeMirror expected = var.getReturnType();
if (expected.getKind() != TypeKind.ARRAY) {
ErrorReporter.errorAbort(
"StubParser: unhandled annotation attribute type: "
+ expr
+ " and expected: "
+ expected);
}
ArrayInitializerExpr aiexpr = (ArrayInitializerExpr) expr;
List<Expression> aiexprvals = aiexpr.getValues();
Object[] elemarr = new Object[aiexprvals.size()];
Expression anaiexpr;
for (int i = 0; i < aiexprvals.size(); ++i) {
anaiexpr = aiexprvals.get(i);
if (anaiexpr instanceof FieldAccessExpr || anaiexpr instanceof NameExpr) {
if (anaiexpr instanceof FieldAccessExpr) {
elemarr[i] = findVariableElement((FieldAccessExpr) anaiexpr);
} else {
elemarr[i] = findVariableElement((NameExpr) anaiexpr);
}
if (elemarr[i] == null) {
// A warning was already issued by findVariableElement;
return;
}
String constval = (String) ((VariableElement) elemarr[i]).getConstantValue();
if (constval != null) {
elemarr[i] = constval;
}
} else if (anaiexpr instanceof IntegerLiteralExpr) {
elemarr[i] = Integer.valueOf(((IntegerLiteralExpr) anaiexpr).getValue());
} else if (anaiexpr instanceof StringLiteralExpr) {
elemarr[i] = ((StringLiteralExpr) anaiexpr).getValue();
} else {
ErrorReporter.errorAbort(
"StubParser: unhandled annotation attribute type: " + anaiexpr);
}
}
builder.setValue(name, elemarr);
} else if (expr instanceof BooleanLiteralExpr) {
BooleanLiteralExpr blexpr = (BooleanLiteralExpr) expr;
ExecutableElement var = builder.findElement(name);
TypeMirror expected = var.getReturnType();
if (expected.getKind() == TypeKind.BOOLEAN) {
builder.setValue(name, blexpr.getValue());
} else if (expected.getKind() == TypeKind.ARRAY) {
Boolean[] arr = {blexpr.getValue()};
builder.setValue(name, arr);
} else {
ErrorReporter.errorAbort(
"StubParser: unhandled annotation attribute type: "
+ blexpr
+ " and expected: "
+ expected);
}
} else {
ErrorReporter.errorAbort(
"StubParser: unhandled annotation attribute type: "
+ expr
+ " class: "
+ expr.getClass());
}
}
private /*@Nullable*/ VariableElement findVariableElement(NameExpr nexpr) {
if (nexprcache.containsKey(nexpr)) {
return nexprcache.get(nexpr);
}
VariableElement res = null;
boolean importFound = false;
for (String imp : imports) {
Pair<String, String> partitionedName = StubUtil.partitionQualifiedName(imp);
String typeName = partitionedName.first;
String fieldName = partitionedName.second;
if (fieldName.equals(nexpr.getName())) {
TypeElement enclType =
findType(
typeName,
String.format(
"Enclosing type of static import %s not found", fieldName));
if (enclType == null) {
return null;
} else {
importFound = true;
res = findFieldElement(enclType, fieldName);
break;
}
}
}
// Imported but invalid types or fields will have warnings from above,
// only warn on fields missing an import
if (res == null && !importFound) {
stubWarnIfNotFound("Static field " + nexpr.getName() + " is not imported");
}
nexprcache.put(nexpr, res);
return res;
}
private /*@Nullable*/ VariableElement findVariableElement(FieldAccessExpr faexpr) {
if (faexprcache.containsKey(faexpr)) {
return faexprcache.get(faexpr);
}
TypeElement rcvElt = elements.getTypeElement(faexpr.getScope().toString());
if (rcvElt == null) {
// Search imports for full annotation name.
for (String imp : imports) {
String[] import_delimited = imp.split("\\.");
if (import_delimited[import_delimited.length - 1].equals(
faexpr.getScope().toString())) {
StringBuilder full_annotation = new StringBuilder();
for (int i = 0; i < import_delimited.length - 1; i++) {
full_annotation.append(import_delimited[i]);
full_annotation.append('.');
}
full_annotation.append(faexpr.getScope().toString());
rcvElt = elements.getTypeElement(full_annotation);
break;
}
}
if (rcvElt == null) {
stubWarnIfNotFound("Type " + faexpr.getScope().toString() + " not found");
return null;
}
}
VariableElement res = findFieldElement(rcvElt, faexpr.getField());
faexprcache.put(faexpr, res);
return res;
}
}