org.checkerframework.framework.stub.StubParser Maven / Gradle / Ivy
Show all versions of framework-all Show documentation
package org.checkerframework.framework.stub;
import com.github.javaparser.ParseProblemException;
import com.github.javaparser.Problem;
import com.github.javaparser.StaticJavaParser;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.ImportDeclaration;
import com.github.javaparser.ast.NodeList;
import com.github.javaparser.ast.PackageDeclaration;
import com.github.javaparser.ast.StubUnit;
import com.github.javaparser.ast.body.BodyDeclaration;
import com.github.javaparser.ast.body.CallableDeclaration;
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
import com.github.javaparser.ast.body.ConstructorDeclaration;
import com.github.javaparser.ast.body.EnumConstantDeclaration;
import com.github.javaparser.ast.body.EnumDeclaration;
import com.github.javaparser.ast.body.FieldDeclaration;
import com.github.javaparser.ast.body.MethodDeclaration;
import com.github.javaparser.ast.body.Parameter;
import com.github.javaparser.ast.body.TypeDeclaration;
import com.github.javaparser.ast.body.VariableDeclarator;
import com.github.javaparser.ast.expr.AnnotationExpr;
import com.github.javaparser.ast.expr.ArrayInitializerExpr;
import com.github.javaparser.ast.expr.BooleanLiteralExpr;
import com.github.javaparser.ast.expr.CharLiteralExpr;
import com.github.javaparser.ast.expr.ClassExpr;
import com.github.javaparser.ast.expr.DoubleLiteralExpr;
import com.github.javaparser.ast.expr.Expression;
import com.github.javaparser.ast.expr.FieldAccessExpr;
import com.github.javaparser.ast.expr.IntegerLiteralExpr;
import com.github.javaparser.ast.expr.LongLiteralExpr;
import com.github.javaparser.ast.expr.MarkerAnnotationExpr;
import com.github.javaparser.ast.expr.MemberValuePair;
import com.github.javaparser.ast.expr.NameExpr;
import com.github.javaparser.ast.expr.NormalAnnotationExpr;
import com.github.javaparser.ast.expr.NullLiteralExpr;
import com.github.javaparser.ast.expr.SingleMemberAnnotationExpr;
import com.github.javaparser.ast.expr.StringLiteralExpr;
import com.github.javaparser.ast.expr.UnaryExpr;
import com.github.javaparser.ast.type.ClassOrInterfaceType;
import com.github.javaparser.ast.type.ReferenceType;
import com.github.javaparser.ast.type.Type;
import com.github.javaparser.ast.type.TypeParameter;
import com.github.javaparser.ast.type.WildcardType;
import java.io.InputStream;
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
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.ArrayType;
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.checker.nullness.qual.Nullable;
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.javacutil.AnnotationBuilder;
import org.checkerframework.javacutil.AnnotationUtils;
import org.checkerframework.javacutil.BugInCF;
import org.checkerframework.javacutil.ElementUtils;
import org.checkerframework.javacutil.Pair;
/**
* Given a stub file, yields the annotated types in it and the declaration annotations in it. The
* main entry point is {@link StubParser#parse(String, InputStream, AnnotatedTypeFactory,
* ProcessingEnvironment, Map, Map)}, which side-effects its last two arguments.
*
* The constructor acts in two parts. First, it calls the Stub Parser to parse a stub file. Then,
* itis walks the Stub Parser's AST to create/collect types and declaration annotations.
*/
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 ignore missing classes even when warnIfNotFound is set to true. This allows the
* stubs to contain classes not in the classpath (even if another class in the classpath has the
* same package), but still warn if members of the class (methods, fields) are missing. This
* option does nothing unless warnIfNotFound is also set.
*/
private final boolean warnIfNotFoundIgnoresClasses;
/** Whether to print warnings about stub files that overwrite annotations from bytecode. */
private final boolean warnIfStubOverwritesBytecode;
/** Whether to print verbose debugging messages. */
private final boolean debugStubParser;
/** The name of the stub file being processed; used only for diagnostic messages. */
private final String filename;
/**
* The AST of the parsed stub file that this class is processing. May be null if there was a
* problem parsing the stub file. (TODO: Should the Checker Framework just halt in that case?)
*/
// Not final in order to accommodate a default value.
private StubUnit stubUnit;
private final ProcessingEnvironment processingEnv;
private final AnnotatedTypeFactory atypeFactory;
private final Elements elements;
/**
* The set of annotations found in the stub file. 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.)
*
* @see #getAllStubAnnotations
*/
private Map allStubAnnotations;
/**
* A list of the fully-qualified names of enum constants and static fields with constant values
* that have been imported.
*/
private final List importedConstants = new ArrayList<>();
/** A map of imported fully-qualified type names to type elements. */
private final Map importedTypes = new HashMap<>();
/** The annotation {@code @FromStubFile}. */
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 typeParameters = new ArrayList<>();
// The following variables are stored in the StubParser because otherwise they would need to be
// passed through everywhere, which would be verbose.
/** The type that is currently being parsed. */
FqName parseState;
/** Output variable: .... */
Map atypes;
/** Output variable: .... */
Map> declAnnos;
/** The line separator. */
private static final String LINE_SEPARATOR = System.lineSeparator().intern();
/**
* Create a new StubParser object, which will parse and extract annotations from the given stub
* file.
*
* @param filename name of stub file, used only for diagnostic messages
* @param atypeFactory AnnotatedtypeFactory to use
* @param processingEnv ProcessingEnviroment to use
*/
public StubParser(
String filename,
AnnotatedTypeFactory atypeFactory,
ProcessingEnvironment processingEnv,
Map atypes,
Map> declAnnos) {
this.filename = filename;
this.atypeFactory = atypeFactory;
this.processingEnv = processingEnv;
this.elements = processingEnv.getElementUtils();
Map options = processingEnv.getOptions();
this.warnIfNotFound = options.containsKey("stubWarnIfNotFound");
this.warnIfNotFoundIgnoresClasses = options.containsKey("stubWarnIfNotFoundIgnoresClasses");
this.warnIfStubOverwritesBytecode = options.containsKey("stubWarnIfOverwritesBytecode");
this.debugStubParser = options.containsKey("stubDebug");
this.fromStubFile = AnnotationBuilder.fromClass(elements, FromStubFile.class);
this.atypes = atypes;
this.declAnnos = declAnnos;
}
/**
* All annotations defined in the package (but not those nested within classes in the package).
* Keys are simple names.
*/
private Map annosInPackage(PackageElement packageElement) {
return createImportedAnnotationsMap(
ElementFilter.typesIn(packageElement.getEnclosedElements()));
}
/** All annotations declared (directly) within a class. Keys are simple names. */
private Map annosInType(TypeElement typeElement) {
return createImportedAnnotationsMap(
ElementFilter.typesIn(typeElement.getEnclosedElements()));
}
private Map createImportedAnnotationsMap(
List typeElements) {
Map result = new HashMap<>();
for (TypeElement typeElm : typeElements) {
if (typeElm.getKind() == ElementKind.ANNOTATION_TYPE) {
AnnotationMirror anno =
AnnotationBuilder.fromName(elements, typeElm.getQualifiedName());
putNoOverride(result, typeElm.getSimpleName().toString(), anno);
}
}
return result;
}
/**
* Get all members of a Type that are importable in a stub file. Currently these are values of
* enums, or compile time constants.
*
* @return a list fully-qualified member names
*/
private static List getImportableMembers(TypeElement typeElement) {
List result = new ArrayList<>();
List 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;
}
/**
* Returns all annotations found in the stub file, as a value for {@link #allStubAnnotations}.
* Note that this also modifies {@link #importedConstants} and {@link #importedTypes}.
*
* @see #allStubAnnotations
*/
private Map getAllStubAnnotations() {
Map result = new HashMap<>();
assert !stubUnit.getCompilationUnits().isEmpty();
CompilationUnit cu = stubUnit.getCompilationUnits().get(0);
if (cu.getImports() == null) {
return result;
}
for (ImportDeclaration importDecl : cu.getImports()) {
String imported = importDecl.getNameAsString();
try {
if (importDecl.isAsterisk()) {
if (importDecl.isStatic()) {
// Wildcard import of members of a type (class or interface)
TypeElement element = getTypeElement(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));
importedConstants.addAll(getImportableMembers(element));
addEnclosingTypesToImportedTypes(element);
}
} else {
// Wildcard import of members of a package
PackageElement element = findPackage(imported);
if (element != null) {
putAllNew(result, annosInPackage(element));
addEnclosingTypesToImportedTypes(element);
}
}
} else {
// A single (non-wildcard) import
final TypeElement importType = elements.getTypeElement(imported);
if (importType == null && !importDecl.isStatic()) {
// Class or nested class (according to JSL), but we can't resolve
stubWarnNotFound("Imported type not found: " + imported);
} else if (importType == null) {
// Nested Field
Pair typeParts = StubUtil.partitionQualifiedName(imported);
String type = typeParts.first;
String fieldName = typeParts.second;
TypeElement enclType =
getTypeElement(
type,
String.format(
"Enclosing type of static field %s not found",
fieldName));
if (enclType != null) {
if (findFieldElement(enclType, fieldName) != null) {
importedConstants.add(imported);
}
}
} else if (importType.getKind() == ElementKind.ANNOTATION_TYPE) {
// Single annotation or nested annotation
AnnotationMirror anno = AnnotationBuilder.fromName(elements, imported);
if (anno != null) {
Element annoElt = anno.getAnnotationType().asElement();
putNoOverride(result, annoElt.getSimpleName().toString(), anno);
importedTypes.put(
annoElt.getSimpleName().toString(), (TypeElement) annoElt);
} else {
stubWarnNotFound("Could not load import: " + imported);
}
} else {
// Class or nested class
// TODO: Is this needed?
importedConstants.add(imported);
TypeElement element = getTypeElement(imported, "Imported type not found");
importedTypes.put(element.getSimpleName().toString(), element);
}
}
} catch (AssertionError error) {
stubWarnNotFound("" + error);
}
}
return result;
}
// If a member is imported, then consider every containing class to also be imported.
private void addEnclosingTypesToImportedTypes(Element element) {
for (Element enclosedEle : element.getEnclosedElements()) {
if (enclosedEle.getKind().isClass()) {
importedTypes.put(
enclosedEle.getSimpleName().toString(), (TypeElement) enclosedEle);
}
}
}
/**
* The main entry point. Parse a stub file and side-effects the last two arguments.
*
* @param filename name of stub file, used only for diagnostic messages
* @param inputStream of stub file to parse
* @param atypeFactory AnnotatedtypeFactory to use
* @param processingEnv ProcessingEnviroment to use
*/
public static void parse(
String filename,
InputStream inputStream,
AnnotatedTypeFactory atypeFactory,
ProcessingEnvironment processingEnv,
Map atypes,
Map> declAnnos) {
StubParser sp = new StubParser(filename, atypeFactory, processingEnv, atypes, declAnnos);
try {
sp.parseStubUnit(inputStream);
sp.process();
} catch (ParseProblemException e) {
StringBuilder message =
new StringBuilder(
"exception while parsing stub file "
+ filename
+ ". Encountered problems: ");
// Manually build up the message, to get verbose location information.
for (Problem p : e.getProblems()) {
message.append(p.getVerboseMessage());
message.append(LINE_SEPARATOR);
}
sp.stubWarn(message.toString());
}
}
/**
* Delegate to the Stub Parser to parse the stub file to an AST. Subsequently, all work uses the
* AST.
*/
private void parseStubUnit(InputStream inputStream) {
if (debugStubParser) {
stubDebug(String.format("parsing stub file %s", filename));
}
stubUnit = StaticJavaParser.parseStubUnit(inputStream);
// getAllStubAnnotations() also modifies importedConstants and importedTypes. This should
// be refactored to be nicer.
allStubAnnotations = getAllStubAnnotations();
if (allStubAnnotations.isEmpty()) {
stubWarnNotFound(
String.format(
"No supported annotations found! This likely means stub file %s doesn't import them correctly.",
filename));
}
}
/** Process {@link #stubUnit}, which is the AST produced by {@link #parseStubUnit}. */
private void process() {
processStubUnit(this.stubUnit);
}
/** Parse the given StubUnit. */
private void processStubUnit(StubUnit index) {
for (CompilationUnit cu : index.getCompilationUnits()) {
processCompilationUnit(cu);
}
}
private void processCompilationUnit(CompilationUnit cu) {
final List packageAnnos;
if (!cu.getPackageDeclaration().isPresent()) {
packageAnnos = null;
parseState = new FqName(null, null);
} else {
PackageDeclaration pDecl = cu.getPackageDeclaration().get();
packageAnnos = pDecl.getAnnotations();
processPackage(pDecl);
}
if (cu.getTypes() != null) {
for (TypeDeclaration> typeDeclaration : cu.getTypes()) {
processTypeDecl(typeDeclaration, null, packageAnnos);
}
}
}
private void processPackage(PackageDeclaration packDecl) {
assert (packDecl != null);
String packageName = packDecl.getNameAsString();
parseState = new FqName(packageName, null);
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???
}
/** @param outertypeName the name of the containing class, when processing a nested class */
private void processTypeDecl(
TypeDeclaration> typeDecl, String outertypeName, List packageAnnos) {
assert parseState != null;
String innerName =
(outertypeName == null ? "" : outertypeName + ".") + typeDecl.getNameAsString();
parseState = new FqName(parseState.packageName, innerName);
String fqTypeName = parseState.toString();
TypeElement typeElt = elements.getTypeElement(fqTypeName);
if (typeElt == null) {
if (debugStubParser
|| (!hasNoStubParserWarning(typeDecl.getAnnotations())
&& !hasNoStubParserWarning(packageAnnos)
&& !warnIfNotFoundIgnoresClasses)) {
stubWarnNotFound("Type not found: " + fqTypeName);
}
return;
}
if (typeElt.getKind() == ElementKind.ENUM) {
typeParameters.addAll(processEnum((EnumDeclaration) typeDecl, typeElt));
} else if (typeElt.getKind() == ElementKind.ANNOTATION_TYPE) {
stubWarnNotFound("Skipping annotation type: " + fqTypeName);
} else if (typeDecl instanceof ClassOrInterfaceDeclaration) {
typeParameters.addAll(processType((ClassOrInterfaceDeclaration) typeDecl, typeElt));
} // else it's an EmptyTypeDeclaration. TODO: An EmptyTypeDeclaration can have
// annotations, right?
Map> elementsToDecl = getMembers(typeElt, typeDecl);
for (Map.Entry> entry : elementsToDecl.entrySet()) {
final Element elt = entry.getKey();
final BodyDeclaration> decl = entry.getValue();
switch (elt.getKind()) {
case FIELD:
processField((FieldDeclaration) decl, (VariableElement) elt);
break;
case ENUM_CONSTANT:
processEnumConstant((EnumConstantDeclaration) decl, (VariableElement) elt);
break;
case CONSTRUCTOR:
case METHOD:
processCallableDeclaration(
(CallableDeclaration>) decl, (ExecutableElement) elt);
break;
case CLASS:
case INTERFACE:
processTypeDecl((ClassOrInterfaceDeclaration) decl, innerName, packageAnnos);
break;
case ENUM:
processTypeDecl((EnumDeclaration) decl, innerName, packageAnnos);
break;
default:
/* do nothing */
stubWarnNotFound("StubParser ignoring: " + elt);
break;
}
}
typeParameters.clear();
}
/** True if the argument contains {@code @NoStubParserWarning}. */
private boolean hasNoStubParserWarning(Iterable aexprs) {
if (aexprs == null) {
return false;
}
for (AnnotationExpr anno : aexprs) {
if (anno.getNameAsString().contentEquals("NoStubParserWarning")) {
return true;
}
}
return false;
}
/** @return list of AnnotatedTypeVariable of the type's type parameter declarations */
private List processType(
ClassOrInterfaceDeclaration decl, TypeElement elt) {
annotateDecl(declAnnos, elt, decl.getAnnotations());
AnnotatedDeclaredType type = atypeFactory.fromElement(elt);
annotate(type, decl.getAnnotations());
final List extends AnnotatedTypeMirror> typeArguments = type.getTypeArguments();
final List typeParameters = decl.getTypeParameters();
// It can be the case that args=[] and params=null, so don't crash in that case.
// 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 (debugStubParser) {
int numParams = (typeParameters == null ? 0 : typeParameters.size());
int numArgs = (typeArguments == null ? 0 : typeArguments.size());
if (numParams != numArgs) {
stubDebug(
String.format(
"parseType: mismatched sizes for typeParameters=%s (size %d) and typeArguments=%s (size %d); decl=%s; elt=%s (%s); type=%s (%s); parseState=%s",
typeParameters,
numParams,
typeArguments,
numArgs,
decl.toString().replace(LINE_SEPARATOR, " "),
elt.toString().replace(LINE_SEPARATOR, " "),
elt.getClass(),
type,
type.getClass(),
parseState));
stubDebug("Proceeding despite mismatched sizes");
}
}
annotateTypeParameters(decl, elt, atypes, typeArguments, typeParameters);
annotateSupertypes(decl, type);
putNew(atypes, elt, type);
List typeVariables = new ArrayList<>();
for (AnnotatedTypeMirror typeV : type.getTypeArguments()) {
if (typeV.getKind() != TypeKind.TYPEVAR) {
stubWarn(
"expected an AnnotatedTypeVariable but found type kind "
+ typeV.getKind()
+ ": "
+ typeV);
} else {
typeVariables.add((AnnotatedTypeVariable) typeV);
}
}
return typeVariables;
}
/**
* Gathers and returns a list of AnnotatedTypeVariable of the enum's type parameter
* declarations.
*
* @param decl actual enum declaration
* @param elt element representing enum
* @param atypes map of annotated types
* @param declAnnos map of declarations annotations
* @return list of AnnotatedTypeVariable of the enum's type parameter declarations
*/
private List processEnum(EnumDeclaration decl, TypeElement elt) {
annotateDecl(declAnnos, elt, decl.getAnnotations());
AnnotatedDeclaredType type = atypeFactory.fromElement(elt);
annotate(type, decl.getAnnotations());
putNew(atypes, elt, type);
List typeVariables = new ArrayList<>();
for (AnnotatedTypeMirror typeV : type.getTypeArguments()) {
if (typeV.getKind() != TypeKind.TYPEVAR) {
stubWarn(
"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.getExtendedTypes() != null) {
for (ClassOrInterfaceType superType : typeDecl.getExtendedTypes()) {
AnnotatedDeclaredType foundType = findType(superType, type.directSuperTypes());
if (foundType == null) {
stubWarn(
"could not find superclass "
+ superType
+ " from type "
+ type
+ LINE_SEPARATOR
+ "Stub file does not match bytecode");
} else {
annotate(foundType, superType, null);
}
}
}
if (typeDecl.getImplementedTypes() != null) {
for (ClassOrInterfaceType superType : typeDecl.getImplementedTypes()) {
AnnotatedDeclaredType foundType = findType(superType, type.directSuperTypes());
if (foundType == null) {
stubWarn(
"could not find superinterface "
+ superType
+ " from type "
+ type
+ LINE_SEPARATOR
+ "Stub file does not match bytecode");
} else {
annotate(foundType, superType, null);
}
}
}
}
/** Adds type and declaration annotations from {@code decl}. */
private void processCallableDeclaration(CallableDeclaration> decl, ExecutableElement elt) {
// Declaration annotations
annotateDecl(declAnnos, elt, decl.getAnnotations());
if (decl.isMethodDeclaration()) {
// StubParser parses all annotations in type annotation position as type annotations
annotateDecl(declAnnos, elt, ((MethodDeclaration) decl).getType().getAnnotations());
}
addDeclAnnotations(declAnnos, elt);
AnnotatedExecutableType methodType = atypeFactory.fromElement(elt);
// Type Parameters
annotateTypeParameters(
decl, elt, atypes, methodType.getTypeVariables(), decl.getTypeParameters());
typeParameters.addAll(methodType.getTypeVariables());
// Type annotations
if (decl.isMethodDeclaration()) {
annotate(
methodType.getReturnType(),
((MethodDeclaration) decl).getType(),
decl.getAnnotations());
} else {
annotate(methodType.getReturnType(), decl.getAnnotations());
}
// Parameters
processParameters(decl, elt, declAnnos, methodType);
// Receiver
if (decl.getReceiverParameter().isPresent()
&& !decl.getReceiverParameter().get().getAnnotations().isEmpty()) {
if (methodType.getReceiverType() == null) {
if (decl.isConstructorDeclaration()) {
stubWarn(
"parseParameter: constructor of a top-level class cannot have receiver annotations%n"
+ "Constructor: %s%n"
+ "Receiver annotations: %s",
methodType, decl.getReceiverParameter().get().getAnnotations());
} else {
stubWarn(
"parseParameter: static methods cannot have receiver annotations%n"
+ "Method: %s%n"
+ "Receiver annotations: %s",
methodType, decl.getReceiverParameter().get().getAnnotations());
}
} else {
annotate(
methodType.getReceiverType(),
decl.getReceiverParameter().get().getAnnotations());
}
}
// Store the type.
putNew(atypes, elt, methodType);
typeParameters.removeAll(methodType.getTypeVariables());
}
/**
* Adds declaration and type annotations to the parameters of {@code methodType}, which is
* either a method or constructor.
*
* @param method Method or Constructor declaration
* @param elt ExecutableElement of {@code method}
* @param declAnnos map of declaration elements strings to annotations
* @param methodType annotated type of {@code method}
*/
private void processParameters(
CallableDeclaration> method,
ExecutableElement elt,
Map> declAnnos,
AnnotatedExecutableType methodType) {
List params = method.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()) {
assert paramType.getKind() == TypeKind.ARRAY;
// The "type" of param is actually the component type of the vararg.
// For example, "Object..." the type would be "Object".
annotate(
((AnnotatedArrayType) paramType).getComponentType(),
param.getType(),
param.getAnnotations());
// The "VarArgsAnnotations" are those just before "...".
annotate(paramType, param.getVarArgsAnnotations());
} else {
annotate(paramType, param.getType(), param.getAnnotations());
}
}
}
/**
* Clear (remove) existing annotations on the type.
*
* Stub files override annotations read from .class files. Using {@code replaceAnnotation}
* usually achieves this; however, for annotations on type variables, it is sometimes necessary
* to remove an existing annotation, leaving no annotation on the type variable. This method
* does so.
*
* @param atype the type to modify
* @param typeDef the type from the stub file, used only for diagnostic messages
*/
@SuppressWarnings("unused") // for disabled warning message
private void clearAnnotations(AnnotatedTypeMirror atype, Type typeDef) {
Set annos = atype.getAnnotations();
// TODO: This should check whether the stub file is @AnnotatedFor the current type system.
// @AnnotatedFor isn't integrated in stub files yet.
if (annos != null && !annos.isEmpty()) {
// TODO: only produce output if the removed annotation
// isn't the top and default annotation in the type hierarchy.
if (false) {
stubWarnOverwritesBytecode(
String.format(
"in file %s at line %s removed existing annotations on type: %s",
filename.substring(filename.lastIndexOf('/') + 1),
typeDef.getBegin().get().line,
atype.toString(true)));
}
// 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> declAnnos, Element elt) {
Set annos = declAnnos.get(ElementUtils.getVerboseName(elt));
if (annos == null) {
annos = AnnotationUtils.createAnnotationSet();
putOrAddToMap(declAnnos, ElementUtils.getVerboseName(elt), annos);
}
annos.add(fromStubFile);
}
/**
* Add the annotations from {@code type} to {@code atype}. Type annotations that parsed as
* declaration annotations (ie those in {@code declAnnos} are applied to the innermost component
* type.
*
* @param atype annotated type to which to add annotations
* @param type parsed type
* @param declAnnos annotations stored on the declaration of the variable with this type or null
*/
private void annotateAsArray(
AnnotatedArrayType atype, ReferenceType type, NodeList declAnnos) {
annotateInnermostComponentType(atype, declAnnos);
Type typeDef = type;
AnnotatedTypeMirror currentAtype = atype;
while (typeDef.isArrayType() && currentAtype.getKind() == TypeKind.ARRAY) {
// handle generic type
clearAnnotations(currentAtype, typeDef);
List annotations = typeDef.getAnnotations();
if (annotations != null) {
annotate(currentAtype, annotations);
}
typeDef = ((com.github.javaparser.ast.type.ArrayType) typeDef).getComponentType();
currentAtype = ((AnnotatedArrayType) currentAtype).getComponentType();
if (typeDef.isArrayType() ^ currentAtype.getKind() == TypeKind.ARRAY) {
stubWarn("Mismatched array lengths; atype: " + atype + "%n type: " + type);
}
}
}
private ClassOrInterfaceType unwrapDeclaredType(Type type) {
if (type instanceof ClassOrInterfaceType) {
return (ClassOrInterfaceType) type;
} else if (type instanceof ReferenceType && type.getArrayLevel() == 0) {
return unwrapDeclaredType(type.getElementType());
} else {
return null;
}
}
/**
* Add to {@code atype}:
*
*
* - the annotations from {@code typeDef}, and
*
- any type annotations that parsed as declaration annotations (ie those in {@code
* declAnnos}).
*
*
* @param atype annotated type to which to add annotations
* @param typeDef parsed type
* @param declAnnos annotations stored on the declaration of the variable with this type, or
* null
*/
private void annotate(
AnnotatedTypeMirror atype, Type typeDef, NodeList declAnnos) {
if (atype.getKind() == TypeKind.ARRAY) {
if (typeDef instanceof ReferenceType) {
annotateAsArray((AnnotatedArrayType) atype, (ReferenceType) typeDef, declAnnos);
} else {
stubWarn("expected ReferenceType but found: " + typeDef);
}
return;
}
clearAnnotations(atype, typeDef);
// Primary annotations for the type of a variable declaration are not stored in typeDef, but
// rather as declaration annotations (passed as declAnnos to this method). But, if typeDef
// is not the type of a variable, then the primary annotations are stored in typeDef.
NodeList primaryAnnotations;
if (typeDef.getAnnotations().isEmpty() && declAnnos != null) {
primaryAnnotations = declAnnos;
} else {
primaryAnnotations = typeDef.getAnnotations();
}
if (atype.getKind() != TypeKind.WILDCARD) {
// The primary annotation on a wildcard applies to the super or extends bound and
// are added below.
annotate(atype, primaryAnnotations);
}
switch (atype.getKind()) {
case DECLARED:
ClassOrInterfaceType declType = unwrapDeclaredType(typeDef);
if (declType == null) {
break;
}
AnnotatedDeclaredType adeclType = (AnnotatedDeclaredType) atype;
if (declType.getTypeArguments().isPresent()
&& !declType.getTypeArguments().get().isEmpty()
&& !adeclType.getTypeArguments().isEmpty()) {
if (declType.getTypeArguments().get().size()
!= adeclType.getTypeArguments().size()) {
stubWarn(
String.format(
"Mismatch in type argument size between %s (%d) and %s (%d)",
declType,
declType.getTypeArguments().get().size(),
adeclType,
adeclType.getTypeArguments().size()));
}
for (int i = 0; i < declType.getTypeArguments().get().size(); ++i) {
annotate(
adeclType.getTypeArguments().get(i),
declType.getTypeArguments().get().get(i),
null);
}
}
break;
case WILDCARD:
AnnotatedWildcardType wildcardType = (AnnotatedWildcardType) atype;
// Ensure that the stub also has a wildcard type, report an error otherwise
if (!typeDef.isWildcardType()) {
// We throw an error here, as otherwise we are just getting a generic cast error
// on the very next line.
stubWarn(
"Wildcard type <"
+ atype
+ "> doesn't match type in stubs file: <"
+ typeDef
+ ">"
+ LINE_SEPARATOR
+ "In file "
+ filename
+ LINE_SEPARATOR
+ "While parsing "
+ parseState);
return;
}
WildcardType wildcardDef = (WildcardType) typeDef;
if (wildcardDef.getExtendedType().isPresent()) {
annotate(
wildcardType.getExtendsBound(),
wildcardDef.getExtendedType().get(),
null);
annotate(wildcardType.getSuperBound(), primaryAnnotations);
} else if (wildcardDef.getSuperType().isPresent()) {
annotate(wildcardType.getSuperBound(), wildcardDef.getSuperType().get(), null);
annotate(wildcardType.getExtendsBound(), primaryAnnotations);
} else {
annotate(atype, primaryAnnotations);
}
break;
case 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());
}
}
break;
default:
// No additional annotations to add.
}
}
private void processField(FieldDeclaration decl, VariableElement elt) {
addDeclAnnotations(declAnnos, elt);
annotateDecl(declAnnos, elt, decl.getAnnotations());
// StubParser parses all annotations in type annotation position as type annotations
annotateDecl(declAnnos, elt, decl.getElementType().getAnnotations());
AnnotatedTypeMirror fieldType = atypeFactory.fromElement(elt);
VariableDeclarator fieldVarDecl = null;
String eltName = elt.getSimpleName().toString();
for (VariableDeclarator var : decl.getVariables()) {
if (var.getName().toString().equals(eltName)) {
fieldVarDecl = var;
break;
}
}
assert fieldVarDecl != null;
annotate(fieldType, fieldVarDecl.getType(), decl.getAnnotations());
putNew(atypes, elt, fieldType);
}
/**
* Adds the annotations present on the declaration of an enum constant to the ATM of that
* constant.
*/
private void processEnumConstant(EnumConstantDeclaration decl, VariableElement elt) {
addDeclAnnotations(declAnnos, elt);
annotateDecl(declAnnos, elt, decl.getAnnotations());
AnnotatedTypeMirror enumConstType = atypeFactory.fromElement(elt);
annotate(enumConstType, decl.getAnnotations());
putNew(atypes, elt, enumConstType);
}
/**
* Returns the innermost component type of {@code type}.
*
* @param type array type
*/
private AnnotatedTypeMirror innermostComponentType(AnnotatedArrayType type) {
AnnotatedTypeMirror componentType = type;
while (componentType.getKind() == TypeKind.ARRAY) {
componentType = ((AnnotatedArrayType) componentType).getComponentType();
}
return componentType;
}
/**
* Adds {@code annotations} to the innermost component type of {@code type}.
*
* @param type array type
* @param annotations annotations to add
*/
private void annotateInnermostComponentType(
AnnotatedArrayType type, List annotations) {
annotate(innermostComponentType(type), annotations);
}
private void annotate(AnnotatedTypeMirror type, List annotations) {
if (annotations == null) {
return;
}
for (AnnotationExpr annotation : annotations) {
AnnotationMirror annoMirror = getAnnotation(annotation, allStubAnnotations);
if (annoMirror != null) {
type.replaceAnnotation(annoMirror);
} else {
stubWarnNotFound("Unknown annotation: " + annotation);
}
}
}
private void annotateDecl(
Map> declAnnos,
Element elt,
List annotations) {
if (annotations == null) {
return;
}
Set annos = AnnotationUtils.createAnnotationSet();
for (AnnotationExpr annotation : annotations) {
AnnotationMirror annoMirror = getAnnotation(annotation, allStubAnnotations);
if (annoMirror != null) {
Target target =
annoMirror.getAnnotationType().asElement().getAnnotation(Target.class);
// Only add the declaration annotation if the annotation applies to the element.
if (AnnotationUtils.getElementKindsForTarget(target).contains(elt.getKind())) {
annos.add(annoMirror);
}
}
}
String key = ElementUtils.getVerboseName(elt);
putOrAddToMap(declAnnos, key, annos);
}
private void annotateTypeParameters(
BodyDeclaration> decl, // for debugging
Object elt, // for debugging; TypeElement or ExecutableElement
Map atypes,
List extends AnnotatedTypeMirror> typeArguments,
List typeParameters) {
if (typeParameters == null) {
return;
}
if (typeParameters.size() != typeArguments.size()) {
String msg =
String.format(
"annotateTypeParameters: mismatched sizes: typeParameters (size %d)=%s; typeArguments (size %d)=%s; decl=%s; elt=%s (%s).",
typeParameters.size(),
typeParameters,
typeArguments.size(),
typeArguments,
decl.toString().replace(LINE_SEPARATOR, " "),
elt.toString().replace(LINE_SEPARATOR, " "),
elt.getClass());
if (!debugStubParser) {
msg = msg + "%n For more details, run with -AstubDebug";
}
stubWarn(msg);
return;
}
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().isEmpty()) {
annotate(paramType.getLowerBound(), param.getAnnotations());
annotate(paramType.getUpperBound(), param.getTypeBound().get(0), null);
if (param.getTypeBound().size() > 1) {
// TODO: add support for intersection types
stubWarnNotFound("Annotations on intersection types are not yet supported");
}
}
putNew(atypes, paramType.getUnderlyingType().asElement(), paramType);
}
}
private Map> getMembers(
TypeElement typeElt, TypeDeclaration> typeDecl) {
assert (typeElt.getSimpleName().contentEquals(typeDecl.getNameAsString())
|| typeDecl.getNameAsString().endsWith("$" + typeElt.getSimpleName()))
: String.format("%s %s", typeElt.getSimpleName(), typeDecl.getName());
Map> result = new LinkedHashMap<>();
// For an enum type declaration, also add the enum constants
if (typeDecl instanceof EnumDeclaration) {
EnumDeclaration enumDecl = (EnumDeclaration) typeDecl;
// getEntries() gives the list of enum constant declarations
for (BodyDeclaration> member : enumDecl.getEntries()) {
putNewElement(typeElt, result, member, typeDecl.getNameAsString());
}
}
for (BodyDeclaration> member : typeDecl.getMembers()) {
putNewElement(typeElt, result, member, typeDecl.getNameAsString());
}
return result;
}
private void putNewElement(
TypeElement typeElt,
Map> result,
BodyDeclaration> member,
String typeDeclName) {
if (member instanceof MethodDeclaration) {
Element elt = findElement(typeElt, (MethodDeclaration) member);
if (elt != null) {
putNoOverride(result, elt, member);
}
} else if (member instanceof ConstructorDeclaration) {
Element elt = findElement(typeElt, (ConstructorDeclaration) member);
if (elt != null) {
putNoOverride(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) {
putNoOverride(result, varelt, fieldDecl);
}
}
} else if (member instanceof EnumConstantDeclaration) {
Element elt = findElement(typeElt, (EnumConstantDeclaration) member);
if (elt != null) {
putNoOverride(result, elt, member);
}
} else if (member instanceof ClassOrInterfaceDeclaration) {
Element elt = findElement(typeElt, (ClassOrInterfaceDeclaration) member);
if (elt != null) {
putNoOverride(result, elt, member);
}
} else if (member instanceof EnumDeclaration) {
Element elt = findElement(typeElt, (EnumDeclaration) member);
if (elt != null) {
putNoOverride(result, elt, member);
}
} else {
stubWarnNotFound(
String.format(
"Ignoring element of type %s in %s", member.getClass(), typeDeclName));
}
}
/** Return the element of {@code types} whose name matches {@code type}. */
private AnnotatedDeclaredType findType(
ClassOrInterfaceType type, List types) {
String typeString = type.getNameAsString();
for (AnnotatedDeclaredType superType : types) {
if (superType
.getUnderlyingType()
.asElement()
.getSimpleName()
.contentEquals(typeString)) {
return superType;
}
}
stubWarnNotFound("Supertype " + typeString + " not found");
if (debugStubParser) {
stubDebug("Supertypes that were searched:");
for (AnnotatedDeclaredType superType : types) {
stubDebug(String.format(" %s", superType));
}
}
return null;
}
/**
* Looks for the nested type element in the typeElt and returns it if the element has the same
* name as provided class or interface declaration. In case nested element is not found it
* returns null.
*
* @param typeElt an element where nested type element should be looked for
* @param ciDecl class or interface declaration which name should be found among nested elements
* of the typeElt
* @return nested in typeElt element with the name of the class or interface or null if nested
* element is not found
*/
private Element findElement(TypeElement typeElt, ClassOrInterfaceDeclaration ciDecl) {
final String wantedClassOrInterfaceName = ciDecl.getNameAsString();
for (TypeElement typeElement : ElementUtils.getAllTypeElementsIn(typeElt)) {
if (wantedClassOrInterfaceName.equals(typeElement.getSimpleName().toString())) {
return typeElement;
}
}
stubWarnNotFound(
"Class/interface " + wantedClassOrInterfaceName + " not found in type " + typeElt);
if (debugStubParser) {
stubDebug(String.format(" Here are the type declarations of %s:", typeElt));
for (TypeElement method : ElementFilter.typesIn(typeElt.getEnclosedElements())) {
stubDebug(String.format(" %s", method));
}
}
return null;
}
/**
* Looks for the nested enum element in the typeElt and returns it if the element has the same
* name as provided enum declaration. In case nested element is not found it returns null.
*
* @param typeElt an element where nested enum element should be looked for
* @param enumDecl enum declaration which name should be found among nested elements of the
* typeElt
* @return nested in typeElt enum element with the name of the provided enum or null if nested
* element is not found
*/
private Element findElement(TypeElement typeElt, EnumDeclaration enumDecl) {
final String wantedEnumName = enumDecl.getNameAsString();
for (TypeElement typeElement : ElementUtils.getAllTypeElementsIn(typeElt)) {
if (wantedEnumName.equals(typeElement.getSimpleName().toString())) {
return typeElement;
}
}
stubWarnNotFound("Enum " + wantedEnumName + " not found in type " + typeElt);
if (debugStubParser) {
stubDebug(String.format(" Here are the type declarations of %s:", typeElt));
for (TypeElement method : ElementFilter.typesIn(typeElt.getEnclosedElements())) {
stubDebug(String.format(" %s", method));
}
}
return null;
}
/**
* Looks for an enum constant element in the typeElt and returns it if the element has the same
* name as provided. In case enum constant element is not found it returns null.
*
* @param typeElt type element where enum constant element should be looked for
* @param enumConstDecl the declaration of the enum constant
* @return enum constant element in typeElt with the provided name or null if enum constant
* element is not found
*/
private VariableElement findElement(
TypeElement typeElt, EnumConstantDeclaration enumConstDecl) {
final String enumConstName = enumConstDecl.getNameAsString();
return findFieldElement(typeElt, enumConstName);
}
/**
* Looks for method element in the typeElt and returns it if the element has the same signature
* as provided method declaration. In case method element is not found it returns null.
*
* @param typeElt type element where method element should be looked for
* @param methodDecl method declaration with signature that should be found among methods in the
* typeElt
* @return method element in typeElt with the same signature as the provided method declaration
* or null if method element is not found
*/
private ExecutableElement findElement(TypeElement typeElt, MethodDeclaration methodDecl) {
final String wantedMethodName = methodDecl.getNameAsString();
final int wantedMethodParams =
(methodDecl.getParameters() == null) ? 0 : methodDecl.getParameters().size();
final String wantedMethodString = StubUtil.toString(methodDecl);
for (ExecutableElement method : ElementUtils.getAllMethodsIn(typeElt, elements)) {
// do heuristics first
if (wantedMethodParams == method.getParameters().size()
&& wantedMethodName.contentEquals(method.getSimpleName().toString())
&& ElementUtils.getSimpleName(method).equals(wantedMethodString)) {
return method;
}
}
stubWarnNotFound("Method " + wantedMethodString + " not found in type " + typeElt);
if (debugStubParser) {
stubDebug(String.format(" Here are the methods of %s:", typeElt));
for (ExecutableElement method :
ElementFilter.methodsIn(typeElt.getEnclosedElements())) {
stubDebug(String.format(" %s", method));
}
}
return null;
}
/**
* Looks for a constructor element in the typeElt and returns it if the element has the same
* signature as provided constructor declaration. In case constructor element is not found it
* returns null.
*
* @param typeElt type element where constructor element should be looked for
* @param constructorDecl constructor declaration with signature that should be found among
* constructors in the typeElt
* @return constructor element in typeElt with the same signature as the provided constructor
* declaration or null if constructor element is not found
*/
private ExecutableElement findElement(
TypeElement typeElt, ConstructorDeclaration constructorDecl) {
final int wantedMethodParams =
(constructorDecl.getParameters() == null)
? 0
: constructorDecl.getParameters().size();
final String wantedMethodString = StubUtil.toString(constructorDecl);
for (ExecutableElement method :
ElementFilter.constructorsIn(typeElt.getEnclosedElements())) {
// do heuristics first
if (wantedMethodParams == method.getParameters().size()
&& ElementUtils.getSimpleName(method).equals(wantedMethodString)) {
return method;
}
}
stubWarnNotFound("Constructor " + wantedMethodString + " not found in type " + typeElt);
if (debugStubParser) {
for (ExecutableElement method :
ElementFilter.constructorsIn(typeElt.getEnclosedElements())) {
stubDebug(String.format(" %s", method));
}
}
return null;
}
private VariableElement findElement(TypeElement typeElt, VariableDeclarator variable) {
final String fieldName = variable.getNameAsString();
return findFieldElement(typeElt, fieldName);
}
/**
* Looks for a field element in the typeElt and returns it if the element has the same name as
* provided. In case field element is not found it returns null.
*
* @param typeElt type element where field element should be looked for
* @param fieldName field name that should be found
* @return field element in typeElt with the provided name or null if field element is not found
*/
private VariableElement findFieldElement(TypeElement typeElt, String fieldName) {
for (VariableElement field : ElementUtils.getAllFieldsIn(typeElt, elements)) {
// field.getSimpleName() is a CharSequence, not a String
if (fieldName.equals(field.getSimpleName().toString())) {
return field;
}
}
stubWarnNotFound("Field " + fieldName + " not found in type " + typeElt);
if (debugStubParser) {
for (VariableElement field : ElementFilter.fieldsIn(typeElt.getEnclosedElements())) {
stubDebug(String.format(" %s", field));
}
}
return null;
}
/**
* Given a fully-qualified type name, return a TypeElement for it, or null if none exists. Also
* cache in importedTypes.
*/
private TypeElement getTypeElementOrNull(String name) {
TypeElement typeElement = elements.getTypeElement(name);
if (typeElement != null) {
importedTypes.put(name, typeElement);
}
// for debugging: stubWarn("getTypeElementOrNull(%s) => %s%n", name, typeElement);
return typeElement;
}
/**
* Get the type element for the given fully-qualified type name, or issue a warning if none is
* found.
*/
private TypeElement getTypeElement(String typeName, String... msg) {
TypeElement classElement = elements.getTypeElement(typeName);
if (classElement == null) {
if (msg.length == 0) {
stubWarnNotFound("Type not found: " + typeName);
} else {
stubWarnNotFound(msg[0] + ": " + typeName);
}
}
return classElement;
}
private PackageElement findPackage(String packageName) {
PackageElement packageElement = elements.getPackageElement(packageName);
if (packageElement == null) {
stubWarnNotFound("Imported package not found: " + packageName);
}
return packageElement;
}
/**
* Convert {@code annotation} into an AnnotationMirror. Returns null if the annotation isn't
* supported by the checker or if some error occurred while converting it.
*/
private AnnotationMirror getAnnotation(
AnnotationExpr annotation, Map allStubAnnotations) {
AnnotationMirror annoMirror;
if (annotation instanceof MarkerAnnotationExpr) {
String annoName = ((MarkerAnnotationExpr) annotation).getNameAsString();
annoMirror = allStubAnnotations.get(annoName);
} else if (annotation instanceof NormalAnnotationExpr) {
NormalAnnotationExpr nrmanno = (NormalAnnotationExpr) annotation;
String annoName = nrmanno.getNameAsString();
annoMirror = allStubAnnotations.get(annoName);
if (annoMirror == null) {
// Not a supported qualifier -> ignore
return null;
}
AnnotationBuilder builder = new AnnotationBuilder(processingEnv, annoMirror);
List pairs = nrmanno.getPairs();
if (pairs != null) {
for (MemberValuePair mvp : pairs) {
String member = mvp.getNameAsString();
Expression exp = mvp.getValue();
boolean success = handleExpr(builder, member, exp);
if (!success) {
stubWarn(
"Annotation expression, %s, could not be processed for annotation: %s. ",
exp, annotation);
return null;
}
}
}
return builder.build();
} else if (annotation instanceof SingleMemberAnnotationExpr) {
SingleMemberAnnotationExpr sglanno = (SingleMemberAnnotationExpr) annotation;
String annoName = sglanno.getNameAsString();
annoMirror = allStubAnnotations.get(annoName);
if (annoMirror == null) {
// Not a supported qualifier -> ignore
return null;
}
AnnotationBuilder builder = new AnnotationBuilder(processingEnv, annoMirror);
Expression valexpr = sglanno.getMemberValue();
boolean success = handleExpr(builder, "value", valexpr);
if (!success) {
stubWarn(
"Annotation expression, %s, could not be processed for annotation: %s. ",
valexpr, annotation);
return null;
}
return builder.build();
} else {
throw new BugInCF("StubParser: unknown annotation type: " + annotation);
}
return annoMirror;
}
/** Returns the value of {@code expr}, or null if some problem occurred getting the value. */
private Object getValueOfExpressionInAnnotation(
String name, Expression expr, TypeKind valueKind) {
if (expr instanceof FieldAccessExpr || expr instanceof NameExpr) {
VariableElement elem;
if (expr instanceof NameExpr) {
elem = findVariableElement((NameExpr) expr);
} else {
elem = findVariableElement((FieldAccessExpr) expr);
}
if (elem == null) {
stubWarn("Field not found: " + expr);
return null;
}
Object value = elem.getConstantValue() != null ? elem.getConstantValue() : elem;
if (value instanceof Number) {
return convert((Number) value, valueKind);
} else {
return value;
}
} else if (expr instanceof StringLiteralExpr) {
return ((StringLiteralExpr) expr).asString();
} else if (expr instanceof BooleanLiteralExpr) {
return ((BooleanLiteralExpr) expr).getValue();
} else if (expr instanceof CharLiteralExpr) {
return convert((int) ((CharLiteralExpr) expr).asChar(), valueKind);
} else if (expr instanceof DoubleLiteralExpr) {
// No conversion needed if the expression is a double, the annotation value must be a
// double, too.
return ((DoubleLiteralExpr) expr).asDouble();
} else if (expr instanceof IntegerLiteralExpr) {
return convert(((IntegerLiteralExpr) expr).asInt(), valueKind);
} else if (expr instanceof LongLiteralExpr) {
return convert(((LongLiteralExpr) expr).asLong(), valueKind);
} else if (expr instanceof UnaryExpr) {
if (((UnaryExpr) expr).getOperator() == UnaryExpr.Operator.MINUS) {
Object value =
getValueOfExpressionInAnnotation(
name, ((UnaryExpr) expr).getExpression(), valueKind);
if (value instanceof Number) {
return convert((Number) value, valueKind, true);
}
}
stubWarn("Unexpected Unary annotation expression: " + expr);
return null;
} else if (expr instanceof ClassExpr) {
ClassExpr classExpr = (ClassExpr) expr;
String className = classExpr.getType().toString();
if (importedTypes.containsKey(className)) {
return importedTypes.get(className).asType();
}
TypeElement typeElement = findTypeOfName(className);
if (typeElement == null) {
stubWarn("StubParser: unknown class name " + className);
return null;
}
return typeElement.asType();
} else if (expr instanceof NullLiteralExpr) {
stubWarn("Null found as value for %s. Null isn't allowed as an annotation value", name);
return null;
} else {
stubWarn("Unexpected annotation expression: " + expr);
return null;
}
}
/**
* Returns the TypeElement with the name {@code name}, if one exists. Otherwise, checks the
* class and package of {@code parseState} for a class named {@code name}.
*
* @param name classname (simple, or Outer.Inner, or fully-qualified)
* @return the TypeElement for {@code name}, or null if not found
*/
private @Nullable TypeElement findTypeOfName(String name) {
String packageName = parseState.packageName;
String packagePrefix = (packageName == null) ? "" : packageName + ".";
// stubWarn("findTypeOfName(%s), parseState %s %s%n", name, packageName, enclosingClass);
// As soon as typeElement is set to a non-null value, it will be returned.
TypeElement typeElement = getTypeElementOrNull(name);
if (typeElement == null && packageName != null) {
typeElement = getTypeElementOrNull(packagePrefix + name);
}
String enclosingClass = parseState.className;
while (typeElement == null && enclosingClass != null) {
typeElement = getTypeElementOrNull(packagePrefix + enclosingClass + "." + name);
int lastDot = enclosingClass.lastIndexOf('.');
if (lastDot == -1) {
break;
} else {
enclosingClass = enclosingClass.substring(0, lastDot);
}
}
if (typeElement == null && !"java.lang".equals(packageName)) {
typeElement = getTypeElementOrNull("java.lang." + name);
}
return typeElement;
}
/**
* Converts {@code number} to {@code expectedKind}.
*
* {@code @interface Anno { long value();})
* {@code @Anno(1)}
*
* To properly build @Anno, the IntegerLiteralExpr "1" must be converted from an int to a long.
* */
private Object convert(Number number, TypeKind expectedKind) {
return convert(number, expectedKind, false);
}
/**
* Converts {@code number} to {@code expectedKind}. The value converted is multiplied by -1 if
* {@code negate} is true
*
* @param number Number value to be converted
* @param expectedKind one of type {byte, short, int, long, char, float, double}
* @param negate whether to negate the value of the Number Object while converting
* @return the converted Object
*/
private Object convert(Number number, TypeKind expectedKind, boolean negate) {
byte scalefactor = (byte) (negate ? -1 : 1);
switch (expectedKind) {
case BYTE:
return number.byteValue() * scalefactor;
case SHORT:
return number.shortValue() * scalefactor;
case INT:
return number.intValue() * scalefactor;
case LONG:
return number.longValue() * scalefactor;
case CHAR:
/* char is not multiplied by the scale factor since it's not possible for `number` to be negative
when `expectedkind` is a CHAR and casting a negative value to char is illegal */
return (char) number.intValue();
case FLOAT:
return number.floatValue() * scalefactor;
case DOUBLE:
return number.doubleValue() * scalefactor;
default:
throw new BugInCF("Unexpected expectedKind: " + expectedKind);
}
}
/**
* Adds an annotation element (argument) to {@code builder}. The element is a Java expression.
*
* @return true if the expression was parsed and added to {@code builder}, false otherwise
*/
private boolean handleExpr(AnnotationBuilder builder, String name, Expression expr) {
ExecutableElement var = builder.findElement(name);
TypeMirror expected = var.getReturnType();
TypeKind valueKind;
if (expected.getKind() == TypeKind.ARRAY) {
valueKind = ((ArrayType) expected).getComponentType().getKind();
} else {
valueKind = expected.getKind();
}
if (expr instanceof ArrayInitializerExpr) {
if (expected.getKind() != TypeKind.ARRAY) {
stubWarn(
"unhandled annotation attribute type: "
+ expr
+ " and expected: "
+ expected);
return false;
}
List arrayExpressions = ((ArrayInitializerExpr) expr).getValues();
Object[] values = new Object[arrayExpressions.size()];
for (int i = 0; i < arrayExpressions.size(); ++i) {
values[i] =
getValueOfExpressionInAnnotation(name, arrayExpressions.get(i), valueKind);
if (values[i] == null) {
return false;
}
}
builder.setValue(name, values);
} else {
Object value = getValueOfExpressionInAnnotation(name, expr, valueKind);
if (value == null) {
return false;
}
if (expected.getKind() == TypeKind.ARRAY) {
Object[] valueArray = {value};
builder.setValue(name, valueArray);
} else {
builderSetValue(builder, name, value);
}
}
return true;
}
/**
* Cast to non-array values so that correct the correct AnnotationBuilder#setValue method is
* called. (Different types of values are handled differently.)
*/
private void builderSetValue(AnnotationBuilder builder, String name, Object value) {
if (value instanceof Boolean) {
builder.setValue(name, (Boolean) value);
} else if (value instanceof Character) {
builder.setValue(name, (Character) value);
} else if (value instanceof Class>) {
builder.setValue(name, (Class>) value);
} else if (value instanceof Double) {
builder.setValue(name, (Double) value);
} else if (value instanceof Enum>) {
builder.setValue(name, (Enum>) value);
} else if (value instanceof Float) {
builder.setValue(name, (Float) value);
} else if (value instanceof Integer) {
builder.setValue(name, (Integer) value);
} else if (value instanceof Long) {
builder.setValue(name, (Long) value);
} else if (value instanceof Short) {
builder.setValue(name, (Short) value);
} else if (value instanceof String) {
builder.setValue(name, (String) value);
} else if (value instanceof TypeMirror) {
builder.setValue(name, (TypeMirror) value);
} else if (value instanceof VariableElement) {
builder.setValue(name, (VariableElement) value);
} else {
throw new BugInCF("Unexpected builder value: %s", value);
}
}
/**
* Mapping of a name access expression that has already been encountered to the resolved
* variable element.
*/
private final Map findVariableElementNameCache = new HashMap<>();
private @Nullable VariableElement findVariableElement(NameExpr nexpr) {
if (findVariableElementNameCache.containsKey(nexpr)) {
return findVariableElementNameCache.get(nexpr);
}
VariableElement res = null;
boolean importFound = false;
for (String imp : importedConstants) {
Pair partitionedName = StubUtil.partitionQualifiedName(imp);
String typeName = partitionedName.first;
String fieldName = partitionedName.second;
if (fieldName.equals(nexpr.getNameAsString())) {
TypeElement enclType =
getTypeElement(
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) {
stubWarnNotFound("Static field " + nexpr.getName() + " is not imported");
}
findVariableElementNameCache.put(nexpr, res);
return res;
}
/**
* Mapping of a field access expression that has already been encountered to the resolved
* variable element.
*/
private final Map findVariableElementFieldCache =
new HashMap<>();
private @Nullable VariableElement findVariableElement(FieldAccessExpr faexpr) {
if (findVariableElementFieldCache.containsKey(faexpr)) {
return findVariableElementFieldCache.get(faexpr);
}
TypeElement rcvElt = elements.getTypeElement(faexpr.getScope().toString());
if (rcvElt == null) {
// Search importedConstants for full annotation name.
for (String imp : importedConstants) {
// TODO: should this use StubUtil.partitionQualifiedName?
String[] importDelimited = imp.split("\\.");
if (importDelimited[importDelimited.length - 1].equals(
faexpr.getScope().toString())) {
StringBuilder fullAnnotation = new StringBuilder();
for (int i = 0; i < importDelimited.length - 1; i++) {
fullAnnotation.append(importDelimited[i]);
fullAnnotation.append('.');
}
fullAnnotation.append(faexpr.getScope().toString());
rcvElt = elements.getTypeElement(fullAnnotation);
break;
}
}
if (rcvElt == null) {
stubWarnNotFound("Type " + faexpr.getScope() + " not found");
return null;
}
}
VariableElement res = findFieldElement(rcvElt, faexpr.getNameAsString());
findVariableElementFieldCache.put(faexpr, res);
return res;
}
///////////////////////////////////////////////////////////////////////////
/// Map utilities
///
/** Just like Map.put, but does not override any existing value in the map. */
private static void putNoOverride(Map m, K key, V value) {
if (key == null) {
throw new BugInCF("StubParser: key is null");
}
if (!m.containsKey(key)) {
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> map, String key, Set annos) {
if (map.containsKey(key)) {
map.get(key).addAll(annos);
} else {
map.put(key, annos);
}
}
/**
* Just like Map.put, but merges with any existing value for the given key, instead ef replacing
* it.
*/
private static void putNew(
Map m, Element key, AnnotatedTypeMirror value) {
if (key == null) {
throw new BugInCF("StubParser: key is null");
}
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 merges with existing values using {@link #putNew}. */
private static void putAllNew(Map m, Map m2) {
for (Map.Entry e2 : m2.entrySet()) {
putNoOverride(m, e2.getKey(), e2.getValue());
}
}
///////////////////////////////////////////////////////////////////////////
/// Issue warnings
///
// The warnings that have been issued so far.
private static final Set warnings = new HashSet<>();
/**
* Issues the given warning about missing elements, only if it has not been previously issued
* and the -AstubWarnIfNotFound command-line argument was passed.
*/
private void stubWarnNotFound(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 and the -AstubWarnIfOverwritesBytecode command-line argument was passed.
*/
private void stubWarnOverwritesBytecode(String warning) {
if (warnings.add(warning) && (warnIfStubOverwritesBytecode || debugStubParser)) {
processingEnv
.getMessager()
.printMessage(javax.tools.Diagnostic.Kind.WARNING, "StubParser: " + warning);
}
}
/**
* Issues a warning, only if it has not been previously issued.
*
* @param warning a format string
* @param args the arguments for {@code warning}
*/
private void stubWarn(String warning, Object... args) {
warning = String.format(warning, args);
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);
}
}
///////////////////////////////////////////////////////////////////////////
/// Parse state
///
/** Represents a class: its package name and simple name. */
private static class FqName {
/** Name of the package being parsed, or null. */
public String packageName;
/** Name of the type being parsed. Includes outer class names if any. */
public String className;
public FqName(String packageName, String className) {
this.packageName = packageName;
this.className = className;
}
/** Fully-qualified name of the class. */
@Override
public String toString() {
if (packageName == null) {
return className;
} else {
return packageName + "." + className;
}
}
}
}