| package org.checkerframework.framework.util; |
| |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.List; |
| import javax.lang.model.element.AnnotationMirror; |
| import javax.tools.Diagnostic.Kind; |
| import org.checkerframework.framework.source.DiagMessage; |
| import org.checkerframework.framework.type.AnnotatedTypeFactory; |
| import org.checkerframework.framework.type.QualifierHierarchy; |
| import org.checkerframework.javacutil.BugInCF; |
| |
| /** |
| * Represents field invariants, which the user states by writing {@code @FieldInvariant}. Think of |
| * this as a set of (field, qualifier) pairs. |
| * |
| * <p>A FieldInvariants object may be malformed (inconsistent number of fields and qualifiers). In |
| * this case, the BaseTypeVisitor will issue an error. |
| */ |
| public class FieldInvariants { |
| |
| /** |
| * A list of simple field names. A field may appear more than once in this list. This list has the |
| * same length as {@code qualifiers}. |
| */ |
| private final List<String> fields; |
| |
| /** |
| * A list of qualifiers that apply to the field at the same index in {@code fields}. In a |
| * well-formed FieldInvariants, has the same length as {@code fields}. |
| */ |
| private final List<AnnotationMirror> qualifiers; |
| |
| /** |
| * Creates a new FieldInvariants object. The result is well-formed if length of qualifiers is |
| * either 1 or equal to length of {@code fields}. |
| * |
| * @param fields list of fields |
| * @param qualifiers list of qualifiers |
| */ |
| public FieldInvariants(List<String> fields, List<AnnotationMirror> qualifiers) { |
| this(null, fields, qualifiers); |
| } |
| |
| /** |
| * Creates a new object with all the invariants in {@code other}, plus those specified by {@code |
| * fields} and {@code qualifiers}. The result is well-formed if length of qualifiers is either 1 |
| * or equal to length of {@code fields}. |
| * |
| * @param other other invariant object, may be null |
| * @param fields list of fields |
| * @param qualifiers list of qualifiers |
| */ |
| public FieldInvariants( |
| FieldInvariants other, List<String> fields, List<AnnotationMirror> qualifiers) { |
| if (qualifiers.size() == 1) { |
| while (fields.size() > qualifiers.size()) { |
| qualifiers.add(qualifiers.get(0)); |
| } |
| } |
| if (other != null) { |
| fields.addAll(other.fields); |
| qualifiers.addAll(other.qualifiers); |
| } |
| |
| this.fields = Collections.unmodifiableList(fields); |
| this.qualifiers = qualifiers; |
| } |
| |
| /** The simple names of the fields that have a qualifier. May contain duplicates. */ |
| public List<String> getFields() { |
| return fields; |
| } |
| |
| /** |
| * Returns a list of qualifiers for {@code field}. If {@code field} has no qualifiers, returns an |
| * empty list. |
| * |
| * @param field simple field name |
| * @return a list of qualifiers for {@code field}, possibly empty |
| */ |
| public List<AnnotationMirror> getQualifiersFor(CharSequence field) { |
| if (!isWellFormed()) { |
| throw new BugInCF("malformed FieldInvariants"); |
| } |
| String fieldString = field.toString(); |
| int index = fields.indexOf(fieldString); |
| if (index == -1) { |
| return Collections.emptyList(); |
| } |
| List<AnnotationMirror> list = new ArrayList<>(); |
| for (int i = 0; i < fields.size(); i++) { |
| if (fields.get(i).equals(fieldString)) { |
| list.add(qualifiers.get(i)); |
| } |
| } |
| return list; |
| } |
| |
| /** |
| * Returns true if there is a qualifier for each field in {@code fields}. |
| * |
| * @return true if there is a qualifier for each field in {@code fields} |
| */ |
| public boolean isWellFormed() { |
| return qualifiers.size() == fields.size(); |
| } |
| |
| /** |
| * Returns null if {@code superInvar} is a super invariant, otherwise returns the error message. |
| * |
| * @param superInvar the value to check for being a super invariant |
| * @param factory the type factory |
| * @return null if {@code superInvar} is a super invariant, otherwise returns the error message |
| */ |
| public DiagMessage isSuperInvariant(FieldInvariants superInvar, AnnotatedTypeFactory factory) { |
| QualifierHierarchy qualifierHierarchy = factory.getQualifierHierarchy(); |
| if (!this.fields.containsAll(superInvar.fields)) { |
| List<String> missingFields = new ArrayList<>(superInvar.fields); |
| missingFields.removeAll(fields); |
| return new DiagMessage( |
| Kind.ERROR, "field.invariant.not.found.superclass", String.join(", ", missingFields)); |
| } |
| |
| for (String field : superInvar.fields) { |
| List<AnnotationMirror> superQualifiers = superInvar.getQualifiersFor(field); |
| List<AnnotationMirror> subQualifiers = this.getQualifiersFor(field); |
| for (AnnotationMirror superA : superQualifiers) { |
| AnnotationMirror sub = |
| qualifierHierarchy.findAnnotationInSameHierarchy(subQualifiers, superA); |
| if (sub == null || !qualifierHierarchy.isSubtype(sub, superA)) { |
| return new DiagMessage( |
| Kind.ERROR, "field.invariant.not.subtype.superclass", field, sub, superA); |
| } |
| } |
| } |
| return null; |
| } |
| } |