| package org.checkerframework.framework.util; |
| |
| import java.lang.annotation.Annotation; |
| import java.util.ArrayDeque; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Queue; |
| import java.util.Set; |
| import java.util.TreeMap; |
| import java.util.TreeSet; |
| import org.checkerframework.checker.initialization.qual.UnderInitialization; |
| import org.checkerframework.checker.interning.qual.Interned; |
| import org.checkerframework.checker.nullness.qual.KeyFor; |
| import org.checkerframework.checker.nullness.qual.MonotonicNonNull; |
| import org.checkerframework.checker.nullness.qual.Nullable; |
| import org.checkerframework.checker.nullness.qual.RequiresNonNull; |
| import org.checkerframework.checker.signature.qual.CanonicalName; |
| import org.checkerframework.dataflow.qual.Pure; |
| import org.checkerframework.framework.qual.AnnotatedFor; |
| import org.checkerframework.framework.qual.PolymorphicQualifier; |
| import org.checkerframework.framework.qual.SubtypeOf; |
| import org.checkerframework.javacutil.BugInCF; |
| import org.checkerframework.javacutil.TypeSystemError; |
| import org.plumelib.util.StringsPlume; |
| |
| /** |
| * This is the default implementation of {@link QualifierKindHierarchy}. |
| * |
| * <p>By default, the subtyping information and information about polymorphic qualifiers is read |
| * from meta-annotations on the annotation classes. This information is used to infer further |
| * information such as top and bottom qualifiers. Subclasses can override the following methods to |
| * change this behavior: |
| * |
| * <ul> |
| * <li>{@link #createQualifierKinds(Collection)} |
| * <li>{@link #createDirectSuperMap()} |
| * <li>{@link #initializePolymorphicQualifiers()} |
| * <li>{@link #initializeQualifierKindFields(Map)} |
| * <li>{@link #createLubsMap()} |
| * <li>{@link #createGlbsMap()} |
| * </ul> |
| * |
| * {@link DefaultQualifierKindHierarchy.DefaultQualifierKind} is the implementation used for {@link |
| * QualifierKind} by this class. |
| */ |
| @AnnotatedFor("nullness") |
| public class DefaultQualifierKindHierarchy implements QualifierKindHierarchy { |
| |
| /** |
| * A mapping from canonical name of a qualifier class to the QualifierKind object representing |
| * that class. |
| */ |
| protected final Map<@Interned @CanonicalName String, DefaultQualifierKind> nameToQualifierKind; |
| |
| /** |
| * A list of all {@link QualifierKind}s for this DefaultQualifierKindHierarchy, sorted in |
| * ascending order. |
| */ |
| protected final List<DefaultQualifierKind> qualifierKinds; |
| |
| /** All the qualifier kinds that are the top qualifier in their hierarchy. */ |
| private final Set<DefaultQualifierKind> tops; |
| |
| /** All the qualifier kinds that are the bottom qualifier in their hierarchy. */ |
| private final Set<DefaultQualifierKind> bottoms; |
| |
| /** |
| * Holds the lub of qualifier kinds. {@code lubs.get(kind1).get(kind2)} returns the lub of kind1 |
| * and kind2. |
| */ |
| private final Map<QualifierKind, Map<QualifierKind, QualifierKind>> lubs; |
| |
| /** |
| * Holds the glb of qualifier kinds. {@code glbs.get(kind1).get(kind2)} returns the glb of kind1 |
| * and kind2. |
| */ |
| private final Map<QualifierKind, Map<QualifierKind, QualifierKind>> glbs; |
| |
| @Override |
| public Set<? extends QualifierKind> getTops() { |
| return tops; |
| } |
| |
| @Override |
| public Set<? extends QualifierKind> getBottoms() { |
| return bottoms; |
| } |
| |
| @Override |
| public @Nullable QualifierKind leastUpperBound(QualifierKind q1, QualifierKind q2) { |
| @SuppressWarnings("nullness:dereference.of.nullable") // All QualifierKinds are keys in lubs. |
| QualifierKind result = lubs.get(q1).get(q2); |
| return result; |
| } |
| |
| @Override |
| public @Nullable QualifierKind greatestLowerBound(QualifierKind q1, QualifierKind q2) { |
| @SuppressWarnings("nullness:dereference.of.nullable") // All QualifierKinds are keys in glbs. |
| QualifierKind result = glbs.get(q1).get(q2); |
| return result; |
| } |
| |
| @Override |
| public List<? extends QualifierKind> allQualifierKinds() { |
| return qualifierKinds; |
| } |
| |
| @Override |
| public @Nullable QualifierKind getQualifierKind(@CanonicalName String name) { |
| return nameToQualifierKind.get(name); |
| } |
| |
| /** |
| * Creates a {@link DefaultQualifierKindHierarchy}. Also, creates and initializes all its |
| * qualifier kinds. |
| * |
| * @param qualifierClasses all the classes of qualifiers supported by this hierarchy |
| */ |
| public DefaultQualifierKindHierarchy(Collection<Class<? extends Annotation>> qualifierClasses) { |
| this(qualifierClasses, null, null); |
| } |
| |
| /** |
| * Creates a {@link DefaultQualifierKindHierarchy}. Also, creates and initializes all its |
| * qualifier kinds. |
| * |
| * <p>For some type systems, qualifiers may be added at run time, so the {@link SubtypeOf} |
| * meta-annotation on the bottom qualifier class cannot specify all other qualifiers. For those |
| * type systems, use this constructor. Otherwise, use {@link |
| * #DefaultQualifierKindHierarchy(Collection)}. |
| * |
| * @param qualifierClasses all the classes of qualifiers supported by this hierarchy |
| * @param bottom the bottom qualifier of this hierarchy |
| */ |
| public DefaultQualifierKindHierarchy( |
| Collection<Class<? extends Annotation>> qualifierClasses, |
| Class<? extends Annotation> bottom) { |
| this(qualifierClasses, bottom, null); |
| } |
| |
| /** |
| * Private constructor that sets the bottom qualifier if {@code bottom} is nonnull. |
| * |
| * @param qualifierClasses all the classes of qualifiers supported by this hierarchy |
| * @param bottom the bottom qualifier of this hierarchy or null if bottom can be inferred from the |
| * meta-annotations |
| * @param voidParam void parameter to differentiate from {@link |
| * #DefaultQualifierKindHierarchy(Collection, Class)} |
| */ |
| private DefaultQualifierKindHierarchy( |
| Collection<Class<? extends Annotation>> qualifierClasses, |
| @Nullable Class<? extends Annotation> bottom, |
| @SuppressWarnings("UnusedVariable") Void voidParam) { |
| this.nameToQualifierKind = createQualifierKinds(qualifierClasses); |
| this.qualifierKinds = new ArrayList<>(nameToQualifierKind.values()); |
| Collections.sort(qualifierKinds); |
| |
| Map<DefaultQualifierKind, Set<DefaultQualifierKind>> directSuperMap = createDirectSuperMap(); |
| if (bottom != null) { |
| setBottom(bottom, directSuperMap); |
| } |
| this.tops = createTopsSet(directSuperMap); |
| this.bottoms = createBottomsSet(directSuperMap); |
| initializePolymorphicQualifiers(); |
| initializeQualifierKindFields(directSuperMap); |
| this.lubs = createLubsMap(); |
| this.glbs = createGlbsMap(); |
| |
| verifyHierarchy(directSuperMap); |
| } |
| |
| /** |
| * Verifies that the {@link DefaultQualifierKindHierarchy} is a valid hierarchy. |
| * |
| * @param directSuperMap mapping from qualifier to its direct supertypes; used to verify that a |
| * polymorphic annotation does not have a {@link SubtypeOf} meta-annotation |
| * @throws TypeSystemError if the hierarchy isn't valid |
| */ |
| @RequiresNonNull({"this.qualifierKinds", "this.tops", "this.bottoms"}) |
| protected void verifyHierarchy( |
| @UnderInitialization DefaultQualifierKindHierarchy this, |
| Map<DefaultQualifierKind, Set<DefaultQualifierKind>> directSuperMap) { |
| for (DefaultQualifierKind qualifierKind : qualifierKinds) { |
| boolean isPoly = qualifierKind.isPoly(); |
| boolean hasSubtypeOfAnno = directSuperMap.containsKey(qualifierKind); |
| if (isPoly && hasSubtypeOfAnno) { |
| // Polymorphic qualifiers with upper and lower bounds are currently not supported. |
| throw new TypeSystemError( |
| "AnnotatedTypeFactory: " |
| + qualifierKind |
| + " is polymorphic and specifies super qualifiers.%n" |
| + "Remove the @PolymorphicQualifier or @SubtypeOf annotation from it."); |
| } else if (!isPoly && !hasSubtypeOfAnno) { |
| throw new TypeSystemError( |
| "AnnotatedTypeFactory: %s does not specify its super qualifiers.%n" |
| + "Add an @SubtypeOf or @PolymorphicQualifier annotation to it,%n" |
| + "or if it is an alias, exclude it from `createSupportedTypeQualifiers()`.", |
| qualifierKind); |
| } else if (isPoly) { |
| if (qualifierKind.top == null) { |
| throw new TypeSystemError( |
| "PolymorphicQualifier, %s, has to specify a type hierarchy in its" |
| + " @PolymorphicQualifier meta-annotation, if more than one exists; top types:" |
| + " [%s].", |
| qualifierKind, StringsPlume.join(", ", tops)); |
| } else if (!tops.contains(qualifierKind.top)) { |
| throw new TypeSystemError( |
| "Polymorphic qualifier %s has invalid top %s. Top qualifiers: %s", |
| qualifierKind, qualifierKind.top, StringsPlume.join(", ", tops)); |
| } |
| } |
| } |
| |
| if (bottoms.size() != tops.size()) { |
| throw new TypeSystemError( |
| "Number of tops not equal to number of bottoms: Tops: [%s] Bottoms: [%s]", |
| StringsPlume.join(", ", tops), StringsPlume.join(", ", bottoms)); |
| } |
| } |
| |
| /** |
| * Creates all QualifierKind objects for the given qualifier classes and adds them to |
| * qualifierClassMap. This method does not initialize all fields in the {@link QualifierKind}; |
| * that is done by {@link #initializeQualifierKindFields(Map)}. |
| * |
| * @param qualifierClasses classes of annotations that are type qualifiers |
| * @return a mapping from the canonical name of an annotation class to {@link QualifierKind} |
| */ |
| protected Map<@Interned @CanonicalName String, DefaultQualifierKind> createQualifierKinds( |
| @UnderInitialization DefaultQualifierKindHierarchy this, |
| Collection<Class<? extends Annotation>> qualifierClasses) { |
| TreeMap<@Interned @CanonicalName String, DefaultQualifierKind> nameToQualifierKind = |
| new TreeMap<>(); |
| for (Class<? extends Annotation> clazz : qualifierClasses) { |
| @SuppressWarnings("interning") // uniqueness is tested immediately below |
| @Interned DefaultQualifierKind qualifierKind = new DefaultQualifierKind(clazz); |
| if (nameToQualifierKind.containsKey(qualifierKind.getName())) { |
| throw new TypeSystemError("Duplicate QualifierKind " + qualifierKind.getName()); |
| } |
| nameToQualifierKind.put(qualifierKind.getName(), qualifierKind); |
| } |
| return Collections.unmodifiableMap(nameToQualifierKind); |
| } |
| |
| /** |
| * Creates a mapping from a {@link QualifierKind} to a set of its direct super qualifier kinds. |
| * The direct super qualifier kinds do not contain the qualifier itself. This mapping is used to |
| * create the bottom set, to create the top set, and by {@link |
| * #initializeQualifierKindFields(Map)}. |
| * |
| * <p>This implementation uses the {@link SubtypeOf} meta-annotation. Subclasses may override this |
| * method to create the direct super map some other way. |
| * |
| * <p>Note that this method is called from the constructor when {@link #nameToQualifierKind} and |
| * {@link #qualifierKinds} are the only fields that have nonnull values. This method is not |
| * static, so it can be overridden by subclasses. |
| * |
| * @return a mapping from each {@link QualifierKind} to a set of its direct super qualifiers |
| */ |
| @RequiresNonNull({"this.nameToQualifierKind", "this.qualifierKinds"}) |
| protected Map<DefaultQualifierKind, Set<DefaultQualifierKind>> createDirectSuperMap( |
| @UnderInitialization DefaultQualifierKindHierarchy this) { |
| Map<DefaultQualifierKind, Set<DefaultQualifierKind>> directSuperMap = new TreeMap<>(); |
| for (DefaultQualifierKind qualifierKind : qualifierKinds) { |
| SubtypeOf subtypeOfMetaAnno = |
| qualifierKind.getAnnotationClass().getAnnotation(SubtypeOf.class); |
| if (subtypeOfMetaAnno == null) { |
| // qualifierKind has no @SubtypeOf: it must be top or polymorphic |
| continue; |
| } |
| Set<DefaultQualifierKind> directSupers = new TreeSet<>(); |
| for (Class<? extends Annotation> superClazz : subtypeOfMetaAnno.value()) { |
| String superName = QualifierKindHierarchy.annotationClassName(superClazz); |
| DefaultQualifierKind superQualifier = nameToQualifierKind.get(superName); |
| if (superQualifier == null) { |
| throw new TypeSystemError( |
| "%s @Subtype argument %s isn't in the hierarchy. Qualifiers: [%s]", |
| qualifierKind, superName, StringsPlume.join(", ", qualifierKinds)); |
| } |
| directSupers.add(superQualifier); |
| } |
| directSuperMap.put(qualifierKind, directSupers); |
| } |
| return directSuperMap; |
| } |
| |
| /** |
| * This method sets bottom to the given class and modifies {@code directSuperMap} to add all |
| * leaves to its super qualifier kinds. Leaves are qualifier kinds that are not super qualifier |
| * kinds of another qualifier kind and are not polymorphic. |
| * |
| * @param bottom the class of the bottom qualifier in the hierarchy |
| * @param directSuperMap a mapping from a {@link QualifierKind} to a set of its direct super |
| * qualifiers; side-effected by this method |
| */ |
| @RequiresNonNull({"this.nameToQualifierKind", "this.qualifierKinds"}) |
| private void setBottom( |
| @UnderInitialization DefaultQualifierKindHierarchy this, |
| Class<? extends Annotation> bottom, |
| Map<DefaultQualifierKind, Set<DefaultQualifierKind>> directSuperMap) { |
| DefaultQualifierKind bottomKind = |
| nameToQualifierKind.get(QualifierKindHierarchy.annotationClassName(bottom)); |
| if (bottomKind == null) { |
| throw new TypeSystemError( |
| "QualifierKindHierarchy#setBottom: %s is not in the hierarchy", |
| bottom.getCanonicalName()); |
| } |
| |
| Set<DefaultQualifierKind> leaves = new TreeSet<>(qualifierKinds); |
| leaves.remove(bottomKind); |
| directSuperMap.forEach((sub, supers) -> leaves.removeAll(supers)); |
| Set<DefaultQualifierKind> bottomDirectSuperQuals = directSuperMap.get(bottomKind); |
| if (bottomDirectSuperQuals == null) { |
| directSuperMap.put(bottomKind, leaves); |
| } else { |
| bottomDirectSuperQuals.addAll(leaves); |
| } |
| } |
| |
| /** |
| * Creates the set of top {@link QualifierKind}s by searching {@code directSuperMap} for qualifier |
| * kinds without any direct super qualifier kinds. |
| * |
| * <p>Subclasses should override {@link #createDirectSuperMap} to change the tops and not this |
| * method, because other methods expect the directSuperMap to be complete. |
| * |
| * @param directSuperMap a mapping from a {@link QualifierKind} to a set of its direct super |
| * qualifier kinds; created by {@link #createDirectSuperMap()} |
| * @return the set of top {@link QualifierKind}s |
| */ |
| private Set<DefaultQualifierKind> createTopsSet( |
| @UnderInitialization DefaultQualifierKindHierarchy this, |
| Map<DefaultQualifierKind, Set<DefaultQualifierKind>> directSuperMap) { |
| Set<DefaultQualifierKind> tops = new TreeSet<>(); |
| directSuperMap.forEach( |
| (qualifierKind, superQuals) -> { |
| if (superQuals.isEmpty()) { |
| tops.add(qualifierKind); |
| } |
| }); |
| return tops; |
| } |
| |
| /** |
| * Creates the set of bottom {@link QualifierKind}s by searching {@code directSuperMap} for |
| * qualifiers that are not a direct super qualifier kind of another qualifier kind. |
| * |
| * <p>Subclasses should override {@link #createDirectSuperMap} or {@link #setBottom} to change the |
| * bottoms and not this method, because other methods expect the directSuperMap to be complete. |
| * |
| * @param directSuperMap a mapping from a {@link QualifierKind} to a set of its direct super |
| * qualifier kinds; created by {@link #createDirectSuperMap()} |
| * @return the set of bottom {@link QualifierKind}s |
| */ |
| private Set<DefaultQualifierKind> createBottomsSet( |
| @UnderInitialization DefaultQualifierKindHierarchy this, |
| Map<DefaultQualifierKind, Set<DefaultQualifierKind>> directSuperMap) { |
| Set<DefaultQualifierKind> bottoms = new HashSet<>(directSuperMap.keySet()); |
| for (Set<DefaultQualifierKind> superKinds : directSuperMap.values()) { |
| bottoms.removeAll(superKinds); |
| } |
| return bottoms; |
| } |
| |
| /** |
| * Iterates over all the qualifier kinds and adds all polymorphic qualifier kinds to |
| * polymorphicQualifiers. Also sets {@link DefaultQualifierKind#poly} and {@link |
| * DefaultQualifierKind#top} for the polymorphic qualifiers, and sets {@link |
| * DefaultQualifierKind#poly} for the top qualifiers. |
| * |
| * <p>Requires that tops has been initialized. |
| */ |
| @RequiresNonNull({"this.nameToQualifierKind", "this.qualifierKinds", "this.tops"}) |
| protected void initializePolymorphicQualifiers( |
| @UnderInitialization DefaultQualifierKindHierarchy this) { |
| for (DefaultQualifierKind qualifierKind : qualifierKinds) { |
| Class<? extends Annotation> clazz = qualifierKind.getAnnotationClass(); |
| PolymorphicQualifier polyMetaAnno = clazz.getAnnotation(PolymorphicQualifier.class); |
| if (polyMetaAnno == null) { |
| continue; |
| } |
| qualifierKind.poly = qualifierKind; |
| String topName = QualifierKindHierarchy.annotationClassName(polyMetaAnno.value()); |
| if (nameToQualifierKind.containsKey(topName)) { |
| qualifierKind.top = nameToQualifierKind.get(topName); |
| } else if (topName.equals(Annotation.class.getCanonicalName())) { |
| // Annotation.class is the default value of PolymorphicQualifier. If it is used, |
| // then there must be exactly one top. |
| if (tops.size() == 1) { |
| qualifierKind.top = tops.iterator().next(); |
| } else { |
| throw new TypeSystemError( |
| "Polymorphic qualifier %s did not specify a top annotation class. Tops: [%s]", |
| qualifierKind, StringsPlume.join(", ", tops)); |
| } |
| } else { |
| throw new TypeSystemError( |
| "Polymorphic qualifier %s's top, %s, is not a qualifier.", qualifierKind, topName); |
| } |
| qualifierKind.strictSuperTypes = Collections.singleton(qualifierKind.top); |
| qualifierKind.top.poly = qualifierKind; |
| } |
| } |
| |
| /** |
| * For each qualifier kind in {@code directSuperMap}, initializes {@link |
| * DefaultQualifierKind#strictSuperTypes}, {@link DefaultQualifierKind#top}, {@link |
| * DefaultQualifierKind#bottom}, and {@link DefaultQualifierKind#poly}. |
| * |
| * <p>Requires tops, bottoms, and polymorphicQualifiers to be initialized. |
| * |
| * @param directSuperMap a mapping from a {@link QualifierKind} to a set of its direct super |
| * qualifier kinds; created by {@link #createDirectSuperMap()} |
| */ |
| @RequiresNonNull({"this.qualifierKinds", "this.tops", "this.bottoms"}) |
| protected void initializeQualifierKindFields( |
| @UnderInitialization DefaultQualifierKindHierarchy this, |
| Map<DefaultQualifierKind, Set<DefaultQualifierKind>> directSuperMap) { |
| for (DefaultQualifierKind qualifierKind : directSuperMap.keySet()) { |
| if (!qualifierKind.isPoly()) { |
| qualifierKind.strictSuperTypes = findAllTheSupers(qualifierKind, directSuperMap); |
| } |
| } |
| for (DefaultQualifierKind qualifierKind : qualifierKinds) { |
| for (DefaultQualifierKind top : tops) { |
| if (qualifierKind.isSubtypeOf(top)) { |
| if (qualifierKind.top == null) { |
| qualifierKind.top = top; |
| } else if (qualifierKind.top != top) { |
| throw new TypeSystemError( |
| "Multiple tops found for qualifier %s. Tops: %s and %s.", |
| qualifierKind, top, qualifierKind.top); |
| } |
| } |
| } |
| if (qualifierKind.top == null) { |
| throw new TypeSystemError( |
| "Qualifier %s isn't a subtype of any top. tops = %s", qualifierKind, tops); |
| } |
| qualifierKind.poly = qualifierKind.top.poly; |
| } |
| for (DefaultQualifierKind qualifierKind : qualifierKinds) { |
| for (DefaultQualifierKind bot : bottoms) { |
| if (bot.top != qualifierKind.top) { |
| continue; |
| } |
| if (qualifierKind.bottom == null) { |
| qualifierKind.bottom = bot; |
| } else if (qualifierKind.top != bot) { |
| throw new TypeSystemError( |
| "Multiple bottoms found for qualifier %s. Bottoms: %s and %s.", |
| qualifierKind, bot, qualifierKind.bottom); |
| } |
| if (qualifierKind.isPoly()) { |
| assert bot.strictSuperTypes != null |
| : "@AssumeAssertion(nullness): strictSuperTypes should be nonnull."; |
| bot.strictSuperTypes.add(qualifierKind); |
| } |
| } |
| if (qualifierKind.bottom == null) { |
| throw new TypeSystemError( |
| "Cannot find a bottom qualifier for %s. bottoms = %s", qualifierKind, bottoms); |
| } |
| } |
| } |
| |
| /** |
| * Returns the set of all qualifier kinds that are a strict supertype of {@code qualifierKind}. |
| * |
| * @param qualifierKind the qualifier kind whose super types should be returned |
| * @param directSuperMap a mapping from a {@link QualifierKind} to a set of its direct super |
| * qualifier kinds; created by {@link #createDirectSuperMap()} |
| * @return the set of all qualifier kinds that are a strict supertype of {@code qualifierKind} |
| */ |
| private Set<QualifierKind> findAllTheSupers( |
| @UnderInitialization DefaultQualifierKindHierarchy this, |
| @KeyFor("#2") QualifierKind qualifierKind, |
| Map<DefaultQualifierKind, Set<DefaultQualifierKind>> directSuperMap) { |
| |
| Set<QualifierKind> allSupers = new TreeSet<>(directSuperMap.get(qualifierKind)); |
| |
| // Visit every super qualifier kind and add its super qualifier kinds to allSupers. |
| Queue<DefaultQualifierKind> toVisit = new ArrayDeque<>(directSuperMap.get(qualifierKind)); |
| Set<DefaultQualifierKind> visited = new HashSet<>(); |
| while (!toVisit.isEmpty()) { |
| DefaultQualifierKind superQualKind = toVisit.remove(); |
| if (superQualKind == qualifierKind) { |
| throw new TypeSystemError("Cycle in hierarchy: %s", qualifierKind); |
| } |
| |
| if (!visited.add(superQualKind) || superQualKind.isPoly()) { |
| continue; |
| } |
| |
| Set<DefaultQualifierKind> superSuperQuals = directSuperMap.get(superQualKind); |
| if (superSuperQuals == null) { |
| throw new TypeSystemError(superQualKind + " is not a key in the directSuperMap"); |
| } |
| toVisit.addAll(superSuperQuals); |
| allSupers.addAll(superSuperQuals); |
| } |
| return allSupers; |
| } |
| |
| /** |
| * Creates the lub of qualifier kinds. {@code lubs.get(kind1).get(kind2)} returns the lub of kind1 |
| * and kind2. |
| * |
| * @return a mapping of lubs |
| */ |
| @RequiresNonNull("this.qualifierKinds") |
| protected Map<QualifierKind, Map<QualifierKind, QualifierKind>> createLubsMap( |
| @UnderInitialization DefaultQualifierKindHierarchy this) { |
| Map<QualifierKind, Map<QualifierKind, QualifierKind>> lubs = new HashMap<>(); |
| for (QualifierKind qual1 : qualifierKinds) { |
| for (QualifierKind qual2 : qualifierKinds) { |
| if (qual1.getTop() != qual2.getTop()) { |
| continue; |
| } |
| QualifierKind lub = findLub(qual1, qual2); |
| addToMapOfMap(lubs, qual1, qual2, lub, "lub"); |
| addToMapOfMap(lubs, qual2, qual1, lub, "lub"); |
| } |
| } |
| return lubs; |
| } |
| |
| /** |
| * Returns the least upper bound of {@code qual1} and {@code qual2}. |
| * |
| * @param qual1 a qualifier kind |
| * @param qual2 a qualifier kind |
| * @return the least upper bound of {@code qual1} and {@code qual2} |
| */ |
| private QualifierKind findLub( |
| @UnderInitialization DefaultQualifierKindHierarchy this, |
| QualifierKind qual1, |
| QualifierKind qual2) { |
| if (qual1 == qual2) { |
| return qual1; |
| } else if (qual1.isSubtypeOf(qual2)) { |
| return qual2; |
| } else if (qual2.isSubtypeOf(qual1)) { |
| return qual1; |
| } |
| Set<QualifierKind> allSuperTypes = new TreeSet<>(qual1.getStrictSuperTypes()); |
| Set<? extends QualifierKind> qual2StrictSuperTypes = qual2.getStrictSuperTypes(); |
| allSuperTypes.retainAll(qual2StrictSuperTypes); |
| Set<? extends QualifierKind> lubs = findLowestQualifiers(allSuperTypes); |
| if (lubs.size() != 1) { |
| throw new TypeSystemError( |
| "lub(%s, %s) should have size 1: [%s]", qual1, qual2, StringsPlume.join(", ", lubs)); |
| } |
| QualifierKind lub = lubs.iterator().next(); |
| if (lub.isPoly() && !qual1.isPoly() && !qual2.isPoly()) { |
| throw new TypeSystemError("lub(%s, %s) can't be poly: %s", qual1, qual2, lub); |
| } |
| return lub; |
| } |
| |
| /** |
| * Returns the lowest qualifiers in the passed set. |
| * |
| * @param qualifierKinds a set of qualifiers |
| * @return the lowest qualifiers in the passed set |
| */ |
| protected static Set<QualifierKind> findLowestQualifiers(Set<QualifierKind> qualifierKinds) { |
| Set<QualifierKind> lowestQualifiers = new TreeSet<>(qualifierKinds); |
| for (QualifierKind a1 : qualifierKinds) { |
| lowestQualifiers.removeIf(a2 -> a1 != a2 && a1.isSubtypeOf(a2)); |
| } |
| return lowestQualifiers; |
| } |
| |
| /** |
| * Creates the glb of qualifier kinds. {@code glbs.get(kind1).get(kind2)} returns the glb of kind1 |
| * and kind2. |
| * |
| * @return a mapping of glb |
| */ |
| @RequiresNonNull("this.qualifierKinds") |
| protected Map<QualifierKind, Map<QualifierKind, QualifierKind>> createGlbsMap( |
| @UnderInitialization DefaultQualifierKindHierarchy this) { |
| Map<QualifierKind, Map<QualifierKind, QualifierKind>> glbs = new TreeMap<>(); |
| for (QualifierKind qual1 : qualifierKinds) { |
| for (QualifierKind qual2 : qualifierKinds) { |
| if (qual1.getTop() != qual2.getTop()) { |
| continue; |
| } |
| QualifierKind glb = findGlb(qual1, qual2); |
| addToMapOfMap(glbs, qual1, qual2, glb, "glb"); |
| addToMapOfMap(glbs, qual2, qual1, glb, "glb"); |
| } |
| } |
| return glbs; |
| } |
| |
| /** |
| * Returns the greatest lower bound of {@code qual1} and {@code qual2}. |
| * |
| * @param qual1 a qualifier kind |
| * @param qual2 a qualifier kind |
| * @return the greatest lower bound of {@code qual1} and {@code qual2} |
| */ |
| @RequiresNonNull("this.qualifierKinds") |
| private QualifierKind findGlb( |
| @UnderInitialization DefaultQualifierKindHierarchy this, |
| QualifierKind qual1, |
| QualifierKind qual2) { |
| if (qual1 == qual2) { |
| return qual1; |
| } else if (qual1.isSubtypeOf(qual2)) { |
| return qual1; |
| } else if (qual2.isSubtypeOf(qual1)) { |
| return qual2; |
| } |
| Set<QualifierKind> allSubTypes = new TreeSet<>(); |
| for (QualifierKind qualifierKind : qualifierKinds) { |
| if (qualifierKind.isSubtypeOf(qual1) && qualifierKind.isSubtypeOf(qual2)) { |
| allSubTypes.add(qualifierKind); |
| } |
| } |
| Set<QualifierKind> glbs = findHighestQualifiers(allSubTypes); |
| if (glbs.size() != 1) { |
| throw new TypeSystemError( |
| "glb(%s, %s) should have size 1: [%s]", qual1, qual2, StringsPlume.join(", ", glbs)); |
| } |
| QualifierKind glb = glbs.iterator().next(); |
| if (glb.isPoly() && !qual1.isPoly() && !qual2.isPoly()) { |
| throw new TypeSystemError("glb(%s, %s) can't be poly: %s", qual1, qual2, glb); |
| } |
| return glb; |
| } |
| |
| /** |
| * Returns the highest qualifiers in the passed set. |
| * |
| * @param qualifierKinds a set of qualifiers |
| * @return the highest qualifiers in the passed set |
| */ |
| protected static Set<QualifierKind> findHighestQualifiers(Set<QualifierKind> qualifierKinds) { |
| Set<QualifierKind> highestQualifiers = new TreeSet<>(qualifierKinds); |
| for (QualifierKind a1 : qualifierKinds) { |
| highestQualifiers.removeIf(a2 -> a1 != a2 && a2.isSubtypeOf(a1)); |
| } |
| return highestQualifiers; |
| } |
| |
| /** |
| * Add Key: qual1, Value: (Key: qual2, Value: value) to {@code map}. If already in map, throw an |
| * exception if value is different. |
| * |
| * @param map mapping to side-effect |
| * @param qual1 the first qualifier kind |
| * @param qual2 the second qualifier kind |
| * @param value the value to add |
| * @param operationName "lub" or "glb"; used only for error messages |
| */ |
| private static void addToMapOfMap( |
| Map<QualifierKind, Map<QualifierKind, QualifierKind>> map, |
| QualifierKind qual1, |
| QualifierKind qual2, |
| QualifierKind value, |
| String operationName) { |
| Map<QualifierKind, QualifierKind> qual1Map = map.computeIfAbsent(qual1, k -> new HashMap<>()); |
| QualifierKind existingValue = qual1Map.get(qual2); |
| if (existingValue == null) { |
| qual1Map.put(qual2, value); |
| } else { |
| if (existingValue != value) { |
| throw new TypeSystemError( |
| "Multiple %ss for qualifiers %s and %s. Found map %s and %s", |
| operationName, qual1, qual2, value, existingValue); |
| } |
| } |
| } |
| |
| /** |
| * The default implementation of {@link QualifierKind}. |
| * |
| * <p>The fields in this class that refer to {@link QualifierKind}s are set when creating the |
| * {@link DefaultQualifierKindHierarchy}. So the getter methods for these fields should not be |
| * called until after the {@code DefaultQualifierKindHierarchy} is created. |
| */ |
| @AnnotatedFor("nullness") |
| public @Interned static class DefaultQualifierKind implements QualifierKind { |
| |
| /** The canonical name of the annotation class of this. */ |
| private final @Interned @CanonicalName String name; |
| |
| /** The annotation class for this. */ |
| private final Class<? extends Annotation> clazz; |
| |
| /** True if the annotation class of this has annotation elements/arguments. */ |
| private final boolean hasElements; |
| |
| /** The top of the hierarchy to which this belongs. */ |
| // Set while creating the QualifierKindHierarchy. |
| protected @MonotonicNonNull DefaultQualifierKind top; |
| |
| /** The bottom of the hierarchy to which this belongs. */ |
| // Set while creating the QualifierKindHierarchy. |
| protected @MonotonicNonNull DefaultQualifierKind bottom; |
| |
| /** The polymorphic qualifier of the hierarchy to which this belongs. */ |
| // Set while creating the QualifierKindHierarchy. |
| protected @Nullable DefaultQualifierKind poly; |
| |
| /** |
| * All the qualifier kinds that are a strict super qualifier kind of this. Does not include this |
| * qualifier kind itself. |
| */ |
| // Set while creating the QualifierKindHierarchy. |
| protected @MonotonicNonNull Set<QualifierKind> strictSuperTypes; |
| |
| /** |
| * Creates a {@link DefaultQualifierKind} for the given annotation class. |
| * |
| * @param clazz annotation class for a qualifier |
| */ |
| DefaultQualifierKind(Class<? extends Annotation> clazz) { |
| this.clazz = clazz; |
| this.hasElements = clazz.getDeclaredMethods().length != 0; |
| this.name = QualifierKindHierarchy.annotationClassName(clazz).intern(); |
| this.poly = null; |
| } |
| |
| @Override |
| public @Interned @CanonicalName String getName() { |
| return name; |
| } |
| |
| @Override |
| public Class<? extends Annotation> getAnnotationClass() { |
| return clazz; |
| } |
| |
| @Override |
| public QualifierKind getTop() { |
| if (top == null) { |
| throw new BugInCF( |
| "DefaultQualifierKindHierarchy#getTop: Top is null for QualifierKind %s." |
| + " Don't call this method during initialization of DefaultQualifierKindHierarchy.", |
| name); |
| } |
| return top; |
| } |
| |
| @Override |
| public boolean isTop() { |
| return this.top == this; |
| } |
| |
| @Override |
| public QualifierKind getBottom() { |
| if (bottom == null) { |
| throw new BugInCF( |
| "DefaultQualifierKind#getBottom:Bottom is null for QualifierKind %s. Don't call this" |
| + " method during initialization of DefaultQualifierKindHierarchy.", |
| name); |
| } |
| return bottom; |
| } |
| |
| @Override |
| public boolean isBottom() { |
| return this.bottom == this; |
| } |
| |
| @Override |
| public @Nullable QualifierKind getPolymorphic() { |
| return poly; |
| } |
| |
| @Pure |
| @Override |
| public boolean isPoly() { |
| return this.poly == this; |
| } |
| |
| @Override |
| public boolean hasElements() { |
| return hasElements; |
| } |
| |
| @Override |
| public Set<? extends QualifierKind> getStrictSuperTypes() { |
| if (strictSuperTypes == null) { |
| throw new BugInCF( |
| "DefaultQualifierKind#getStrictSuperTypes: strictSuperTypes was null. Don't call this" |
| + " method during initialization of DefaultQualifierKindHierarchy."); |
| } |
| return strictSuperTypes; |
| } |
| |
| @Override |
| public boolean isInSameHierarchyAs(QualifierKind other) { |
| return this.top == other.getTop(); |
| } |
| |
| @Override |
| public boolean isSubtypeOf(QualifierKind superQualKind) { |
| if (strictSuperTypes == null) { |
| throw new BugInCF( |
| "DefaultQualifierKind#isSubtypeOf: strictSuperTypes was null. Don't call this method" |
| + " during initialization of DefaultQualifierKindHierarchy."); |
| } |
| return this == superQualKind || strictSuperTypes.contains(superQualKind); |
| } |
| |
| @Override |
| public String toString() { |
| return clazz.getSimpleName(); |
| } |
| } |
| } |