package org.checkerframework.framework.type;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.Tree;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.TypeParameterElement;
import javax.lang.model.type.ArrayType;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedPrimitiveType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType;
import org.checkerframework.framework.type.visitor.AnnotatedTypeScanner;
import org.checkerframework.framework.type.visitor.SimpleAnnotatedTypeVisitor;
import org.checkerframework.javacutil.ElementUtils;
import org.checkerframework.javacutil.ErrorReporter;
import org.checkerframework.javacutil.TreeUtils;
/**
* Finds the direct supertypes of an input AnnotatedTypeMirror. See
* http://docs.oracle.com/javase/specs/jls/se8/html/jls-4.html#jls-4.10.2
*
* @see Types#directSupertypes(TypeMirror)
*/
class SupertypeFinder {
// Version of method below for declared types
/** @see Types#directSupertypes(TypeMirror) */
public static List<AnnotatedDeclaredType> directSuperTypes(AnnotatedDeclaredType type) {
SupertypeFindingVisitor supertypeFindingVisitor =
new SupertypeFindingVisitor(type.atypeFactory);
List<AnnotatedDeclaredType> supertypes = supertypeFindingVisitor.visitDeclared(type, null);
type.atypeFactory.postDirectSuperTypes(type, supertypes);
return supertypes;
}
// Version of method above for all types
/** @see Types#directSupertypes(TypeMirror) */
public static final List<? extends AnnotatedTypeMirror> directSuperTypes(
AnnotatedTypeMirror type) {
SupertypeFindingVisitor supertypeFindingVisitor =
new SupertypeFindingVisitor(type.atypeFactory);
List<? extends AnnotatedTypeMirror> supertypes = supertypeFindingVisitor.visit(type, null);
type.atypeFactory.postDirectSuperTypes(type, supertypes);
return supertypes;
}
private static class SupertypeFindingVisitor
extends SimpleAnnotatedTypeVisitor<List<? extends AnnotatedTypeMirror>, Void> {
private final Types types;
private final AnnotatedTypeFactory atypeFactory;
private final TypeParamReplacer typeParamReplacer;
SupertypeFindingVisitor(AnnotatedTypeFactory atypeFactory) {
this.atypeFactory = atypeFactory;
this.types = atypeFactory.types;
this.typeParamReplacer = new TypeParamReplacer(types);
}
@Override
public List<AnnotatedTypeMirror> defaultAction(AnnotatedTypeMirror t, Void p) {
return new ArrayList<AnnotatedTypeMirror>();
}
/**
* Primitive Rules:
*
* <pre>{@code
* double >1 float
* float >1 long
* long >1 int
* int >1 char
* int >1 short
* short >1 byte
* }</pre>
*
* For easiness:
*
* <pre>{@code
* boxed(primitiveType) >: primitiveType
* }</pre>
*/
@Override
public List<AnnotatedTypeMirror> visitPrimitive(AnnotatedPrimitiveType type, Void p) {
List<AnnotatedTypeMirror> superTypes = new ArrayList<AnnotatedTypeMirror>();
Set<AnnotationMirror> annotations = type.getAnnotations();
// Find Boxed type
TypeElement boxed = types.boxedClass(type.getUnderlyingType());
AnnotatedDeclaredType boxedType = atypeFactory.getAnnotatedType(boxed);
boxedType.replaceAnnotations(annotations);
superTypes.add(boxedType);
TypeKind superPrimitiveType = null;
if (type.getKind() == TypeKind.BOOLEAN) {
// Nothing
} else if (type.getKind() == TypeKind.BYTE) {
superPrimitiveType = TypeKind.SHORT;
} else if (type.getKind() == TypeKind.CHAR) {
superPrimitiveType = TypeKind.INT;
} else if (type.getKind() == TypeKind.DOUBLE) {
// Nothing
} else if (type.getKind() == TypeKind.FLOAT) {
superPrimitiveType = TypeKind.DOUBLE;
} else if (type.getKind() == TypeKind.INT) {
superPrimitiveType = TypeKind.LONG;
} else if (type.getKind() == TypeKind.LONG) {
superPrimitiveType = TypeKind.FLOAT;
} else if (type.getKind() == TypeKind.SHORT) {
superPrimitiveType = TypeKind.INT;
} else {
assert false : "Forgot the primitive " + type;
}
if (superPrimitiveType != null) {
AnnotatedPrimitiveType superPrimitive =
(AnnotatedPrimitiveType)
atypeFactory.toAnnotatedType(
types.getPrimitiveType(superPrimitiveType), false);
superPrimitive.addAnnotations(annotations);
superTypes.add(superPrimitive);
}
return superTypes;
}
@Override
public List<AnnotatedDeclaredType> visitDeclared(AnnotatedDeclaredType type, Void p) {
List<AnnotatedDeclaredType> supertypes = new ArrayList<AnnotatedDeclaredType>();
// Set<AnnotationMirror> annotations = type.getAnnotations();
TypeElement typeElement = (TypeElement) type.getUnderlyingType().asElement();
// Mapping of type variable to actual types
Map<TypeParameterElement, AnnotatedTypeMirror> mapping = new HashMap<>();
if (type.getTypeArguments().size() != typeElement.getTypeParameters().size()) {
if (!type.wasRaw()) {
ErrorReporter.errorAbort(
"AnnotatedDeclaredType's element has a different number of type parameters than type.\n"
+ "type="
+ type
+ "\n"
+ "element="
+ typeElement);
}
}
for (int i = 0; i < type.getTypeArguments().size(); ++i) {
mapping.put(typeElement.getTypeParameters().get(i), type.getTypeArguments().get(i));
}
ClassTree classTree = atypeFactory.trees.getTree(typeElement);
// Testing against enum and annotation. Ideally we can simply use element!
if (classTree != null) {
supertypes.addAll(supertypesFromTree(type, classTree));
} else {
supertypes.addAll(supertypesFromElement(type, typeElement));
// final Element elem = type.getElement() == null ? typeElement : type.getElement();
}
if (typeElement.getKind() == ElementKind.ANNOTATION_TYPE) {
TypeElement jlaElement =
atypeFactory.elements.getTypeElement(Annotation.class.getCanonicalName());
AnnotatedDeclaredType jlaAnnotation = atypeFactory.fromElement(jlaElement);
jlaAnnotation.addAnnotations(type.getAnnotations());
supertypes.add(jlaAnnotation);
}
for (AnnotatedDeclaredType dt : supertypes) {
typeParamReplacer.visit(dt, mapping);
}
return supertypes;
}
private List<AnnotatedDeclaredType> supertypesFromElement(
AnnotatedDeclaredType type, TypeElement typeElement) {
List<AnnotatedDeclaredType> supertypes = new ArrayList<AnnotatedDeclaredType>();
// Find the super types: Start with enums and superclass
if (typeElement.getKind() == ElementKind.ENUM) {
DeclaredType dt = (DeclaredType) typeElement.getSuperclass();
AnnotatedDeclaredType adt =
(AnnotatedDeclaredType) atypeFactory.toAnnotatedType(dt, false);
List<AnnotatedTypeMirror> tas = adt.getTypeArguments();
List<AnnotatedTypeMirror> newtas = new ArrayList<AnnotatedTypeMirror>();
for (AnnotatedTypeMirror t : tas) {
// If the type argument of super is the same as the input type
if (atypeFactory.types.isSameType(
t.getUnderlyingType(), type.getUnderlyingType())) {
t.addAnnotations(type.getAnnotations());
newtas.add(t);
}
}
adt.setTypeArguments(newtas);
supertypes.add(adt);
} else if (typeElement.getSuperclass().getKind() != TypeKind.NONE) {
DeclaredType superClass = (DeclaredType) typeElement.getSuperclass();
AnnotatedDeclaredType dt =
(AnnotatedDeclaredType) atypeFactory.toAnnotatedType(superClass, false);
supertypes.add(dt);
} else if (!ElementUtils.isObject(typeElement)) {
supertypes.add(AnnotatedTypeMirror.createTypeOfObject(atypeFactory));
}
for (TypeMirror st : typeElement.getInterfaces()) {
if (type.wasRaw()) {
st = types.erasure(st);
}
AnnotatedDeclaredType ast =
(AnnotatedDeclaredType) atypeFactory.toAnnotatedType(st, false);
supertypes.add(ast);
if (type.wasRaw()) {
if (st.getKind() == TypeKind.DECLARED) {
final List<? extends TypeMirror> typeArgs =
((DeclaredType) st).getTypeArguments();
final List<AnnotatedTypeMirror> annotatedTypeArgs = ast.getTypeArguments();
for (int i = 0; i < typeArgs.size(); i++) {
atypeFactory.addComputedTypeAnnotations(
types.asElement(typeArgs.get(i)), annotatedTypeArgs.get(i));
}
}
}
}
ElementAnnotationApplier.annotateSupers(supertypes, typeElement);
if (type.wasRaw()) {
for (AnnotatedDeclaredType adt : supertypes) {
adt.setWasRaw();
}
}
return supertypes;
}
private List<AnnotatedDeclaredType> supertypesFromTree(
AnnotatedDeclaredType type, ClassTree classTree) {
List<AnnotatedDeclaredType> supertypes = new ArrayList<AnnotatedDeclaredType>();
if (classTree.getExtendsClause() != null) {
AnnotatedDeclaredType adt =
(AnnotatedDeclaredType)
atypeFactory.getAnnotatedTypeFromTypeTree(
classTree.getExtendsClause());
supertypes.add(adt);
} else if (!ElementUtils.isObject(TreeUtils.elementFromDeclaration(classTree))) {
supertypes.add(AnnotatedTypeMirror.createTypeOfObject(atypeFactory));
}
for (Tree implemented : classTree.getImplementsClause()) {
AnnotatedDeclaredType adt =
(AnnotatedDeclaredType)
atypeFactory.getAnnotatedTypeFromTypeTree(implemented);
supertypes.add(adt);
}
TypeElement elem = TreeUtils.elementFromDeclaration(classTree);
if (elem.getKind() == ElementKind.ENUM) {
DeclaredType dt = (DeclaredType) elem.getSuperclass();
AnnotatedDeclaredType adt =
(AnnotatedDeclaredType) atypeFactory.toAnnotatedType(dt, false);
List<AnnotatedTypeMirror> tas = adt.getTypeArguments();
List<AnnotatedTypeMirror> newtas = new ArrayList<AnnotatedTypeMirror>();
for (AnnotatedTypeMirror t : tas) {
// If the type argument of super is the same as the input type
if (atypeFactory.types.isSameType(
t.getUnderlyingType(), type.getUnderlyingType())) {
t.addAnnotations(type.getAnnotations());
newtas.add(t);
}
}
adt.setTypeArguments(newtas);
supertypes.add(adt);
}
if (type.wasRaw()) {
for (AnnotatedDeclaredType adt : supertypes) {
adt.setWasRaw();
}
}
return supertypes;
}
/**
*
*
* <pre>{@code
* For type = A[ ] ==>
* Object >: A[ ]
* Clonable >: A[ ]
* java.io.Serializable >: A[ ]
*
* if A is reference type, then also
* B[ ] >: A[ ] for any B[ ] >: A[ ]
* }</pre>
*/
@Override
public List<AnnotatedTypeMirror> visitArray(AnnotatedArrayType type, Void p) {
List<AnnotatedTypeMirror> superTypes = new ArrayList<AnnotatedTypeMirror>();
Set<AnnotationMirror> annotations = type.getAnnotations();
Elements elements = atypeFactory.elements;
final AnnotatedTypeMirror objectType =
atypeFactory.getAnnotatedType(elements.getTypeElement("java.lang.Object"));
objectType.addAnnotations(annotations);
superTypes.add(objectType);
final AnnotatedTypeMirror cloneableType =
atypeFactory.getAnnotatedType(elements.getTypeElement("java.lang.Cloneable"));
cloneableType.addAnnotations(annotations);
superTypes.add(cloneableType);
final AnnotatedTypeMirror serializableType =
atypeFactory.getAnnotatedType(elements.getTypeElement("java.io.Serializable"));
serializableType.addAnnotations(annotations);
superTypes.add(serializableType);
for (AnnotatedTypeMirror sup : type.getComponentType().directSuperTypes()) {
ArrayType arrType = atypeFactory.types.getArrayType(sup.getUnderlyingType());
AnnotatedArrayType aarrType =
(AnnotatedArrayType) atypeFactory.toAnnotatedType(arrType, false);
aarrType.setComponentType(sup);
aarrType.addAnnotations(annotations);
superTypes.add(aarrType);
}
return superTypes;
}
@Override
public List<AnnotatedTypeMirror> visitTypeVariable(AnnotatedTypeVariable type, Void p) {
List<AnnotatedTypeMirror> superTypes = new ArrayList<>();
superTypes.add(type.getUpperBound().deepCopy());
return superTypes;
}
@Override
public List<AnnotatedTypeMirror> visitWildcard(AnnotatedWildcardType type, Void p) {
List<AnnotatedTypeMirror> superTypes = new ArrayList<>();
superTypes.add(type.getExtendsBound().deepCopy());
return superTypes;
}
/**
* Note: The explanation below is my interpretation of why we have this code. I am not sure
* if this was the author's original intent but I can see no other reasoning, exercise
* caution:
*
* <p>Classes may have type parameters that are used in extends or implements clauses. E.g.
* {@code class MyList<T> extends List<T>}
*
* <p>Direct supertypes will contain a type {@code List<T>} but the type T may become out of
* sync with the annotations on type {@code MyList<T>}. To keep them in-sync, we substitute
* out the copy of T with the same reference to T that is on {@code MyList<T>}
*/
private static class TypeParamReplacer
extends AnnotatedTypeScanner<Void, Map<TypeParameterElement, AnnotatedTypeMirror>> {
private final Types types;
public TypeParamReplacer(Types types) {
this.types = types;
}
@Override
public Void visitDeclared(
AnnotatedDeclaredType type,
Map<TypeParameterElement, AnnotatedTypeMirror> mapping) {
if (visitedNodes.containsKey(type)) {
return visitedNodes.get(type);
}
visitedNodes.put(type, null);
List<AnnotatedTypeMirror> args = new ArrayList<AnnotatedTypeMirror>();
for (AnnotatedTypeMirror arg : type.getTypeArguments()) {
Element elem = types.asElement(arg.getUnderlyingType());
if ((elem != null)
&& (elem.getKind() == ElementKind.TYPE_PARAMETER)
&& (mapping.containsKey(elem))) {
AnnotatedTypeMirror other = mapping.get(elem);
other.replaceAnnotations(arg.getAnnotationsField());
args.add(other);
} else {
args.add(arg);
scan(arg, mapping);
}
}
type.setTypeArguments(args);
return null;
}
@Override
public Void visitArray(
AnnotatedArrayType type,
Map<TypeParameterElement, AnnotatedTypeMirror> mapping) {
AnnotatedTypeMirror comptype = type.getComponentType();
Element elem = types.asElement(comptype.getUnderlyingType());
AnnotatedTypeMirror other;
if ((elem != null)
&& (elem.getKind() == ElementKind.TYPE_PARAMETER)
&& (mapping.containsKey(elem))) {
other = mapping.get(elem);
other.replaceAnnotations(comptype.getAnnotationsField());
type.setComponentType(other);
} else {
scan(type.getComponentType(), mapping);
}
return null;
}
}
}
}