// TODO: These are special cases for isRegex(String, int) and asRegex(String, int).
  // They should be replaced by adding an @EnsuresQualifierIf annotation that supports
  // specifying attributes.
  @Override
  public TransferResult<CFValue, CFStore> visitMethodInvocation(
      MethodInvocationNode n, TransferInput<CFValue, CFStore> in) {
    RegexClassicAnnotatedTypeFactory factory =
        (RegexClassicAnnotatedTypeFactory) analysis.getTypeFactory();
    TransferResult<CFValue, CFStore> result = super.visitMethodInvocation(n, in);

    // refine result for some helper methods
    MethodAccessNode target = n.getTarget();
    ExecutableElement method = target.getMethod();
    Node receiver = target.getReceiver();
    if (!(receiver instanceof ClassNameNode)) {
      return result;
    }
    ClassNameNode cn = (ClassNameNode) receiver;
    String receiverName = cn.getElement().toString();

    if (isRegexUtil(receiverName)) {
      if (ElementUtils.matchesElement(method, IS_REGEX_METHOD_NAME, String.class, int.class)) {
        // RegexUtil.isRegex(s, groups) method
        // (No special case is needed for isRegex(String) because of
        // the annotation on that method's definition.)

        CFStore thenStore = result.getRegularStore();
        CFStore elseStore = thenStore.copy();
        ConditionalTransferResult<CFValue, CFStore> newResult =
            new ConditionalTransferResult<>(result.getResultValue(), thenStore, elseStore);
        Receiver firstParam =
            FlowExpressions.internalReprOf(
                factory.getContext().getAnnotationProvider(), n.getArgument(0));

        // add annotation with correct group count (if possible,
        // regex annotation without count otherwise)
        Node count = n.getArgument(1);
        if (count instanceof IntegerLiteralNode) {
          IntegerLiteralNode iln = (IntegerLiteralNode) count;
          Integer groupCount = iln.getValue();
          AnnotationMirror regexAnnotation = factory.createRegexAnnotation(groupCount);
          thenStore.insertValue(firstParam, regexAnnotation);
        } else {
          AnnotationMirror regexAnnotation =
              AnnotationUtils.fromClass(factory.getElementUtils(), Regex.class);
          thenStore.insertValue(firstParam, regexAnnotation);
        }
        return newResult;

      } else if (ElementUtils.matchesElement(
          method, AS_REGEX_METHOD_NAME, String.class, int.class)) {
        // RegexUtil.asRegex(s, groups) method
        // (No special case is needed for asRegex(String) because of
        // the annotation on that method's definition.)

        // add annotation with correct group count (if possible,
        // regex annotation without count otherwise)
        AnnotationMirror regexAnnotation;
        Node count = n.getArgument(1);
        if (count instanceof IntegerLiteralNode) {
          IntegerLiteralNode iln = (IntegerLiteralNode) count;
          Integer groupCount = iln.getValue();
          regexAnnotation = factory.createRegexAnnotation(groupCount);
        } else {
          regexAnnotation = AnnotationUtils.fromClass(factory.getElementUtils(), Regex.class);
        }
        CFValue newResultValue =
            analysis.createSingleAnnotationValue(
                regexAnnotation, result.getResultValue().getType().getUnderlyingType());
        return new RegularTransferResult<>(newResultValue, result.getRegularStore());
      }
    }

    return result;
  };
  /*
   * Provided that m is of a type that implements interface java.util.Map:
   * -Given a call m.containsKey(k), ensures that k is @KeyFor("m") in the thenStore of the transfer result.
   * -Given a call m.put(k, ...), ensures that k is @KeyFor("m") in the thenStore and elseStore of the transfer result.
   */
  @Override
  public TransferResult<CFValue, CFStore> visitMethodInvocation(
      MethodInvocationNode node, TransferInput<CFValue, CFStore> in) {

    TransferResult<CFValue, CFStore> result = super.visitMethodInvocation(node, in);

    String methodName = node.getTarget().getMethod().toString();

    // First verify if the method name is containsKey or put. This is an inexpensive check.

    boolean containsKey = methodName.startsWith("containsKey(");
    boolean put = methodName.startsWith("put(");

    if (containsKey || put) {
      // Now verify that the receiver of the method invocation is of a type
      // that extends that java.util.Map interface. This is a more expensive check.

      javax.lang.model.util.Types types = analysis.getTypes();

      TypeMirror mapInterfaceTypeMirror =
          types.erasure(
              TypesUtils.typeFromClass(types, analysis.getEnv().getElementUtils(), Map.class));

      TypeMirror receiverType = types.erasure(node.getTarget().getReceiver().getType());

      if (types.isSubtype(receiverType, mapInterfaceTypeMirror)) {

        FlowExpressionContext flowExprContext =
            FlowExpressionParseUtil.buildFlowExprContextForUse(node, checker);

        String mapName = flowExprContext.receiver.toString();
        Receiver keyReceiver = flowExprContext.arguments.get(0);

        KeyForAnnotatedTypeFactory atypeFactory =
            (KeyForAnnotatedTypeFactory) analysis.getTypeFactory();

        LinkedHashSet<String> keyForMaps = new LinkedHashSet<>();
        keyForMaps.add(mapName);

        final CFValue previousKeyValue = in.getValueOfSubNode(node.getArgument(0));
        if (previousKeyValue != null) {
          final AnnotationMirror prevAm =
              previousKeyValue.getType().getAnnotationInHierarchy(KEYFOR);
          if (prevAm != null && AnnotationUtils.areSameByClass(prevAm, KeyFor.class)) {
            keyForMaps.addAll(getKeys(prevAm));
          }
        }

        AnnotationMirror am = atypeFactory.createKeyForAnnotationMirrorWithValue(keyForMaps);

        if (containsKey) {
          ConditionalTransferResult<CFValue, CFStore> conditionalResult =
              (ConditionalTransferResult<CFValue, CFStore>) result;
          conditionalResult.getThenStore().insertValue(keyReceiver, am);
        } else if (put) {
          result.getThenStore().insertValue(keyReceiver, am);
          result.getElseStore().insertValue(keyReceiver, am);
        }
      }
    }

    return result;
  }