package checkers.util.stub;
import java.io.InputStream;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.*;
import javax.lang.model.type.TypeKind;
import javax.lang.model.util.ElementFilter;
import javax.lang.model.util.Elements;
import checkers.types.AnnotatedTypeFactory;
import checkers.types.AnnotatedTypeMirror;
import checkers.types.AnnotatedTypeMirror.*;
import checkers.util.AnnotationUtils;
import japa.parser.JavaParser;
import japa.parser.ast.*;
import japa.parser.ast.body.*;
import japa.parser.ast.expr.AnnotationExpr;
import japa.parser.ast.type.*;
public class StubParser {
final IndexUnit index;
final AnnotatedTypeFactory atypeFactory;
final AnnotationUtils annoUtils;
final ProcessingEnvironment env;
final Elements elements;
final Map<String, AnnotationMirror> annotations;
public StubParser(InputStream inputStream, AnnotatedTypeFactory factory, ProcessingEnvironment env) {
try {
this.index = JavaParser.parse(inputStream);
} catch (Exception e) {
throw new Error(e);
}
this.atypeFactory = factory;
this.env = env;
this.annoUtils = AnnotationUtils.getInstance(env);
this.elements = env.getElementUtils();
annotations = getSupportedAnnotations();
}
private Map<String, AnnotationMirror> annoWithinPackage(String packageName) {
AnnotationUtils annoUtils = AnnotationUtils.getInstance(env);
Map<String, AnnotationMirror> r = new HashMap<String, AnnotationMirror>();
PackageElement pkg = this.elements.getPackageElement(packageName);
if (pkg == null)
return r;
for (TypeElement typeElm : ElementFilter.typesIn(pkg.getEnclosedElements())) {
if (typeElm.getKind() == ElementKind.ANNOTATION_TYPE) {
AnnotationMirror anno = annoUtils.fromName(typeElm.getQualifiedName());
r.put(typeElm.getSimpleName().toString(), anno);
}
}
return r;
}
private Map<String, AnnotationMirror> getSupportedAnnotations() {
assert !index.getCompilationUnits().isEmpty();
CompilationUnit cu = index.getCompilationUnits().get(0);
AnnotationUtils annoUtils = AnnotationUtils.getInstance(env);
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()) {
AnnotationMirror anno = annoUtils.fromName(imported);
if (anno != null ) {
Element annoElt = anno.getAnnotationType().asElement();
result.put(annoElt.getSimpleName().toString(), anno);
} else {
// System.err.println("StubParser: Could not load import: " + imported);
}
} else {
result.putAll(annoWithinPackage(imported));
}
} catch (AssertionError error) {
// do nothing
// System.err.println("StubParser: " + error);
}
}
return result;
}
public Map<Element, AnnotatedTypeMirror> parse() {
Map<Element, AnnotatedTypeMirror> result = new HashMap<Element, AnnotatedTypeMirror>();
parse(result);
return result;
}
public void parse(Map<Element, AnnotatedTypeMirror> result) {
parse(this.index, result);
}
private void parse(IndexUnit index, Map<Element, AnnotatedTypeMirror> result) {
for (CompilationUnit cu : index.getCompilationUnits())
parse(cu, result);
}
private void parse(CompilationUnit cu, Map<Element, AnnotatedTypeMirror> result) {
final String packageName;
if (cu.getPackage() == null)
packageName = null;
else
packageName = cu.getPackage().getName().toString();
if (cu.getTypes() != null) {
for (TypeDeclaration typeDecl : cu.getTypes())
parse(typeDecl, packageName, result);
}
}
private void parse(TypeDeclaration typeDecl, String packageName, Map<Element, AnnotatedTypeMirror> result) {
String typeName = (packageName == null ? "" : packageName + ".") + typeDecl.getName();
TypeElement typeElt = elements.getTypeElement(typeName);
// couldn't find type. not in class path
// TODO: Should throw exception?!
if (typeElt == null
|| typeElt.getKind() == ElementKind.ENUM
|| typeElt.getKind() == ElementKind.ANNOTATION_TYPE) {
// System.err.println("StubParser: Type not found: " + typeName);
return;
}
if (typeDecl instanceof ClassOrInterfaceDeclaration) {
parseType((ClassOrInterfaceDeclaration)typeDecl, typeElt, result);
}
Map<Element, BodyDeclaration> elementsToDecl = mapMembers(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, result);
else if (elt.getKind() == ElementKind.CONSTRUCTOR)
parseConstructor((ConstructorDeclaration)decl, (ExecutableElement)elt, result);
else if (elt.getKind() == ElementKind.METHOD)
parseMethod((MethodDeclaration)decl, (ExecutableElement)elt, result);
else { /* do nothing */
// System.err.println("Ignoring: " + elt);
}
}
}
private void parseType(ClassOrInterfaceDeclaration decl, TypeElement elt, Map<Element, AnnotatedTypeMirror> result) {
AnnotatedDeclaredType type = atypeFactory.fromElement(elt);
annotate(type, decl.getAnnotations());
annotateParameters(type.getTypeArguments(), decl.getTypeParameters());
annotateSupertypes(decl, type);
result.put(elt, type);
}
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;
if (foundType != null) annotate(foundType, superType);
}
}
if (typeDecl.getImplements() != null) {
for (ClassOrInterfaceType superType : typeDecl.getImplements()) {
AnnotatedDeclaredType foundType = findType(superType, type.directSuperTypes());
assert foundType != null;
if (foundType != null) annotate(foundType, superType);
}
}
}
private void parseMethod(MethodDeclaration decl, ExecutableElement elt,
Map<Element, AnnotatedTypeMirror> result) {
AnnotatedExecutableType methodType = atypeFactory.fromElement(elt);
annotateParameters(methodType.getTypeVariables(), decl.getTypeParameters());
annotate(methodType.getReturnType(), decl.getType());
for (int i = 0; i < methodType.getParameterTypes().size(); ++i) {
AnnotatedTypeMirror paramType = methodType.getParameterTypes().get(i);
Parameter param = decl.getParameters().get(i);
if (param.isVarArgs()) {
// workaround
assert paramType.getKind() == TypeKind.ARRAY;
annotate(((AnnotatedArrayType)paramType).getComponentType(), param.getType());
} else {
annotate(paramType, param.getType());
}
}
annotate(methodType.getReceiverType(), decl.getReceiverAnnotations());
result.put(elt, methodType);
}
private List<AnnotatedTypeMirror> arrayList(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 = arrayList(atype);
assert typeDef.getArrayCount() == arrayTypes.size() - 1;
for (int i = 0; i < typeDef.getArrayCount(); ++i) {
List<AnnotationExpr> annotations = typeDef.getAnnotationsAtLevel(i);
if (annotations != null) {
annotate(arrayTypes.get(i), annotations);
}
}
// handle generic type on base
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;
}
if (typeDef.getAnnotations() != null)
annotate(atype, typeDef.getAnnotations());
ClassOrInterfaceType declType = unwrapDeclaredType(typeDef);
if (atype.getKind() == TypeKind.DECLARED
&& declType != null) {
AnnotatedDeclaredType adeclType = (AnnotatedDeclaredType)atype;
if (declType.getTypeArgs() != null
&& !declType.getTypeArgs().isEmpty()
&& adeclType.isParameterized()) {
assert declType.getTypeArgs().size() == 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());
} else if (wildcardDef.getSuper() != null) {
annotate(wildcardType.getSuperBound(), wildcardDef.getSuper());
}
}
}
private void parseConstructor(ConstructorDeclaration decl,
ExecutableElement elt, Map<Element, AnnotatedTypeMirror> result) {
AnnotatedExecutableType methodType = atypeFactory.fromElement(elt);
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());
}
annotate(methodType.getReceiverType(), decl.getReceiverAnnotations());
result.put(elt, methodType);
}
private void parseField(FieldDeclaration decl,
VariableElement elt, Map<Element, AnnotatedTypeMirror> result) {
AnnotatedTypeMirror fieldType = atypeFactory.fromElement(elt);
annotate(fieldType, decl.getType());
result.put(elt, fieldType);
}
private void annotate(AnnotatedTypeMirror type, List<AnnotationExpr> annotations) {
if (annotations == null)
return;
for (AnnotationExpr annotation : annotations) {
String annoName = StubUtil.getAnnotationName(annotation);
AnnotationMirror annoMirror = this.annotations.get(annoName);
if (annoMirror != null)
type.addAnnotation(annoMirror);
}
}
private void annotateParameters(List<? extends AnnotatedTypeMirror> typeArguments,
List<TypeParameter> typeParameters) {
if (typeParameters == null)
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().size() == 1) {
annotate(paramType.getUpperBound(), param.getTypeBound().get(0));
}
}
}
private Map<Element, BodyDeclaration> mapMembers(TypeElement typeElt, TypeDeclaration typeDecl) {
assert typeElt.getSimpleName().contentEquals(typeDecl.getName());
Map<Element, BodyDeclaration> result = new HashMap<Element, BodyDeclaration>();
for (BodyDeclaration member : typeDecl.getMembers()) {
if (member instanceof MethodDeclaration) {
Element elt = findElement(typeElt, (MethodDeclaration)member);
result.put(elt, member);
} else if (member instanceof ConstructorDeclaration) {
Element elt = findElement(typeElt, (ConstructorDeclaration)member);
result.put(elt, member);
} else if (member instanceof FieldDeclaration) {
FieldDeclaration fieldDecl = (FieldDeclaration)member;
for (VariableDeclarator var : fieldDecl.getVariables())
result.put(findElement(typeElt, var), fieldDecl);
} else if (member instanceof ClassOrInterfaceDeclaration) {
// TODO: handle nested classes
// ClassOrInterfaceDeclaration ciDecl = (ClassOrInterfaceDeclaration) member;
} else {
// System.out.println("StubParser: Ignoring in mapMembers: " + member.getClass());
}
}
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;
}
// System.err.println("StubParser: Type " + typeString + " not found");
return null;
}
public 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 : ElementFilter.methodsIn(typeElt.getEnclosedElements())) {
// do heuristics first
if (wantedMethodParams == method.getParameters().size()
&& wantedMethodName.contentEquals(method.getSimpleName())
&& StubUtil.toString(method).equals(wantedMethodString))
return method;
}
// System.err.println("StubParser: Method " + wantedMethodString + " not found in type " + typeElt);
return null;
}
public 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;
}
// System.err.println("StubParser: Constructor " + wantedMethodString + " not found in type " + typeElt);
return null;
}
public VariableElement findElement(TypeElement typeElt, VariableDeclarator variable) {
final String fieldName = variable.getId().getName();
for (VariableElement field : ElementFilter.fieldsIn(typeElt.getEnclosedElements())) {
if (fieldName.contains(field.getSimpleName()))
return field;
}
// System.err.println("StubParser: Field " + fieldName + " not found in type " + typeElt);
return null;
}
}