/** Is the annotation {@code anno} an initialization qualifier? */
 protected boolean isInitializationAnnotation(AnnotationMirror anno) {
   assert anno != null;
   return AnnotationUtils.areSameIgnoringValues(anno, UNCLASSIFIED)
       || AnnotationUtils.areSameIgnoringValues(anno, FREE)
       || AnnotationUtils.areSameIgnoringValues(anno, COMMITTED)
       || AnnotationUtils.areSameIgnoringValues(anno, FBCBOTTOM);
 }
    /**
     * Determines the least upper bound of a1 and a2. If a1 and a2 are both the same type of Value
     * annotation, then the LUB is the result of taking all values from both a1 and a2 and removing
     * duplicates. If a1 and a2 are not the same type of Value annotation they may still be
     * mergeable because some values can be implicitly cast as others. If a1 and a2 are both in
     * {DoubleVal, IntVal} then they will be converted upwards: IntVal → DoubleVal to arrive at
     * a common annotation type.
     *
     * @return the least upper bound of a1 and a2
     */
    @Override
    public AnnotationMirror leastUpperBound(AnnotationMirror a1, AnnotationMirror a2) {
      if (!AnnotationUtils.areSameIgnoringValues(getTopAnnotation(a1), getTopAnnotation(a2))) {
        return null;
      } else if (isSubtype(a1, a2)) {
        return a2;
      } else if (isSubtype(a2, a1)) {
        return a1;
      }
      // If both are the same type, determine the type and merge:
      else if (AnnotationUtils.areSameIgnoringValues(a1, a2)) {
        List<Object> a1Values =
            AnnotationUtils.getElementValueArray(a1, "value", Object.class, true);
        List<Object> a2Values =
            AnnotationUtils.getElementValueArray(a2, "value", Object.class, true);
        HashSet<Object> newValues = new HashSet<Object>(a1Values.size() + a2Values.size());

        newValues.addAll(a1Values);
        newValues.addAll(a2Values);

        return createAnnotation(a1.getAnnotationType().toString(), newValues);
      }
      // Annotations are in this hierarchy, but they are not the same
      else {
        // If either is UNKNOWNVAL, ARRAYLEN, STRINGVAL, or BOOLEAN then
        // the LUB is
        // UnknownVal
        if (!(AnnotationUtils.areSameByClass(a1, IntVal.class)
            || AnnotationUtils.areSameByClass(a1, DoubleVal.class)
            || AnnotationUtils.areSameByClass(a2, IntVal.class)
            || AnnotationUtils.areSameByClass(a2, DoubleVal.class))) {
          return UNKNOWNVAL;
        } else {
          // At this point one of them must be a DoubleVal and one an
          // IntVal
          AnnotationMirror doubleAnno;
          AnnotationMirror intAnno;

          if (AnnotationUtils.areSameByClass(a2, DoubleVal.class)) {
            doubleAnno = a2;
            intAnno = a1;
          } else {
            doubleAnno = a1;
            intAnno = a2;
          }
          List<Long> intVals = getIntValues(intAnno);
          List<Double> doubleVals = getDoubleValues(doubleAnno);

          for (Long n : intVals) {
            doubleVals.add(n.doubleValue());
          }

          return createDoubleValAnnotation(doubleVals);
        }
      }
    }
    /**
     * Recursive method to handle array initializations. Recursively descends the initializer to
     * find each dimension's size and create the appropriate annotation for it.
     *
     * @param dimensions a list of ExpressionTrees where each ExpressionTree is a specifier of the
     *     size of that dimension (should be an IntVal).
     * @param type the AnnotatedTypeMirror of the array
     */
    private void handleDimensions(
        List<? extends ExpressionTree> dimensions, AnnotatedArrayType type) {
      if (dimensions.size() > 1) {
        handleDimensions(
            dimensions.subList(1, dimensions.size()), (AnnotatedArrayType) type.getComponentType());
      }

      AnnotationMirror dimType =
          getAnnotatedType(dimensions.get(0)).getAnnotationInHierarchy(UNKNOWNVAL);
      if (!AnnotationUtils.areSameIgnoringValues(dimType, UNKNOWNVAL)) {
        List<Long> longLengths = getIntValues(dimType);

        HashSet<Integer> lengths = new HashSet<Integer>(longLengths.size());
        for (Long l : longLengths) {
          lengths.add(l.intValue());
        }
        AnnotationMirror newQual = createArrayLenAnnotation(new ArrayList<>(lengths));
        type.replaceAnnotation(newQual);
      }
    }
    /**
     * Computes subtyping as per the subtyping in the qualifier hierarchy structure unless both
     * annotations are Value. In this case, rhs is a subtype of lhs iff lhs contains at least every
     * element of rhs
     *
     * @return true if rhs is a subtype of lhs, false otherwise
     */
    @Override
    public boolean isSubtype(AnnotationMirror rhs, AnnotationMirror lhs) {

      if (AnnotationUtils.areSameByClass(lhs, UnknownVal.class)
          || AnnotationUtils.areSameByClass(rhs, BottomVal.class)) {
        return true;
      } else if (AnnotationUtils.areSameByClass(rhs, UnknownVal.class)
          || AnnotationUtils.areSameByClass(lhs, BottomVal.class)) {
        return false;
      } else if (AnnotationUtils.areSameIgnoringValues(lhs, rhs)) {
        // Same type, so might be subtype
        List<Object> lhsValues =
            AnnotationUtils.getElementValueArray(lhs, "value", Object.class, true);
        List<Object> rhsValues =
            AnnotationUtils.getElementValueArray(rhs, "value", Object.class, true);
        return lhsValues.containsAll(rhsValues);
      } else if (AnnotationUtils.areSameByClass(lhs, DoubleVal.class)
          && AnnotationUtils.areSameByClass(rhs, IntVal.class)) {
        List<Long> rhsValues;
        rhsValues = AnnotationUtils.getElementValueArray(rhs, "value", Long.class, true);
        List<Double> lhsValues =
            AnnotationUtils.getElementValueArray(lhs, "value", Double.class, true);
        boolean same = false;
        for (Long rhsLong : rhsValues) {
          for (Double lhsDbl : lhsValues) {
            if (lhsDbl.doubleValue() == rhsLong.doubleValue()) {
              same = true;
              break;
            }
          }
          if (!same) {
            return false;
          }
        }
        return same;
      }
      return false;
    }