package checkers.util;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.Name;
import checkers.quals.PolymorphicQualifier;
import checkers.nullness.quals.*;
import checkers.types.QualifierHierarchy;
import static checkers.util.AnnotationUtils.*;
// It's functional, but requires optimization and better documentation
//
/**
* Represents the type qualifier hierarchy of a type system.
*
* This class is immutable and can be only created through {@link Factory}.
*/
public class GraphQualifierHierarchy extends QualifierHierarchy {
/**
* Factory used to create an instance of {@link GraphQualifierHierarchy}.
* A factory can be used to create at most one {@link GraphQualifierHierarchy}.
*
* To create a hierarchy, a client may do so in three steps:
* 1. add qualifiers using {@link #addQualifier(AnnotationMirror)};
* 2. add subtype relations using {@link #addSubtype(AnnotationMirror, AnnotationMirror)}
* 3. build the hierarchy and gets using {@link #build()}.
*
* Notice that {@link #addSubtype(AnnotationMirror, AnnotationMirror)} adds
* the two qualifiers to the hierarchy if they are not already in.
*
* Also, once the client builds a hierarchy through {@link #build()},
* no further modifications are allowed nor can it making a new instance.
*
* Clients build the hierarchy using {@link #addQualifier(AnnotationMirror)}
* and {@link #addSubtype(AnnotationMirror, AnnotationMirror)}, then get
* the instance with calling {@link #build()}
*/
public static class Factory {
/** map: qualifier --> supertypesMap of the qualifier */
// supertypesMap is immutable once GraphQualifierHierarchy is built
private Map<AnnotationMirror, Set<AnnotationMirror>> supertypes;
private AnnotationMirror polyQualifier;
private boolean wasBuilt = false;
public Factory() {
supertypes = createAnnotationMap();
}
/**
* Adds the passed qualifier to the hierarchy. Clients need to specify
* its super qualifiers in subsequent calls to
* {@link #addSubtype(AnnotationMirror, AnnotationMirror)}.
*/
public void addQualifier(AnnotationMirror qual) {
assertNotBuilt();
if (supertypes.containsKey(qual))
return;
supertypes.put(qual, createAnnotationSet());
if (isPolymorphic(qual))
this.polyQualifier = qual;
}
private boolean isPolymorphic(AnnotationMirror qual) {
if (qual == null)
return false;
Element qualElt = qual.getAnnotationType().asElement();
return qualElt.getAnnotation(PolymorphicQualifier.class) != null;
}
/**
* Adds a subtype relationship between the two type qualifiers.
*
* @param sub the sub type qualifier
* @param sup the super type qualifier
*/
public void addSubtype(AnnotationMirror sub, AnnotationMirror sup) {
assertNotBuilt();
addQualifier(sub);
addQualifier(sup);
supertypes.get(sub).add(sup);
}
/**
* Returns an instance of {@link GraphQualifierHierarchy} that
* represents the hierarchy built so far
*/
public GraphQualifierHierarchy build() {
assertNotBuilt();
addPolyRelations();
wasBuilt = true;
return new GraphQualifierHierarchy(this);
}
private void assertNotBuilt() {
if (wasBuilt)
throw new IllegalStateException("qualifier hierarchy already built");
}
/**
* add the relationships for polymorphic qualifiers.
*
* A polymorphic qualifier needs to be (take {@link PolyNull} for example)
* 1. a subtype of the root qualifier (e.g. {@link Nullable})
* 2. a supertype of all the bottom qualifiers (e.g. {@link NonNull})
*/
private void addPolyRelations() {
if (polyQualifier == null)
return;
// find its supertypesMap
if (supertypes.get(polyQualifier).isEmpty()) {
AnnotationMirror root = findRoot(supertypes, polyQualifier);
addSubtype(polyQualifier, root);
}
Set<AnnotationMirror> bottoms = findBottoms(supertypes, polyQualifier);
for (AnnotationMirror bottom : bottoms)
addSubtype(bottom, polyQualifier);
}
}
private final Map<AnnotationMirror, Set<AnnotationMirror>> supertypesGraph;
/** immutable map: qualifier --> supertypesMap of the qualifier**/
private final Map<AnnotationMirror, Set<AnnotationMirror>> supertypesMap;
/** the root of all the qualifiers **/
private final AnnotationMirror root;
private final AnnotationMirror bottom;
private GraphQualifierHierarchy(Factory f) {
// // no need for copying as f.supertypes has no mutable references to it
this.supertypesGraph = f.supertypes;
this.supertypesMap = buildFullMap(f.supertypes);
this.root = findRoot(this.supertypesMap, null);
this.bottom = findBottom(this.supertypesMap, null);
}
protected GraphQualifierHierarchy(GraphQualifierHierarchy h) {
this.supertypesGraph = h.supertypesGraph;
this.supertypesMap = h.supertypesMap;
this.root = h.root;
this.lubs = h.lubs;
this.bottom = h.bottom;
}
/**
* Returns the root qualifier for this hierarchy.
*
* The root qualifier is inferred from the hierarchy, as being the only
* one without any super qualifiers
*/
@Override
public AnnotationMirror getRootAnnotation() {
return root;
}
@Override
public AnnotationMirror getBottomQualifier() {
return this.bottom;
}
private Set<Name> typeQualifiers = null;
@Override
public Set<Name> getTypeQualifiers() {
if (typeQualifiers != null)
return typeQualifiers;
Set<Name> names = new HashSet<Name>();
for (AnnotationMirror anno: supertypesMap.keySet())
names.add(annotationName(anno));
typeQualifiers = names;
return typeQualifiers;
}
// For caching results of lubs
Map<AnnotationPair, AnnotationMirror> lubs = null;
@Override
public AnnotationMirror leastUpperBound(AnnotationMirror a1, AnnotationMirror a2) {
if (areSameIgnoringValues(a1, a2))
return areSame(a1, a2) ? a1 : root;
if (lubs == null) {
lubs = new HashMap<AnnotationPair, AnnotationMirror>();
lubs = calculateLubs();
}
AnnotationPair pair = new AnnotationPair(a1, a2);
return lubs.get(pair);
}
/**
* Most qualifiers have no value fields. However, two annotations with
* values are subtype of each other only if they have the same values.
* i.e. I(m) is a subtype of I(n) iff m = n
*
* When client specifies an annotation, a1, to be a subtype of annotation
* with values, a2, then a1 is a subtype of all instances of a2 regardless
* of a2 values. i.e. IGJBottom is a subtype of all instances of
* {@code @I}.
*
*/
@Override
public boolean isSubtype(AnnotationMirror anno1, AnnotationMirror anno2) {
if (areSame(root, anno2))
return true;
if (areSameIgnoringValues(anno1, anno2))
return areSame(anno1, anno2);
checkAnnoInGraph(anno1);
checkAnnoInGraph(anno2);
return this.supertypesMap.get(anno1).contains(anno2);
}
private final void checkAnnoInGraph(AnnotationMirror a) {
if (supertypesMap.containsKey(a))
return;
if (a == null)
throw new IllegalArgumentException(
"Found an unqualified type. Please ensure that " +
"your implicit rules cover all cases and/or " +
"use a @DefaulQualifierInHierarchy annotation");
else
throw new IllegalArgumentException("Unrecognized qualifier: " + a);
}
/**
* Infer the root for the subtype hierarchy. Simply finds the one (and only
* one) qualifier that is not a subtype of any other qualifier
*
* @param ignore
* a qualifier that cannot be a root candidate, like polymorphic
* qualifier
*/
private static AnnotationMirror
findRoot(Map<AnnotationMirror, Set<AnnotationMirror>> supertypes, AnnotationMirror ignore) {
List<AnnotationMirror> possibleRoots = new LinkedList<AnnotationMirror>();
for (AnnotationMirror anno : supertypes.keySet()) {
if (supertypes.get(anno).isEmpty())
possibleRoots.add(anno);
}
if (ignore != null)
possibleRoots.remove(ignore);
assert possibleRoots.size() == 1 : "Other than one possible root: "
+ possibleRoots + "\n"
+ "Does the checker know about all type qualifiers?";
return possibleRoots.get(0);
}
private static AnnotationMirror
findBottom(Map<AnnotationMirror, Set<AnnotationMirror>> supertypes, AnnotationMirror ignore) {
Set<AnnotationMirror> bottoms = findBottoms(supertypes, ignore);
if (bottoms.size() == 1)
return bottoms.iterator().next();
else
return null;
}
/**
* Infer the bottoms of the subtype hierarchy. Simple finds the qualifiers
* are not supertypesMap of other qualifiers.
*
* @param ignore
* a qualifier that cannot be a bottom candidate, like polymorphic
* qualifier
*/
private static Set<AnnotationMirror>
findBottoms(Map<AnnotationMirror, Set<AnnotationMirror>> supertypes, AnnotationMirror ignore) {
Set<AnnotationMirror> bottoms = createAnnotationSet();
bottoms.addAll(supertypes.keySet());
for (Set<AnnotationMirror> supers : supertypes.values()) {
bottoms.removeAll(supers);
}
if (ignore != null)
bottoms.remove(ignore);
return bottoms;
}
private static Map<AnnotationMirror, Set<AnnotationMirror>>
buildFullMap(Map<AnnotationMirror, Set<AnnotationMirror>> supertypes) {
Map<AnnotationMirror, Set<AnnotationMirror>> fullMap = createAnnotationMap();
for (AnnotationMirror anno : supertypes.keySet()) {
findAllSupers(anno, supertypes, fullMap);
}
return fullMap;
}
private Map<AnnotationPair, AnnotationMirror> calculateLubs() {
Map<AnnotationPair, AnnotationMirror> lubs = new HashMap<AnnotationPair, AnnotationMirror>();
for (AnnotationMirror a1 : supertypesGraph.keySet())
for (AnnotationMirror a2 : supertypesGraph.keySet()) {
if (areSameIgnoringValues(a1, a2))
continue;
AnnotationPair pair = new AnnotationPair(a1, a2);
if (lubs.containsKey(pair))
continue;
AnnotationMirror lub = findLub(a1, a2);
lubs.put(pair, lub);
}
return lubs;
}
private AnnotationMirror findLub(AnnotationMirror a1, AnnotationMirror a2) {
if (isSubtype(a1, a2))
return a2;
if (isSubtype(a2, a1))
return a1;
for (AnnotationMirror a1Super : supertypesMap.get(a1)) {
AnnotationMirror a1Lub = findLub(a1Super, a2);
if (a1Lub != null)
return a1Lub;
}
throw new AssertionError("Could not determine LUB for " + a1 + " and " + a2 + "\n" +
"Does the checker know about all type qualifiers?");
}
/**
* Finds all the super qualifiers for an qualifier
*
* @param anno
* @param supertypesMap
* @return
*/
private static Set<AnnotationMirror>
findAllSupers(AnnotationMirror anno,
Map<AnnotationMirror, Set<AnnotationMirror>> supertypes,
Map<AnnotationMirror, Set<AnnotationMirror>> allSupersSoFar) {
Set<AnnotationMirror> supers = createAnnotationSet();
if (allSupersSoFar.containsKey(anno))
return Collections.unmodifiableSet(allSupersSoFar.get(anno));
for (AnnotationMirror superAnno : supertypes.get(anno)) {
supers.add(superAnno);
supers.addAll(findAllSupers(superAnno, supertypes, allSupersSoFar));
}
allSupersSoFar.put(anno, Collections.unmodifiableSet(supers));
return supers;
}
private static class AnnotationPair {
public final AnnotationMirror a1;
public final AnnotationMirror a2;
private int hashCode = -1;
public AnnotationPair(AnnotationMirror a1, AnnotationMirror a2) {
this.a1 = a1;
this.a2 = a2;
}
public int hashCode() {
if (hashCode == -1) {
hashCode = 31;
if (a1 != null)
hashCode += 17 * a1.toString().hashCode();
if (a2 != null)
hashCode += 17 * a2.toString().hashCode();
}
return hashCode;
}
public boolean equals(Object o) {
if (!(o instanceof AnnotationPair))
return false;
AnnotationPair other = (AnnotationPair)o;
if (areSameIgnoringValues(a1, other.a1)
&& areSameIgnoringValues(a2, other.a2))
return true;
if (areSameIgnoringValues(a2, other.a1)
&& areSameIgnoringValues(a1, other.a2))
return true;
return false;
}
}
}