blob: 7897c888b24c01f584facfaa21def2276236a5ea [file] [log] [blame]
package org.checkerframework.framework.type;
import java.lang.annotation.Annotation;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import org.checkerframework.checker.signature.qual.CanonicalName;
import org.checkerframework.framework.qual.UpperBoundFor;
import org.checkerframework.framework.util.AnnotationMirrorSet;
import org.checkerframework.javacutil.AnnotationBuilder;
import org.checkerframework.javacutil.BugInCF;
import org.checkerframework.javacutil.TypesUtils;
/** Class that computes and stores the qualifier upper bounds for type uses. */
public class QualifierUpperBounds {
/** Map from {@link TypeKind} to annotations. */
private final Map<TypeKind, Set<AnnotationMirror>> typeKinds;
/** Map from canonical class name strings to annotations. */
private final Map<@CanonicalName String, Set<AnnotationMirror>> types;
/** {@link QualifierHierarchy} */
private final QualifierHierarchy qualHierarchy;
private final AnnotatedTypeFactory atypeFactory;
/**
* Creates a {@link QualifierUpperBounds} from the given checker, using that checker to determine
* the annotations that are in the type hierarchy.
*/
public QualifierUpperBounds(AnnotatedTypeFactory typeFactory) {
this.atypeFactory = typeFactory;
this.typeKinds = new EnumMap<>(TypeKind.class);
this.types = new HashMap<>();
this.qualHierarchy = typeFactory.getQualifierHierarchy();
// Get type qualifiers from the checker.
Set<Class<? extends Annotation>> quals = typeFactory.getSupportedTypeQualifiers();
// For each qualifier, read the @UpperBoundFor annotation and put its type classes and kinds
// into maps.
for (Class<? extends Annotation> qual : quals) {
UpperBoundFor upperBoundFor = qual.getAnnotation(UpperBoundFor.class);
if (upperBoundFor == null) {
continue;
}
AnnotationMirror theQual = AnnotationBuilder.fromClass(typeFactory.getElementUtils(), qual);
for (org.checkerframework.framework.qual.TypeKind typeKind : upperBoundFor.typeKinds()) {
TypeKind mappedTk = mapTypeKinds(typeKind);
addTypeKind(mappedTk, theQual);
}
for (Class<?> typeName : upperBoundFor.types()) {
addType(typeName, theQual);
}
}
}
/**
* Map between {@link org.checkerframework.framework.qual.TypeKind} and {@link TypeKind}.
*
* @param typeKind the Checker Framework TypeKind
* @return the javax TypeKind
*/
private TypeKind mapTypeKinds(org.checkerframework.framework.qual.TypeKind typeKind) {
return TypeKind.valueOf(typeKind.name());
}
/** Add default qualifier, {@code theQual}, for the given TypeKind. */
public void addTypeKind(TypeKind typeKind, AnnotationMirror theQual) {
boolean res = qualHierarchy.updateMappingToMutableSet(typeKinds, typeKind, theQual);
if (!res) {
throw new BugInCF(
"QualifierUpperBounds: invalid update of typeKinds $s at %s with %s.",
typeKinds, typeKind, theQual);
}
}
/** Add default qualifier, {@code theQual}, for the given class. */
public void addType(Class<?> type, AnnotationMirror theQual) {
String typeNameString = type.getCanonicalName();
boolean res = qualHierarchy.updateMappingToMutableSet(types, typeNameString, theQual);
if (!res) {
throw new BugInCF(
"QualifierUpperBounds: invalid update of types $s at %s with %s.", types, type, theQual);
}
}
/**
* Returns the set of qualifiers that are the upper bounds for a use of the type.
*
* @param type the TypeMirror
* @return the set of qualifiers that are the upper bounds for a use of the type
*/
public Set<AnnotationMirror> getBoundQualifiers(TypeMirror type) {
AnnotationMirrorSet bounds = new AnnotationMirrorSet();
String qname;
if (type.getKind() == TypeKind.DECLARED) {
DeclaredType declaredType = (DeclaredType) type;
bounds.addAll(getAnnotationFromElement(declaredType.asElement()));
qname = TypesUtils.getQualifiedName(declaredType);
} else if (type.getKind().isPrimitive()) {
qname = type.toString();
} else {
qname = null;
}
if (qname != null && types.containsKey(qname)) {
Set<AnnotationMirror> fnd = types.get(qname);
addMissingAnnotations(bounds, fnd);
}
// If the type's kind is in the appropriate map, annotate the type.
if (typeKinds.containsKey(type.getKind())) {
Set<AnnotationMirror> fnd = typeKinds.get(type.getKind());
addMissingAnnotations(bounds, fnd);
}
addMissingAnnotations(bounds, atypeFactory.getDefaultTypeDeclarationBounds());
return bounds;
}
/**
* Returns the explicit annotations on the element. Subclass can override this behavior to add
* annotations.
*
* @param element element whose annotations to return
* @return the explicit annotations on the element
*/
protected Set<AnnotationMirror> getAnnotationFromElement(Element element) {
return atypeFactory.fromElement(element).getAnnotations();
}
/**
* Adds each annotation in {@code missing} to {@code annos}, for which no annotation from the same
* qualifier hierarchy is present.
*
* @param annos an annotation set to side-effect
* @param missing annotations to add to {@code annos}, if {@code annos} does not have an
* annotation from the same qualifier hierarchy
*/
private void addMissingAnnotations(
AnnotationMirrorSet annos, Set<? extends AnnotationMirror> missing) {
for (AnnotationMirror miss : missing) {
if (atypeFactory.getQualifierHierarchy().findAnnotationInSameHierarchy(annos, miss) == null) {
annos.add(miss);
}
}
}
}