/**
  * Returns a set of triples {@code (expr, (result, annotation))} of conditional postconditions
  * according to the given {@link EnsuresQualifierIf}.
  */
 private Set<Pair<String, Pair<Boolean, String>>> getConditionalPostcondition(
     AnnotationMirror ensuresAnnotationIf) {
   if (ensuresAnnotationIf == null) {
     return Collections.emptySet();
   }
   Set<Pair<String, Pair<Boolean, String>>> result = new HashSet<>();
   List<String> expressions =
       AnnotationUtils.getElementValueArray(
           ensuresAnnotationIf, "expression", String.class, false);
   String annotation =
       AnnotationUtils.getElementValueClassName(ensuresAnnotationIf, "qualifier", false)
           .toString();
   boolean annoResult =
       AnnotationUtils.getElementValue(ensuresAnnotationIf, "result", Boolean.class, false);
   for (String expr : expressions) {
     result.add(Pair.of(expr, Pair.of(annoResult, annotation)));
   }
   return result;
 }
  /**
   * Given a category string that may be prepended with "fixable-", return the category enum that
   * corresponds with the category and whether or not it is a isFixable error
   */
  private static Pair<DiagnosticKind, Boolean> parseCategoryString(String category) {
    final String fixable = "fixable-";
    final boolean isFixable = category.startsWith(fixable);
    if (isFixable) {
      category = category.substring(fixable.length());
    }
    DiagnosticKind categoryEnum = DiagnosticKind.fromParseString(category);

    return Pair.of(categoryEnum, isFixable);
  }
  /**
   * Returns a set of triples {@code (expr, (result, annotation))} of conditional postconditions on
   * the method {@code methodElement}.
   */
  public Set<Pair<String, Pair<Boolean, String>>> getConditionalPostconditions(
      ExecutableElement methodElement) {
    Set<Pair<String, Pair<Boolean, String>>> result = new HashSet<>();
    // Check for a single contract.
    AnnotationMirror ensuresAnnotationIf =
        factory.getDeclAnnotation(methodElement, EnsuresQualifierIf.class);
    result.addAll(getConditionalPostcondition(ensuresAnnotationIf));

    // Check for multiple contracts.
    AnnotationMirror ensuresAnnotationsIf =
        factory.getDeclAnnotation(methodElement, EnsuresQualifiersIf.class);
    if (ensuresAnnotationsIf != null) {
      List<AnnotationMirror> annotations =
          AnnotationUtils.getElementValueArray(
              ensuresAnnotationsIf, "value", AnnotationMirror.class, false);
      for (AnnotationMirror a : annotations) {
        result.addAll(getConditionalPostcondition(a));
      }
    }

    // Check type-system specific annotations.
    Class<ConditionalPostconditionAnnotation> metaAnnotation =
        ConditionalPostconditionAnnotation.class;
    List<Pair<AnnotationMirror, AnnotationMirror>> declAnnotations =
        factory.getDeclAnnotationWithMetaAnnotation(methodElement, metaAnnotation);
    for (Pair<AnnotationMirror, AnnotationMirror> r : declAnnotations) {
      AnnotationMirror anno = r.first;
      AnnotationMirror metaAnno = r.second;
      List<String> expressions =
          AnnotationUtils.getElementValueArray(anno, "expression", String.class, false);
      String annotationString =
          AnnotationUtils.getElementValueClassName(metaAnno, "qualifier", false).toString();
      boolean annoResult = AnnotationUtils.getElementValue(anno, "result", Boolean.class, false);
      for (String expr : expressions) {
        result.add(Pair.of(expr, Pair.of(annoResult, annotationString)));
      }
    }
    return result;
  }
 /**
  * Returns a set of pairs {@code (expr, annotation)} of preconditions according to the given
  * {@link RequiresQualifier}.
  */
 private Set<Pair<String, String>> getPrecondition(AnnotationMirror requiresAnnotation) {
   if (requiresAnnotation == null) {
     return Collections.emptySet();
   }
   Set<Pair<String, String>> result = new HashSet<>();
   List<String> expressions =
       AnnotationUtils.getElementValueArray(requiresAnnotation, "expression", String.class, false);
   String annotation =
       AnnotationUtils.getElementValueClassName(requiresAnnotation, "qualifier", false).toString();
   for (String expr : expressions) {
     result.add(Pair.of(expr, annotation));
   }
   return result;
 }
  /**
   * Returns a set of pairs {@code (expr, annotation)} of preconditions on the element {@code
   * element}.
   */
  public Set<Pair<String, String>> getPreconditions(Element element) {
    Set<Pair<String, String>> result = new HashSet<>();
    // Check for a single contract.
    AnnotationMirror requiresAnnotation =
        factory.getDeclAnnotation(element, RequiresQualifier.class);
    result.addAll(getPrecondition(requiresAnnotation));

    // Check for multiple contracts.
    AnnotationMirror requiresAnnotations =
        factory.getDeclAnnotation(element, RequiresQualifiers.class);
    if (requiresAnnotations != null) {
      List<AnnotationMirror> annotations =
          AnnotationUtils.getElementValueArray(
              requiresAnnotations, "value", AnnotationMirror.class, false);
      for (AnnotationMirror a : annotations) {
        result.addAll(getPrecondition(a));
      }
    }

    // Check type-system specific annotations.
    Class<PreconditionAnnotation> metaAnnotation = PreconditionAnnotation.class;
    List<Pair<AnnotationMirror, AnnotationMirror>> declAnnotations =
        factory.getDeclAnnotationWithMetaAnnotation(element, metaAnnotation);
    for (Pair<AnnotationMirror, AnnotationMirror> r : declAnnotations) {
      AnnotationMirror anno = r.first;
      AnnotationMirror metaAnno = r.second;
      List<String> expressions =
          AnnotationUtils.getElementValueArray(anno, "value", String.class, false);
      String annotationString =
          AnnotationUtils.getElementValueClassName(metaAnno, "qualifier", false).toString();
      for (String expr : expressions) {
        result.add(Pair.of(expr, annotationString));
      }
    }
    return result;
  }
 static Pair<Boolean, String> dropParentheses(final String str) {
   if (str.charAt(0) == '(' && str.charAt(str.length() - 1) == ')') {
     return Pair.of(true, str.substring(1, str.length() - 1));
   }
   return Pair.of(false, str);
 }
  private Pair<ParameterizedTypeTree, AnnotatedDeclaredType> extractParameterizedTypeTree(
      Tree tree, AnnotatedDeclaredType type) {
    ParameterizedTypeTree typeargtree = null;

    switch (tree.getKind()) {
      case VARIABLE:
        Tree lt = ((VariableTree) tree).getType();
        if (lt instanceof ParameterizedTypeTree) {
          typeargtree = (ParameterizedTypeTree) lt;
        } else {
          // System.out.println("Found a: " + lt);
        }
        break;
      case PARAMETERIZED_TYPE:
        typeargtree = (ParameterizedTypeTree) tree;
        break;
      case NEW_CLASS:
        NewClassTree nct = (NewClassTree) tree;
        ExpressionTree nctid = nct.getIdentifier();
        if (nctid.getKind() == Tree.Kind.PARAMETERIZED_TYPE) {
          typeargtree = (ParameterizedTypeTree) nctid;
          /*
           * This is quite tricky... for anonymous class instantiations,
           * the type at this point has no type arguments. By doing the
           * following, we get the type arguments again.
           */
          type = (AnnotatedDeclaredType) atypeFactory.getAnnotatedType(typeargtree);
        }
        break;
      case ANNOTATED_TYPE:
        AnnotatedTypeTree tr = (AnnotatedTypeTree) tree;
        ExpressionTree undtr = tr.getUnderlyingType();
        if (undtr instanceof ParameterizedTypeTree) {
          typeargtree = (ParameterizedTypeTree) undtr;
        } else if (undtr instanceof IdentifierTree) {
          // @Something D -> Nothing to do
        } else {
          // TODO: add more test cases to ensure that nested types are
          // handled correctly,
          // e.g. @Nullable() List<@Nullable Object>[][]
          Pair<ParameterizedTypeTree, AnnotatedDeclaredType> p =
              extractParameterizedTypeTree(undtr, type);
          typeargtree = p.first;
          type = p.second;
        }
        break;
      case IDENTIFIER:
      case ARRAY_TYPE:
      case NEW_ARRAY:
      case MEMBER_SELECT:
      case UNBOUNDED_WILDCARD:
      case EXTENDS_WILDCARD:
      case SUPER_WILDCARD:
      case TYPE_PARAMETER:
        // Nothing to do.
        // System.out.println("Found a: " + (tree instanceof
        // ParameterizedTypeTree));
        break;
      default:
        // the parameterized type is the result of some expression tree.
        // No need to do anything further.
        break;
        // System.err.printf("TypeValidator.visitDeclared unhandled tree: %s of kind %s\n",
        //                 tree, tree.getKind());
    }

    return Pair.of(typeargtree, type);
  }